mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[FIX] component: tricky concurrency issue
This is a really interesting problem: there was a situation where a component would not have an event handler properly bound. The reason was that we were in a situation where the scheduler task queue was flushed at an unfortunate timing: - parent component template is rendered - subcomponent is prepared: - sub component is willStarted - it is rendered (so, fiber counter is set to 0) - scheduler task queue is flushed => we patch the DOM - the code registered in the __prepare.then(handler) is executed, which add the createHook to the vnode (but after the dom is patched) Usually, the last two steps happen in a different order, but in an environment with connected components, we sometimes flush the task queue at various moments, so some code could be executed between the end of __prepare and the registered handler. The fix is interesting: we give a callback to the __prepare function instead of registering a .then handler to the deferred. This callback is then guaranteed to be called at exactly the proper timing. Thanks seb for the hard work of finding the root cause of this issue closes #520
This commit is contained in:
@@ -304,7 +304,7 @@ export class Component<T extends Env, Props extends {}> {
|
|||||||
const fiber = new Fiber(null, this, false, target);
|
const fiber = new Fiber(null, this, false, target);
|
||||||
fiber.shouldPatch = false;
|
fiber.shouldPatch = false;
|
||||||
if (!__owl__.vnode) {
|
if (!__owl__.vnode) {
|
||||||
this.__prepareAndRender(fiber);
|
this.__prepareAndRender(fiber, () => {});
|
||||||
} else {
|
} else {
|
||||||
this.__render(fiber);
|
this.__render(fiber);
|
||||||
}
|
}
|
||||||
@@ -544,7 +544,7 @@ export class Component<T extends Env, Props extends {}> {
|
|||||||
* subcomponent is created. It gets its scope and vars, if any, from the
|
* subcomponent is created. It gets its scope and vars, if any, from the
|
||||||
* parent template.
|
* parent template.
|
||||||
*/
|
*/
|
||||||
__prepare(parentFiber: Fiber, scope: any, vars: any) {
|
__prepare(parentFiber: Fiber, scope: any, vars: any, cb: CallableFunction): Fiber {
|
||||||
this.__owl__.scope = scope;
|
this.__owl__.scope = scope;
|
||||||
this.__owl__.vars = vars;
|
this.__owl__.vars = vars;
|
||||||
const fiber = new Fiber(parentFiber, this, parentFiber.force, null);
|
const fiber = new Fiber(parentFiber, this, parentFiber.force, null);
|
||||||
@@ -555,7 +555,8 @@ export class Component<T extends Env, Props extends {}> {
|
|||||||
parentFiber.lastChild!.sibling = fiber;
|
parentFiber.lastChild!.sibling = fiber;
|
||||||
}
|
}
|
||||||
parentFiber.lastChild = fiber;
|
parentFiber.lastChild = fiber;
|
||||||
return this.__prepareAndRender(fiber);
|
this.__prepareAndRender(fiber, cb);
|
||||||
|
return fiber;
|
||||||
}
|
}
|
||||||
|
|
||||||
__getTemplate(qweb: QWeb): string {
|
__getTemplate(qweb: QWeb): string {
|
||||||
@@ -577,7 +578,7 @@ export class Component<T extends Env, Props extends {}> {
|
|||||||
}
|
}
|
||||||
return p._template;
|
return p._template;
|
||||||
}
|
}
|
||||||
async __prepareAndRender(fiber: Fiber) {
|
async __prepareAndRender(fiber: Fiber, cb: CallableFunction) {
|
||||||
try {
|
try {
|
||||||
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) {
|
||||||
@@ -589,6 +590,7 @@ export class Component<T extends Env, Props extends {}> {
|
|||||||
}
|
}
|
||||||
if (!fiber.isCompleted) {
|
if (!fiber.isCompleted) {
|
||||||
this.__render(fiber);
|
this.__render(fiber);
|
||||||
|
cb();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -226,7 +226,6 @@ QWeb.addDirective({
|
|||||||
let propStr = Object.keys(props)
|
let propStr = Object.keys(props)
|
||||||
.map(k => k + ":" + props[k])
|
.map(k => k + ":" + props[k])
|
||||||
.join(",");
|
.join(",");
|
||||||
let defID = ctx.generateID();
|
|
||||||
let componentID = ctx.generateID();
|
let componentID = ctx.generateID();
|
||||||
|
|
||||||
const templateKey = ctx.generateTemplateKey();
|
const templateKey = ctx.generateTemplateKey();
|
||||||
@@ -439,16 +438,14 @@ QWeb.addDirective({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.addLine(`let def${defID} = w${componentID}.__prepare(extra.fiber, ${scopeVars});`);
|
ctx.addLine(
|
||||||
|
`let fiber = w${componentID}.__prepare(extra.fiber, ${scopeVars}, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; ${createHook}});`
|
||||||
|
);
|
||||||
// hack: specify empty remove hook to prevent the node from being removed from the DOM
|
// hack: specify empty remove hook to prevent the node from being removed from the DOM
|
||||||
const insertHook = refExpr ? `insert(vn) {${refExpr}},` : "";
|
const insertHook = refExpr ? `insert(vn) {${refExpr}},` : "";
|
||||||
ctx.addLine(
|
ctx.addLine(
|
||||||
`let pvnode = h('dummy', {key: ${templateKey}, hook: {${insertHook}remove() {},destroy(vn) {${finalizeComponentCode}}}});`
|
`let pvnode = h('dummy', {key: ${templateKey}, hook: {${insertHook}remove() {},destroy(vn) {${finalizeComponentCode}}}});`
|
||||||
);
|
);
|
||||||
ctx.addLine(`const fiber = w${componentID}.__owl__.currentFiber;`);
|
|
||||||
ctx.addLine(
|
|
||||||
`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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,37 +11,35 @@ exports[`animations t-transition combined with component 1`] = `
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//COMPONENT
|
//COMPONENT
|
||||||
let k4 = \`__5__\`;
|
let k3 = \`__4__\`;
|
||||||
let w3 = k4 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k4]] : false;
|
let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : false;
|
||||||
let props3 = {};
|
let props2 = {};
|
||||||
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
|
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
||||||
w3.destroy();
|
w2.destroy();
|
||||||
w3 = false;
|
w2 = false;
|
||||||
}
|
}
|
||||||
if (w3) {
|
if (w2) {
|
||||||
w3.__updateProps(props3, extra.fiber, undefined, undefined);
|
w2.__updateProps(props2, extra.fiber, undefined, undefined);
|
||||||
let pvnode = w3.__owl__.pvnode;
|
let pvnode = w2.__owl__.pvnode;
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
} else {
|
} else {
|
||||||
let componentKey3 = \`Child\`;
|
let componentKey2 = \`Child\`;
|
||||||
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| context['Child'];
|
let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| context['Child'];
|
||||||
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
|
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
|
||||||
w3 = new W3(parent, props3);
|
w2 = new W2(parent, props2);
|
||||||
const __patch3 = w3.__patch;
|
const __patch2 = w2.__patch;
|
||||||
w3.__patch = fiber => {__patch3.call(w3, fiber); if(!w3.__owl__.transitionInserted){w3.__owl__.transitionInserted = true;utils.transitionInsert(w3.__owl__.vnode, 'chimay');}};
|
w2.__patch = fiber => {__patch2.call(w2, fiber); if(!w2.__owl__.transitionInserted){w2.__owl__.transitionInserted = true;utils.transitionInsert(w2.__owl__.vnode, 'chimay');}};
|
||||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
parent.__owl__.cmap[k3] = w2.__owl__.id;
|
||||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
let fiber = w2.__prepare(extra.fiber, undefined, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {let finalize = () => {
|
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {let finalize = () => {
|
||||||
w3.destroy();
|
w2.destroy();
|
||||||
};
|
};
|
||||||
delete w3.__owl__.transitionInserted;
|
delete w2.__owl__.transitionInserted;
|
||||||
utils.transitionRemove(vn, 'chimay', finalize);}}});
|
utils.transitionRemove(vn, 'chimay', finalize);}}});
|
||||||
const fiber = w3.__owl__.currentFiber;
|
|
||||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
w3.__owl__.pvnode = pvnode;
|
w2.__owl__.pvnode = pvnode;
|
||||||
}
|
}
|
||||||
w3.__owl__.parentLastFiberId = extra.fiber.id;
|
w2.__owl__.parentLastFiberId = extra.fiber.id;
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
@@ -58,37 +56,35 @@ exports[`animations t-transition combined with t-component and t-if 1`] = `
|
|||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
if (context['state'].display) {
|
if (context['state'].display) {
|
||||||
//COMPONENT
|
//COMPONENT
|
||||||
let k4 = \`__5__\`;
|
let k3 = \`__4__\`;
|
||||||
let w3 = k4 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k4]] : false;
|
let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : false;
|
||||||
let props3 = {};
|
let props2 = {};
|
||||||
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
|
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
||||||
w3.destroy();
|
w2.destroy();
|
||||||
w3 = false;
|
w2 = false;
|
||||||
}
|
}
|
||||||
if (w3) {
|
if (w2) {
|
||||||
w3.__updateProps(props3, extra.fiber, undefined, undefined);
|
w2.__updateProps(props2, extra.fiber, undefined, undefined);
|
||||||
let pvnode = w3.__owl__.pvnode;
|
let pvnode = w2.__owl__.pvnode;
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
} else {
|
} else {
|
||||||
let componentKey3 = \`Child\`;
|
let componentKey2 = \`Child\`;
|
||||||
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| context['Child'];
|
let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| context['Child'];
|
||||||
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
|
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
|
||||||
w3 = new W3(parent, props3);
|
w2 = new W2(parent, props2);
|
||||||
const __patch3 = w3.__patch;
|
const __patch2 = w2.__patch;
|
||||||
w3.__patch = fiber => {__patch3.call(w3, fiber); if(!w3.__owl__.transitionInserted){w3.__owl__.transitionInserted = true;utils.transitionInsert(w3.__owl__.vnode, 'chimay');}};
|
w2.__patch = fiber => {__patch2.call(w2, fiber); if(!w2.__owl__.transitionInserted){w2.__owl__.transitionInserted = true;utils.transitionInsert(w2.__owl__.vnode, 'chimay');}};
|
||||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
parent.__owl__.cmap[k3] = w2.__owl__.id;
|
||||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
let fiber = w2.__prepare(extra.fiber, undefined, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {let finalize = () => {
|
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {let finalize = () => {
|
||||||
w3.destroy();
|
w2.destroy();
|
||||||
};
|
};
|
||||||
delete w3.__owl__.transitionInserted;
|
delete w2.__owl__.transitionInserted;
|
||||||
utils.transitionRemove(vn, 'chimay', finalize);}}});
|
utils.transitionRemove(vn, 'chimay', finalize);}}});
|
||||||
const fiber = w3.__owl__.currentFiber;
|
|
||||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
w3.__owl__.pvnode = pvnode;
|
w2.__owl__.pvnode = pvnode;
|
||||||
}
|
}
|
||||||
w3.__owl__.parentLastFiberId = extra.fiber.id;
|
w2.__owl__.parentLastFiberId = extra.fiber.id;
|
||||||
}
|
}
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
@@ -106,37 +102,35 @@ exports[`animations t-transition combined with t-component, remove and re-add be
|
|||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
if (context['state'].flag) {
|
if (context['state'].flag) {
|
||||||
//COMPONENT
|
//COMPONENT
|
||||||
let k4 = \`__5__\`;
|
let k3 = \`__4__\`;
|
||||||
let w3 = k4 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k4]] : false;
|
let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : false;
|
||||||
let props3 = {};
|
let props2 = {};
|
||||||
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
|
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
||||||
w3.destroy();
|
w2.destroy();
|
||||||
w3 = false;
|
w2 = false;
|
||||||
}
|
}
|
||||||
if (w3) {
|
if (w2) {
|
||||||
w3.__updateProps(props3, extra.fiber, undefined, undefined);
|
w2.__updateProps(props2, extra.fiber, undefined, undefined);
|
||||||
let pvnode = w3.__owl__.pvnode;
|
let pvnode = w2.__owl__.pvnode;
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
} else {
|
} else {
|
||||||
let componentKey3 = \`Child\`;
|
let componentKey2 = \`Child\`;
|
||||||
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| context['Child'];
|
let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| context['Child'];
|
||||||
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
|
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
|
||||||
w3 = new W3(parent, props3);
|
w2 = new W2(parent, props2);
|
||||||
const __patch3 = w3.__patch;
|
const __patch2 = w2.__patch;
|
||||||
w3.__patch = fiber => {__patch3.call(w3, fiber); if(!w3.__owl__.transitionInserted){w3.__owl__.transitionInserted = true;utils.transitionInsert(w3.__owl__.vnode, 'chimay');}};
|
w2.__patch = fiber => {__patch2.call(w2, fiber); if(!w2.__owl__.transitionInserted){w2.__owl__.transitionInserted = true;utils.transitionInsert(w2.__owl__.vnode, 'chimay');}};
|
||||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
parent.__owl__.cmap[k3] = w2.__owl__.id;
|
||||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
let fiber = w2.__prepare(extra.fiber, undefined, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {let finalize = () => {
|
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {let finalize = () => {
|
||||||
w3.destroy();
|
w2.destroy();
|
||||||
};
|
};
|
||||||
delete w3.__owl__.transitionInserted;
|
delete w2.__owl__.transitionInserted;
|
||||||
utils.transitionRemove(vn, 'chimay', finalize);}}});
|
utils.transitionRemove(vn, 'chimay', finalize);}}});
|
||||||
const fiber = w3.__owl__.currentFiber;
|
|
||||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
w3.__owl__.pvnode = pvnode;
|
w2.__owl__.pvnode = pvnode;
|
||||||
}
|
}
|
||||||
w3.__owl__.parentLastFiberId = extra.fiber.id;
|
w2.__owl__.parentLastFiberId = extra.fiber.id;
|
||||||
}
|
}
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
|
|||||||
@@ -254,11 +254,11 @@ describe("animations", () => {
|
|||||||
widget.state.display = false;
|
widget.state.display = false;
|
||||||
patchNextFrame(cb => {
|
patchNextFrame(cb => {
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
'<div><span class="chimay-leave chimay-leave-active" data-owl-key="__5__">blue</span></div>'
|
'<div><span class="chimay-leave chimay-leave-active" data-owl-key="__4__">blue</span></div>'
|
||||||
);
|
);
|
||||||
cb();
|
cb();
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
'<div><span class="chimay-leave-active chimay-leave-to" data-owl-key="__5__">blue</span></div>'
|
'<div><span class="chimay-leave-active chimay-leave-to" data-owl-key="__4__">blue</span></div>'
|
||||||
);
|
);
|
||||||
def.resolve();
|
def.resolve();
|
||||||
});
|
});
|
||||||
@@ -371,7 +371,7 @@ describe("animations", () => {
|
|||||||
|
|
||||||
await def; // wait for the mocked repaint to be done
|
await def; // wait for the mocked repaint to be done
|
||||||
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend")); // mock end of css transition
|
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend")); // mock end of css transition
|
||||||
expect(fixture.innerHTML).toBe('<div><span class="" data-owl-key="__5__">blue</span></div>');
|
expect(fixture.innerHTML).toBe('<div><span class="" data-owl-key="__4__">blue</span></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test("transitionInsert is called the correct amount of times", async () => {
|
test("transitionInsert is called the correct amount of times", async () => {
|
||||||
@@ -405,14 +405,14 @@ describe("animations", () => {
|
|||||||
widget.state.flag = false;
|
widget.state.flag = false;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
'<div><span class="chimay-leave-active chimay-leave-to" data-owl-key="__5__">blue</span></div>'
|
'<div><span class="chimay-leave-active chimay-leave-to" data-owl-key="__4__">blue</span></div>'
|
||||||
);
|
);
|
||||||
expect(QWeb.utils.transitionInsert).toBeCalledTimes(1);
|
expect(QWeb.utils.transitionInsert).toBeCalledTimes(1);
|
||||||
|
|
||||||
widget.state.flag = true;
|
widget.state.flag = true;
|
||||||
await nextTick();
|
await nextTick();
|
||||||
expect(fixture.innerHTML).toBe(
|
expect(fixture.innerHTML).toBe(
|
||||||
'<div><span class="chimay-enter-active chimay-enter-to" data-owl-key="__5__">blue</span></div>'
|
'<div><span class="chimay-enter-active chimay-enter-to" data-owl-key="__4__">blue</span></div>'
|
||||||
);
|
);
|
||||||
expect(QWeb.utils.transitionInsert).toBeCalledTimes(2);
|
expect(QWeb.utils.transitionInsert).toBeCalledTimes(2);
|
||||||
|
|
||||||
@@ -423,7 +423,7 @@ describe("animations", () => {
|
|||||||
|
|
||||||
expect(QWeb.utils.transitionInsert).toBeCalledTimes(3);
|
expect(QWeb.utils.transitionInsert).toBeCalledTimes(3);
|
||||||
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend"));
|
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend"));
|
||||||
expect(fixture.innerHTML).toBe('<div><span class="" data-owl-key="__5__">blue</span></div>');
|
expect(fixture.innerHTML).toBe('<div><span class="" data-owl-key="__4__">blue</span></div>');
|
||||||
QWeb.utils.transitionInsert = oldTransitionInsert;
|
QWeb.utils.transitionInsert = oldTransitionInsert;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,31 +11,29 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
|
|||||||
let c1 = [], p1 = {key:1};
|
let c1 = [], p1 = {key:1};
|
||||||
var vn1 = h('div', p1, c1);
|
var vn1 = h('div', p1, c1);
|
||||||
//COMPONENT
|
//COMPONENT
|
||||||
let k4 = \`__5__\`;
|
let k3 = \`__4__\`;
|
||||||
let w3 = k4 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k4]] : false;
|
let w2 = k3 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k3]] : false;
|
||||||
let props3 = {message:1};
|
let props2 = {message:1};
|
||||||
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
|
if (w2 && w2.__owl__.currentFiber && !w2.__owl__.vnode) {
|
||||||
w3.destroy();
|
w2.destroy();
|
||||||
w3 = false;
|
w2 = false;
|
||||||
}
|
}
|
||||||
if (w3) {
|
if (w2) {
|
||||||
w3.__updateProps(props3, extra.fiber, undefined, undefined);
|
w2.__updateProps(props2, extra.fiber, undefined, undefined);
|
||||||
let pvnode = w3.__owl__.pvnode;
|
let pvnode = w2.__owl__.pvnode;
|
||||||
c1.push(pvnode);
|
c1.push(pvnode);
|
||||||
} else {
|
} else {
|
||||||
let componentKey3 = \`Child\`;
|
let componentKey2 = \`Child\`;
|
||||||
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| context['Child'];
|
let W2 = context.constructor.components[componentKey2] || QWeb.components[componentKey2]|| context['Child'];
|
||||||
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
|
if (!W2) {throw new Error('Cannot find the definition of component \\"' + componentKey2 + '\\"')}
|
||||||
w3 = new W3(parent, props3);
|
w2 = new W2(parent, props2);
|
||||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
parent.__owl__.cmap[k3] = w2.__owl__.id;
|
||||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
let fiber = w2.__prepare(extra.fiber, undefined, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
||||||
const fiber = w3.__owl__.currentFiber;
|
|
||||||
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;
|
w2.__owl__.pvnode = pvnode;
|
||||||
}
|
}
|
||||||
w3.__owl__.parentLastFiberId = extra.fiber.id;
|
w2.__owl__.parentLastFiberId = extra.fiber.id;
|
||||||
return vn1;
|
return vn1;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -1849,6 +1849,38 @@ describe("other directives with t-component", () => {
|
|||||||
expect(widget.n).toBe(1);
|
expect(widget.n).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("t-on works, even if flush is called many times", async () => {
|
||||||
|
let flag = false;
|
||||||
|
let mounted = false;
|
||||||
|
class Child extends Component<any, any> {
|
||||||
|
static template = xml`<span>child</span>`;
|
||||||
|
mounted() {
|
||||||
|
mounted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Parent extends Component<any, any> {
|
||||||
|
static template = xml`<div><Child t-on-click="doSomething"/></div>`;
|
||||||
|
static components = { Child };
|
||||||
|
|
||||||
|
doSomething() {
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = new Parent();
|
||||||
|
parent.mount(fixture);
|
||||||
|
while (!mounted) {
|
||||||
|
await nextMicroTick();
|
||||||
|
Component.scheduler.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(fixture.innerHTML).toBe("<div><span>child</span></div>");
|
||||||
|
expect(flag).toBe(false);
|
||||||
|
fixture.querySelector("span")!.click();
|
||||||
|
expect(flag).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
test("t-on with handler bound to argument", async () => {
|
test("t-on with handler bound to argument", async () => {
|
||||||
expect.assertions(3);
|
expect.assertions(3);
|
||||||
env.qweb.addTemplates(`
|
env.qweb.addTemplates(`
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ exports[`Link component can render simple cases 1`] = `
|
|||||||
let utils = this.constructor.utils;
|
let utils = this.constructor.utils;
|
||||||
let owner = context;
|
let owner = context;
|
||||||
var h = this.h;
|
var h = this.h;
|
||||||
let _6 = utils.toObj({'router-link-active':context['isActive']});
|
let _5 = utils.toObj({'router-link-active':context['isActive']});
|
||||||
var _7 = context['href'];
|
var _6 = context['href'];
|
||||||
let c8 = [], p8 = {key:8,attrs:{href: _7},class:_6,on:{}};
|
let c7 = [], p7 = {key:7,attrs:{href: _6},class:_5,on:{}};
|
||||||
var vn8 = h('a', p8, c8);
|
var vn7 = h('a', p7, c7);
|
||||||
extra.handlers['click' + 8] = extra.handlers['click' + 8] || function (e) {const fn = context['navigate'];if (fn) { fn.call(owner, e); } else { context.navigate; }};
|
extra.handlers['click' + 7] = extra.handlers['click' + 7] || function (e) {const fn = context['navigate'];if (fn) { fn.call(owner, e); } else { context.navigate; }};
|
||||||
p8.on['click'] = extra.handlers['click' + 8];
|
p7.on['click'] = extra.handlers['click' + 7];
|
||||||
const slot9 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
const slot8 = this.constructor.slots[context.__owl__.slotId + '_' + 'default'];
|
||||||
if (slot9) {
|
if (slot8) {
|
||||||
slot9.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c8, vars: extra.vars, parent: extra.parent || owner}));
|
slot8.call(this, context.__owl__.parent, Object.assign({}, extra, {parentNode: c7, vars: extra.vars, parent: extra.parent || owner}));
|
||||||
}
|
}
|
||||||
return vn8;
|
return vn7;
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -10,35 +10,33 @@ exports[`RouteComponent can render simple cases 1`] = `
|
|||||||
let result;
|
let result;
|
||||||
var h = this.h;
|
var h = this.h;
|
||||||
if (context['routeComponent']) {
|
if (context['routeComponent']) {
|
||||||
const nodeKey6 = context['env'].router.currentRouteName;
|
const nodeKey5 = context['env'].router.currentRouteName;
|
||||||
//COMPONENT
|
//COMPONENT
|
||||||
let k9 = \`__10__\` + nodeKey6;
|
let k7 = \`__8__\` + nodeKey5;
|
||||||
let w8 = k9 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k9]] : false;
|
let w6 = k7 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k7]] : false;
|
||||||
let vn11 = {};
|
let vn9 = {};
|
||||||
result = vn11;
|
result = vn9;
|
||||||
let props8 = Object.assign({}, context['env'].router.currentParams);
|
let props6 = Object.assign({}, context['env'].router.currentParams);
|
||||||
if (w8 && w8.__owl__.currentFiber && !w8.__owl__.vnode) {
|
if (w6 && w6.__owl__.currentFiber && !w6.__owl__.vnode) {
|
||||||
w8.destroy();
|
w6.destroy();
|
||||||
w8 = false;
|
w6 = false;
|
||||||
}
|
}
|
||||||
if (w8) {
|
if (w6) {
|
||||||
w8.__updateProps(props8, extra.fiber, undefined, undefined);
|
w6.__updateProps(props6, extra.fiber, undefined, undefined);
|
||||||
let pvnode = w8.__owl__.pvnode;
|
let pvnode = w6.__owl__.pvnode;
|
||||||
utils.defineProxy(vn11, pvnode);
|
utils.defineProxy(vn9, pvnode);
|
||||||
} else {
|
} else {
|
||||||
let componentKey8 = \`routeComponent\`;
|
let componentKey6 = \`routeComponent\`;
|
||||||
let W8 = context.constructor.components[componentKey8] || QWeb.components[componentKey8]|| context['routeComponent'];
|
let W6 = context.constructor.components[componentKey6] || QWeb.components[componentKey6]|| context['routeComponent'];
|
||||||
if (!W8) {throw new Error('Cannot find the definition of component \\"' + componentKey8 + '\\"')}
|
if (!W6) {throw new Error('Cannot find the definition of component \\"' + componentKey6 + '\\"')}
|
||||||
w8 = new W8(parent, props8);
|
w6 = new W6(parent, props6);
|
||||||
parent.__owl__.cmap[k9] = w8.__owl__.id;
|
parent.__owl__.cmap[k7] = w6.__owl__.id;
|
||||||
let def7 = w8.__prepare(extra.fiber, undefined, undefined);
|
let fiber = w6.__prepare(extra.fiber, undefined, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||||
let pvnode = h('dummy', {key: k9, hook: {remove() {},destroy(vn) {w8.destroy();}}});
|
let pvnode = h('dummy', {key: k7, hook: {remove() {},destroy(vn) {w6.destroy();}}});
|
||||||
const fiber = w8.__owl__.currentFiber;
|
utils.defineProxy(vn9, pvnode);
|
||||||
def7.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
w6.__owl__.pvnode = pvnode;
|
||||||
utils.defineProxy(vn11, pvnode);
|
|
||||||
w8.__owl__.pvnode = pvnode;
|
|
||||||
}
|
}
|
||||||
w8.__owl__.parentLastFiberId = extra.fiber.id;
|
w6.__owl__.parentLastFiberId = extra.fiber.id;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}"
|
}"
|
||||||
|
|||||||
Reference in New Issue
Block a user