[REF] initial prototype of owl 2

This commit is contained in:
Géry Debongnie
2020-11-26 16:45:25 +01:00
parent 4e3b7c74da
commit 7ac20f4fc2
187 changed files with 29162 additions and 32486 deletions
+161 -107
View File
@@ -1,59 +1,49 @@
import { Env, Component, STATUS } from "../src/component/component";
import { scheduler } from "../src/component/scheduler";
import { EvalContext, QWeb } from "../src/qweb/qweb";
import { CompilationContext } from "../src/qweb/compilation_context";
import { patch } from "../src/vdom";
import "../src/qweb/base_directives";
import "../src/qweb/extensions";
import "../src/component/directive";
import { browser } from "../src/browser";
import {
onDestroyed,
onWillPatch,
onWillUnmount,
onMounted,
onPatched,
onWillStart,
onWillUpdateProps,
useComponent,
status,
onRender,
Component,
} from "../src";
import { TemplateSet, globalTemplates, UTILS } from "../src/qweb/template_helpers";
import { blockDom } from "../src";
import { BDom } from "../src/blockdom";
// import { mountBlock } from "../src/blockdom/block";
import { compileTemplate, Template } from "../src/qweb/compiler";
import { xml } from "../src/tags";
// import { UTILS } from "../src/template_utils";
// Some static cleanup
let nextSlotId;
let slots;
let nextId;
let TEMPLATES;
const mount = blockDom.mount;
beforeEach(() => {
nextSlotId = QWeb.nextSlotId;
CompilationContext.nextID = 1;
slots = Object.assign({}, QWeb.slots);
nextId = QWeb.nextId;
TEMPLATES = Object.assign({}, QWeb.TEMPLATES);
Component.scheduler.tasks = [];
});
afterEach(() => {
QWeb.nextSlotId = nextSlotId;
QWeb.slots = slots;
QWeb.nextId = nextId;
QWeb.TEMPLATES = TEMPLATES;
Component.scheduler.tasks = [];
});
// helpers
export function nextMicroTick(): Promise<void> {
return Promise.resolve();
}
export async function nextTick(): Promise<void> {
await new Promise((resolve) => setTimeout(resolve));
await new Promise((resolve) => scheduler.requestAnimationFrame(resolve));
}
export async function nextFrame(): Promise<void> {
await new Promise((resolve) => scheduler.requestAnimationFrame(resolve));
await new Promise((resolve) => scheduler.requestAnimationFrame(resolve));
}
let lastFixture: any = null;
export function makeTestFixture() {
let fixture = document.createElement("div");
document.body.appendChild(fixture);
if (lastFixture) {
lastFixture.remove();
}
lastFixture = fixture;
return fixture;
}
export function normalize(str: string): string {
return str.replace(/\s+/g, "");
export function snapshotTemplateCode(template: string) {
expect(compileTemplate(template).toString()).toMatchSnapshot();
}
export async function nextTick(): Promise<void> {
await new Promise((resolve) => setTimeout(resolve));
await new Promise((resolve) => requestAnimationFrame(resolve));
}
interface Deferred extends Promise<any> {
@@ -67,85 +57,149 @@ export function makeDeferred(): Deferred {
resolve = _resolve;
reject = _reject;
});
(<Deferred>def).resolve = resolve;
(<Deferred>def).reject = reject;
(def as any).resolve = resolve;
(def as any).reject = reject;
return <Deferred>def;
}
export function makeTestEnv(): Env {
return {
qweb: new QWeb(),
browser: browser,
};
}
/**
* Return the global template xml string corresponding to the given name
*/
// export function fromName(name: string): string {
// return globalTemplates[name];
// }
export function trim(str: string): string {
return str.replace(/\s/g, "");
}
export function renderToDOM(
qweb: QWeb,
template: string,
context: EvalContext = {},
extra?: any
): HTMLElement | Text {
if (!context.__owl__) {
// we add `__owl__` to better simulate a component as context. This is
// particularly important for event handlers added with the `t-on` directive.
context.__owl__ = { status: STATUS.MOUNTED };
}
const vnode = qweb.render(template, context, extra);
// we snapshot here the compiled code. This is useful to prevent unwanted code
// change.
expect(qweb.templates[template].fn.toString()).toMatchSnapshot();
if (vnode.sel === undefined) {
return document.createTextNode(vnode.text!);
}
const node = document.createElement(vnode.sel!);
const result = patch(node, vnode);
return result.elm as HTMLElement;
export function addTemplate(name: string, template: string): string {
globalTemplates[name] = template;
return name;
}
/**
* Render a template to an html string. The big difference with the
* renderToString method in QWeb is that we use the renderToDom method, which
* snapshots the resulting template function. Doing so gives us a large body
* of reference to evaluate changes in the generated QWeb code.
*
* Note that the result of renderToString is guaranteed to be the same as the
* one from QWeb.
*/
export function renderToString(
qweb: QWeb,
t: string,
context: EvalContext = {},
extra?: any
): string {
const result = qweb.renderToString(t, context, extra);
expect(qweb.templates[t].fn.toString()).toMatchSnapshot();
return result;
// -----------------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------------
export function compile(template: string): Template {
// register here the template globally so snapshotEverything
// can get it
globalTemplates[template] = template;
const templateFunction = compileTemplate(template);
return templateFunction(blockDom, UTILS);
}
// hereafter, we define two helpers to patch/unpatch the nextFrame utils. This
// is useful for animations tests, as we hook before repaints to trigger
// animations (thanks to requestAnimationFrame). Patching nextFrame allows to
// simulate calls to this hook. One must not forget to unpatch afterwards.
let _nextFrame = QWeb.utils.nextFrame;
export function patchNextFrame(f: Function) {
QWeb.utils.nextFrame = (cb: () => void) => {
setTimeout(() => f(cb));
export function renderToBdom(template: string, context: any = {}, node: any = {}): BDom {
return compile(template)(context, node);
}
export function renderToString(template: string, context: any = {}): string {
const fixture = makeTestFixture();
const bdom = renderToBdom(template, context);
mount(bdom, fixture);
return fixture.innerHTML;
}
export class TestContext extends TemplateSet {
renderToString(name: string, context: any = {}): string {
const renderFn = this.getTemplate(name);
const bdom = renderFn(context, {});
const fixture = makeTestFixture();
mount(bdom, fixture);
return fixture.innerHTML;
}
}
export function snapshotEverything() {
const consolewarn = console.warn;
const originalAddTemplate = TemplateSet.prototype.addTemplate;
TemplateSet.prototype.addTemplate = function (name: string, template: string, options) {
originalAddTemplate.call(this, name, template, options);
// register it so snapshotEverything can get it
globalTemplates[name] = template;
};
let globalSet: any;
beforeAll(() => {
globalSet = new Set(Object.keys(globalTemplates));
});
beforeEach(() => {
xml.nextId = 9;
});
afterEach(() => {
console.warn = () => {};
for (let k in globalTemplates) {
if (globalSet.has(k)) {
// ignore generic templates
continue;
}
try {
snapshotTemplateCode(globalTemplates[k]);
} catch (e) {
// ignore error
}
delete globalTemplates[k];
}
console.warn = consolewarn;
});
}
export function unpatchNextFrame() {
QWeb.utils.nextFrame = _nextFrame;
export function useLogLifecycle(steps: string[]) {
const component = useComponent();
const name = component.constructor.name;
steps.push(`${name}:setup`);
expect(name + ": " + status(component)).toBe(name + ": " + "new");
onWillStart(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "new");
steps.push(`${name}:willStart`);
});
onMounted(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
steps.push(`${name}:mounted`);
});
onWillUpdateProps(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
steps.push(`${name}:willUpdateProps`);
});
onRender(() => {
steps.push(`${name}:render`);
});
onWillPatch(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
steps.push(`${name}:willPatch`);
});
onPatched(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
steps.push(`${name}:patched`);
});
onWillUnmount(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "mounted");
steps.push(`${name}:willUnmount`);
});
onDestroyed(() => {
expect(name + ": " + status(component)).toBe(name + ": " + "destroyed");
steps.push(`${name}:destroyed`);
});
}
export async function editInput(input: HTMLInputElement | HTMLTextAreaElement, value: string) {
input.value = value;
input.dispatchEvent(new Event("input"));
input.dispatchEvent(new Event("change"));
return nextTick();
export function children(w: Component): Component[] {
const childrenMap = w.__owl__.children;
return Object.keys(childrenMap).map((id) => childrenMap[id].component);
}
export function isDirectChildOf(child: Component, parent: Component): boolean {
return children(parent).includes(child);
}