[FIX] component: reorganize fiber update algorithm

Closes #473
Closes #484
Closes #489
Closes #492
Closes #512

Co-authored-by: Aaron Bohy <aab@odoo.com>
This commit is contained in:
Géry Debongnie
2019-11-21 11:23:05 +01:00
parent 7f6782d009
commit 8c17bb0411
15 changed files with 458 additions and 163 deletions
+8 -9
View File
@@ -781,15 +781,14 @@ component (with some code like `app.mount(document.body)`).
1. `willStart` is called on `E`
2. template `E` is rendered
3. component `A` is patched into a detached DOM element. This will create the actual
component `A` DOM structure. The patching process will cause recursively the
patching of the `B`, `C`, `D` and `E` DOM trees. (so the actual full DOM tree is created
3. each components are patched into a detached DOM element, in the following order:
`E`, `D`, `C`, `B`, `A`. (so the actual full DOM tree is created
in one pass)
4. the component `A` root element is actually appended to `document.body`
5. The method `mounted` is called recursively on all components in the following
order: `B`, `D`, `E`, `C`, `A`.
order: `E`, `D`, `C`, `B`, `A`.
**Scenario 2: rerendering a component**. Now, let's assume that the user clicked on some
button in `C`, and this results in a state update, which is supposed to:
@@ -823,14 +822,14 @@ Here is what Owl will do:
3. `willPatch` hooks are called recursively on components `C`, `D` (not on `F`,
because it is not mounted yet)
4. component `C` is patched, which will cause recursively:
4. components `F`, `D` are patched in that order
2. `willUnmount` hook on `E`, then destruction of `E`,
3. (initial) patching of `F`, then hook `mounted` is called on `F`
5. component `C` is patched, which will cause recursively:
5. patching of `D`
1. `willUnmount` hook on `E`
2. destruction of `E`,
6. `patched` hooks are called on `D`, `C`
6. `mounted` hook is called on `F`, `patched` hooks are called on `D`, `C`
### Props Validation
+8 -31
View File
@@ -295,13 +295,12 @@ export class Component<T extends Env, Props extends {}> {
return Promise.resolve();
}
if (!(target instanceof HTMLElement || target instanceof DocumentFragment)) {
let message = `Component '${
this.constructor.name
}' cannot be mounted: the target is not a valid DOM node.`;
let message = `Component '${this.constructor.name}' cannot be mounted: the target is not a valid DOM node.`;
message += `\nMaybe the DOM is not ready yet? (in that case, you can use owl.utils.whenReady)`;
throw new Error(message);
}
const fiber = new Fiber(null, this, false, target);
fiber.shouldPatch = false;
if (!__owl__.vnode) {
this.__prepareAndRender(fiber);
} else {
@@ -451,13 +450,7 @@ export class Component<T extends Env, Props extends {}> {
__callMounted() {
const __owl__ = this.__owl__;
const children = __owl__.children;
for (let id in children) {
const comp = children[id];
if (!comp.__owl__.isMounted && this.el!.contains(comp.el)) {
comp.__callMounted();
}
}
__owl__.isMounted = true;
__owl__.currentFiber = null;
this.mounted();
@@ -473,6 +466,10 @@ export class Component<T extends Env, Props extends {}> {
}
this.willUnmount();
__owl__.isMounted = false;
if (this.__owl__.currentFiber) {
this.__owl__.currentFiber.isCompleted = true;
this.__owl__.currentFiber.root.counter = 0;
}
const children = __owl__.children;
for (let id in children) {
const comp = children[id];
@@ -524,7 +521,7 @@ export class Component<T extends Env, Props extends {}> {
* Main patching method. We call the virtual dom patch method here to convert
* a virtual dom vnode into some actual dom.
*/
__patch(vnode) {
__patch(vnode: VNode) {
const __owl__ = this.__owl__;
const target = __owl__.vnode || document.createElement(vnode.sel!);
__owl__.vnode = patch(target, vnode);
@@ -629,26 +626,6 @@ export class Component<T extends Env, Props extends {}> {
}
}
/**
* Only called by qweb t-component directive
*/
__mount(fiber: Fiber, elm: HTMLElement): VNode {
if (fiber !== this.__owl__.currentFiber) {
fiber = this.__owl__.currentFiber!; // TODO: check if we can remove fiber arg
}
const vnode = fiber.vnode!;
const __owl__ = this.__owl__;
if (__owl__.classObj) {
(<any>vnode).data.class = Object.assign((<any>vnode).data.class || {}, __owl__.classObj);
}
__owl__.vnode = patch(elm, vnode);
__owl__.currentFiber = null;
if (__owl__.parent!.__owl__.isMounted && !__owl__.isMounted) {
this.__callMounted();
}
return __owl__.vnode;
}
/**
* Only called by qweb t-component directive (when t-keepalive is set)
*/
+9 -5
View File
@@ -239,10 +239,6 @@ QWeb.addDirective({
ctx.addLine(`const ${refKey} = ${ctx.interpolate(ref)};`);
refExpr = `context.__owl__.refs[${refKey}] = w${componentID};`;
}
let transitionsInsertCode = "";
if (transition) {
transitionsInsertCode = `utils.transitionInsert(vn, '${transition}');`;
}
let finalizeComponentCode = `w${componentID}.destroy();`;
if (ref) {
finalizeComponentCode += `delete context.__owl__.refs[${refKey}];`;
@@ -251,6 +247,7 @@ QWeb.addDirective({
finalizeComponentCode = `let finalize = () => {
${finalizeComponentCode}
};
delete w${componentID}.__owl__.transitionInserted;
utils.transitionRemove(vn, '${transition}', finalize);`;
}
@@ -409,6 +406,12 @@ QWeb.addDirective({
`if (!W${componentID}) {throw new Error('Cannot find the definition of component "' + componentKey${componentID} + '"')}`
);
ctx.addLine(`w${componentID} = new W${componentID}(parent, props${componentID});`);
if (transition) {
ctx.addLine(`const __patch${componentID} = w${componentID}.__patch;`);
ctx.addLine(
`w${componentID}.__patch = fiber => {__patch${componentID}.call(w${componentID}, fiber); if(!w${componentID}.__owl__.transitionInserted){w${componentID}.__owl__.transitionInserted = true;utils.transitionInsert(w${componentID}.__owl__.vnode, '${transition}');}};`
);
}
ctx.addLine(`parent.__owl__.cmap[${templateKey}] = w${componentID}.__owl__.id;`);
if (hasSlots) {
@@ -438,8 +441,9 @@ QWeb.addDirective({
ctx.addLine(`let def${defID} = w${componentID}.__prepare(extra.fiber, ${scopeVars});`);
// hack: specify empty remove hook to prevent the node from being removed from the DOM
const insertHook = refExpr ? `insert(vn) {${refExpr}},` : "";
ctx.addLine(
`let pvnode = h('dummy', {key: ${templateKey}, hook: {insert(vn) { let nvn=w${componentID}.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;${refExpr}${transitionsInsertCode}},remove() {},destroy(vn) {${finalizeComponentCode}}}});`
`let pvnode = h('dummy', {key: ${templateKey}, hook: {${insertHook}remove() {},destroy(vn) {${finalizeComponentCode}}}});`
);
ctx.addLine(`const fiber = w${componentID}.__owl__.currentFiber;`);
ctx.addLine(
+44 -35
View File
@@ -120,6 +120,7 @@ export class Fiber {
*/
_remapFiber(oldFiber: Fiber) {
oldFiber.cancel();
this.shouldPatch = oldFiber.shouldPatch;
if (oldFiber === oldFiber.root) {
oldFiber.counter++;
}
@@ -178,54 +179,62 @@ export class Fiber {
* are ready, and the scheduler decides to process it.
*/
complete() {
const component = this.component;
if (this.target) {
component.__patch(this.vnode);
this.target.appendChild(component.el!);
if (document.body.contains(this.target)) {
component.__callMounted();
}
} else if (component.__owl__.isMounted && this === this.root) {
this.patchComponents();
}
let component = this.component;
this.isCompleted = true;
}
if (!this.target && !component.__owl__.isMounted) {
return;
}
/**
* Compute and apply the patch queue of the fiber.
* 1) Call 'willPatch' on the component of each patch
* 2) Call '__patch' on the component of each patch
* 3) Call 'patched' on the component of each patch, in reverse order
*/
patchComponents() {
// build patchQueue
const patchQueue: Fiber[] = [];
const doWork: (Fiber) => Fiber | null = function(f) {
if (f.shouldPatch) {
patchQueue.push(f);
return f.child;
}
patchQueue.push(f);
return f.child;
};
this._walk(doWork);
let component: Component<any, any> = this.component;
const patchLen = patchQueue.length;
for (let i = 0; i < patchLen; i++) {
component = patchQueue[i].component;
if (component.__owl__.willPatchCB) {
component.__owl__.willPatchCB();
}
component.willPatch();
}
// call willPatch hook on each fiber of patchQueue
for (let i = 0; i < patchLen; i++) {
const fiber = patchQueue[i];
if (fiber.shouldPatch) {
component = fiber.component;
if (component.__owl__.willPatchCB) {
component.__owl__.willPatchCB();
}
component.willPatch();
}
}
// call __patch on each fiber of (reversed) patchQueue
for (let i = patchLen - 1; i >= 0; i--) {
const fiber = patchQueue[i];
component = fiber.component;
component.__patch(fiber.vnode);
component.__patch(fiber.vnode!);
if (!fiber.shouldPatch && (!fiber.target || i !== 0)) {
component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm;
}
component.__owl__.currentFiber = null;
}
// insert into the DOM (mount case)
let inDOM = false;
if (this.target) {
this.target.appendChild(this.component.el!);
inDOM = document.body.contains(this.target);
}
// call patched/mounted hook on each fiber of (reversed) patchQueue
for (let i = patchLen - 1; i >= 0; i--) {
component = patchQueue[i].component;
component.patched();
if (component.__owl__.patchedCB) {
component.__owl__.patchedCB();
const fiber = patchQueue[i];
component = fiber.component;
if (fiber.shouldPatch && !this.target) {
component.patched();
if (component.__owl__.patchedCB) {
component.__owl__.patchedCB();
}
} else if (this.target ? inDOM : true) {
component.__callMounted();
}
}
}
+3
View File
@@ -26,6 +26,9 @@ export class Scheduler {
}
addFiber(fiber): Promise<void> {
// if the fiber was remapped into a larger rendering fiber, it may not be a
// root fiber. But we only want to register root fibers
fiber = fiber.root;
return new Promise((resolve, reject) => {
if (fiber.error) {
return reject(fiber.error);
+2 -2
View File
@@ -131,7 +131,7 @@ export class CompilationContext {
subContext(key: keyof CompilationContext, value: any): CompilationContext {
const newContext = Object.create(this);
newContext[key] = value;
if (key === 'caller') {
if (key === "caller") {
newContext.callingLevel++;
newContext.inliningLevel++;
}
@@ -191,7 +191,7 @@ export class CompilationContext {
* in which we are for inlining archs (t-raw="0")
*/
getInliningContext(): CompilationContext {
return this.subContext('inliningLevel', this.inliningLevel - 1);
return this.subContext("inliningLevel", this.inliningLevel - 1);
}
getValue(val: any): QWebVar | string {
+5
View File
@@ -105,6 +105,8 @@ QWeb.utils.transitionInsert = function(vn: VNode, name: string) {
elm.classList.add(name + "-enter");
elm.classList.add(name + "-enter-active");
elm.classList.remove(name + "-leave-active");
elm.classList.remove(name + "-leave-to");
const finalize = () => {
elm.classList.remove(name + "-enter-active");
elm.classList.remove(name + "-enter-to");
@@ -123,6 +125,9 @@ QWeb.utils.transitionRemove = function(vn: VNode, name: string, rm: () => void)
elm.classList.add(name + "-leave");
elm.classList.add(name + "-leave-active");
const finalize = () => {
if (!elm.classList.contains(name + "-leave-active")) {
return;
}
elm.classList.remove(name + "-leave-active");
elm.classList.remove(name + "-leave-to");
rm();
+6 -4
View File
@@ -176,10 +176,12 @@ export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {
}
vnode.elm = api.createComment(vnode.text as string);
} else if (sel !== undefined) {
const elm = (vnode.elm =
isDef(data) && isDef((i = (data as VNodeData).ns))
? api.createElementNS(i, sel)
: api.createElement(sel));
const elm =
vnode.elm ||
(vnode.elm =
isDef(data) && isDef((i = (data as VNodeData).ns))
? api.createElementNS(i, sel)
: api.createElement(sel));
for (i = 0, iLen = cbs.create.length; i < iLen; ++i) cbs.create[i](emptyNode, vnode);
if (array(children)) {
for (i = 0, iLen = children.length; i < iLen; ++i) {
+56 -2
View File
@@ -27,11 +27,14 @@ exports[`animations t-transition combined with component 1`] = `
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| context['Child'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3);
const __patch3 = w3.__patch;
w3.__patch = fiber => {__patch3.call(w3, fiber); if(!w3.__owl__.transitionInserted){w3.__owl__.transitionInserted = true;utils.transitionInsert(w3.__owl__.vnode, 'chimay');}};
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;utils.transitionInsert(vn, 'chimay');},remove() {},destroy(vn) {let finalize = () => {
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {let finalize = () => {
w3.destroy();
};
delete w3.__owl__.transitionInserted;
utils.transitionRemove(vn, 'chimay', finalize);}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
@@ -71,11 +74,62 @@ exports[`animations t-transition combined with t-component and t-if 1`] = `
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| context['Child'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3);
const __patch3 = w3.__patch;
w3.__patch = fiber => {__patch3.call(w3, fiber); if(!w3.__owl__.transitionInserted){w3.__owl__.transitionInserted = true;utils.transitionInsert(w3.__owl__.vnode, 'chimay');}};
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;utils.transitionInsert(vn, 'chimay');},remove() {},destroy(vn) {let finalize = () => {
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {let finalize = () => {
w3.destroy();
};
delete w3.__owl__.transitionInserted;
utils.transitionRemove(vn, 'chimay', finalize);}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode);
w3.__owl__.pvnode = pvnode;
}
w3.__owl__.parentLastFiberId = extra.fiber.id;
}
return vn1;
}"
`;
exports[`animations t-transition combined with t-component, remove and re-add before transitionend 1`] = `
"function anonymous(context,extra
) {
let utils = this.constructor.utils;
let QWeb = this.constructor;
let parent = context;
let owner = context;
var h = this.h;
let c1 = [], p1 = {key:1};
var vn1 = h('div', p1, c1);
if (context['state'].flag) {
//COMPONENT
let k4 = \`__5__\`;
let w3 = k4 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k4]] : false;
let props3 = {};
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
w3.destroy();
w3 = false;
}
if (w3) {
w3.__updateProps(props3, extra.fiber, undefined, undefined);
let pvnode = w3.__owl__.pvnode;
c1.push(pvnode);
} else {
let componentKey3 = \`Child\`;
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| context['Child'];
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
w3 = new W3(parent, props3);
const __patch3 = w3.__patch;
w3.__patch = fiber => {__patch3.call(w3, fiber); if(!w3.__owl__.transitionInserted){w3.__owl__.transitionInserted = true;utils.transitionInsert(w3.__owl__.vnode, 'chimay');}};
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {let finalize = () => {
w3.destroy();
};
delete w3.__owl__.transitionInserted;
utils.transitionRemove(vn, 'chimay', finalize);}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
+72 -23
View File
@@ -1,13 +1,15 @@
import { Component, Env } from "../src/component/component";
import { QWeb } from "../src/qweb/index";
import { useState, useRef } from "../src/hooks";
import { xml } from "../src/tags";
import {
makeDeferred,
makeTestFixture,
makeTestEnv,
patchNextFrame,
renderToDOM,
unpatchNextFrame
unpatchNextFrame,
nextTick
} from "./helpers";
//------------------------------------------------------------------------------
@@ -321,30 +323,23 @@ describe("animations", () => {
});
test("t-transition combined with t-component, remove and re-add before transitionend", async () => {
expect.assertions(11);
expect.assertions(12);
env.qweb.addTemplates(
`<templates>
<div t-name="Parent">
<button t-on-click="toggle">Toggle</button>
<t t-if="state.flag" t-component="Child" t-transition="chimay"/>
</div>
<span t-name="Child">blue</span>
</templates>`
);
class Child extends Widget {}
class Child extends Widget {
static template = xml`<span>blue</span>`;
}
class Parent extends Widget {
static template = xml`
<div t-name="Parent">
<t t-if="state.flag" t-component="Child" t-transition="chimay"/>
</div>`;
static components = { Child };
state = useState({ flag: false });
toggle() {
this.state.flag = !this.state.flag;
}
}
const widget = new Parent();
await widget.mount(fixture);
let button = widget.el!.querySelector("button");
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
let def = makeDeferred();
let phase = "enter";
@@ -357,24 +352,78 @@ describe("animations", () => {
def.resolve();
});
// click display the span
button!.click();
// display the span
widget.state.flag = true;
await def; // wait for the mocked repaint to be done
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend")); // mock end of css transition
expect(fixture.innerHTML).toBe('<div><button>Toggle</button><span class="">blue</span></div>');
expect(fixture.innerHTML).toBe('<div><span class="">blue</span></div>');
// click to remove the span, and click again to re-add it before transitionend
def = makeDeferred();
phase = "leave";
button!.click();
widget.state.flag = false;
await def; // wait for the mocked repaint to be done
def = makeDeferred();
phase = "enter";
button!.click();
widget.state.flag = true;
await def; // wait for the mocked repaint to be done
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend")); // mock end of css transition
expect(fixture.innerHTML).toBe('<div><button>Toggle</button><span class="">blue</span></div>');
expect(fixture.innerHTML).toBe('<div><span class="" data-owl-key="__5__">blue</span></div>');
});
test("transitionInsert is called the correct amount of times", async () => {
const oldTransitionInsert = QWeb.utils.transitionInsert;
QWeb.utils.transitionInsert = jest.fn(oldTransitionInsert);
class Child extends Widget {
static template = xml`<span>blue</span>`;
}
class Parent extends Widget {
static template = xml`
<div t-name="Parent">
<Child t-if="state.flag" t-transition="chimay"/>
</div>`;
static components = { Child };
state = useState({ flag: false });
}
patchNextFrame(cb => cb());
const widget = new Parent();
await widget.mount(fixture);
widget.state.flag = true;
await nextTick();
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend"));
expect(fixture.innerHTML).toBe('<div><span class="">blue</span></div>');
expect(QWeb.utils.transitionInsert).toBeCalledTimes(1);
widget.state.flag = false;
await nextTick();
expect(fixture.innerHTML).toBe(
'<div><span class="chimay-leave-active chimay-leave-to" data-owl-key="__5__">blue</span></div>'
);
expect(QWeb.utils.transitionInsert).toBeCalledTimes(1);
widget.state.flag = true;
await nextTick();
expect(fixture.innerHTML).toBe(
'<div><span class="chimay-enter-active chimay-enter-to" data-owl-key="__5__">blue</span></div>'
);
expect(QWeb.utils.transitionInsert).toBeCalledTimes(2);
widget.state.flag = false;
await nextTick();
widget.state.flag = true;
await nextTick();
expect(QWeb.utils.transitionInsert).toBeCalledTimes(3);
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend"));
expect(fixture.innerHTML).toBe('<div><span class="" data-owl-key="__5__">blue</span></div>');
QWeb.utils.transitionInsert = oldTransitionInsert;
});
});
@@ -58,7 +58,7 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
w9 = new W9(parent, props9);
parent.__owl__.cmap[k10] = w9.__owl__.id;
let def8 = w9.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k10, hook: {insert(vn) { let nvn=w9.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w9.destroy();}}});
let pvnode = h('dummy', {key: k10, hook: {remove() {},destroy(vn) {w9.destroy();}}});
const fiber = w9.__owl__.currentFiber;
def8.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode);
@@ -102,7 +102,7 @@ exports[`basic widget properties t-key on a component with t-if, and a sibling c
w4 = new W4(parent, props4);
parent.__owl__.cmap[k5] = w4.__owl__.id;
let def3 = w4.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k5, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
let pvnode = h('dummy', {key: k5, hook: {remove() {},destroy(vn) {w4.destroy();}}});
const fiber = w4.__owl__.currentFiber;
def3.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode);
@@ -129,7 +129,7 @@ exports[`basic widget properties t-key on a component with t-if, and a sibling c
w8 = new W8(parent, props8);
parent.__owl__.cmap[k9] = w8.__owl__.id;
let def7 = w8.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k9, hook: {insert(vn) { let nvn=w8.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w8.destroy();}}});
let pvnode = h('dummy', {key: k9, hook: {remove() {},destroy(vn) {w8.destroy();}}});
const fiber = w8.__owl__.currentFiber;
def7.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode);
@@ -170,7 +170,7 @@ exports[`class and style attributes with t-component dynamic t-att-style is prop
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.style = _6;}};});
c1.push(pvnode);
@@ -214,7 +214,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;context.__owl__.refs[ref6] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref6];}}});
let pvnode = h('dummy', {key: k4, hook: {insert(vn) {context.__owl__.refs[ref6] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref6];}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){}};});
c1.push(pvnode);
@@ -272,7 +272,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;context.__owl__.refs[ref6] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref6];}}});
let pvnode = h('dummy', {key: k4, hook: {insert(vn) {context.__owl__.refs[ref6] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref6];}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){}};});
c1.push(pvnode);
@@ -342,7 +342,7 @@ exports[`composition sub components with some state rendered in a loop 1`] = `
w7 = new W7(parent, props7);
parent.__owl__.cmap[k8] = w7.__owl__.id;
let def6 = w7.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k8, hook: {insert(vn) { let nvn=w7.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});
let pvnode = h('dummy', {key: k8, hook: {remove() {},destroy(vn) {w7.destroy();}}});
const fiber = w7.__owl__.currentFiber;
def6.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode);
@@ -383,7 +383,7 @@ exports[`composition t-component with dynamic value 1`] = `
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode);
@@ -423,7 +423,7 @@ exports[`composition t-component with dynamic value 2 1`] = `
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode);
@@ -463,7 +463,7 @@ exports[`dynamic t-props basic use 1`] = `
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode);
@@ -507,7 +507,7 @@ exports[`other directives with t-component t-on with getter as handler 1`] = `
w4 = new W4(parent, props4);
parent.__owl__.cmap[k5] = w4.__owl__.id;
let def3 = w4.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k5, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
let pvnode = h('dummy', {key: k5, hook: {remove() {},destroy(vn) {w4.destroy();}}});
const fiber = w4.__owl__.currentFiber;
def3.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['handler'];if (fn) { fn.call(owner, e); } else { owner.handler; }});}};});
c1.push(pvnode);
@@ -547,7 +547,7 @@ exports[`other directives with t-component t-on with handler bound to argument 1
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, 3, e); } else { owner.onEv; }});}};});
c1.push(pvnode);
@@ -587,7 +587,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, {}, e); } else { owner.onEv; }});}};});
c1.push(pvnode);
@@ -627,7 +627,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, {}, e); } else { owner.onEv; }});}};});
c1.push(pvnode);
@@ -667,7 +667,7 @@ exports[`other directives with t-component t-on with handler bound to object 1`]
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, {val:3}, e); } else { owner.onEv; }});}};});
c1.push(pvnode);
@@ -711,7 +711,7 @@ exports[`other directives with t-component t-on with inline statement 1`] = `
w4 = new W4(parent, props4);
parent.__owl__.cmap[k5] = w4.__owl__.id;
let def3 = w4.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k5, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
let pvnode = h('dummy', {key: k5, hook: {remove() {},destroy(vn) {w4.destroy();}}});
const fiber = w4.__owl__.currentFiber;
def3.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['state.counter++'];if (fn) { fn.call(owner, e); } else { owner.state.counter++; }});}};});
c1.push(pvnode);
@@ -751,7 +751,7 @@ exports[`other directives with t-component t-on with no handler (only modifiers)
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, e); } else { owner.onEv; }});}};});
c1.push(pvnode);
@@ -791,7 +791,7 @@ exports[`other directives with t-component t-on with prevent and self modifiers
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {e.preventDefault();if (e.target !== vn.elm) {return}const fn = owner['onEv'];if (fn) { fn.call(owner, e); } else { owner.onEv; }});}};});
c1.push(pvnode);
@@ -831,7 +831,7 @@ exports[`other directives with t-component t-on with self and prevent modifiers
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (e.target !== vn.elm) {return}e.preventDefault();const fn = owner['onEv'];if (fn) { fn.call(owner, e); } else { owner.onEv; }});}};});
c1.push(pvnode);
@@ -871,7 +871,7 @@ exports[`other directives with t-component t-on with self modifier 1`] = `
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {const fn = owner['onEv1'];if (fn) { fn.call(owner, e); } else { owner.onEv1; }});vn.elm.addEventListener('ev-2', function (e) {if (e.target !== vn.elm) {return}const fn = owner['onEv2'];if (fn) { fn.call(owner, e); } else { owner.onEv2; }});}};});
c1.push(pvnode);
@@ -911,7 +911,7 @@ exports[`other directives with t-component t-on with stop and/or prevent modifie
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {e.stopPropagation();const fn = owner['onEv1'];if (fn) { fn.call(owner, e); } else { owner.onEv1; }});vn.elm.addEventListener('ev-2', function (e) {e.preventDefault();const fn = owner['onEv2'];if (fn) { fn.call(owner, e); } else { owner.onEv2; }});vn.elm.addEventListener('ev-3', function (e) {e.stopPropagation();e.preventDefault();const fn = owner['onEv3'];if (fn) { fn.call(owner, e); } else { owner.onEv3; }});}};});
c1.push(pvnode);
@@ -969,7 +969,7 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
w4 = new W4(parent, props4);
parent.__owl__.cmap[k5] = w4.__owl__.id;
let def3 = w4.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k5, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
let pvnode = h('dummy', {key: k5, hook: {remove() {},destroy(vn) {w4.destroy();}}});
const fiber = w4.__owl__.currentFiber;
def3.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode);
@@ -1026,7 +1026,7 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument
w7 = new W7(parent, props7);
parent.__owl__.cmap[k8] = w7.__owl__.id;
let def6 = w7.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k8, hook: {insert(vn) { let nvn=w7.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});
let pvnode = h('dummy', {key: k8, hook: {remove() {},destroy(vn) {w7.destroy();}}});
const fiber = w7.__owl__.currentFiber;
def6.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, arg10, e); } else { owner.onEv; }});}};});
c1.push(pvnode);
@@ -1318,7 +1318,7 @@ exports[`t-slot directive can define and call slots 1`] = `
parent.__owl__.cmap[k4] = w3.__owl__.id;
w3.__owl__.slotId = 1;
let def2 = w3.__prepare(extra.fiber, {}, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode);
@@ -1553,7 +1553,7 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
parent.__owl__.cmap[k10] = w9.__owl__.id;
w9.__owl__.slotId = 1;
let def8 = w9.__prepare(extra.fiber, Object.assign({}, scope), undefined);
let pvnode = h('dummy', {key: k10, hook: {insert(vn) { let nvn=w9.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w9.destroy();}}});
let pvnode = h('dummy', {key: k10, hook: {remove() {},destroy(vn) {w9.destroy();}}});
const fiber = w9.__owl__.currentFiber;
def8.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c7.push(pvnode);
@@ -1654,7 +1654,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
parent.__owl__.cmap[k11] = w10.__owl__.id;
w10.__owl__.slotId = 1;
let def9 = w10.__prepare(extra.fiber, Object.assign({}, scope), {_8});
let pvnode = h('dummy', {key: k11, hook: {insert(vn) { let nvn=w10.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w10.destroy();}}});
let pvnode = h('dummy', {key: k11, hook: {remove() {},destroy(vn) {w10.destroy();}}});
const fiber = w10.__owl__.currentFiber;
def9.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c7.push(pvnode);
@@ -1710,7 +1710,7 @@ exports[`t-slot directive slots are rendered with proper context, part 4 1`] = `
parent.__owl__.cmap[k5] = w4.__owl__.id;
w4.__owl__.slotId = 1;
let def3 = w4.__prepare(extra.fiber, {}, {_2});
let pvnode = h('dummy', {key: k5, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
let pvnode = h('dummy', {key: k5, hook: {remove() {},destroy(vn) {w4.destroy();}}});
const fiber = w4.__owl__.currentFiber;
def3.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode);
@@ -1782,7 +1782,7 @@ exports[`top level sub widgets basic use 1`] = `
w2 = new W2(parent, props2);
parent.__owl__.cmap[k3] = w2.__owl__.id;
let def1 = w2.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k3, hook: {insert(vn) { let nvn=w2.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w2.destroy();}}});
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}});
const fiber = w2.__owl__.currentFiber;
def1.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
utils.defineProxy(vn5, pvnode);
@@ -1824,7 +1824,7 @@ exports[`top level sub widgets can select a sub widget 1`] = `
w2 = new W2(parent, props2);
parent.__owl__.cmap[k3] = w2.__owl__.id;
let def1 = w2.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k3, hook: {insert(vn) { let nvn=w2.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w2.destroy();}}});
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}});
const fiber = w2.__owl__.currentFiber;
def1.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
utils.defineProxy(vn5, pvnode);
@@ -1854,7 +1854,7 @@ exports[`top level sub widgets can select a sub widget 1`] = `
w7 = new W7(parent, props7);
parent.__owl__.cmap[k8] = w7.__owl__.id;
let def6 = w7.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k8, hook: {insert(vn) { let nvn=w7.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});
let pvnode = h('dummy', {key: k8, hook: {remove() {},destroy(vn) {w7.destroy();}}});
const fiber = w7.__owl__.currentFiber;
def6.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
utils.defineProxy(vn10, pvnode);
@@ -29,7 +29,7 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
w3 = new W3(parent, props3);
parent.__owl__.cmap[k4] = w3.__owl__.id;
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber;
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode);
+211 -19
View File
@@ -1513,6 +1513,25 @@ describe("composition", () => {
"<div><span>11</span><span>2</span><span>13</span></div>"
);
});
test("three level of components with collapsing root nodes", async () => {
class GrandChild extends Component<any, any> {
static template = xml`<div>2</div>`;
}
class Child extends Component<any, any> {
static components = { GrandChild };
static template = xml`<GrandChild/>`;
}
class Parent extends Component<any, any> {
static components = { Child };
static template = xml`<Child></Child>`;
}
const app = new Parent();
await app.mount(fixture);
expect(fixture.innerHTML).toBe("<div>2</div>");
});
});
describe("props evaluation ", () => {
@@ -2136,6 +2155,40 @@ describe("other directives with t-component", () => {
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
});
test("t-on on nested components with collapsing root nodes", async () => {
const steps: string[] = [];
let grandChild;
class GrandChild extends Component<any, any> {
static template = xml`<span t-on-ev="_onEv"/>`;
constructor() {
super(...arguments);
grandChild = this;
}
_onEv() {
steps.push('GrandChild');
}
}
class Child extends Component<any, any> {
static template = xml`<GrandChild t-on-ev="_onEv"/>`;
static components = { GrandChild };
_onEv() {
steps.push('Child');
}
}
class Parent extends Component<any, any> {
static template = xml`<Child t-on-ev="_onEv"/>`;
static components = { Child };
_onEv() {
steps.push('Parent');
}
}
const parent = new Parent();
await parent.mount(fixture);
grandChild.trigger("ev");
expect(steps).toEqual(['GrandChild', 'Child', 'Parent']);
});
test("t-if works with t-component", async () => {
env.qweb.addTemplate("ParentWidget", `<div><t t-component="child" t-if="state.flag"/></div>`);
class Child extends Widget {}
@@ -2244,9 +2297,9 @@ describe("other directives with t-component", () => {
<t t-esc="state.val"/>
<t t-esc="props.val"/>
</span>`;
state = useState({ val: 'A' });
state = useState({ val: "A" });
mounted() {
this.state.val = 'B';
this.state.val = "B";
}
}
class ParentWidget extends Widget {
@@ -2366,10 +2419,6 @@ describe("random stuff/miscellaneous", () => {
steps.push(`${this.name}:__patch`);
super.__patch(vnode);
}
__mount(vnode, elm) {
steps.push(`${this.name}:__patch(from __mount)`);
return super.__mount(vnode, elm);
}
mounted() {
steps.push(`${this.name}:mounted`);
}
@@ -2466,15 +2515,15 @@ describe("random stuff/miscellaneous", () => {
"E:willStart",
"D:render",
"E:render",
"E:__patch",
"D:__patch",
"C:__patch",
"B:__patch",
"A:__patch",
"B:__patch(from __mount)",
"C:__patch(from __mount)",
"D:__patch(from __mount)",
"E:__patch(from __mount)",
"B:mounted",
"D:mounted",
"E:mounted",
"D:mounted",
"C:mounted",
"B:mounted",
"A:mounted"
]);
@@ -2491,12 +2540,12 @@ describe("random stuff/miscellaneous", () => {
"F:render",
"C:willPatch",
"D:willPatch",
"F:__patch",
"D:__patch",
"C:__patch",
"E:willUnmount",
"E:destroy",
"F:__patch(from __mount)",
"F:mounted",
"D:__patch",
"D:patched",
"C:patched"
]);
@@ -3599,6 +3648,43 @@ describe("async rendering", () => {
expect(Parent.prototype.__render).toHaveBeenCalledTimes(3);
});
test("concurrent renderings scenario 13", async () => {
let lastChild;
class Child extends Component<any, any> {
static template = xml`<span><t t-esc="state.val"/></span>`;
state = useState({ val: 0 });
mounted() {
if (lastChild) {
lastChild.state.val = 0;
}
lastChild = this;
this.state.val = 1;
}
}
class Parent extends Component<any, any> {
static template = xml`
<div>
<Child/>
<Child t-if="state.bool"/>
</div>`;
static components = { Child };
state = useState({ bool: false });
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>0</span></div>");
await nextTick(); // wait for changes triggered in mounted to be applied
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
parent.state.bool = true;
await nextTick(); // wait for this change to be applied
await nextTick(); // wait for changes triggered in mounted to be applied
expect(fixture.innerHTML).toBe("<div><span>0</span><span>1</span></div>");
});
test("change state and call manually render: no unnecessary rendering", async () => {
class Widget extends Component<any, any> {
static template = xml`<div><t t-esc="state.val"/></div>`;
@@ -4240,9 +4326,9 @@ describe("t-slot directive", () => {
test("slots in t-foreach and re-rendering", async () => {
class Child extends Widget {
static template = xml`<span><t t-esc="state.val"/><t t-slot="default"/></span>`;
state = useState({ val: 'A' });
state = useState({ val: "A" });
mounted() {
this.state.val = 'B';
this.state.val = "B";
}
}
class Parent extends Widget {
@@ -4269,9 +4355,9 @@ describe("t-slot directive", () => {
<t t-esc="state.val"/>
<t t-slot="default"/>
</span>`;
state = useState({ val: 'A' });
state = useState({ val: "A" });
mounted() {
this.state.val = 'B';
this.state.val = "B";
}
}
class ParentWidget extends Widget {
@@ -4752,7 +4838,7 @@ describe("component error handling (catchError)", () => {
expect(handler).toBeCalledTimes(1);
});
test("can catch an error in the initial call of a component render function", async () => {
test("can catch an error in the initial call of a component render function (parent mounted)", async () => {
const handler = jest.fn();
env.qweb.on("error", null, handler);
const consoleError = console.error;
@@ -4788,6 +4874,45 @@ describe("component error handling (catchError)", () => {
expect(handler).toBeCalledTimes(1);
});
test("can catch an error in the initial call of a component render function (parent updated)", async () => {
const handler = jest.fn();
env.qweb.on("error", null, handler);
const consoleError = console.error;
console.error = jest.fn();
class ErrorComponent extends Component<any, any> {
static template = xml`<div>hey<t t-esc="state.this.will.crash"/></div>`;
}
class ErrorBoundary extends Component<any, any> {
static template = xml`
<div>
<t t-if="state.error">Error handled</t>
<t t-else="1"><t t-slot="default" /></t>
</div>`;
state = useState({ error: false });
catchError() {
this.state.error = true;
}
}
class App extends Component<any, any> {
static template = xml`
<div>
<ErrorBoundary t-if="state.flag"><ErrorComponent /></ErrorBoundary>
</div>`;
state = useState({ flag: false });
static components = { ErrorBoundary, ErrorComponent };
}
const app = new App();
await app.mount(fixture);
app.state.flag = true;
await nextTick();
expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>");
expect(console.error).toBeCalledTimes(0);
console.error = consoleError;
expect(handler).toBeCalledTimes(1);
});
test("can catch an error in the constructor call of a component render function", async () => {
const handler = jest.fn();
env.qweb.on("error", null, handler);
@@ -5458,6 +5583,73 @@ describe("unmounting and remounting", () => {
expect(TestWidget.prototype.__patch).toHaveBeenCalledTimes(2);
expect(steps).toEqual([2, 2, 3]);
});
test("change state while component is unmounted", async () => {
let child;
class Child extends Component<any, any> {
static template = xml`<span t-esc="state.val"/>`;
state = useState({
val: "C1"
});
constructor(parent, props) {
super(parent, props);
child = this;
}
}
class Parent extends Component<any, any> {
static components = { Child };
static template = xml`<div><t t-esc="state.val"/><Child/></div>`;
state = useState({ val: "P1" });
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div>P1<span>C1</span></div>");
parent.unmount();
expect(fixture.innerHTML).toBe("");
parent.state.val = "P2";
child.state.val = "C2";
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div>P2<span>C2</span></div>");
});
test("unmount component during a re-rendering", async () => {
const def = makeDeferred();
class Child extends Widget {
static template = xml`<span><t t-esc="props.val"/></span>`;
willUpdateProps() {
return def;
}
}
Child.prototype.__render = jest.fn(Child.prototype.__render);
class Parent extends Widget {
static template = xml`<div><Child val="state.val"/></div>`;
static components = { Child };
state = useState({ val: 1 });
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
expect(Child.prototype.__render).toBeCalledTimes(1);
parent.state.val = 2;
await nextTick();
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
parent.unmount();
expect(fixture.innerHTML).toBe("");
def.resolve();
await nextTick();
expect(fixture.innerHTML).toBe("");
expect(Child.prototype.__render).toBeCalledTimes(1);
});
});
describe("dynamic root nodes", () => {
+2 -1
View File
@@ -702,7 +702,8 @@ describe("t-call (template calling", () => {
</div>
</templates>
`);
const expected = "<div><div><div><div><span>cascade 2</span><span>cascade 1</span><span>cascade 0</span><span>hey</span> <span>yay</span></div></div></div></div>";
const expected =
"<div><div><div><div><span>cascade 2</span><span>cascade 1</span><span>cascade 0</span><span>hey</span> <span>yay</span></div></div></div></div>";
expect(renderToString(qweb, "main")).toBe(expected);
});
@@ -32,7 +32,7 @@ exports[`RouteComponent can render simple cases 1`] = `
w8 = new W8(parent, props8);
parent.__owl__.cmap[k9] = w8.__owl__.id;
let def7 = w8.__prepare(extra.fiber, undefined, undefined);
let pvnode = h('dummy', {key: k9, hook: {insert(vn) { let nvn=w8.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w8.destroy();}}});
let pvnode = h('dummy', {key: k9, hook: {remove() {},destroy(vn) {w8.destroy();}}});
const fiber = w8.__owl__.currentFiber;
def7.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
utils.defineProxy(vn11, pvnode);