mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[FIX] component: reorganize fiber update algorithm
Closes #473 Closes #484 Closes #489 Closes #492 Closes #512 Co-authored-by: Aaron Bohy <aab@odoo.com>
This commit is contained in:
@@ -27,11 +27,14 @@ exports[`animations t-transition combined with component 1`] = `
|
||||
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| context['Child'];
|
||||
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
|
||||
w3 = new W3(parent, props3);
|
||||
const __patch3 = w3.__patch;
|
||||
w3.__patch = fiber => {__patch3.call(w3, fiber); if(!w3.__owl__.transitionInserted){w3.__owl__.transitionInserted = true;utils.transitionInsert(w3.__owl__.vnode, 'chimay');}};
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;utils.transitionInsert(vn, 'chimay');},remove() {},destroy(vn) {let finalize = () => {
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {let finalize = () => {
|
||||
w3.destroy();
|
||||
};
|
||||
delete w3.__owl__.transitionInserted;
|
||||
utils.transitionRemove(vn, 'chimay', finalize);}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
@@ -71,11 +74,62 @@ exports[`animations t-transition combined with t-component and t-if 1`] = `
|
||||
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| context['Child'];
|
||||
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
|
||||
w3 = new W3(parent, props3);
|
||||
const __patch3 = w3.__patch;
|
||||
w3.__patch = fiber => {__patch3.call(w3, fiber); if(!w3.__owl__.transitionInserted){w3.__owl__.transitionInserted = true;utils.transitionInsert(w3.__owl__.vnode, 'chimay');}};
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;utils.transitionInsert(vn, 'chimay');},remove() {},destroy(vn) {let finalize = () => {
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {let finalize = () => {
|
||||
w3.destroy();
|
||||
};
|
||||
delete w3.__owl__.transitionInserted;
|
||||
utils.transitionRemove(vn, 'chimay', finalize);}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c1.push(pvnode);
|
||||
w3.__owl__.pvnode = pvnode;
|
||||
}
|
||||
w3.__owl__.parentLastFiberId = extra.fiber.id;
|
||||
}
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`animations t-transition combined with t-component, remove and re-add before transitionend 1`] = `
|
||||
"function anonymous(context,extra
|
||||
) {
|
||||
let utils = this.constructor.utils;
|
||||
let QWeb = this.constructor;
|
||||
let parent = context;
|
||||
let owner = context;
|
||||
var h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
var vn1 = h('div', p1, c1);
|
||||
if (context['state'].flag) {
|
||||
//COMPONENT
|
||||
let k4 = \`__5__\`;
|
||||
let w3 = k4 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k4]] : false;
|
||||
let props3 = {};
|
||||
if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) {
|
||||
w3.destroy();
|
||||
w3 = false;
|
||||
}
|
||||
if (w3) {
|
||||
w3.__updateProps(props3, extra.fiber, undefined, undefined);
|
||||
let pvnode = w3.__owl__.pvnode;
|
||||
c1.push(pvnode);
|
||||
} else {
|
||||
let componentKey3 = \`Child\`;
|
||||
let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| context['Child'];
|
||||
if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')}
|
||||
w3 = new W3(parent, props3);
|
||||
const __patch3 = w3.__patch;
|
||||
w3.__patch = fiber => {__patch3.call(w3, fiber); if(!w3.__owl__.transitionInserted){w3.__owl__.transitionInserted = true;utils.transitionInsert(w3.__owl__.vnode, 'chimay');}};
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {let finalize = () => {
|
||||
w3.destroy();
|
||||
};
|
||||
delete w3.__owl__.transitionInserted;
|
||||
utils.transitionRemove(vn, 'chimay', finalize);}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
|
||||
+72
-23
@@ -1,13 +1,15 @@
|
||||
import { Component, Env } from "../src/component/component";
|
||||
import { QWeb } from "../src/qweb/index";
|
||||
import { useState, useRef } from "../src/hooks";
|
||||
import { xml } from "../src/tags";
|
||||
import {
|
||||
makeDeferred,
|
||||
makeTestFixture,
|
||||
makeTestEnv,
|
||||
patchNextFrame,
|
||||
renderToDOM,
|
||||
unpatchNextFrame
|
||||
unpatchNextFrame,
|
||||
nextTick
|
||||
} from "./helpers";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -321,30 +323,23 @@ describe("animations", () => {
|
||||
});
|
||||
|
||||
test("t-transition combined with t-component, remove and re-add before transitionend", async () => {
|
||||
expect.assertions(11);
|
||||
expect.assertions(12);
|
||||
|
||||
env.qweb.addTemplates(
|
||||
`<templates>
|
||||
<div t-name="Parent">
|
||||
<button t-on-click="toggle">Toggle</button>
|
||||
<t t-if="state.flag" t-component="Child" t-transition="chimay"/>
|
||||
</div>
|
||||
<span t-name="Child">blue</span>
|
||||
</templates>`
|
||||
);
|
||||
class Child extends Widget {}
|
||||
class Child extends Widget {
|
||||
static template = xml`<span>blue</span>`;
|
||||
}
|
||||
class Parent extends Widget {
|
||||
static template = xml`
|
||||
<div t-name="Parent">
|
||||
<t t-if="state.flag" t-component="Child" t-transition="chimay"/>
|
||||
</div>`;
|
||||
static components = { Child };
|
||||
state = useState({ flag: false });
|
||||
|
||||
toggle() {
|
||||
this.state.flag = !this.state.flag;
|
||||
}
|
||||
}
|
||||
|
||||
const widget = new Parent();
|
||||
await widget.mount(fixture);
|
||||
let button = widget.el!.querySelector("button");
|
||||
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
||||
|
||||
let def = makeDeferred();
|
||||
let phase = "enter";
|
||||
@@ -357,24 +352,78 @@ describe("animations", () => {
|
||||
def.resolve();
|
||||
});
|
||||
|
||||
// click display the span
|
||||
button!.click();
|
||||
// display the span
|
||||
widget.state.flag = true;
|
||||
await def; // wait for the mocked repaint to be done
|
||||
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend")); // mock end of css transition
|
||||
expect(fixture.innerHTML).toBe('<div><button>Toggle</button><span class="">blue</span></div>');
|
||||
expect(fixture.innerHTML).toBe('<div><span class="">blue</span></div>');
|
||||
|
||||
// click to remove the span, and click again to re-add it before transitionend
|
||||
def = makeDeferred();
|
||||
phase = "leave";
|
||||
button!.click();
|
||||
|
||||
widget.state.flag = false;
|
||||
|
||||
await def; // wait for the mocked repaint to be done
|
||||
def = makeDeferred();
|
||||
phase = "enter";
|
||||
button!.click();
|
||||
widget.state.flag = true;
|
||||
|
||||
await def; // wait for the mocked repaint to be done
|
||||
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend")); // mock end of css transition
|
||||
expect(fixture.innerHTML).toBe('<div><button>Toggle</button><span class="">blue</span></div>');
|
||||
expect(fixture.innerHTML).toBe('<div><span class="" data-owl-key="__5__">blue</span></div>');
|
||||
});
|
||||
|
||||
test("transitionInsert is called the correct amount of times", async () => {
|
||||
const oldTransitionInsert = QWeb.utils.transitionInsert;
|
||||
QWeb.utils.transitionInsert = jest.fn(oldTransitionInsert);
|
||||
|
||||
class Child extends Widget {
|
||||
static template = xml`<span>blue</span>`;
|
||||
}
|
||||
class Parent extends Widget {
|
||||
static template = xml`
|
||||
<div t-name="Parent">
|
||||
<Child t-if="state.flag" t-transition="chimay"/>
|
||||
</div>`;
|
||||
static components = { Child };
|
||||
state = useState({ flag: false });
|
||||
}
|
||||
|
||||
patchNextFrame(cb => cb());
|
||||
|
||||
const widget = new Parent();
|
||||
await widget.mount(fixture);
|
||||
|
||||
widget.state.flag = true;
|
||||
|
||||
await nextTick();
|
||||
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend"));
|
||||
expect(fixture.innerHTML).toBe('<div><span class="">blue</span></div>');
|
||||
expect(QWeb.utils.transitionInsert).toBeCalledTimes(1);
|
||||
|
||||
widget.state.flag = false;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe(
|
||||
'<div><span class="chimay-leave-active chimay-leave-to" data-owl-key="__5__">blue</span></div>'
|
||||
);
|
||||
expect(QWeb.utils.transitionInsert).toBeCalledTimes(1);
|
||||
|
||||
widget.state.flag = true;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe(
|
||||
'<div><span class="chimay-enter-active chimay-enter-to" data-owl-key="__5__">blue</span></div>'
|
||||
);
|
||||
expect(QWeb.utils.transitionInsert).toBeCalledTimes(2);
|
||||
|
||||
widget.state.flag = false;
|
||||
await nextTick();
|
||||
widget.state.flag = true;
|
||||
await nextTick();
|
||||
|
||||
expect(QWeb.utils.transitionInsert).toBeCalledTimes(3);
|
||||
widget.el!.querySelector("span")!.dispatchEvent(new Event("transitionend"));
|
||||
expect(fixture.innerHTML).toBe('<div><span class="" data-owl-key="__5__">blue</span></div>');
|
||||
QWeb.utils.transitionInsert = oldTransitionInsert;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,7 +58,7 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for
|
||||
w9 = new W9(parent, props9);
|
||||
parent.__owl__.cmap[k10] = w9.__owl__.id;
|
||||
let def8 = w9.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k10, hook: {insert(vn) { let nvn=w9.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w9.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k10, hook: {remove() {},destroy(vn) {w9.destroy();}}});
|
||||
const fiber = w9.__owl__.currentFiber;
|
||||
def8.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c1.push(pvnode);
|
||||
@@ -102,7 +102,7 @@ exports[`basic widget properties t-key on a component with t-if, and a sibling c
|
||||
w4 = new W4(parent, props4);
|
||||
parent.__owl__.cmap[k5] = w4.__owl__.id;
|
||||
let def3 = w4.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k5, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k5, hook: {remove() {},destroy(vn) {w4.destroy();}}});
|
||||
const fiber = w4.__owl__.currentFiber;
|
||||
def3.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c1.push(pvnode);
|
||||
@@ -129,7 +129,7 @@ exports[`basic widget properties t-key on a component with t-if, and a sibling c
|
||||
w8 = new W8(parent, props8);
|
||||
parent.__owl__.cmap[k9] = w8.__owl__.id;
|
||||
let def7 = w8.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k9, hook: {insert(vn) { let nvn=w8.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w8.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k9, hook: {remove() {},destroy(vn) {w8.destroy();}}});
|
||||
const fiber = w8.__owl__.currentFiber;
|
||||
def7.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c1.push(pvnode);
|
||||
@@ -170,7 +170,7 @@ exports[`class and style attributes with t-component dynamic t-att-style is prop
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.style = _6;}};});
|
||||
c1.push(pvnode);
|
||||
@@ -214,7 +214,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;context.__owl__.refs[ref6] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref6];}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) {context.__owl__.refs[ref6] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref6];}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){}};});
|
||||
c1.push(pvnode);
|
||||
@@ -272,7 +272,7 @@ exports[`class and style attributes with t-component t-att-class is properly add
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;context.__owl__.refs[ref6] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref6];}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) {context.__owl__.refs[ref6] = w3;},remove() {},destroy(vn) {w3.destroy();delete context.__owl__.refs[ref6];}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){}};});
|
||||
c1.push(pvnode);
|
||||
@@ -342,7 +342,7 @@ exports[`composition sub components with some state rendered in a loop 1`] = `
|
||||
w7 = new W7(parent, props7);
|
||||
parent.__owl__.cmap[k8] = w7.__owl__.id;
|
||||
let def6 = w7.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k8, hook: {insert(vn) { let nvn=w7.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k8, hook: {remove() {},destroy(vn) {w7.destroy();}}});
|
||||
const fiber = w7.__owl__.currentFiber;
|
||||
def6.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c1.push(pvnode);
|
||||
@@ -383,7 +383,7 @@ exports[`composition t-component with dynamic value 1`] = `
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c1.push(pvnode);
|
||||
@@ -423,7 +423,7 @@ exports[`composition t-component with dynamic value 2 1`] = `
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c1.push(pvnode);
|
||||
@@ -463,7 +463,7 @@ exports[`dynamic t-props basic use 1`] = `
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c1.push(pvnode);
|
||||
@@ -507,7 +507,7 @@ exports[`other directives with t-component t-on with getter as handler 1`] = `
|
||||
w4 = new W4(parent, props4);
|
||||
parent.__owl__.cmap[k5] = w4.__owl__.id;
|
||||
let def3 = w4.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k5, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k5, hook: {remove() {},destroy(vn) {w4.destroy();}}});
|
||||
const fiber = w4.__owl__.currentFiber;
|
||||
def3.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['handler'];if (fn) { fn.call(owner, e); } else { owner.handler; }});}};});
|
||||
c1.push(pvnode);
|
||||
@@ -547,7 +547,7 @@ exports[`other directives with t-component t-on with handler bound to argument 1
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, 3, e); } else { owner.onEv; }});}};});
|
||||
c1.push(pvnode);
|
||||
@@ -587,7 +587,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, {}, e); } else { owner.onEv; }});}};});
|
||||
c1.push(pvnode);
|
||||
@@ -627,7 +627,7 @@ exports[`other directives with t-component t-on with handler bound to empty obje
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, {}, e); } else { owner.onEv; }});}};});
|
||||
c1.push(pvnode);
|
||||
@@ -667,7 +667,7 @@ exports[`other directives with t-component t-on with handler bound to object 1`]
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, {val:3}, e); } else { owner.onEv; }});}};});
|
||||
c1.push(pvnode);
|
||||
@@ -711,7 +711,7 @@ exports[`other directives with t-component t-on with inline statement 1`] = `
|
||||
w4 = new W4(parent, props4);
|
||||
parent.__owl__.cmap[k5] = w4.__owl__.id;
|
||||
let def3 = w4.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k5, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k5, hook: {remove() {},destroy(vn) {w4.destroy();}}});
|
||||
const fiber = w4.__owl__.currentFiber;
|
||||
def3.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['state.counter++'];if (fn) { fn.call(owner, e); } else { owner.state.counter++; }});}};});
|
||||
c1.push(pvnode);
|
||||
@@ -751,7 +751,7 @@ exports[`other directives with t-component t-on with no handler (only modifiers)
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, e); } else { owner.onEv; }});}};});
|
||||
c1.push(pvnode);
|
||||
@@ -791,7 +791,7 @@ exports[`other directives with t-component t-on with prevent and self modifiers
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {e.preventDefault();if (e.target !== vn.elm) {return}const fn = owner['onEv'];if (fn) { fn.call(owner, e); } else { owner.onEv; }});}};});
|
||||
c1.push(pvnode);
|
||||
@@ -831,7 +831,7 @@ exports[`other directives with t-component t-on with self and prevent modifiers
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {if (e.target !== vn.elm) {return}e.preventDefault();const fn = owner['onEv'];if (fn) { fn.call(owner, e); } else { owner.onEv; }});}};});
|
||||
c1.push(pvnode);
|
||||
@@ -871,7 +871,7 @@ exports[`other directives with t-component t-on with self modifier 1`] = `
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {const fn = owner['onEv1'];if (fn) { fn.call(owner, e); } else { owner.onEv1; }});vn.elm.addEventListener('ev-2', function (e) {if (e.target !== vn.elm) {return}const fn = owner['onEv2'];if (fn) { fn.call(owner, e); } else { owner.onEv2; }});}};});
|
||||
c1.push(pvnode);
|
||||
@@ -911,7 +911,7 @@ exports[`other directives with t-component t-on with stop and/or prevent modifie
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev-1', function (e) {e.stopPropagation();const fn = owner['onEv1'];if (fn) { fn.call(owner, e); } else { owner.onEv1; }});vn.elm.addEventListener('ev-2', function (e) {e.preventDefault();const fn = owner['onEv2'];if (fn) { fn.call(owner, e); } else { owner.onEv2; }});vn.elm.addEventListener('ev-3', function (e) {e.stopPropagation();e.preventDefault();const fn = owner['onEv3'];if (fn) { fn.call(owner, e); } else { owner.onEv3; }});}};});
|
||||
c1.push(pvnode);
|
||||
@@ -969,7 +969,7 @@ exports[`random stuff/miscellaneous snapshotting compiled code 1`] = `
|
||||
w4 = new W4(parent, props4);
|
||||
parent.__owl__.cmap[k5] = w4.__owl__.id;
|
||||
let def3 = w4.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k5, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k5, hook: {remove() {},destroy(vn) {w4.destroy();}}});
|
||||
const fiber = w4.__owl__.currentFiber;
|
||||
def3.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c1.push(pvnode);
|
||||
@@ -1026,7 +1026,7 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument
|
||||
w7 = new W7(parent, props7);
|
||||
parent.__owl__.cmap[k8] = w7.__owl__.id;
|
||||
let def6 = w7.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k8, hook: {insert(vn) { let nvn=w7.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k8, hook: {remove() {},destroy(vn) {w7.destroy();}}});
|
||||
const fiber = w7.__owl__.currentFiber;
|
||||
def6.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; vnode.data.hook = {create(_, vn){vn.elm.addEventListener('ev', function (e) {const fn = owner['onEv'];if (fn) { fn.call(owner, arg10, e); } else { owner.onEv; }});}};});
|
||||
c1.push(pvnode);
|
||||
@@ -1318,7 +1318,7 @@ exports[`t-slot directive can define and call slots 1`] = `
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
w3.__owl__.slotId = 1;
|
||||
let def2 = w3.__prepare(extra.fiber, {}, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c1.push(pvnode);
|
||||
@@ -1553,7 +1553,7 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = `
|
||||
parent.__owl__.cmap[k10] = w9.__owl__.id;
|
||||
w9.__owl__.slotId = 1;
|
||||
let def8 = w9.__prepare(extra.fiber, Object.assign({}, scope), undefined);
|
||||
let pvnode = h('dummy', {key: k10, hook: {insert(vn) { let nvn=w9.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w9.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k10, hook: {remove() {},destroy(vn) {w9.destroy();}}});
|
||||
const fiber = w9.__owl__.currentFiber;
|
||||
def8.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c7.push(pvnode);
|
||||
@@ -1654,7 +1654,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = `
|
||||
parent.__owl__.cmap[k11] = w10.__owl__.id;
|
||||
w10.__owl__.slotId = 1;
|
||||
let def9 = w10.__prepare(extra.fiber, Object.assign({}, scope), {_8});
|
||||
let pvnode = h('dummy', {key: k11, hook: {insert(vn) { let nvn=w10.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w10.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k11, hook: {remove() {},destroy(vn) {w10.destroy();}}});
|
||||
const fiber = w10.__owl__.currentFiber;
|
||||
def9.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c7.push(pvnode);
|
||||
@@ -1710,7 +1710,7 @@ exports[`t-slot directive slots are rendered with proper context, part 4 1`] = `
|
||||
parent.__owl__.cmap[k5] = w4.__owl__.id;
|
||||
w4.__owl__.slotId = 1;
|
||||
let def3 = w4.__prepare(extra.fiber, {}, {_2});
|
||||
let pvnode = h('dummy', {key: k5, hook: {insert(vn) { let nvn=w4.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w4.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k5, hook: {remove() {},destroy(vn) {w4.destroy();}}});
|
||||
const fiber = w4.__owl__.currentFiber;
|
||||
def3.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c1.push(pvnode);
|
||||
@@ -1782,7 +1782,7 @@ exports[`top level sub widgets basic use 1`] = `
|
||||
w2 = new W2(parent, props2);
|
||||
parent.__owl__.cmap[k3] = w2.__owl__.id;
|
||||
let def1 = w2.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k3, hook: {insert(vn) { let nvn=w2.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w2.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
||||
const fiber = w2.__owl__.currentFiber;
|
||||
def1.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
utils.defineProxy(vn5, pvnode);
|
||||
@@ -1824,7 +1824,7 @@ exports[`top level sub widgets can select a sub widget 1`] = `
|
||||
w2 = new W2(parent, props2);
|
||||
parent.__owl__.cmap[k3] = w2.__owl__.id;
|
||||
let def1 = w2.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k3, hook: {insert(vn) { let nvn=w2.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w2.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k3, hook: {remove() {},destroy(vn) {w2.destroy();}}});
|
||||
const fiber = w2.__owl__.currentFiber;
|
||||
def1.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
utils.defineProxy(vn5, pvnode);
|
||||
@@ -1854,7 +1854,7 @@ exports[`top level sub widgets can select a sub widget 1`] = `
|
||||
w7 = new W7(parent, props7);
|
||||
parent.__owl__.cmap[k8] = w7.__owl__.id;
|
||||
let def6 = w7.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k8, hook: {insert(vn) { let nvn=w7.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w7.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k8, hook: {remove() {},destroy(vn) {w7.destroy();}}});
|
||||
const fiber = w7.__owl__.currentFiber;
|
||||
def6.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
utils.defineProxy(vn10, pvnode);
|
||||
|
||||
@@ -29,7 +29,7 @@ exports[`props validation props are validated in dev mode (code snapshot) 1`] =
|
||||
w3 = new W3(parent, props3);
|
||||
parent.__owl__.cmap[k4] = w3.__owl__.id;
|
||||
let def2 = w3.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k4, hook: {insert(vn) { let nvn=w3.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w3.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k4, hook: {remove() {},destroy(vn) {w3.destroy();}}});
|
||||
const fiber = w3.__owl__.currentFiber;
|
||||
def2.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
c1.push(pvnode);
|
||||
|
||||
@@ -1513,6 +1513,25 @@ describe("composition", () => {
|
||||
"<div><span>11</span><span>2</span><span>13</span></div>"
|
||||
);
|
||||
});
|
||||
|
||||
test("three level of components with collapsing root nodes", async () => {
|
||||
class GrandChild extends Component<any, any> {
|
||||
static template = xml`<div>2</div>`;
|
||||
}
|
||||
class Child extends Component<any, any> {
|
||||
static components = { GrandChild };
|
||||
static template = xml`<GrandChild/>`;
|
||||
}
|
||||
class Parent extends Component<any, any> {
|
||||
static components = { Child };
|
||||
static template = xml`<Child></Child>`;
|
||||
}
|
||||
|
||||
const app = new Parent();
|
||||
await app.mount(fixture);
|
||||
|
||||
expect(fixture.innerHTML).toBe("<div>2</div>");
|
||||
});
|
||||
});
|
||||
|
||||
describe("props evaluation ", () => {
|
||||
@@ -2136,6 +2155,40 @@ describe("other directives with t-component", () => {
|
||||
expect(env.qweb.templates[Parent.template].fn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("t-on on nested components with collapsing root nodes", async () => {
|
||||
const steps: string[] = [];
|
||||
let grandChild;
|
||||
class GrandChild extends Component<any, any> {
|
||||
static template = xml`<span t-on-ev="_onEv"/>`;
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
grandChild = this;
|
||||
}
|
||||
_onEv() {
|
||||
steps.push('GrandChild');
|
||||
}
|
||||
}
|
||||
class Child extends Component<any, any> {
|
||||
static template = xml`<GrandChild t-on-ev="_onEv"/>`;
|
||||
static components = { GrandChild };
|
||||
_onEv() {
|
||||
steps.push('Child');
|
||||
}
|
||||
}
|
||||
class Parent extends Component<any, any> {
|
||||
static template = xml`<Child t-on-ev="_onEv"/>`;
|
||||
static components = { Child };
|
||||
_onEv() {
|
||||
steps.push('Parent');
|
||||
}
|
||||
}
|
||||
const parent = new Parent();
|
||||
await parent.mount(fixture);
|
||||
|
||||
grandChild.trigger("ev");
|
||||
expect(steps).toEqual(['GrandChild', 'Child', 'Parent']);
|
||||
});
|
||||
|
||||
test("t-if works with t-component", async () => {
|
||||
env.qweb.addTemplate("ParentWidget", `<div><t t-component="child" t-if="state.flag"/></div>`);
|
||||
class Child extends Widget {}
|
||||
@@ -2244,9 +2297,9 @@ describe("other directives with t-component", () => {
|
||||
<t t-esc="state.val"/>
|
||||
<t t-esc="props.val"/>
|
||||
</span>`;
|
||||
state = useState({ val: 'A' });
|
||||
state = useState({ val: "A" });
|
||||
mounted() {
|
||||
this.state.val = 'B';
|
||||
this.state.val = "B";
|
||||
}
|
||||
}
|
||||
class ParentWidget extends Widget {
|
||||
@@ -2366,10 +2419,6 @@ describe("random stuff/miscellaneous", () => {
|
||||
steps.push(`${this.name}:__patch`);
|
||||
super.__patch(vnode);
|
||||
}
|
||||
__mount(vnode, elm) {
|
||||
steps.push(`${this.name}:__patch(from __mount)`);
|
||||
return super.__mount(vnode, elm);
|
||||
}
|
||||
mounted() {
|
||||
steps.push(`${this.name}:mounted`);
|
||||
}
|
||||
@@ -2466,15 +2515,15 @@ describe("random stuff/miscellaneous", () => {
|
||||
"E:willStart",
|
||||
"D:render",
|
||||
"E:render",
|
||||
"E:__patch",
|
||||
"D:__patch",
|
||||
"C:__patch",
|
||||
"B:__patch",
|
||||
"A:__patch",
|
||||
"B:__patch(from __mount)",
|
||||
"C:__patch(from __mount)",
|
||||
"D:__patch(from __mount)",
|
||||
"E:__patch(from __mount)",
|
||||
"B:mounted",
|
||||
"D:mounted",
|
||||
"E:mounted",
|
||||
"D:mounted",
|
||||
"C:mounted",
|
||||
"B:mounted",
|
||||
"A:mounted"
|
||||
]);
|
||||
|
||||
@@ -2491,12 +2540,12 @@ describe("random stuff/miscellaneous", () => {
|
||||
"F:render",
|
||||
"C:willPatch",
|
||||
"D:willPatch",
|
||||
"F:__patch",
|
||||
"D:__patch",
|
||||
"C:__patch",
|
||||
"E:willUnmount",
|
||||
"E:destroy",
|
||||
"F:__patch(from __mount)",
|
||||
"F:mounted",
|
||||
"D:__patch",
|
||||
"D:patched",
|
||||
"C:patched"
|
||||
]);
|
||||
@@ -3599,6 +3648,43 @@ describe("async rendering", () => {
|
||||
expect(Parent.prototype.__render).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
test("concurrent renderings scenario 13", async () => {
|
||||
let lastChild;
|
||||
class Child extends Component<any, any> {
|
||||
static template = xml`<span><t t-esc="state.val"/></span>`;
|
||||
state = useState({ val: 0 });
|
||||
mounted() {
|
||||
if (lastChild) {
|
||||
lastChild.state.val = 0;
|
||||
}
|
||||
lastChild = this;
|
||||
this.state.val = 1;
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component<any, any> {
|
||||
static template = xml`
|
||||
<div>
|
||||
<Child/>
|
||||
<Child t-if="state.bool"/>
|
||||
</div>`;
|
||||
static components = { Child };
|
||||
state = useState({ bool: false });
|
||||
}
|
||||
|
||||
const parent = new Parent();
|
||||
await parent.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><span>0</span></div>");
|
||||
|
||||
await nextTick(); // wait for changes triggered in mounted to be applied
|
||||
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
|
||||
|
||||
parent.state.bool = true;
|
||||
await nextTick(); // wait for this change to be applied
|
||||
await nextTick(); // wait for changes triggered in mounted to be applied
|
||||
expect(fixture.innerHTML).toBe("<div><span>0</span><span>1</span></div>");
|
||||
});
|
||||
|
||||
test("change state and call manually render: no unnecessary rendering", async () => {
|
||||
class Widget extends Component<any, any> {
|
||||
static template = xml`<div><t t-esc="state.val"/></div>`;
|
||||
@@ -4240,9 +4326,9 @@ describe("t-slot directive", () => {
|
||||
test("slots in t-foreach and re-rendering", async () => {
|
||||
class Child extends Widget {
|
||||
static template = xml`<span><t t-esc="state.val"/><t t-slot="default"/></span>`;
|
||||
state = useState({ val: 'A' });
|
||||
state = useState({ val: "A" });
|
||||
mounted() {
|
||||
this.state.val = 'B';
|
||||
this.state.val = "B";
|
||||
}
|
||||
}
|
||||
class Parent extends Widget {
|
||||
@@ -4269,9 +4355,9 @@ describe("t-slot directive", () => {
|
||||
<t t-esc="state.val"/>
|
||||
<t t-slot="default"/>
|
||||
</span>`;
|
||||
state = useState({ val: 'A' });
|
||||
state = useState({ val: "A" });
|
||||
mounted() {
|
||||
this.state.val = 'B';
|
||||
this.state.val = "B";
|
||||
}
|
||||
}
|
||||
class ParentWidget extends Widget {
|
||||
@@ -4752,7 +4838,7 @@ describe("component error handling (catchError)", () => {
|
||||
expect(handler).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test("can catch an error in the initial call of a component render function", async () => {
|
||||
test("can catch an error in the initial call of a component render function (parent mounted)", async () => {
|
||||
const handler = jest.fn();
|
||||
env.qweb.on("error", null, handler);
|
||||
const consoleError = console.error;
|
||||
@@ -4788,6 +4874,45 @@ describe("component error handling (catchError)", () => {
|
||||
expect(handler).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test("can catch an error in the initial call of a component render function (parent updated)", async () => {
|
||||
const handler = jest.fn();
|
||||
env.qweb.on("error", null, handler);
|
||||
const consoleError = console.error;
|
||||
console.error = jest.fn();
|
||||
class ErrorComponent extends Component<any, any> {
|
||||
static template = xml`<div>hey<t t-esc="state.this.will.crash"/></div>`;
|
||||
}
|
||||
class ErrorBoundary extends Component<any, any> {
|
||||
static template = xml`
|
||||
<div>
|
||||
<t t-if="state.error">Error handled</t>
|
||||
<t t-else="1"><t t-slot="default" /></t>
|
||||
</div>`;
|
||||
state = useState({ error: false });
|
||||
|
||||
catchError() {
|
||||
this.state.error = true;
|
||||
}
|
||||
}
|
||||
class App extends Component<any, any> {
|
||||
static template = xml`
|
||||
<div>
|
||||
<ErrorBoundary t-if="state.flag"><ErrorComponent /></ErrorBoundary>
|
||||
</div>`;
|
||||
state = useState({ flag: false });
|
||||
static components = { ErrorBoundary, ErrorComponent };
|
||||
}
|
||||
const app = new App();
|
||||
await app.mount(fixture);
|
||||
app.state.flag = true;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><div>Error handled</div></div>");
|
||||
|
||||
expect(console.error).toBeCalledTimes(0);
|
||||
console.error = consoleError;
|
||||
expect(handler).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
test("can catch an error in the constructor call of a component render function", async () => {
|
||||
const handler = jest.fn();
|
||||
env.qweb.on("error", null, handler);
|
||||
@@ -5458,6 +5583,73 @@ describe("unmounting and remounting", () => {
|
||||
expect(TestWidget.prototype.__patch).toHaveBeenCalledTimes(2);
|
||||
expect(steps).toEqual([2, 2, 3]);
|
||||
});
|
||||
|
||||
test("change state while component is unmounted", async () => {
|
||||
let child;
|
||||
class Child extends Component<any, any> {
|
||||
static template = xml`<span t-esc="state.val"/>`;
|
||||
state = useState({
|
||||
val: "C1"
|
||||
});
|
||||
constructor(parent, props) {
|
||||
super(parent, props);
|
||||
child = this;
|
||||
}
|
||||
}
|
||||
|
||||
class Parent extends Component<any, any> {
|
||||
static components = { Child };
|
||||
static template = xml`<div><t t-esc="state.val"/><Child/></div>`;
|
||||
state = useState({ val: "P1" });
|
||||
}
|
||||
|
||||
const parent = new Parent();
|
||||
await parent.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>P1<span>C1</span></div>");
|
||||
|
||||
parent.unmount();
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
|
||||
parent.state.val = "P2";
|
||||
child.state.val = "C2";
|
||||
|
||||
await parent.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div>P2<span>C2</span></div>");
|
||||
});
|
||||
|
||||
test("unmount component during a re-rendering", async () => {
|
||||
const def = makeDeferred();
|
||||
class Child extends Widget {
|
||||
static template = xml`<span><t t-esc="props.val"/></span>`;
|
||||
willUpdateProps() {
|
||||
return def;
|
||||
}
|
||||
}
|
||||
Child.prototype.__render = jest.fn(Child.prototype.__render);
|
||||
|
||||
class Parent extends Widget {
|
||||
static template = xml`<div><Child val="state.val"/></div>`;
|
||||
static components = { Child };
|
||||
state = useState({ val: 1 });
|
||||
}
|
||||
|
||||
const parent = new Parent();
|
||||
await parent.mount(fixture);
|
||||
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
|
||||
expect(Child.prototype.__render).toBeCalledTimes(1);
|
||||
|
||||
parent.state.val = 2;
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("<div><span>1</span></div>");
|
||||
|
||||
parent.unmount();
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
|
||||
def.resolve();
|
||||
await nextTick();
|
||||
expect(fixture.innerHTML).toBe("");
|
||||
expect(Child.prototype.__render).toBeCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("dynamic root nodes", () => {
|
||||
|
||||
@@ -702,7 +702,8 @@ describe("t-call (template calling", () => {
|
||||
</div>
|
||||
</templates>
|
||||
`);
|
||||
const expected = "<div><div><div><div><span>cascade 2</span><span>cascade 1</span><span>cascade 0</span><span>hey</span> <span>yay</span></div></div></div></div>";
|
||||
const expected =
|
||||
"<div><div><div><div><span>cascade 2</span><span>cascade 1</span><span>cascade 0</span><span>hey</span> <span>yay</span></div></div></div></div>";
|
||||
expect(renderToString(qweb, "main")).toBe(expected);
|
||||
});
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ exports[`RouteComponent can render simple cases 1`] = `
|
||||
w8 = new W8(parent, props8);
|
||||
parent.__owl__.cmap[k9] = w8.__owl__.id;
|
||||
let def7 = w8.__prepare(extra.fiber, undefined, undefined);
|
||||
let pvnode = h('dummy', {key: k9, hook: {insert(vn) { let nvn=w8.__mount(fiber, pvnode.elm);pvnode.elm=nvn.elm;},remove() {},destroy(vn) {w8.destroy();}}});
|
||||
let pvnode = h('dummy', {key: k9, hook: {remove() {},destroy(vn) {w8.destroy();}}});
|
||||
const fiber = w8.__owl__.currentFiber;
|
||||
def7.then(function () { if (fiber.isCompleted) { return; } const vnode = fiber.vnode; pvnode.sel = vnode.sel; });
|
||||
utils.defineProxy(vn11, pvnode);
|
||||
|
||||
Reference in New Issue
Block a user