import { makeDeferred, makeTestEnv, makeTestFixture, nextTick } from "./helpers"; import { Component, Env } from "../src/component/component"; import { Context, useContext } from "../src/context"; import { xml } from "../src/tags"; import { useState } from "../src/hooks"; //------------------------------------------------------------------------------ // Setup and helpers //------------------------------------------------------------------------------ // We create before each test: // - fixture: a div, appended to the DOM, intended to be the target of dom // manipulations. Note that it is removed after each test. // - env: a WEnv, necessary to create new components let fixture: HTMLElement; let env: Env; beforeEach(() => { fixture = makeTestFixture(); env = makeTestEnv(); }); afterEach(() => { fixture.remove(); }); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("Context", () => { test("very simple use, with initial value", async () => { const testContext = new Context({ value: 123 }); class Test extends Component { static template = xml`
`; contextObj = useContext(testContext); } const test = new Test(env); await test.mount(fixture); expect(fixture.innerHTML).toBe("
123
"); }); test("useContext hook is reactive, for one component", async () => { const testContext = new Context({ value: 123 }); class Test extends Component { static template = xml`
`; contextObj = useContext(testContext); } const test = new Test(env); await test.mount(fixture); expect(fixture.innerHTML).toBe("
123
"); test.contextObj.value = 321; await nextTick(); expect(fixture.innerHTML).toBe("
321
"); }); test("two components can subscribe to same context", async () => { const testContext = new Context({ value: 123 }); class Child extends Component { static template = xml``; contextObj = useContext(testContext); } class Parent extends Component { static template = xml`
`; static components = { Child }; } const parent = new Parent(env); await parent.mount(fixture); expect(fixture.innerHTML).toBe("
123123
"); testContext.state.value = 321; await nextTick(); expect(fixture.innerHTML).toBe("
321321
"); }); test("one components can subscribe twice to same context", async () => { const testContext = new Context({ a: 1, b: 2 }); const steps: string[] = []; class Child extends Component { static template = xml``; contextObj1 = useContext(testContext); contextObj2 = useContext(testContext); __render(fiber) { steps.push("child"); return super.__render(fiber); } } class Parent extends Component { static template = xml`
`; static components = { Child }; } const parent = new Parent(env); await parent.mount(fixture); expect(fixture.innerHTML).toBe("
12
"); expect(steps).toEqual(["child"]); testContext.state.a = 3; await nextTick(); expect(fixture.innerHTML).toBe("
32
"); expect(steps).toEqual(["child", "child"]); }); test("parent and children subscribed to same context", async () => { const testContext = new Context({ a: 123, b: 321 }); const steps: string[] = []; class Child extends Component { static template = xml``; contextObj = useContext(testContext); __render(fiber) { steps.push("child"); return super.__render(fiber); } } class Parent extends Component { static template = xml`
`; static components = { Child }; contextObj = useContext(testContext); __render(fiber) { steps.push("parent"); return super.__render(fiber); } } const parent = new Parent(env); await parent.mount(fixture); expect(fixture.innerHTML).toBe("
123321
"); expect(steps).toEqual(["parent", "child"]); parent.contextObj.a = 124; await nextTick(); expect(fixture.innerHTML).toBe("
124321
"); // we only want one render from the child here, not two expect(steps).toEqual(["parent", "child", "parent", "child"]); }); test("destroyed component is inactive", async () => { const testContext = new Context({ a: 123 }); const steps: string[] = []; class Child extends Component { static template = xml``; contextObj = useContext(testContext); __render(fiber) { steps.push("child"); return super.__render(fiber); } } class Parent extends Component { static template = xml`
`; static components = { Child }; state = useState({ flag: true }); __render(fiber) { steps.push("parent"); return super.__render(fiber); } } const parent = new Parent(env); await parent.mount(fixture); expect(fixture.innerHTML).toBe("
123
"); expect(steps).toEqual(["parent", "child"]); expect(testContext.subscriptions.update.length).toBe(1); parent.state.flag = false; await nextTick(); expect(fixture.innerHTML).toBe("
"); expect(steps).toEqual(["parent", "child", "parent"]); // kind of whitebox... // we make sure we do not have any pending subscriptions to the 'update' // event expect(testContext.subscriptions.update.length).toBe(0); }); test("concurrent renderings", async () => { const testContext = new Context({ x: { n: 1 }, key: "x" }); const def = makeDeferred(); let stateC; class ComponentC extends Component { static template = xml``; context = useContext(testContext); state = useState({ x: "a" }); constructor(parent, props) { super(parent, props); stateC = this.state; } } class ComponentB extends Component { static components = { ComponentC }; static template = xml`

`; willUpdateProps() { return def; } } class ComponentA extends Component { static components = { ComponentB }; static template = xml`
`; context = useContext(testContext); } const component = new ComponentA(env); await component.mount(fixture); expect(fixture.innerHTML).toBe("

1a

"); testContext.state.key = "y"; testContext.state.y = { n: 2 }; delete testContext.state.x; await nextTick(); expect(fixture.innerHTML).toBe("

1a

"); stateC.x = "b"; await nextTick(); expect(fixture.innerHTML).toBe("

1a

"); def.resolve(); await nextTick(); expect(fixture.innerHTML).toBe("

2b

"); }); });