diff --git a/doc/reference/component.md b/doc/reference/component.md index 858cfff0..af850295 100644 --- a/doc/reference/component.md +++ b/doc/reference/component.md @@ -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 diff --git a/src/component/component.ts b/src/component/component.ts index 50315a5f..46ed3727 100644 --- a/src/component/component.ts +++ b/src/component/component.ts @@ -295,13 +295,12 @@ export class Component { 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 { __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 { } 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 { * 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 { } } - /** - * 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) { - (vnode).data.class = Object.assign((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) */ diff --git a/src/component/directive.ts b/src/component/directive.ts index f33d1d56..ee29500e 100644 --- a/src/component/directive.ts +++ b/src/component/directive.ts @@ -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( diff --git a/src/component/fiber.ts b/src/component/fiber.ts index 2b583598..1e4c967f 100644 --- a/src/component/fiber.ts +++ b/src/component/fiber.ts @@ -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 = 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(); } } } diff --git a/src/component/scheduler.ts b/src/component/scheduler.ts index cb83307b..5013702b 100644 --- a/src/component/scheduler.ts +++ b/src/component/scheduler.ts @@ -26,6 +26,9 @@ export class Scheduler { } addFiber(fiber): Promise { + // 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); diff --git a/src/qweb/compilation_context.ts b/src/qweb/compilation_context.ts index 6fe8b319..8e2b25d2 100644 --- a/src/qweb/compilation_context.ts +++ b/src/qweb/compilation_context.ts @@ -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 { diff --git a/src/qweb/extensions.ts b/src/qweb/extensions.ts index c3176c1a..76eaf533 100644 --- a/src/qweb/extensions.ts +++ b/src/qweb/extensions.ts @@ -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(); diff --git a/src/vdom/vdom.ts b/src/vdom/vdom.ts index 58845c3e..d7533588 100644 --- a/src/vdom/vdom.ts +++ b/src/vdom/vdom.ts @@ -176,10 +176,12 @@ export function init(modules: Array>, 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) { diff --git a/tests/__snapshots__/animations.test.ts.snap b/tests/__snapshots__/animations.test.ts.snap index db65d34d..24609375 100644 --- a/tests/__snapshots__/animations.test.ts.snap +++ b/tests/__snapshots__/animations.test.ts.snap @@ -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; }); diff --git a/tests/animations.test.ts b/tests/animations.test.ts index 9203a2bf..e3c3ff9e 100644 --- a/tests/animations.test.ts +++ b/tests/animations.test.ts @@ -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( - ` -
- - -
- blue -
` - ); - class Child extends Widget {} + class Child extends Widget { + static template = xml`blue`; + } class Parent extends Widget { + static template = xml` +
+ +
`; 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('
blue
'); + expect(fixture.innerHTML).toBe('
blue
'); // 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('
blue
'); + expect(fixture.innerHTML).toBe('
blue
'); + }); + + 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`blue`; + } + class Parent extends Widget { + static template = xml` +
+ +
`; + 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('
blue
'); + expect(QWeb.utils.transitionInsert).toBeCalledTimes(1); + + widget.state.flag = false; + await nextTick(); + expect(fixture.innerHTML).toBe( + '
blue
' + ); + expect(QWeb.utils.transitionInsert).toBeCalledTimes(1); + + widget.state.flag = true; + await nextTick(); + expect(fixture.innerHTML).toBe( + '
blue
' + ); + 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('
blue
'); + QWeb.utils.transitionInsert = oldTransitionInsert; }); }); diff --git a/tests/component/__snapshots__/component.test.ts.snap b/tests/component/__snapshots__/component.test.ts.snap index 0b3c1ad5..3c1a5b86 100644 --- a/tests/component/__snapshots__/component.test.ts.snap +++ b/tests/component/__snapshots__/component.test.ts.snap @@ -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); diff --git a/tests/component/__snapshots__/props_validation.test.ts.snap b/tests/component/__snapshots__/props_validation.test.ts.snap index 5a30f5c0..258ce7ae 100644 --- a/tests/component/__snapshots__/props_validation.test.ts.snap +++ b/tests/component/__snapshots__/props_validation.test.ts.snap @@ -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); diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts index cefc59b8..c2ace746 100644 --- a/tests/component/component.test.ts +++ b/tests/component/component.test.ts @@ -1513,6 +1513,25 @@ describe("composition", () => { "
11213
" ); }); + + test("three level of components with collapsing root nodes", async () => { + class GrandChild extends Component { + static template = xml`
2
`; + } + class Child extends Component { + static components = { GrandChild }; + static template = xml``; + } + class Parent extends Component { + static components = { Child }; + static template = xml``; + } + + const app = new Parent(); + await app.mount(fixture); + + expect(fixture.innerHTML).toBe("
2
"); + }); }); 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 { + static template = xml``; + constructor() { + super(...arguments); + grandChild = this; + } + _onEv() { + steps.push('GrandChild'); + } + } + class Child extends Component { + static template = xml``; + static components = { GrandChild }; + _onEv() { + steps.push('Child'); + } + } + class Parent extends Component { + static template = xml``; + 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", `
`); class Child extends Widget {} @@ -2244,9 +2297,9 @@ describe("other directives with t-component", () => {
`; - 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 { + static template = xml``; + state = useState({ val: 0 }); + mounted() { + if (lastChild) { + lastChild.state.val = 0; + } + lastChild = this; + this.state.val = 1; + } + } + + class Parent extends Component { + static template = xml` +
+ + +
`; + static components = { Child }; + state = useState({ bool: false }); + } + + const parent = new Parent(); + await parent.mount(fixture); + expect(fixture.innerHTML).toBe("
0
"); + + await nextTick(); // wait for changes triggered in mounted to be applied + expect(fixture.innerHTML).toBe("
1
"); + + 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("
01
"); + }); + test("change state and call manually render: no unnecessary rendering", async () => { class Widget extends Component { static template = xml`
`; @@ -4240,9 +4326,9 @@ describe("t-slot directive", () => { test("slots in t-foreach and re-rendering", async () => { class Child extends Widget { static template = xml``; - 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", () => { `; - 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 { + static template = xml`
hey
`; + } + class ErrorBoundary extends Component { + static template = xml` +
+ Error handled + +
`; + state = useState({ error: false }); + + catchError() { + this.state.error = true; + } + } + class App extends Component { + static template = xml` +
+ +
`; + 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("
Error handled
"); + + 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 { + static template = xml``; + state = useState({ + val: "C1" + }); + constructor(parent, props) { + super(parent, props); + child = this; + } + } + + class Parent extends Component { + static components = { Child }; + static template = xml`
`; + state = useState({ val: "P1" }); + } + + const parent = new Parent(); + await parent.mount(fixture); + expect(fixture.innerHTML).toBe("
P1C1
"); + + parent.unmount(); + expect(fixture.innerHTML).toBe(""); + + parent.state.val = "P2"; + child.state.val = "C2"; + + await parent.mount(fixture); + expect(fixture.innerHTML).toBe("
P2C2
"); + }); + + test("unmount component during a re-rendering", async () => { + const def = makeDeferred(); + class Child extends Widget { + static template = xml``; + willUpdateProps() { + return def; + } + } + Child.prototype.__render = jest.fn(Child.prototype.__render); + + class Parent extends Widget { + static template = xml`
`; + static components = { Child }; + state = useState({ val: 1 }); + } + + const parent = new Parent(); + await parent.mount(fixture); + expect(fixture.innerHTML).toBe("
1
"); + expect(Child.prototype.__render).toBeCalledTimes(1); + + parent.state.val = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("
1
"); + + parent.unmount(); + expect(fixture.innerHTML).toBe(""); + + def.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe(""); + expect(Child.prototype.__render).toBeCalledTimes(1); + }); }); describe("dynamic root nodes", () => { diff --git a/tests/qweb/qweb.test.ts b/tests/qweb/qweb.test.ts index 6a45f66d..01e8de53 100644 --- a/tests/qweb/qweb.test.ts +++ b/tests/qweb/qweb.test.ts @@ -702,7 +702,8 @@ describe("t-call (template calling", () => { `); - const expected = "
cascade 2cascade 1cascade 0hey yay
"; + const expected = + "
cascade 2cascade 1cascade 0hey yay
"; expect(renderToString(qweb, "main")).toBe(expected); }); diff --git a/tests/router/__snapshots__/route_component.test.ts.snap b/tests/router/__snapshots__/route_component.test.ts.snap index 97ec99b2..7923dbf9 100644 --- a/tests/router/__snapshots__/route_component.test.ts.snap +++ b/tests/router/__snapshots__/route_component.test.ts.snap @@ -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);