From 0290f63ba365678a4505c415795a947afe1a7384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Wed, 3 Feb 2021 13:12:00 +0100 Subject: [PATCH] [FIX] component: various issues while mounting manually components The initial problem solved by this commit is that it was possible to get into a situation where a mounting/rendering was started, then the component was updated, but then another mounting operation begins, and it tries to reuse the previous rendering operation, which is no longer uptodate. The underlying issue is that Owl did not track properly the various internal state change of a component. These issue should be solved by the introduction of the status enum, which currently tracks 6 possible states: - CREATED - WILLSTARTED - RENDERED - MOUNTED - UNMOUNTED - DESTROYED This status number replaces the isMounted and isDestroyed boolean flags. It has the advantage of making sure that the component is in a consistent state (it is no longer possible to be destroyed and mounted, for example) Another advantage is that it gives us an easy way to track the fact that a component has been rendered, but is not in the DOM. This is a subtle situation where some various events can happen, and we need to be able to react to that case. Note that there is a change of behaviour: if a component is mounted in a specific target, then before the mounting is complete, the component is mounted in another target, we no longer reject the first mounting operation. --- src/component/component.ts | 158 +++++++++--------- src/component/directive.ts | 3 +- src/component/fiber.ts | 23 ++- src/qweb/extensions.ts | 3 +- .../__snapshots__/class_style.test.ts.snap | 2 +- .../__snapshots__/component.test.ts.snap | 40 ++--- .../__snapshots__/slots.test.ts.snap | 6 +- tests/component/async.test.ts | 10 +- tests/component/component.test.ts | 27 ++- tests/component/error_handling.test.ts | 4 +- tests/component/un_mounting.test.ts | 42 ++++- tests/helpers.ts | 4 +- tests/qweb/__snapshots__/qweb.test.ts.snap | 52 +++--- tests/router/__snapshots__/link.test.ts.snap | 2 +- tools/debug.js | 2 +- 15 files changed, 212 insertions(+), 166 deletions(-) diff --git a/src/component/component.ts b/src/component/component.ts index 24cb1a351..dabf88970 100644 --- a/src/component/component.ts +++ b/src/component/component.ts @@ -44,6 +44,15 @@ interface MountOptions { position?: MountPosition; } +export const enum STATUS { + CREATED, + WILLSTARTED, // willstart has been called + RENDERED, // first render is completed (so, vnode is now defined) + MOUNTED, // is ready, and in DOM. It has a valid el + UNMOUNTED, // has a valid el, but is not in DOM + DESTROYED, +} + /** * This is mostly an internal detail of implementation. The Meta interface is * useful to typecheck and describe the internal keys used by Owl to manage the @@ -56,8 +65,7 @@ interface Internal { depth: number; vnode: VNode | null; pvnode: VNode | null; - isMounted: boolean; - isDestroyed: boolean; + status: STATUS; // parent and children keys are obviously useful to setup the parent-children // relationship. @@ -164,16 +172,18 @@ export class Component { this.env.browser = browser; } this.env.qweb.on("update", this, () => { - if (this.__owl__.isMounted) { - this.render(true); - } - if (this.__owl__.isDestroyed) { - // this is unlikely to happen, but if a root widget is destroyed, - // we want to remove our subscription. The usual way to do that - // would be to perform some check in the destroy method, but since - // it is very performance sensitive, and since this is a rare event, - // we simply do it lazily - this.env.qweb.off("update", this); + switch (this.__owl__.status) { + case STATUS.MOUNTED: + this.render(true); + break; + case STATUS.DESTROYED: + // this is unlikely to happen, but if a root widget is destroyed, + // we want to remove our subscription. The usual way to do that + // would be to perform some check in the destroy method, but since + // it is very performance sensitive, and since this is a rare event, + // we simply do it lazily + this.env.qweb.off("update", this); + break; } }); depth = 0; @@ -186,8 +196,7 @@ export class Component { depth: depth, vnode: null, pvnode: null, - isMounted: false, - isDestroyed: false, + status: STATUS.CREATED, parent: parent || null, children: {}, cmap: {}, @@ -317,49 +326,49 @@ export class Component { * Note that a component can be mounted an unmounted several times */ async mount(target: HTMLElement | DocumentFragment, options: MountOptions = {}): Promise { + if (!(target instanceof HTMLElement || target instanceof DocumentFragment)) { + 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 position = options.position || "last-child"; const __owl__ = this.__owl__; - if (__owl__.isMounted) { - if (position !== "self" && this.el!.parentNode !== target) { - // in this situation, we are trying to mount a component on a different - // target. In this case, we need to unmount first, otherwise it will - // not work. - this.unmount(); - } else { - return Promise.resolve(); + const currentFiber = __owl__.currentFiber; + + switch (__owl__.status) { + case STATUS.CREATED: { + const fiber = new Fiber(null, this, true, target, position); + fiber.shouldPatch = false; + this.__prepareAndRender(fiber, () => {}); + return scheduler.addFiber(fiber); } - } - if (__owl__.isDestroyed) { - throw new Error("Cannot mount a destroyed component"); - } - if (__owl__.currentFiber) { - const currentFiber = __owl__.currentFiber; - if (!currentFiber.target && !currentFiber.position) { - // this means we have a pending rendering, but it was a render operation, - // not a mount operation. We can simply update the fiber with the target - // and the position + case STATUS.WILLSTARTED: + case STATUS.RENDERED: currentFiber.target = target; currentFiber.position = position; return scheduler.addFiber(currentFiber); - } else if (currentFiber.target === target && currentFiber.position === position) { - return scheduler.addFiber(currentFiber); - } else { - scheduler.rejectFiber(currentFiber, "Mounting operation cancelled"); + + case STATUS.UNMOUNTED: { + const fiber = new Fiber(null, this, true, target, position); + fiber.shouldPatch = false; + this.__render(fiber); + return scheduler.addFiber(fiber); } + + case STATUS.MOUNTED: { + if (position !== "self" && this.el!.parentNode !== target) { + const fiber = new Fiber(null, this, true, target, position); + fiber.shouldPatch = false; + this.__render(fiber); + return scheduler.addFiber(fiber); + } else { + return Promise.resolve(); + } + } + + case STATUS.DESTROYED: + throw new Error("Cannot mount a destroyed component"); } - if (!(target instanceof HTMLElement || target instanceof DocumentFragment)) { - 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, true, target, position); - fiber.shouldPatch = false; - if (!__owl__.vnode) { - this.__prepareAndRender(fiber, () => {}); - } else { - this.__render(fiber); - } - return scheduler.addFiber(fiber); } /** @@ -367,7 +376,7 @@ export class Component { * to call willUnmount calls and remove the component from the DOM. */ unmount() { - if (this.__owl__.isMounted) { + if (this.__owl__.status === STATUS.MOUNTED) { this.__callWillUnmount(); this.el!.remove(); } @@ -394,11 +403,11 @@ export class Component { // if we aren't mounted at this point, it implies that there is a // currentFiber that is already rendered (isRendered is true), so we are // about to be mounted - const isMounted = __owl__.isMounted; + const status = __owl__.status; const fiber = new Fiber(null, this, force, null, null); Promise.resolve().then(() => { - if (__owl__.isMounted || !isMounted) { - if (fiber.isCompleted) { + if (__owl__.status === STATUS.MOUNTED || status !== STATUS.MOUNTED) { + if (fiber.isCompleted || fiber.isRendered) { return; } this.__render(fiber); @@ -424,7 +433,7 @@ export class Component { */ destroy() { const __owl__ = this.__owl__; - if (!__owl__.isDestroyed) { + if (__owl__.status !== STATUS.DESTROYED) { const el = this.el; this.__destroy(__owl__.parent); if (el) { @@ -469,13 +478,12 @@ export class Component { */ __destroy(parent: Component | null) { const __owl__ = this.__owl__; - const isMounted = __owl__.isMounted; - if (isMounted) { + if (__owl__.status === STATUS.MOUNTED) { if (__owl__.willUnmountCB) { __owl__.willUnmountCB(); } this.willUnmount(); - __owl__.isMounted = false; + __owl__.status = STATUS.UNMOUNTED; } const children = __owl__.children; for (let key in children) { @@ -486,7 +494,7 @@ export class Component { delete parent.__owl__.children[id]; __owl__.parent = null; } - __owl__.isDestroyed = true; + __owl__.status = STATUS.DESTROYED; delete __owl__.vnode; if (__owl__.currentFiber) { __owl__.currentFiber.isCompleted = true; @@ -496,7 +504,7 @@ export class Component { __callMounted() { const __owl__ = this.__owl__; - __owl__.isMounted = true; + __owl__.status = STATUS.MOUNTED; __owl__.currentFiber = null; this.mounted(); if (__owl__.mountedCB) { @@ -510,7 +518,7 @@ export class Component { __owl__.willUnmountCB(); } this.willUnmount(); - __owl__.isMounted = false; + __owl__.status = STATUS.UNMOUNTED; if (__owl__.currentFiber) { __owl__.currentFiber.isCompleted = true; __owl__.currentFiber.root.counter = 0; @@ -518,7 +526,7 @@ export class Component { const children = __owl__.children; for (let id in children) { const comp = children[id]; - if (comp.__owl__.isMounted) { + if (comp.__owl__.status === STATUS.MOUNTED) { comp.__callWillUnmount(); } } @@ -639,18 +647,25 @@ export class Component { } return p._template; } + async __prepareAndRender(fiber: Fiber, cb: CallableFunction) { try { - await Promise.all([this.willStart(), this.__owl__.willStartCB && this.__owl__.willStartCB()]); + const proms = Promise.all([ + this.willStart(), + this.__owl__.willStartCB && this.__owl__.willStartCB(), + ]); + this.__owl__.status = STATUS.WILLSTARTED; + await proms; + if (this.__owl__.status === STATUS.DESTROYED) { + return Promise.resolve(); + } } catch (e) { fiber.handleError(e); return Promise.resolve(); } - if (this.__owl__.isDestroyed) { - return Promise.resolve(); - } if (!fiber.isCompleted) { this.__render(fiber); + this.__owl__.status = STATUS.RENDERED; cb(); } } @@ -673,7 +688,7 @@ export class Component { for (let childKey in __owl__.children) { const child = __owl__.children[childKey]; const childOwl = child.__owl__; - if (!childOwl.isMounted && childOwl.parentLastFiberId < fiber.id) { + if (childOwl.status !== STATUS.MOUNTED && childOwl.parentLastFiberId < fiber.id) { // we only do here a "soft" destroy, meaning that we leave the child // dom node alone, without removing it. Most of the time, it does not // matter, because the child component is already unmounted. However, @@ -720,17 +735,6 @@ export class Component { } } - /** - * Only called by qweb t-component directive (when t-keepalive is set) - */ - __remount() { - const __owl__ = this.__owl__; - if (!__owl__.isMounted) { - __owl__.isMounted = true; - this.mounted(); - } - } - /** * Apply default props (only top level). * diff --git a/src/component/directive.ts b/src/component/directive.ts index 7d8e7fb72..ff4c28ac0 100644 --- a/src/component/directive.ts +++ b/src/component/directive.ts @@ -1,6 +1,7 @@ import { QWeb } from "../qweb/index"; import { INTERP_REGEXP } from "../qweb/compilation_context"; import { makeHandlerCode, MODS_CODE } from "../qweb/extensions"; +import { STATUS } from "./component"; //------------------------------------------------------------------------------ // t-component @@ -361,7 +362,7 @@ QWeb.addDirective({ // need to update component let styleCode = ""; if (tattStyle) { - styleCode = `.then(()=>{if (w${componentID}.__owl__.isDestroyed) {return};w${componentID}.el.style=${tattStyle};});`; + styleCode = `.then(()=>{if (w${componentID}.__owl__.status === ${STATUS.DESTROYED}) {return};w${componentID}.el.style=${tattStyle};});`; } ctx.addLine( `w${componentID}.__updateProps(props${componentID}, extra.fiber, ${scope})${styleCode};` diff --git a/src/component/fiber.ts b/src/component/fiber.ts index 9433f95ff..c6b47bdc3 100644 --- a/src/component/fiber.ts +++ b/src/component/fiber.ts @@ -1,5 +1,5 @@ import { h, VNode } from "../vdom/index"; -import { Component, MountPosition } from "./component"; +import { Component, MountPosition, STATUS } from "./component"; import { scheduler } from "./scheduler"; /** @@ -107,6 +107,8 @@ export class Fiber { */ _reuseFiber(oldFiber: Fiber) { oldFiber.cancel(); // cancel children fibers + oldFiber.target = this.target || oldFiber.target; + oldFiber.position = this.position || oldFiber.position; oldFiber.isCompleted = false; // keep the root fiber alive oldFiber.isRendered = false; // the fiber has to be re-rendered if (oldFiber.child) { @@ -188,8 +190,8 @@ export class Fiber { complete() { let component = this.component; this.isCompleted = true; - const { isMounted, isDestroyed } = component.__owl__; - if (isDestroyed) { + const status = component.__owl__.status; + if (status === STATUS.DESTROYED) { return; } @@ -203,7 +205,7 @@ export class Fiber { const patchLen = patchQueue.length; // call willPatch hook on each fiber of patchQueue - if (isMounted) { + if (status === STATUS.MOUNTED) { for (let i = 0; i < patchLen; i++) { const fiber = patchQueue[i]; if (fiber.shouldPatch) { @@ -253,8 +255,9 @@ export class Fiber { component.__owl__.pvnode!.elm = component.__owl__.vnode!.elm; } } - if (fiber === component.__owl__.currentFiber) { - component.__owl__.currentFiber = null; + const compOwl = component.__owl__; + if (fiber === compOwl.currentFiber) { + compOwl.currentFiber = null; } } @@ -274,7 +277,7 @@ export class Fiber { } // call patched/mounted hook on each fiber of (reversed) patchQueue - if (isMounted || inDOM) { + if (status === STATUS.MOUNTED || inDOM) { for (let i = patchLen - 1; i >= 0; i--) { const fiber = patchQueue[i]; component = fiber.component; @@ -287,6 +290,12 @@ export class Fiber { component.__callMounted(); } } + } else { + for (let i = patchLen - 1; i >= 0; i--) { + const fiber = patchQueue[i]; + component = fiber.component; + component.__owl__.status = STATUS.UNMOUNTED; + } } } diff --git a/src/qweb/extensions.ts b/src/qweb/extensions.ts index cea02edb8..dc0f4fc5a 100644 --- a/src/qweb/extensions.ts +++ b/src/qweb/extensions.ts @@ -1,3 +1,4 @@ +import { STATUS } from "../component/component"; import { VNode } from "../vdom/index"; import { INTERP_REGEXP } from "./compilation_context"; import { QWeb } from "./qweb"; @@ -76,7 +77,7 @@ export function makeHandlerCode( code = ctx.captureExpression(value); } const modCode = mods.map((mod) => modcodes[mod]).join(""); - let handler = `function (e) {if (context.__owl__.isDestroyed){return}${modCode}${code}}`; + let handler = `function (e) {if (context.__owl__.status === ${STATUS.DESTROYED}){return}${modCode}${code}}`; if (putInCache) { const key = ctx.generateTemplateKey(event); ctx.addLine(`extra.handlers[${key}] = extra.handlers[${key}] || ${handler};`); diff --git a/tests/component/__snapshots__/class_style.test.ts.snap b/tests/component/__snapshots__/class_style.test.ts.snap index 619db63b4..7aa536462 100644 --- a/tests/component/__snapshots__/class_style.test.ts.snap +++ b/tests/component/__snapshots__/class_style.test.ts.snap @@ -20,7 +20,7 @@ exports[`class and style attributes with t-component dynamic t-att-style is prop w2 = false; } if (w2) { - w2.__updateProps(props2, extra.fiber, undefined).then(()=>{if (w2.__owl__.isDestroyed) {return};w2.el.style=_4;});; + w2.__updateProps(props2, extra.fiber, undefined).then(()=>{if (w2.__owl__.status === 5) {return};w2.el.style=_4;});; let pvnode = w2.__owl__.pvnode; c1.push(pvnode); } else { diff --git a/tests/component/__snapshots__/component.test.ts.snap b/tests/component/__snapshots__/component.test.ts.snap index 7eee873e9..a78b1ced5 100644 --- a/tests/component/__snapshots__/component.test.ts.snap +++ b/tests/component/__snapshots__/component.test.ts.snap @@ -444,7 +444,7 @@ exports[`composition t-ref on a node, and t-on-click 2`] = ` if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} w2 = new W2(parent, props2); parent.__owl__.cmap['__3__'] = w2.__owl__.id; - let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('click', function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['doSomething'](e);});}});}); + let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('click', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](e);});}});}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); c1.push(pvnode); w2.__owl__.pvnode = pvnode; @@ -579,7 +579,7 @@ exports[`other directives with t-component t-on expression captured in t-foreach c6.push(vn7); const otherState_8 = scope['otherState']; const iter_8 = scope.iter; - p7.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}otherState_8.vals.push(iter_8+'_'+iter_8)}; + p7.on['click'] = function (e) {if (context.__owl__.status === 5){return}otherState_8.vals.push(iter_8+'_'+iter_8)}; c7.push({text: \`expr\`}); utils.getScope(scope, 'iter').iter = scope.iter+1; } @@ -630,7 +630,7 @@ exports[`other directives with t-component t-on expression in t-foreach 1`] = ` c6.push(vn9); const otherState_10 = scope['otherState']; const val_10 = scope['val']; - p9.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}otherState_10.vals.push(val_10)}; + p9.on['click'] = function (e) {if (context.__owl__.status === 5){return}otherState_10.vals.push(val_10)}; c9.push({text: \`Expr\`}); } scope = _origScope5; @@ -684,7 +684,7 @@ exports[`other directives with t-component t-on expression in t-foreach with t-s const otherState_10 = scope['otherState']; const val_10 = scope['val']; const bossa_10 = scope.bossa; - p9.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}otherState_10.vals.push(val_10+'_'+bossa_10)}; + p9.on['click'] = function (e) {if (context.__owl__.status === 5){return}otherState_10.vals.push(val_10+'_'+bossa_10)}; c9.push({text: \`Expr\`}); } scope = _origScope5; @@ -734,7 +734,7 @@ exports[`other directives with t-component t-on method call in t-foreach 1`] = ` let vn9 = h('button', p9, c9); c6.push(vn9); let args10 = [scope['val']]; - p9.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['addVal'](...args10, e);}; + p9.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['addVal'](...args10, e);}; c9.push({text: \`meth call\`}); } scope = _origScope5; @@ -770,7 +770,7 @@ exports[`other directives with t-component t-on with .capture modifier 1`] = ` if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} w2 = new W2(parent, props2); parent.__owl__.cmap['__3__'] = w2.__owl__.id; - let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('click', function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['capture'](e);}, true);}});}); + let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('click', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['capture'](e);}, true);}});}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); c1.push(pvnode); w2.__owl__.pvnode = pvnode; @@ -812,7 +812,7 @@ exports[`other directives with t-component t-on with getter as handler 1`] = ` if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')} w3 = new W3(parent, props3); parent.__owl__.cmap['__4__'] = w3.__owl__.id; - let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['handler'](e);});}});}); + let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['handler'](e);});}});}); let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; @@ -851,7 +851,7 @@ exports[`other directives with t-component t-on with handler bound to argument 1 if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} w2 = new W2(parent, props2); parent.__owl__.cmap['__3__'] = w2.__owl__.id; - let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['onEv'](...args4, e);});}});}); + let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args4, e);});}});}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); c1.push(pvnode); w2.__owl__.pvnode = pvnode; @@ -890,7 +890,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} w2 = new W2(parent, props2); parent.__owl__.cmap['__3__'] = w2.__owl__.id; - let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['onEv'](...args4, e);});}});}); + let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args4, e);});}});}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); c1.push(pvnode); w2.__owl__.pvnode = pvnode; @@ -929,7 +929,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} w2 = new W2(parent, props2); parent.__owl__.cmap['__3__'] = w2.__owl__.id; - let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['onEv'](...args4, e);});}});}); + let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args4, e);});}});}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); c1.push(pvnode); w2.__owl__.pvnode = pvnode; @@ -968,7 +968,7 @@ exports[`other directives with t-component t-on with handler bound to object 1`] if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} w2 = new W2(parent, props2); parent.__owl__.cmap['__3__'] = w2.__owl__.id; - let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['onEv'](...args4, e);});}});}); + let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args4, e);});}});}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); c1.push(pvnode); w2.__owl__.pvnode = pvnode; @@ -1011,7 +1011,7 @@ exports[`other directives with t-component t-on with inline statement 1`] = ` if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')} w3 = new W3(parent, props3); parent.__owl__.cmap['__4__'] = w3.__owl__.id; - let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.isDestroyed){return}state_5.counter++});}});}); + let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}state_5.counter++});}});}); let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}}); c1.push(pvnode); w3.__owl__.pvnode = pvnode; @@ -1049,7 +1049,7 @@ exports[`other directives with t-component t-on with no handler (only modifiers) if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} w2 = new W2(parent, props2); parent.__owl__.cmap['__3__'] = w2.__owl__.id; - let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['onEv'](e);});}});}); + let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](e);});}});}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); c1.push(pvnode); w2.__owl__.pvnode = pvnode; @@ -1087,7 +1087,7 @@ exports[`other directives with t-component t-on with prevent and self modifiers if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} w2 = new W2(parent, props2); parent.__owl__.cmap['__3__'] = w2.__owl__.id; - let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.isDestroyed){return}e.preventDefault();if (e.target !== vn.elm) {return}utils.getComponent(context)['onEv'](e);});}});}); + let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}e.preventDefault();if (e.target !== vn.elm) {return}utils.getComponent(context)['onEv'](e);});}});}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); c1.push(pvnode); w2.__owl__.pvnode = pvnode; @@ -1125,7 +1125,7 @@ exports[`other directives with t-component t-on with self and prevent modifiers if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} w2 = new W2(parent, props2); parent.__owl__.cmap['__3__'] = w2.__owl__.id; - let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.isDestroyed){return}if (e.target !== vn.elm) {return}e.preventDefault();utils.getComponent(context)['onEv'](e);});}});}); + let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}if (e.target !== vn.elm) {return}e.preventDefault();utils.getComponent(context)['onEv'](e);});}});}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); c1.push(pvnode); w2.__owl__.pvnode = pvnode; @@ -1163,7 +1163,7 @@ exports[`other directives with t-component t-on with self modifier 1`] = ` if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} w2 = new W2(parent, props2); parent.__owl__.cmap['__3__'] = w2.__owl__.id; - let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['onEv1'](e);});vn.elm.addEventListener('ev-2', function (e) {if (context.__owl__.isDestroyed){return}if (e.target !== vn.elm) {return}utils.getComponent(context)['onEv2'](e);});}});}); + let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv1'](e);});vn.elm.addEventListener('ev-2', function (e) {if (context.__owl__.status === 5){return}if (e.target !== vn.elm) {return}utils.getComponent(context)['onEv2'](e);});}});}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); c1.push(pvnode); w2.__owl__.pvnode = pvnode; @@ -1201,7 +1201,7 @@ exports[`other directives with t-component t-on with stop and/or prevent modifie if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')} w2 = new W2(parent, props2); parent.__owl__.cmap['__3__'] = w2.__owl__.id; - let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {if (context.__owl__.isDestroyed){return}e.stopPropagation();utils.getComponent(context)['onEv1'](e);});vn.elm.addEventListener('ev-2', function (e) {if (context.__owl__.isDestroyed){return}e.preventDefault();utils.getComponent(context)['onEv2'](e);});vn.elm.addEventListener('ev-3', function (e) {if (context.__owl__.isDestroyed){return}e.stopPropagation();e.preventDefault();utils.getComponent(context)['onEv3'](e);});}});}); + let fiber = w2.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {if (context.__owl__.status === 5){return}e.stopPropagation();utils.getComponent(context)['onEv1'](e);});vn.elm.addEventListener('ev-2', function (e) {if (context.__owl__.status === 5){return}e.preventDefault();utils.getComponent(context)['onEv2'](e);});vn.elm.addEventListener('ev-3', function (e) {if (context.__owl__.status === 5){return}e.stopPropagation();e.preventDefault();utils.getComponent(context)['onEv3'](e);});}});}); let pvnode = h('dummy', {key: '__3__', hook: {remove() {},destroy(vn) {w2.destroy();}}}); c1.push(pvnode); w2.__owl__.pvnode = pvnode; @@ -1555,7 +1555,7 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument if (!W6) {throw new Error('Cannot find the definition of component \\"' + componentKey6 + '\\"')} w6 = new W6(parent, props6); parent.__owl__.cmap[k7] = w6.__owl__.id; - let fiber = w6.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['onEv'](...args8, e);});}});}); + let fiber = w6.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; utils.assignHooks(vnode.data, {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onEv'](...args8, e);});}});}); let pvnode = h('dummy', {key: k7, hook: {remove() {},destroy(vn) {w6.destroy();}}}); c1.push(pvnode); w6.__owl__.pvnode = pvnode; @@ -1580,7 +1580,7 @@ exports[`t-call handlers are properly bound through a t-call 1`] = ` let vn3 = h('p', p3, c3); c2.push(vn3); let k4 = \`click__4__\${key0}__\`; - extra.handlers[k4] = extra.handlers[k4] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['update'](e);}; + extra.handlers[k4] = extra.handlers[k4] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['update'](e);}; p3.on['click'] = extra.handlers[k4]; c3.push({text: \`lucas\`}); }" @@ -1599,7 +1599,7 @@ exports[`t-call handlers with arguments are properly bound through a t-call 1`] let vn3 = h('p', p3, c3); c2.push(vn3); let args4 = [scope['a']]; - p3.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['update'](...args4, e);}; + p3.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['update'](...args4, e);}; c3.push({text: \`lucas\`}); }" `; diff --git a/tests/component/__snapshots__/slots.test.ts.snap b/tests/component/__snapshots__/slots.test.ts.snap index 118ac0f20..0ab51d8a4 100644 --- a/tests/component/__snapshots__/slots.test.ts.snap +++ b/tests/component/__snapshots__/slots.test.ts.snap @@ -229,7 +229,7 @@ exports[`t-slot directive dynamic t-slot call 1`] = ` let h = this.h; let c10 = [], p10 = {key:10,on:{}}; let vn10 = h('button', p10, c10); - extra.handlers['click__11__'] = extra.handlers['click__11__'] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['toggle'](e);}; + extra.handlers['click__11__'] = extra.handlers['click__11__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['toggle'](e);}; p10.on['click'] = extra.handlers['click__11__']; const slot12 = this.constructor.slots[context.__owl__.slotId + '_' + (scope['current'].slot)]; if (slot12) { @@ -301,7 +301,7 @@ exports[`t-slot directive refs are properly bound in slots 1`] = ` let c9 = [], p9 = {key:9,on:{}}; let vn9 = h('button', p9, c9); c8.push(vn9); - extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['doSomething'](e);}; + extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](e);}; p9.on['click'] = extra.handlers['click__10__']; const ref11 = \`myButton\`; p9.hook = { @@ -326,7 +326,7 @@ exports[`t-slot directive slots are rendered with proper context 1`] = ` let c9 = [], p9 = {key:9,on:{}}; let vn9 = h('button', p9, c9); c8.push(vn9); - extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['doSomething'](e);}; + extra.handlers['click__10__'] = extra.handlers['click__10__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](e);}; p9.on['click'] = extra.handlers['click__10__']; c9.push({text: \`do something\`}); }" diff --git a/tests/component/async.test.ts b/tests/component/async.test.ts index 469f0eeb6..1425c5580 100644 --- a/tests/component/async.test.ts +++ b/tests/component/async.test.ts @@ -1,4 +1,4 @@ -import { Component, Env } from "../../src/component/component"; +import { Component, Env, STATUS } from "../../src/component/component"; import { useState } from "../../src/hooks"; import { xml } from "../../src/tags"; import { makeDeferred, makeTestEnv, makeTestFixture, nextMicroTick, nextTick } from "../helpers"; @@ -40,14 +40,14 @@ describe("async rendering", () => { } } const w = new W(); + expect(w.__owl__.status).toBe(STATUS.CREATED); w.mount(fixture); - expect(w.__owl__.isDestroyed).toBe(false); - expect(w.__owl__.isMounted).toBe(false); + expect(w.__owl__.status).toBe(STATUS.WILLSTARTED); w.destroy(); + expect(w.__owl__.status).toBe(STATUS.DESTROYED); def.resolve(); await nextTick(); - expect(w.__owl__.isDestroyed).toBe(true); - expect(w.__owl__.isMounted).toBe(false); + expect(w.__owl__.status).toBe(STATUS.DESTROYED); }); test("destroying/recreating a subwidget with different props (if start is not over)", async () => { diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts index af2ade973..e493ee203 100644 --- a/tests/component/component.test.ts +++ b/tests/component/component.test.ts @@ -1,4 +1,4 @@ -import { Component, Env, mount } from "../../src/component/component"; +import { Component, Env, mount, STATUS } from "../../src/component/component"; import { EventBus } from "../../src/core/event_bus"; import { useRef, useState } from "../../src/hooks"; import { QWeb } from "../../src/qweb/qweb"; @@ -1035,8 +1035,7 @@ describe("destroy method", () => { expect(document.contains(widget.el)).toBe(true); widget.destroy(); expect(document.contains(widget.el)).toBe(false); - expect(widget.__owl__.isMounted).toBe(false); - expect(widget.__owl__.isDestroyed).toBe(true); + expect(widget.__owl__.status).toBe(STATUS.DESTROYED); }); test("destroying a parent also destroys its children", async () => { @@ -1045,9 +1044,9 @@ describe("destroy method", () => { const child = children(parent)[0]; - expect(child.__owl__.isDestroyed).toBe(false); + expect(child.__owl__.status).toBe(STATUS.MOUNTED); parent.destroy(); - expect(child.__owl__.isDestroyed).toBe(true); + expect(child.__owl__.status).toBe(STATUS.DESTROYED); }); test("destroy remove the parent/children link", async () => { @@ -1073,17 +1072,15 @@ describe("destroy method", () => { } expect(fixture.innerHTML).toBe(""); const widget = new DelayedWidget(); + expect(widget.__owl__.status).toBe(STATUS.CREATED); widget.mount(fixture); - expect(widget.__owl__.isMounted).toBe(false); - expect(widget.__owl__.isDestroyed).toBe(false); + expect(widget.__owl__.status).toBe(STATUS.WILLSTARTED); widget.destroy(); - expect(widget.__owl__.isMounted).toBe(false); - expect(widget.__owl__.isDestroyed).toBe(true); + expect(widget.__owl__.status).toBe(STATUS.DESTROYED); def.resolve(); await nextTick(); - expect(widget.__owl__.isMounted).toBe(false); - expect(widget.__owl__.isDestroyed).toBe(true); + expect(widget.__owl__.status).toBe(STATUS.DESTROYED); expect(widget.__owl__.vnode).toBe(undefined); expect(fixture.innerHTML).toBe(""); expect(isRendered).toBe(false); @@ -1539,7 +1536,7 @@ describe("composition", () => { parent.state.flag = true; await nextTick(); expect(children(parent)[0]).toBe(child); - expect(child.__owl__.isDestroyed).toBe(false); + expect(child.__owl__.status).toBe(STATUS.MOUNTED); expect(normalize(fixture.innerHTML)).toBe( normalize(`
@@ -2287,7 +2284,7 @@ describe("other directives with t-component", () => { el.click(); expect(steps).toEqual(["click"]); parent.unmount(); - expect(child.__owl__.isMounted).toBe(false); + expect(child.__owl__.status).toBe(STATUS.UNMOUNTED); el.click(); expect(steps).toEqual(["click", "click"]); }); @@ -2361,7 +2358,7 @@ describe("other directives with t-component", () => { expect(steps).toEqual(["click"]); parent.state.flag = false; await nextTick(); - expect(child.__owl__.isDestroyed).toBe(true); + expect(child.__owl__.status).toBe(STATUS.DESTROYED); el.click(); expect(steps).toEqual(["click"]); }); @@ -2396,7 +2393,7 @@ describe("other directives with t-component", () => { expect(steps).toEqual(["click"]); parent.state.flag = false; await nextTick(); - expect(child.__owl__.isDestroyed).toBe(true); + expect(child.__owl__.status).toBe(STATUS.DESTROYED); el.click(); expect(steps).toEqual(["click"]); }); diff --git a/tests/component/error_handling.test.ts b/tests/component/error_handling.test.ts index bfae7bf8a..191c939f6 100644 --- a/tests/component/error_handling.test.ts +++ b/tests/component/error_handling.test.ts @@ -1,4 +1,4 @@ -import { Component, Env } from "../../src/component/component"; +import { Component, Env, STATUS } from "../../src/component/component"; import { useState } from "../../src/hooks"; import { xml } from "../../src/tags"; import { makeTestEnv, makeTestFixture, nextTick } from "../helpers"; @@ -108,7 +108,7 @@ describe("component error handling (catchError)", () => { expect(console.error).toBeCalledTimes(0); console.error = consoleError; - expect(app.__owl__.isDestroyed).toBe(true); + expect(app.__owl__.status).toBe(STATUS.DESTROYED); expect(handler).toBeCalledTimes(1); }); diff --git a/tests/component/un_mounting.test.ts b/tests/component/un_mounting.test.ts index da399a8da..1ad872a50 100644 --- a/tests/component/un_mounting.test.ts +++ b/tests/component/un_mounting.test.ts @@ -404,6 +404,40 @@ describe("unmounting and remounting", () => { expect(fixture.innerHTML).toBe("
P2C2
"); }); + test("change state while component is mounted in a fragment", async () => { + class Child1 extends Component { + static template = xml`C1`; + } + + class Child2 extends Component { + static template = xml`C2`; + } + + class Parent extends Component { + static components = { Child1, Child2 }; + static template = xml` +
+ + +
`; + child: string | false = false; + } + + const fragment = document.createDocumentFragment(); + const parent = new Parent(); + await parent.mount(fragment); + expect(parent.el.outerHTML).toBe("
"); + + parent.child = "c1"; + parent.render(); + await Promise.resolve(); + + parent.child = "c2"; + await parent.mount(fixture); + + expect(fixture.innerHTML).toBe("
C2
"); + }); + test("unmount component during a re-rendering", async () => { const def = makeDeferred(); class Child extends Component { @@ -490,17 +524,17 @@ describe("unmounting and remounting", () => { // one full tick. await nextMicroTick(); await nextMicroTick(); - expect(steps).toEqual(["1 catch"]); + expect(steps).toEqual([]); await nextTick(); expect(fixture.innerHTML).toBe("
"); def.resolve(); await nextTick(); - expect(steps).toEqual(["1 catch", "2 resolved"]); + expect(steps).toEqual(["2 resolved"]); expect(fixture.innerHTML).toBe("
Hey
"); }); - test("widget can be mounted on same target, another situation", async () => { + test("component can be mounted on same target, another situation", async () => { const def = makeDeferred(); const steps: string[] = []; @@ -528,8 +562,8 @@ describe("unmounting and remounting", () => { def.resolve(); await nextTick(); - expect(steps).toEqual(["1 resolved", "2 resolved"]); expect(fixture.innerHTML).toBe("
Hey
"); + expect(steps).toEqual(["1 resolved", "2 resolved"]); }); test("mounting a destroyed widget", async () => { diff --git a/tests/helpers.ts b/tests/helpers.ts index d93d643e9..c569329df 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -1,4 +1,4 @@ -import { Env, Component } from "../src/component/component"; +import { Env, Component, STATUS } from "../src/component/component"; import { scheduler } from "../src/component/scheduler"; import { EvalContext, QWeb } from "../src/qweb/qweb"; import { CompilationContext } from "../src/qweb/compilation_context"; @@ -92,7 +92,7 @@ export function renderToDOM( if (!context.__owl__) { // we add `__owl__` to better simulate a component as context. This is // particularly important for event handlers added with the `t-on` directive. - context.__owl__ = { isMounted: true }; + context.__owl__ = { status: STATUS.MOUNTED }; } const vnode = qweb.render(template, context, extra); diff --git a/tests/qweb/__snapshots__/qweb.test.ts.snap b/tests/qweb/__snapshots__/qweb.test.ts.snap index 7616d16c6..31c60ae92 100644 --- a/tests/qweb/__snapshots__/qweb.test.ts.snap +++ b/tests/qweb/__snapshots__/qweb.test.ts.snap @@ -2727,7 +2727,7 @@ exports[`t-on can bind event handler 1`] = ` let h = this.h; let c1 = [], p1 = {key:1,on:{}}; let vn1 = h('button', p1, c1); - extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['add'](e);}; + extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](e);}; p1.on['click'] = extra.handlers['click__2__']; c1.push({text: \`Click\`}); return vn1; @@ -2744,7 +2744,7 @@ exports[`t-on can bind handlers with arguments 1`] = ` let c1 = [], p1 = {key:1,on:{}}; let vn1 = h('button', p1, c1); let args2 = [5]; - p1.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['add'](...args2, e);}; + p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](...args2, e);}; c1.push({text: \`Click\`}); return vn1; }" @@ -2760,7 +2760,7 @@ exports[`t-on can bind handlers with empty object (with non empty inner string) let c1 = [], p1 = {key:1,on:{}}; let vn1 = h('button', p1, c1); let args2 = [{}]; - p1.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['doSomething'](...args2, e);}; + p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](...args2, e);}; c1.push({text: \`Click\`}); return vn1; }" @@ -2776,7 +2776,7 @@ exports[`t-on can bind handlers with empty object 1`] = ` let c1 = [], p1 = {key:1,on:{}}; let vn1 = h('button', p1, c1); let args2 = [{}]; - p1.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['doSomething'](...args2, e);}; + p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](...args2, e);}; c1.push({text: \`Click\`}); return vn1; }" @@ -2815,7 +2815,7 @@ exports[`t-on can bind handlers with loop variable as argument 1`] = ` let vn7 = h('a', p7, c7); c6.push(vn7); let args8 = [scope['action']]; - p7.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['activate'](...args8, e);}; + p7.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['activate'](...args8, e);}; c7.push({text: \`link\`}); } scope = _origScope5; @@ -2833,7 +2833,7 @@ exports[`t-on can bind handlers with object arguments 1`] = ` let c1 = [], p1 = {key:1,on:{}}; let vn1 = h('button', p1, c1); let args2 = [{val:5}]; - p1.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['add'](...args2, e);}; + p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](...args2, e);}; c1.push({text: \`Click\`}); return vn1; }" @@ -2847,9 +2847,9 @@ exports[`t-on can bind two event handlers 1`] = ` let h = this.h; let c1 = [], p1 = {key:1,on:{}}; let vn1 = h('button', p1, c1); - extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['handleClick'](e);}; + extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['handleClick'](e);}; p1.on['click'] = extra.handlers['click__2__']; - extra.handlers['dblclick__3__'] = extra.handlers['dblclick__3__'] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['handleDblClick'](e);}; + extra.handlers['dblclick__3__'] = extra.handlers['dblclick__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['handleDblClick'](e);}; p1.on['dblclick'] = extra.handlers['dblclick__3__']; c1.push({text: \`Click\`}); return vn1; @@ -2864,7 +2864,7 @@ exports[`t-on handler is bound to proper owner 1`] = ` let h = this.h; let c1 = [], p1 = {key:1,on:{}}; let vn1 = h('button', p1, c1); - extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['add'](e);}; + extra.handlers['click__2__'] = extra.handlers['click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['add'](e);}; p1.on['click'] = extra.handlers['click__2__']; c1.push({text: \`Click\`}); return vn1; @@ -2883,7 +2883,7 @@ exports[`t-on t-on combined with t-esc 1`] = ` let c2 = [], p2 = {key:2,on:{}}; let vn2 = h('button', p2, c2); c1.push(vn2); - extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['onClick'](e);}; + extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onClick'](e);}; p2.on['click'] = extra.handlers['click__3__']; let _4 = scope['text']; if (_4 != null) { @@ -2905,7 +2905,7 @@ exports[`t-on t-on combined with t-raw 1`] = ` let c2 = [], p2 = {key:2,on:{}}; let vn2 = h('button', p2, c2); c1.push(vn2); - extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['onClick'](e);}; + extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onClick'](e);}; p2.on['click'] = extra.handlers['click__3__']; let _4 = scope['html']; if (_4 != null) { @@ -2923,12 +2923,12 @@ exports[`t-on t-on with .capture modifier 1`] = ` let h = this.h; let c1 = [], p1 = {key:1,on:{}}; let vn1 = h('div', p1, c1); - extra.handlers['!click__2__'] = extra.handlers['!click__2__'] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['onCapture'](e);}; + extra.handlers['!click__2__'] = extra.handlers['!click__2__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onCapture'](e);}; p1.on['!click'] = extra.handlers['!click__2__']; let c3 = [], p3 = {key:3,on:{}}; let vn3 = h('button', p3, c3); c1.push(vn3); - extra.handlers['click__4__'] = extra.handlers['click__4__'] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['doSomething'](e);}; + extra.handlers['click__4__'] = extra.handlers['click__4__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['doSomething'](e);}; p3.on['click'] = extra.handlers['click__4__']; c3.push({text: \`Button\`}); return vn1; @@ -2946,7 +2946,7 @@ exports[`t-on t-on with empty handler (only modifiers) 1`] = ` let c2 = [], p2 = {key:2,on:{}}; let vn2 = h('button', p2, c2); c1.push(vn2); - p2.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}e.preventDefault();}; + p2.on['click'] = function (e) {if (context.__owl__.status === 5){return}e.preventDefault();}; c2.push({text: \`Button\`}); return vn1; }" @@ -2961,7 +2961,7 @@ exports[`t-on t-on with inline statement (function call) 1`] = ` let c1 = [], p1 = {key:1,on:{}}; let vn1 = h('button', p1, c1); const state_2 = scope['state']; - p1.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}state_2.incrementCounter(2)}; + p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.incrementCounter(2)}; c1.push({text: \`Click\`}); return vn1; }" @@ -2976,7 +2976,7 @@ exports[`t-on t-on with inline statement 1`] = ` let c1 = [], p1 = {key:1,on:{}}; let vn1 = h('button', p1, c1); const state_2 = scope['state']; - p1.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}state_2.counter++}; + p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.counter++}; c1.push({text: \`Click\`}); return vn1; }" @@ -2991,7 +2991,7 @@ exports[`t-on t-on with inline statement, part 2 1`] = ` let c1 = [], p1 = {key:1,on:{}}; let vn1 = h('button', p1, c1); const state_2 = scope['state']; - p1.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}state_2.flag=!state_2.flag}; + p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.flag=!state_2.flag}; c1.push({text: \`Toggle\`}); return vn1; }" @@ -3007,7 +3007,7 @@ exports[`t-on t-on with inline statement, part 3 1`] = ` let vn1 = h('button', p1, c1); const state_2 = scope['state']; const someFunction_2 = scope['someFunction']; - p1.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}state_2.n=someFunction_2(3)}; + p1.on['click'] = function (e) {if (context.__owl__.status === 5){return}state_2.n=someFunction_2(3)}; c1.push({text: \`Toggle\`}); return vn1; }" @@ -3024,7 +3024,7 @@ exports[`t-on t-on with prevent and self modifiers (order matters) 1`] = ` let c2 = [], p2 = {key:2,on:{}}; let vn2 = h('button', p2, c2); c1.push(vn2); - extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.isDestroyed){return}e.preventDefault();if (e.target !== this.elm) {return}utils.getComponent(context)['onClick'](e);}; + extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}e.preventDefault();if (e.target !== this.elm) {return}utils.getComponent(context)['onClick'](e);}; p2.on['click'] = extra.handlers['click__3__']; let c4 = [], p4 = {key:4}; let vn4 = h('span', p4, c4); @@ -3045,19 +3045,19 @@ exports[`t-on t-on with prevent and/or stop modifiers 1`] = ` let c2 = [], p2 = {key:2,on:{}}; let vn2 = h('button', p2, c2); c1.push(vn2); - extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.isDestroyed){return}e.preventDefault();utils.getComponent(context)['onClickPrevented'](e);}; + extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}e.preventDefault();utils.getComponent(context)['onClickPrevented'](e);}; p2.on['click'] = extra.handlers['click__3__']; c2.push({text: \`Button 1\`}); let c4 = [], p4 = {key:4,on:{}}; let vn4 = h('button', p4, c4); c1.push(vn4); - extra.handlers['click__5__'] = extra.handlers['click__5__'] || function (e) {if (context.__owl__.isDestroyed){return}e.stopPropagation();utils.getComponent(context)['onClickStopped'](e);}; + extra.handlers['click__5__'] = extra.handlers['click__5__'] || function (e) {if (context.__owl__.status === 5){return}e.stopPropagation();utils.getComponent(context)['onClickStopped'](e);}; p4.on['click'] = extra.handlers['click__5__']; c4.push({text: \`Button 2\`}); let c6 = [], p6 = {key:6,on:{}}; let vn6 = h('button', p6, c6); c1.push(vn6); - extra.handlers['click__7__'] = extra.handlers['click__7__'] || function (e) {if (context.__owl__.isDestroyed){return}e.preventDefault();e.stopPropagation();utils.getComponent(context)['onClickPreventedAndStopped'](e);}; + extra.handlers['click__7__'] = extra.handlers['click__7__'] || function (e) {if (context.__owl__.status === 5){return}e.preventDefault();e.stopPropagation();utils.getComponent(context)['onClickPreventedAndStopped'](e);}; p6.on['click'] = extra.handlers['click__7__']; c6.push({text: \`Button 3\`}); return vn1; @@ -3097,7 +3097,7 @@ exports[`t-on t-on with prevent modifier in t-foreach 1`] = ` let vn7 = h('a', p7, c7); c1.push(vn7); let args8 = [scope['project'].id]; - p7.on['click'] = function (e) {if (context.__owl__.isDestroyed){return}e.preventDefault();utils.getComponent(context)['onEdit'](...args8, e);}; + p7.on['click'] = function (e) {if (context.__owl__.status === 5){return}e.preventDefault();utils.getComponent(context)['onEdit'](...args8, e);}; c7.push({text: \` Edit \`}); let _9 = scope['project'].name; if (_9 != null) { @@ -3121,7 +3121,7 @@ exports[`t-on t-on with self and prevent modifiers (order matters) 1`] = ` let c2 = [], p2 = {key:2,on:{}}; let vn2 = h('button', p2, c2); c1.push(vn2); - extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.isDestroyed){return}if (e.target !== this.elm) {return}e.preventDefault();utils.getComponent(context)['onClick'](e);}; + extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}if (e.target !== this.elm) {return}e.preventDefault();utils.getComponent(context)['onClick'](e);}; p2.on['click'] = extra.handlers['click__3__']; let c4 = [], p4 = {key:4}; let vn4 = h('span', p4, c4); @@ -3142,7 +3142,7 @@ exports[`t-on t-on with self modifier 1`] = ` let c2 = [], p2 = {key:2,on:{}}; let vn2 = h('button', p2, c2); c1.push(vn2); - extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['onClick'](e);}; + extra.handlers['click__3__'] = extra.handlers['click__3__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['onClick'](e);}; p2.on['click'] = extra.handlers['click__3__']; let c4 = [], p4 = {key:4}; let vn4 = h('span', p4, c4); @@ -3151,7 +3151,7 @@ exports[`t-on t-on with self modifier 1`] = ` let c5 = [], p5 = {key:5,on:{}}; let vn5 = h('button', p5, c5); c1.push(vn5); - extra.handlers['click__6__'] = extra.handlers['click__6__'] || function (e) {if (context.__owl__.isDestroyed){return}if (e.target !== this.elm) {return}utils.getComponent(context)['onClickSelf'](e);}; + extra.handlers['click__6__'] = extra.handlers['click__6__'] || function (e) {if (context.__owl__.status === 5){return}if (e.target !== this.elm) {return}utils.getComponent(context)['onClickSelf'](e);}; p5.on['click'] = extra.handlers['click__6__']; let c7 = [], p7 = {key:7}; let vn7 = h('span', p7, c7); diff --git a/tests/router/__snapshots__/link.test.ts.snap b/tests/router/__snapshots__/link.test.ts.snap index e395f51d5..fb09c0b71 100644 --- a/tests/router/__snapshots__/link.test.ts.snap +++ b/tests/router/__snapshots__/link.test.ts.snap @@ -11,7 +11,7 @@ exports[`Link component can render simple cases 1`] = ` let _6 = scope['href']; let c7 = [], p7 = {key:7,attrs:{href: _6},class:_5,on:{}}; let vn7 = h('a', p7, c7); - extra.handlers['click__8__'] = extra.handlers['click__8__'] || function (e) {if (context.__owl__.isDestroyed){return}utils.getComponent(context)['navigate'](e);}; + extra.handlers['click__8__'] = extra.handlers['click__8__'] || function (e) {if (context.__owl__.status === 5){return}utils.getComponent(context)['navigate'](e);}; p7.on['click'] = extra.handlers['click__8__']; const slot9 = this.constructor.slots[context.__owl__.slotId + '_' + 'default']; if (slot9) { diff --git a/tools/debug.js b/tools/debug.js index 3d38902c7..d5751d7e9 100644 --- a/tools/debug.js +++ b/tools/debug.js @@ -101,7 +101,7 @@ component.render = function(...args) { const __owl__ = component.__owl__; let msg = `render`; - if (!__owl__.isMounted && !__owl__.currentFiber) { + if (__owl__.status !== 3 /* mounted */ && !__owl__.currentFiber) { msg += ` (warning: component is not mounted)`; } log(msg);