[FIX] components: prevent rendering destroyed children in some cases

This commit is contained in:
Géry Debongnie
2022-04-01 14:53:21 +02:00
committed by Sam Degueldre
parent 3e4ebb6378
commit 24b1ea7604
3 changed files with 166 additions and 1 deletions
@@ -1569,6 +1569,70 @@ exports[`rendering parent twice, with different props on child and stuff 2`] = `
}"
`;
exports[`renderings, destruction, patch, stuff, ... yet another variation 1`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
const b2 = text(\`A\`);
const b3 = component(\`B\`, {value: ctx['state'].value}, key + \`__1\`, node, ctx);
const b4 = component(\`D\`, {}, key + \`__2\`, node, ctx);
return multi([b2, b3, b4]);
}
}"
`;
exports[`renderings, destruction, patch, stuff, ... yet another variation 2`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
return function template(ctx, node, key = \\"\\") {
let b2,b3;
b2 = text(\`B\`);
if (ctx['props'].value===33) {
b3 = component(\`C\`, {}, key + \`__1\`, node, ctx);
}
return multi([b2, b3]);
}
}"
`;
exports[`renderings, destruction, patch, stuff, ... yet another variation 3`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block3 = createBlock(\`<p block-handler-0=\\"click\\"><block-text-1/></p>\`);
return function template(ctx, node, key = \\"\\") {
const b2 = text(\`D\`);
let hdlr1 = [ctx['increment'], ctx];
let txt1 = ctx['state'].val;
const b3 = block3([hdlr1, txt1]);
return multi([b2, b3]);
}
}"
`;
exports[`renderings, destruction, patch, stuff, ... yet another variation 4`] = `
"function anonymous(bdom, helpers
) {
let { text, createBlock, list, multi, html, toggler, component, comment } = bdom;
let block3 = createBlock(\`<span block-handler-0=\\"click\\"><block-text-1/></span>\`);
return function template(ctx, node, key = \\"\\") {
const b2 = text(\`C\`);
let hdlr1 = [ctx['increment'], ctx];
let txt1 = ctx['state'].val;
const b3 = block3([hdlr1, txt1]);
return multi([b2, b3]);
}
}"
`;
exports[`t-foreach with dynamic async component 1`] = `
"function anonymous(bdom, helpers
) {
+101
View File
@@ -3883,6 +3883,107 @@ test("delayed rendering, destruction, stuff happens", async () => {
expect(fixture.innerHTML).toBe("AB");
});
test("renderings, destruction, patch, stuff, ... yet another variation", async () => {
const promB = makeDeferred();
class D extends Component {
static template = xml`D<p t-on-click="increment"><t t-esc="state.val"/></p>`;
state = useState({ val: 1 });
setup() {
useLogLifecycle();
}
increment() {
this.state.val++;
}
}
// almost the same as D
class C extends Component {
static template = xml`C<span t-on-click="increment"><t t-esc="state.val"/></span>`;
state = useState({ val: 1 });
setup() {
useLogLifecycle();
}
increment() {
this.state.val++;
}
}
class B extends Component {
static template = xml`B<t t-if="props.value === 33"><C/></t>`;
static components = { C };
setup() {
useLogLifecycle();
onWillUpdateProps(() => promB);
}
}
class A extends Component {
static template = xml`A<B value="state.value"/><D/>`;
static components = { B, D };
state = useState({ value: 33 });
setup() {
useLogLifecycle();
}
}
const parent = await mount(A, fixture);
expect(fixture.innerHTML).toBe("ABC<span>1</span>D<p>1</p>");
expect([
"A:setup",
"A:willStart",
"A:willRender",
"B:setup",
"B:willStart",
"D:setup",
"D:willStart",
"A:rendered",
"B:willRender",
"C:setup",
"C:willStart",
"B:rendered",
"D:willRender",
"D:rendered",
"C:willRender",
"C:rendered",
"C:mounted",
"D:mounted",
"B:mounted",
"A:mounted",
]).toBeLogged();
// render in A, it updates B, will remove C, stopped in B
parent.state.value = 50;
await nextTick();
expect(["A:willRender", "B:willUpdateProps", "A:rendered"]).toBeLogged();
// update C => render should be delayed, because AB is currently rendering
fixture.querySelector("span")!.click();
await nextTick();
expect([]).toBeLogged();
// resolve prom B => render is done, component C is destroyed
promB.resolve();
await nextTick();
expect([
"B:willRender",
"B:rendered",
"A:willPatch",
"B:willPatch",
"C:willUnmount",
"C:willDestroy",
"B:patched",
"A:patched",
]).toBeLogged();
expect(fixture.innerHTML).toBe("ABD<p>1</p>");
// update D => should just render completely independently
fixture.querySelector("p")!.click();
await nextTick();
expect(["D:willRender", "D:rendered", "D:willPatch", "D:patched"]).toBeLogged();
expect(fixture.innerHTML).toBe("ABD<p>2</p>");
});
// test.skip("components with shouldUpdate=false", async () => {
// const state = { p: 1, cc: 10 };