[FIX] component: concurrent calls to mount and render

Closes #450
This commit is contained in:
Aaron Bohy
2019-11-05 14:23:06 +01:00
committed by Géry Debongnie
parent bbdc9d90d7
commit e5940b4b6b
11 changed files with 292 additions and 112 deletions
+29 -53
View File
@@ -1,6 +1,6 @@
import { Observer } from "../core/observer"; import { Observer } from "../core/observer";
import { CompiledTemplate, QWeb } from "../qweb/index"; import { CompiledTemplate, QWeb } from "../qweb/index";
import { h, patch, VNode } from "../vdom/index"; import { patch, VNode } from "../vdom/index";
import "./directive"; import "./directive";
import { Fiber } from "./fiber"; import { Fiber } from "./fiber";
import "./props_validation"; import "./props_validation";
@@ -282,32 +282,19 @@ export class Component<T extends Env, Props extends {}> {
return Promise.resolve(); return Promise.resolve();
} }
if (!(target instanceof HTMLElement)) { if (!(target instanceof HTMLElement)) {
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)`; message += `\nMaybe the DOM is not ready yet? (in that case, you can use owl.utils.whenReady)`;
throw new Error(message); throw new Error(message);
} }
return new Promise((resolve, reject) => { const fiber = new Fiber(null, this, undefined, undefined, false, target);
const fiber = new Fiber(null, this, undefined, undefined, false); if (!__owl__.vnode) {
scheduler.addFiber(fiber, err => { this.__prepareAndRender(fiber);
if (err) { } else {
reject(err); this.__render(fiber);
return; }
} return scheduler.addFiber(fiber);
if (!__owl__.isDestroyed) {
this.__patch(fiber.vnode);
target.appendChild(this.el!);
if (document.body.contains(target)) {
this.__callMounted();
}
}
resolve();
});
if (!__owl__.vnode) {
this.__prepareAndRender(fiber);
} else {
this.__render(fiber);
}
});
} }
/** /**
@@ -332,26 +319,18 @@ export class Component<T extends Env, Props extends {}> {
*/ */
async render(force: boolean = false): Promise<void> { async render(force: boolean = false): Promise<void> {
const __owl__ = this.__owl__; const __owl__ = this.__owl__;
if ( if (!__owl__.isMounted && !__owl__.currentFiber) {
(!__owl__.isMounted && !__owl__.currentFiber) || // if we get here, this means that the component was either never mounted,
(__owl__.currentFiber && !__owl__.currentFiber.isRendered) // or was unmounted and some state change triggered a render. Either way,
) { // we do not want to actually render anything in this case.
return; return;
} }
return new Promise((resolve, reject) => { if (__owl__.currentFiber && !__owl__.currentFiber.isRendered) {
const fiber = new Fiber(null, this, undefined, undefined, force); return scheduler.addFiber(__owl__.currentFiber.root);
scheduler.addFiber(fiber.root, err => { }
if (err) { const fiber = new Fiber(null, this, undefined, undefined, force, null);
reject(err); this.__render(fiber);
return; return scheduler.addFiber(fiber);
}
if (__owl__.isMounted && fiber === fiber.root) {
fiber.patchComponents();
}
resolve();
});
this.__render(fiber);
});
} }
/** /**
@@ -437,7 +416,7 @@ export class Component<T extends Env, Props extends {}> {
__owl__.isDestroyed = true; __owl__.isDestroyed = true;
delete __owl__.vnode; delete __owl__.vnode;
if (__owl__.currentFiber) { if (__owl__.currentFiber) {
__owl__.currentFiber.isCancelled = true; __owl__.currentFiber.isCompleted = true;
} }
} }
@@ -492,7 +471,7 @@ export class Component<T extends Env, Props extends {}> {
const shouldUpdate = parentFiber.force || this.shouldUpdate(nextProps); const shouldUpdate = parentFiber.force || this.shouldUpdate(nextProps);
if (shouldUpdate) { if (shouldUpdate) {
const __owl__ = this.__owl__; const __owl__ = this.__owl__;
const fiber = new Fiber(parentFiber, this, scope, vars, parentFiber.force); const fiber = new Fiber(parentFiber, this, scope, vars, parentFiber.force, null);
if (!parentFiber.child) { if (!parentFiber.child) {
parentFiber.child = fiber; parentFiber.child = fiber;
} else { } else {
@@ -510,7 +489,7 @@ export class Component<T extends Env, Props extends {}> {
this.willUpdateProps(nextProps), this.willUpdateProps(nextProps),
__owl__.willUpdatePropsCB && __owl__.willUpdatePropsCB(nextProps) __owl__.willUpdatePropsCB && __owl__.willUpdatePropsCB(nextProps)
]); ]);
if (fiber.isCancelled) { if (fiber.isCompleted) {
return; return;
} }
this.props = nextProps; this.props = nextProps;
@@ -535,7 +514,7 @@ export class Component<T extends Env, Props extends {}> {
* parent template. * parent template.
*/ */
__prepare(parentFiber: Fiber, scope: any, vars: any, previousSibling?: Fiber | null) { __prepare(parentFiber: Fiber, scope: any, vars: any, previousSibling?: Fiber | null) {
const fiber = new Fiber(parentFiber, this, scope, vars, parentFiber.force); const fiber = new Fiber(parentFiber, this, scope, vars, parentFiber.force, null);
fiber.shouldPatch = false; fiber.shouldPatch = false;
if (!parentFiber.child) { if (!parentFiber.child) {
parentFiber.child = fiber; parentFiber.child = fiber;
@@ -569,13 +548,12 @@ export class Component<T extends Env, Props extends {}> {
await Promise.all([this.willStart(), this.__owl__.willStartCB && this.__owl__.willStartCB()]); await Promise.all([this.willStart(), this.__owl__.willStartCB && this.__owl__.willStartCB()]);
} catch (e) { } catch (e) {
fiber.handleError(e); fiber.handleError(e);
fiber.vnode = h("div"); // -> we render this div at the end
return Promise.resolve(); return Promise.resolve();
} }
if (this.__owl__.isDestroyed) { if (this.__owl__.isDestroyed) {
return Promise.resolve(); return Promise.resolve();
} }
if (!fiber.isCancelled) { if (!fiber.isCompleted) {
this.__render(fiber); this.__render(fiber);
} }
} }
@@ -585,17 +563,14 @@ export class Component<T extends Env, Props extends {}> {
if (__owl__.observer) { if (__owl__.observer) {
__owl__.observer.allowMutations = false; __owl__.observer.allowMutations = false;
} }
let vnode;
try { try {
vnode = __owl__.renderFn!(this, { fiber.vnode = __owl__.renderFn!(this, {
handlers: __owl__.boundHandlers, handlers: __owl__.boundHandlers,
fiber: fiber fiber: fiber
}); });
} catch (e) { } catch (e) {
vnode = __owl__.vnode || h("div");
fiber.handleError(e); fiber.handleError(e);
} }
fiber.vnode = vnode;
if (__owl__.observer) { if (__owl__.observer) {
__owl__.observer.allowMutations = true; __owl__.observer.allowMutations = true;
} }
@@ -604,7 +579,8 @@ export class Component<T extends Env, Props extends {}> {
// template (so, something like <MyComponent class="..."/>) to the actual // template (so, something like <MyComponent class="..."/>) to the actual
// root vnode // root vnode
if (__owl__.classObj) { if (__owl__.classObj) {
vnode.data.class = Object.assign(vnode.data.class || {}, __owl__.classObj); const data = fiber.vnode!.data!;
data.class = Object.assign(data.class || {}, __owl__.classObj);
} }
fiber.root.counter--; fiber.root.counter--;
fiber.isRendered = true; fiber.isRendered = true;
+1 -1
View File
@@ -455,7 +455,7 @@ QWeb.addDirective({
); );
ctx.addLine(`const fiber = w${componentID}.__owl__.currentFiber;`); ctx.addLine(`const fiber = w${componentID}.__owl__.currentFiber;`);
ctx.addLine( ctx.addLine(
`def${defID}.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; ${createHook}});` `def${defID}.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; ${createHook}});`
); );
if (registerCode) { if (registerCode) {
ctx.addLine(registerCode); ctx.addLine(registerCode);
+65 -13
View File
@@ -1,4 +1,4 @@
import { VNode } from "../vdom/index"; import { h, VNode } from "../vdom/index";
import { Component } from "./component"; import { Component } from "./component";
import { scheduler } from "./scheduler"; import { scheduler } from "./scheduler";
@@ -20,9 +20,10 @@ export class Fiber {
// method potentially implemented by a component. It is usually set to false. // method potentially implemented by a component. It is usually set to false.
force: boolean; force: boolean;
// isCancelled means that the rendering corresponding to this fiber and its // isCompleted means that the rendering corresponding to this fiber's work is
// children is cancelled. No extra work should be done. // done, either because the component has been mounted or patched, or because
isCancelled: boolean = false; // fiber has been cancelled.
isCompleted: boolean = false;
// the fibers corresponding to component updates (updateProps) need to call // the fibers corresponding to component updates (updateProps) need to call
// the willPatch and patched hooks from the corresponding component. However, // the willPatch and patched hooks from the corresponding component. However,
@@ -42,6 +43,8 @@ export class Fiber {
// scheduler. // scheduler.
counter: number = 0; counter: number = 0;
target: HTMLElement | null;
scope: any; scope: any;
vars: any; vars: any;
@@ -55,18 +58,25 @@ export class Fiber {
error?: Error; error?: Error;
constructor(parent: Fiber | null, component: Component<any, any>, scope, vars, force) { constructor(parent: Fiber | null, component: Component<any, any>, scope, vars, force, target) {
this.force = force; this.force = force;
this.scope = scope; this.scope = scope;
this.vars = vars; this.vars = vars;
this.component = component; this.component = component;
this.target = target;
this.root = parent ? parent.root : this; this.root = parent ? parent.root : this;
this.parent = parent; this.parent = parent;
let oldFiber = component.__owl__.currentFiber; let oldFiber = component.__owl__.currentFiber;
if (oldFiber && !oldFiber.isCancelled) { if (oldFiber && !oldFiber.isCompleted) {
this._remapFiber(oldFiber); if (oldFiber.root === oldFiber && !parent) {
// both oldFiber and this fiber are root fibers
this._reuseFiber(oldFiber);
return oldFiber;
} else {
this._remapFiber(oldFiber);
}
} }
this.root.counter++; this.root.counter++;
@@ -74,6 +84,25 @@ export class Fiber {
component.__owl__.currentFiber = this; component.__owl__.currentFiber = this;
} }
/**
* When the oldFiber is not completed yet, and both oldFiber and this fiber
* are root fibers, we want to reuse the oldFiber instead of creating a new
* one. Doing so will guarantee that the initiator(s) of those renderings will
* be notified (the promise will resolve) when the last rendering will be done.
*
* This function thus assumes that oldFiber is a root fiber.
*/
_reuseFiber(oldFiber: Fiber) {
oldFiber.cancel(); // cancel children fibers
oldFiber.isCompleted = false; // keep the root fiber alive
if (oldFiber.child) {
// remove relation to children
oldFiber.child.parent = null;
oldFiber.child = null;
}
oldFiber.counter = 1; // re-initialize counter
}
/** /**
* In some cases, a rendering initiated at some component can detect that it * In some cases, a rendering initiated at some component can detect that it
* should be part of a larger rendering initiated somewhere up the component * should be part of a larger rendering initiated somewhere up the component
@@ -83,7 +112,7 @@ export class Fiber {
_remapFiber(oldFiber: Fiber) { _remapFiber(oldFiber: Fiber) {
oldFiber.cancel(); oldFiber.cancel();
if (oldFiber === oldFiber.root) { if (oldFiber === oldFiber.root) {
oldFiber.root.counter++; oldFiber.counter++;
} }
if (oldFiber.parent && !this.parent) { if (oldFiber.parent && !this.parent) {
// re-map links // re-map links
@@ -132,7 +161,28 @@ export class Fiber {
} }
/** /**
* Apply the given patch queue from a fiber. * Successfully complete the work of the fiber: call the mount or patch hooks
* and patch the DOM. This function is called once the fiber and its children
* 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();
}
}
this.isCompleted = true;
}
/**
* Compute and apply the patch queue of the fiber.
* 1) Call 'willPatch' on the component of each patch * 1) Call 'willPatch' on the component of each patch
* 2) Call '__patch' 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 * 3) Call 'patched' on the component of each patch, in reverse order
@@ -142,8 +192,8 @@ export class Fiber {
const doWork: (Fiber) => Fiber | null = function(f) { const doWork: (Fiber) => Fiber | null = function(f) {
if (f.shouldPatch) { if (f.shouldPatch) {
patchQueue.push(f); patchQueue.push(f);
return f.child;
} }
return f.child;
}; };
this._walk(doWork); this._walk(doWork);
let component: Component<any, any> = this.component; let component: Component<any, any> = this.component;
@@ -186,7 +236,7 @@ export class Fiber {
if (!f.isRendered) { if (!f.isRendered) {
f.root.counter--; f.root.counter--;
} }
f.isCancelled = true; f.isCompleted = true;
return f.child; return f.child;
}); });
} }
@@ -200,10 +250,12 @@ export class Fiber {
* being in a corrupted state. * being in a corrupted state.
*/ */
handleError(error: Error) { handleError(error: Error) {
let canCatch = false;
let component = this.component; let component = this.component;
let qweb = component.env.qweb; this.vnode = component.__owl__.vnode || h("div");
const qweb = component.env.qweb;
let root = component; let root = component;
let canCatch = false;
while (component && !(canCatch = !!component.catchError)) { while (component && !(canCatch = !!component.catchError)) {
root = component; root = component;
component = component.__owl__.parent!; component = component.__owl__.parent!;
+25 -8
View File
@@ -25,12 +25,25 @@ export class Scheduler {
this.requestAnimationFrame = requestAnimationFrame; this.requestAnimationFrame = requestAnimationFrame;
} }
addFiber(fiber, callback) { addFiber(fiber): Promise<void> {
this.tasks.push({ fiber, callback }); return new Promise((resolve, reject) => {
if (this.isRunning) { if (fiber.error) {
return; return reject(fiber.error);
} }
this.scheduleTasks(); this.tasks.push({
fiber,
callback: () => {
if (fiber.error) {
reject(fiber.error);
return;
}
resolve();
}
});
if (!this.isRunning) {
this.scheduleTasks();
}
});
} }
/** /**
@@ -41,11 +54,15 @@ export class Scheduler {
let tasks = this.tasks; let tasks = this.tasks;
this.tasks = []; this.tasks = [];
tasks = tasks.filter(task => { tasks = tasks.filter(task => {
if (task.fiber.isCancelled) { if (task.fiber.isCompleted) {
task.callback();
return false; return false;
} }
if (task.fiber.counter === 0) { if (task.fiber.counter === 0) {
task.callback(task.fiber.error); if (!task.fiber.error) {
task.fiber.complete();
}
task.callback();
return false; return false;
} }
return true; return true;
+11 -1
View File
@@ -24,9 +24,19 @@ import { Observer } from "./core/observer";
export function useState<T>(state: T): T { export function useState<T>(state: T): T {
const component: Component<any, any> = Component.current!; const component: Component<any, any> = Component.current!;
const __owl__ = component.__owl__; const __owl__ = component.__owl__;
const renderFn = __owl__.renderFn;
let lastRenderRevNumber;
__owl__.renderFn = function(comp, params) {
lastRenderRevNumber = __owl__.observer!.rev;
return renderFn(comp, params);
};
if (!__owl__.observer) { if (!__owl__.observer) {
__owl__.observer = new Observer(); __owl__.observer = new Observer();
__owl__.observer.notifyCB = component.render.bind(component); __owl__.observer.notifyCB = () => {
if (lastRenderRevNumber < __owl__.observer!.rev) {
component.render();
}
};
} }
return __owl__.observer.observe(state); return __owl__.observer.observe(state);
} }
+6 -2
View File
@@ -236,7 +236,9 @@ QWeb.addDirective({
varCode = `{${content}}`; varCode = `{${content}}`;
} }
ctx.addLine( ctx.addLine(
`this.recursiveFns['${subTemplateName}'].call(this, context, Object.assign({}, extra, {parentNode: c${ctx.parentNode}, fiber: {vars: ${varCode}, scope}}));` `this.recursiveFns['${subTemplateName}'].call(this, context, Object.assign({}, extra, {parentNode: c${
ctx.parentNode
}, fiber: {vars: ${varCode}, scope}}));`
); );
return true; return true;
} }
@@ -305,7 +307,9 @@ QWeb.addDirective({
!node.children[0].hasAttribute("t-key"); !node.children[0].hasAttribute("t-key");
if (shouldWarn) { if (shouldWarn) {
console.warn( console.warn(
`Directive t-foreach should always be used with a t-key! (in template: '${ctx.templateName}')` `Directive t-foreach should always be used with a t-key! (in template: '${
ctx.templateName
}')`
); );
} }
+2 -2
View File
@@ -35,7 +35,7 @@ exports[`animations t-transition combined with component 1`] = `
}; };
utils.transitionRemove(vn, 'chimay', finalize);}}}); utils.transitionRemove(vn, 'chimay', finalize);}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def2.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -80,7 +80,7 @@ exports[`animations t-transition combined with t-component and t-if 1`] = `
}; };
utils.transitionRemove(vn, 'chimay', finalize);}}}); utils.transitionRemove(vn, 'chimay', finalize);}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def2.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -61,7 +61,7 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
let def8 = w9.__prepare(extra.fiber, undefined, undefined, sibling); let def8 = w9.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId9, hook: {insert(vn) { let nvn=w9.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w9.destroy();}}}); let pvnode = h('dummy', {key: templateId9, hook: {insert(vn) { let nvn=w9.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w9.destroy();}}});
const fiber = w9.__owl__.currentFiber; const fiber = w9.__owl__.currentFiber;
def8.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def8.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode); c1.push(pvnode);
w9.__owl__.pvnode = pvnode; w9.__owl__.pvnode = pvnode;
} }
@@ -105,7 +105,7 @@ exports[`class and style attributes with t-component dynamic t-att-style is prop
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.style = _5;}};}); def2.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.style = _5;}};});
c1.push(pvnode); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -150,7 +150,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;context.__owl__.refs[ref5] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref5];}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;context.__owl__.refs[ref5] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref5];}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){}};}); def2.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){}};});
c1.push(pvnode); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -210,7 +210,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;context.__owl__.refs[ref5] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref5];}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;context.__owl__.refs[ref5] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref5];}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){}};}); def2.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){}};});
c1.push(pvnode); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -282,7 +282,7 @@ exports[`composition sub components with some state rendered in a loop 1`] = `
let def6 = w7.__prepare(extra.fiber, undefined, undefined, sibling); let def6 = w7.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId7, hook: {insert(vn) { let nvn=w7.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}}); let pvnode = h('dummy', {key: templateId7, hook: {insert(vn) { let nvn=w7.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});
const fiber = w7.__owl__.currentFiber; const fiber = w7.__owl__.currentFiber;
def6.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def6.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode); c1.push(pvnode);
w7.__owl__.pvnode = pvnode; w7.__owl__.pvnode = pvnode;
} }
@@ -324,7 +324,7 @@ exports[`composition t-component with dynamic value 1`] = `
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def2.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -365,7 +365,7 @@ exports[`composition t-component with dynamic value 2 1`] = `
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def2.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -406,7 +406,7 @@ exports[`dynamic t-props basic use 1`] = `
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def2.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -451,7 +451,7 @@ exports[`other directives with t-component t-on with getter as handler 1`] = `
let def3 = w4.__prepare(extra.fiber, undefined, undefined, sibling); let def3 = w4.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId4, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}}); let pvnode = h('dummy', {key: templateId4, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
const fiber = w4.__owl__.currentFiber; const fiber = w4.__owl__.currentFiber;
def3.then(function () {if (fiber.isCancelled) {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; }});}};}); 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); c1.push(pvnode);
w4.__owl__.pvnode = pvnode; w4.__owl__.pvnode = pvnode;
} }
@@ -492,7 +492,7 @@ exports[`other directives with t-component t-on with handler bound to argument 1
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {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; }});}};}); 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); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -533,7 +533,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {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; }});}};}); 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); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -574,7 +574,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {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; }});}};}); 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); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -615,7 +615,7 @@ exports[`other directives with t-component t-on with handler bound to object 1`]
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {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; }});}};}); 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); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -660,7 +660,7 @@ exports[`other directives with t-component t-on with inline statement 1`] = `
let def3 = w4.__prepare(extra.fiber, undefined, undefined, sibling); let def3 = w4.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId4, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}}); let pvnode = h('dummy', {key: templateId4, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
const fiber = w4.__owl__.currentFiber; const fiber = w4.__owl__.currentFiber;
def3.then(function () {if (fiber.isCancelled) {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++; }});}};}); 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); c1.push(pvnode);
w4.__owl__.pvnode = pvnode; w4.__owl__.pvnode = pvnode;
} }
@@ -701,7 +701,7 @@ exports[`other directives with t-component t-on with no handler (only modifiers)
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {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; }});}};}); 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); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -742,7 +742,7 @@ exports[`other directives with t-component t-on with prevent and self modifiers
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {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; }});}};}); 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); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -783,7 +783,7 @@ exports[`other directives with t-component t-on with self and prevent modifiers
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {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; }});}};}); 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); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -824,7 +824,7 @@ exports[`other directives with t-component t-on with self modifier 1`] = `
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {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; }});}};}); 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); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -865,7 +865,7 @@ exports[`other directives with t-component t-on with stop and/or prevent modifie
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {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; }});}};}); 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); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -925,7 +925,7 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
let def3 = w4.__prepare(extra.fiber, undefined, undefined, sibling); let def3 = w4.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId4, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}}); let pvnode = h('dummy', {key: templateId4, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
const fiber = w4.__owl__.currentFiber; const fiber = w4.__owl__.currentFiber;
def3.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def3.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode); c1.push(pvnode);
w4.__owl__.pvnode = pvnode; w4.__owl__.pvnode = pvnode;
} }
@@ -983,7 +983,7 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument
let def6 = w7.__prepare(extra.fiber, undefined, undefined, sibling); let def6 = w7.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId7, hook: {insert(vn) { let nvn=w7.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}}); let pvnode = h('dummy', {key: templateId7, hook: {insert(vn) { let nvn=w7.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});
const fiber = w7.__owl__.currentFiber; const fiber = w7.__owl__.currentFiber;
def6.then(function () {if (fiber.isCancelled) {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, arg9, e); } else { owner.onEv; }});}};}); 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, arg9, e); } else { owner.onEv; }});}};});
c1.push(pvnode); c1.push(pvnode);
w7.__owl__.pvnode = pvnode; w7.__owl__.pvnode = pvnode;
} }
@@ -1231,7 +1231,7 @@ exports[`t-slot directive can define and call slots 1`] = `
let def2 = w3.__prepare(extra.fiber, {}, undefined, sibling); let def2 = w3.__prepare(extra.fiber, {}, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def2.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
@@ -1477,7 +1477,7 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
let def8 = w9.__prepare(extra.fiber, Object.assign({}, scope), undefined, sibling); let def8 = w9.__prepare(extra.fiber, Object.assign({}, scope), undefined, sibling);
let pvnode = h('dummy', {key: templateId9, hook: {insert(vn) { let nvn=w9.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w9.destroy();}}}); let pvnode = h('dummy', {key: templateId9, hook: {insert(vn) { let nvn=w9.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w9.destroy();}}});
const fiber = w9.__owl__.currentFiber; const fiber = w9.__owl__.currentFiber;
def8.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def8.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c7.push(pvnode); c7.push(pvnode);
w9.__owl__.pvnode = pvnode; w9.__owl__.pvnode = pvnode;
} }
@@ -1581,7 +1581,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
let def9 = w10.__prepare(extra.fiber, Object.assign({}, scope), {_8}, sibling); let def9 = w10.__prepare(extra.fiber, Object.assign({}, scope), {_8}, sibling);
let pvnode = h('dummy', {key: templateId10, hook: {insert(vn) { let nvn=w10.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w10.destroy();}}}); let pvnode = h('dummy', {key: templateId10, hook: {insert(vn) { let nvn=w10.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w10.destroy();}}});
const fiber = w10.__owl__.currentFiber; const fiber = w10.__owl__.currentFiber;
def9.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def9.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c7.push(pvnode); c7.push(pvnode);
w10.__owl__.pvnode = pvnode; w10.__owl__.pvnode = pvnode;
} }
@@ -1639,7 +1639,7 @@ exports[`t-slot directive slots are rendered with proper context, part 4 1`] = `
let def3 = w4.__prepare(extra.fiber, {}, {_2}, sibling); let def3 = w4.__prepare(extra.fiber, {}, {_2}, sibling);
let pvnode = h('dummy', {key: templateId4, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}}); let pvnode = h('dummy', {key: templateId4, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
const fiber = w4.__owl__.currentFiber; const fiber = w4.__owl__.currentFiber;
def3.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def3.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode); c1.push(pvnode);
w4.__owl__.pvnode = pvnode; w4.__owl__.pvnode = pvnode;
} }
@@ -1714,7 +1714,7 @@ exports[`top level sub widgets basic use 1`] = `
let def1 = w2.__prepare(extra.fiber, undefined, undefined, sibling); let def1 = w2.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId2, hook: {insert(vn) { let nvn=w2.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w2.destroy();}}}); let pvnode = h('dummy', {key: templateId2, hook: {insert(vn) { let nvn=w2.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w2.destroy();}}});
const fiber = w2.__owl__.currentFiber; const fiber = w2.__owl__.currentFiber;
def1.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def1.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
utils.defineProxy(vn4, pvnode); utils.defineProxy(vn4, pvnode);
w2.__owl__.pvnode = pvnode; w2.__owl__.pvnode = pvnode;
} }
@@ -1757,7 +1757,7 @@ exports[`top level sub widgets can select a sub widget 1`] = `
let def1 = w2.__prepare(extra.fiber, undefined, undefined, sibling); let def1 = w2.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId2, hook: {insert(vn) { let nvn=w2.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w2.destroy();}}}); let pvnode = h('dummy', {key: templateId2, hook: {insert(vn) { let nvn=w2.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w2.destroy();}}});
const fiber = w2.__owl__.currentFiber; const fiber = w2.__owl__.currentFiber;
def1.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def1.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
utils.defineProxy(vn4, pvnode); utils.defineProxy(vn4, pvnode);
w2.__owl__.pvnode = pvnode; w2.__owl__.pvnode = pvnode;
} }
@@ -1787,7 +1787,7 @@ exports[`top level sub widgets can select a sub widget 1`] = `
let def5 = w6.__prepare(extra.fiber, undefined, undefined, sibling); let def5 = w6.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId6, hook: {insert(vn) { let nvn=w6.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w6.destroy();}}}); let pvnode = h('dummy', {key: templateId6, hook: {insert(vn) { let nvn=w6.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w6.destroy();}}});
const fiber = w6.__owl__.currentFiber; const fiber = w6.__owl__.currentFiber;
def5.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def5.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
utils.defineProxy(vn8, pvnode); utils.defineProxy(vn8, pvnode);
w6.__owl__.pvnode = pvnode; w6.__owl__.pvnode = pvnode;
} }
@@ -32,7 +32,7 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def2.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
c1.push(pvnode); c1.push(pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }
+123 -2
View File
@@ -3348,7 +3348,6 @@ describe("async rendering", () => {
const componentA = new ComponentA(); const componentA = new ComponentA();
await componentA.mount(fixture); await componentA.mount(fixture);
expect(fixture.innerHTML).toBe("<div><p></p></div>"); expect(fixture.innerHTML).toBe("<div><p></p></div>");
stateB.hasChild = true; stateB.hasChild = true;
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div><p></p></div>"); expect(fixture.innerHTML).toBe("<div><p></p></div>");
@@ -3363,6 +3362,64 @@ describe("async rendering", () => {
expect(fixture.innerHTML).toBe("<div><p><span>2</span></p></div>"); expect(fixture.innerHTML).toBe("<div><p><span>2</span></p></div>");
expect(ComponentC.prototype.__render).toHaveBeenCalledTimes(1); expect(ComponentC.prototype.__render).toHaveBeenCalledTimes(1);
}); });
test("concurrent renderings scenario 11", async () => {
// This scenario is the following: we have a component being updated (by props),
// and then rendered (render method), but before the willUpdateProps resolves.
// We check that in that case, the return value of the render method is a promise
// that is resolved when the component is completely rendered (so, properly
// remapped to the promise of the ambient rendering)
const def = makeDeferred();
let child;
class Child extends Component<any, any> {
static template = xml`<span><t t-esc="props.val"/>|<t t-esc="val"/></span>`;
val = 3;
willUpdateProps() {
child = this;
return def;
}
}
class Parent extends Component<any, any> {
static template = xml`<div><Child val="state.valA"/></div>`;
static components = { Child };
state = useState({ valA: 1 });
}
const parent = new Parent();
await parent.mount(fixture);
expect(fixture.innerHTML).toBe("<div><span>1|3</span></div>");
parent.state.valA = 2;
await nextTick();
setTimeout(() => {
def.resolve();
}, 20);
child.val = 5;
await child.render();
expect(fixture.innerHTML).toBe("<div><span>2|5</span></div>");
});
test("change state and call manually render: no unnecessary rendering", async () => {
// when the state is changed, the component isn't notified directly (we wait
// for a microtask tick before calling 'render'), so it may happen that
// another rendering is done meanwhile, already using the new value of the
// state
class Widget extends Component<any, any> {
static template = xml`<div><t t-esc="state.val"/></div>`;
state = useState({ val: 1 });
}
Widget.prototype.__render = jest.fn(Widget.prototype.__render);
const widget = new Widget();
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div>1</div>");
expect(Widget.prototype.__render).toHaveBeenCalledTimes(1);
widget.state.val = 2;
await widget.render();
expect(fixture.innerHTML).toBe("<div>2</div>");
expect(Widget.prototype.__render).toHaveBeenCalledTimes(2);
});
}); });
describe("widget and observable state", () => { describe("widget and observable state", () => {
@@ -4890,7 +4947,7 @@ describe("unmounting and remounting", () => {
expect(fixture.innerHTML).toBe("<div>3</div>"); expect(fixture.innerHTML).toBe("<div>3</div>");
}); });
test("sub component is still active after being unmounted and remounted", async () => { test("sub component is still active after being unmounted and remounted", async () => {
class Child extends Component<any, any> { class Child extends Component<any, any> {
static template = xml` static template = xml`
<p t-on-click="state.value++"> <p t-on-click="state.value++">
@@ -4920,6 +4977,70 @@ describe("unmounting and remounting", () => {
await nextTick(); await nextTick();
expect(fixture.innerHTML).toBe("<div><p>3</p></div>"); expect(fixture.innerHTML).toBe("<div><p>3</p></div>");
}); });
test("change state just before mounting component", async () => {
const steps: number[] = [];
class TestWidget extends Component<any, any> {
static template = xml`
<div><t t-esc="state.val"/></div>
`;
state = useState({ val: 1 });
__render(f) {
steps.push(this.state.val);
return super.__render(f);
}
}
TestWidget.prototype.__render = jest.fn(TestWidget.prototype.__render);
const widget = new TestWidget();
widget.state.val = 2;
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div>2</div>");
expect(TestWidget.prototype.__render).toHaveBeenCalledTimes(1);
// unmount and re-mount, as in this case, willStart won't be called, so it's
// slightly different
widget.unmount();
widget.state.val = 3;
await widget.mount(fixture);
expect(fixture.innerHTML).toBe("<div>3</div>");
expect(TestWidget.prototype.__render).toHaveBeenCalledTimes(2);
expect(steps).toEqual([2, 3]);
});
test("change state while mounting component", async () => {
const steps: number[] = [];
class TestWidget extends Component<any, any> {
static template = xml`
<div><t t-esc="state.val"/></div>
`;
state = useState({ val: 1 });
__render(f) {
steps.push(this.state.val);
return super.__render(f);
}
}
TestWidget.prototype.__render = jest.fn(TestWidget.prototype.__render);
TestWidget.prototype.__patch = jest.fn(TestWidget.prototype.__patch);
const widget = new TestWidget();
let prom = widget.mount(fixture);
widget.state.val = 2;
await prom;
expect(fixture.innerHTML).toBe("<div>2</div>");
expect(TestWidget.prototype.__render).toHaveBeenCalledTimes(1);
// unmount and re-mount, as in this case, willStart won't be called, so it's
// slightly different
widget.unmount();
prom = widget.mount(fixture);
widget.state.val = 3;
await prom;
expect(fixture.innerHTML).toBe("<div>3</div>");
expect(TestWidget.prototype.__render).toHaveBeenCalledTimes(3);
expect(TestWidget.prototype.__patch).toHaveBeenCalledTimes(2);
expect(steps).toEqual([2, 2, 3]);
});
}); });
describe("dynamic root nodes", () => { describe("dynamic root nodes", () => {
@@ -35,7 +35,7 @@ exports[`RouteComponent can render simple cases 1`] = `
let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling); let def2 = w3.__prepare(extra.fiber, undefined, undefined, sibling);
let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}}); let pvnode = h('dummy', {key: templateId3, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
const fiber = w3.__owl__.currentFiber; const fiber = w3.__owl__.currentFiber;
def2.then(function () {if (fiber.isCancelled) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); def2.then(function () {if (fiber.isCompleted) {return;} const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
utils.defineProxy(vn5, pvnode); utils.defineProxy(vn5, pvnode);
w3.__owl__.pvnode = pvnode; w3.__owl__.pvnode = pvnode;
} }