diff --git a/src/component/fiber.ts b/src/component/fiber.ts index a761cc48..e40ed43f 100644 --- a/src/component/fiber.ts +++ b/src/component/fiber.ts @@ -98,6 +98,7 @@ export class Fiber { _reuseFiber(oldFiber: Fiber) { oldFiber.cancel(); // cancel children fibers oldFiber.isCompleted = false; // keep the root fiber alive + oldFiber.isRendered = false; // the fiber has to be re-rendered if (oldFiber.child) { // remove relation to children oldFiber.child.parent = null; diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts index 9cd94647..2b65b6df 100644 --- a/tests/component/component.test.ts +++ b/tests/component/component.test.ts @@ -3463,6 +3463,51 @@ describe("async rendering", () => { expect(fixture.innerHTML).toBe("
2|5
"); }); + test("concurrent renderings scenario 12", async () => { + // In this scenario, we have a parent component that will be re-rendered + // several times simultaneously: + // - once in a tick: it will create a new fiber, render it, but will have + // to wait for its child (blocking) to be completed + // - twice in the next tick: it will twice reuse the same fiber (as it is + // rendered but not completed yet) + const def = makeDeferred(); + + class Child extends Component { + static template = xml``; + willUpdateProps() { + return def; + } + } + + class Parent extends Component { + static template = xml`
`; + static components = { Child }; + state = useState({ val: 1 }); + } + Parent.prototype.__render = jest.fn(Parent.prototype.__render); + + const parent = new Parent(); + await parent.mount(fixture); + expect(fixture.innerHTML).toBe("
1
"); + expect(Parent.prototype.__render).toHaveBeenCalledTimes(1); + + parent.state.val = 2; + await nextTick(); + expect(fixture.innerHTML).toBe("
1
"); + expect(Parent.prototype.__render).toHaveBeenCalledTimes(2); + + parent.state.val = 3; + parent.state.val = 4; + await nextTick(); + expect(fixture.innerHTML).toBe("
1
"); + expect(Parent.prototype.__render).toHaveBeenCalledTimes(3); + + def.resolve(); + await nextTick(); + expect(fixture.innerHTML).toBe("
4
"); + expect(Parent.prototype.__render).toHaveBeenCalledTimes(3); + }); + test("change state and call manually render: no unnecessary rendering", async () => { class Widget extends Component { static template = xml`
`;