mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[REF] initial prototype of owl 2
This commit is contained in:
+161
-107
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user