diff --git a/src/component/scheduler.ts b/src/component/scheduler.ts index 6a302e84..87095702 100644 --- a/src/component/scheduler.ts +++ b/src/component/scheduler.ts @@ -32,7 +32,7 @@ export class Scheduler { let renders = this.delayedRenders; this.delayedRenders = []; for (let f of renders) { - if (f.root) { + if (f.root && f.node.status !== STATUS.DESTROYED) { f.render(); } } diff --git a/tests/components/__snapshots__/concurrency.test.ts.snap b/tests/components/__snapshots__/concurrency.test.ts.snap index 0c29a47b..b470bddd 100644 --- a/tests/components/__snapshots__/concurrency.test.ts.snap +++ b/tests/components/__snapshots__/concurrency.test.ts.snap @@ -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(\`

\`); + + 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(\`\`); + + 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 ) { diff --git a/tests/components/concurrency.test.ts b/tests/components/concurrency.test.ts index f4e40bfa..c7e75c0a 100644 --- a/tests/components/concurrency.test.ts +++ b/tests/components/concurrency.test.ts @@ -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

`; + state = useState({ val: 1 }); + setup() { + useLogLifecycle(); + } + increment() { + this.state.val++; + } + } + + // almost the same as D + class C extends Component { + static template = xml`C`; + state = useState({ val: 1 }); + setup() { + useLogLifecycle(); + } + increment() { + this.state.val++; + } + } + + class B extends Component { + static template = xml`B`; + static components = { C }; + setup() { + useLogLifecycle(); + onWillUpdateProps(() => promB); + } + } + + class A extends Component { + static template = xml`A`; + static components = { B, D }; + state = useState({ value: 33 }); + setup() { + useLogLifecycle(); + } + } + + const parent = await mount(A, fixture); + expect(fixture.innerHTML).toBe("ABC1D

1

"); + 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

1

"); + + // 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

2

"); +}); + // test.skip("components with shouldUpdate=false", async () => { // const state = { p: 1, cc: 10 };