import { App, Component, mount, onMounted, useState, xml } from "../../src/index"; import { makeTestFixture, nextAppError, nextTick, snapshotEverything, steps, useLogLifecycle, } from "../helpers"; snapshotEverything(); let originalconsoleWarn = console.warn; let mockConsoleWarn: any; let fixture: HTMLElement; beforeEach(() => { fixture = makeTestFixture(); mockConsoleWarn = jest.fn(() => {}); console.warn = mockConsoleWarn; }); afterEach(() => { console.warn = originalconsoleWarn; }); describe("list of components", () => { test("simple list", async () => { class Child extends Component { static template = xml``; } class Parent extends Component { static template = xml` `; static components = { Child }; state = useState({ elems: [ { id: 1, value: "a" }, { id: 2, value: "b" }, ], }); } const parent = await mount(Parent, fixture); expect(fixture.innerHTML).toBe("ab"); parent.state.elems.push({ id: 4, value: "d" }); await nextTick(); expect(fixture.innerHTML).toBe("abd"); parent.state.elems.pop(); await nextTick(); expect(fixture.innerHTML).toBe("ab"); }); test("components in a node in a t-foreach ", async () => { class Child extends Component { static template = xml`
`; setup() { useLogLifecycle(); } } class Parent extends Component { static template = xml`
`; static components = { Child }; setup() { useLogLifecycle(); } get items() { return [1, 2]; } } await mount(Parent, fixture); expect(fixture.innerHTML).toBe( "
" ); expect(steps.splice(0)).toMatchInlineSnapshot(` Array [ "Parent:setup", "Parent:willStart", "Parent:willRender", "Child:setup", "Child:willStart", "Child:setup", "Child:willStart", "Parent:rendered", "Child:willRender", "Child:rendered", "Child:willRender", "Child:rendered", "Child:mounted", "Child:mounted", "Parent:mounted", ] `); }); test("reconciliation alg works for t-foreach in t-foreach", async () => { class Child extends Component { static template = xml`
`; } class Parent extends Component { static template = xml`
`; static components = { Child }; state = { s: [{ blips: ["a1", "a2"] }, { blips: ["b1"] }] }; } await mount(Parent, fixture); expect(fixture.innerHTML).toBe("
a1
a2
b1
"); }); test("reconciliation alg works for t-foreach in t-foreach, 2", async () => { class Child extends Component { static template = xml`
`; } class Parent extends Component { static template = xml`

`; static components = { Child }; state = useState({ rows: [1, 2], cols: ["a", "b"] }); } const parent = await mount(Parent, fixture); expect(fixture.innerHTML).toBe( "

1_a

1_b

2_a

2_b

" ); parent.state.rows = [2, 1]; await nextTick(); expect(fixture.innerHTML).toBe( "

2_a

2_b

1_a

1_b

" ); }); test("sub components rendered in a loop", async () => { class Child extends Component { static template = xml`

`; } class Parent extends Component { static template = xml`
`; static components = { Child }; state = useState({ numbers: [1, 2, 3] }); } await mount(Parent, fixture); expect(fixture.innerHTML).toBe(`

1

2

3

`); }); test("sub components with some state rendered in a loop", async () => { let n = 1; class Child extends Component { static template = xml`

`; state: any; setup() { this.state = useState({ n }); n++; } } class Parent extends Component { static template = xml`
`; static components = { Child }; state = useState({ numbers: [1, 2, 3], }); } const parent = await mount(Parent, fixture); parent.state.numbers = [1, 3]; await nextTick(); expect(fixture.innerHTML).toBe(`

1

3

`); }); test("list of sub components inside other nodes", async () => { // this confuses the patching algorithm... class SubComponent extends Component { static template = xml`asdf`; } class Parent extends Component { static template = xml`
`; static components = { SubComponent }; state = useState({ blips: [ { a: "a", id: 1 }, { b: "b", id: 2 }, { c: "c", id: 4 }, ], }); } const parent = await mount(Parent, fixture); expect(fixture.innerHTML).toBe( "
asdf
asdf
asdf
" ); parent.state.blips.splice(0, 1); await nextTick(); expect(fixture.innerHTML).toBe( "
asdf
asdf
" ); }); test("t-foreach with t-component, and update", async () => { class Child extends Component { static template = xml` `; state = useState({ val: "A" }); setup() { onMounted(() => { this.state.val = "B"; }); } } class Parent extends Component { static components = { Child }; static template = xml`
`; } await mount(Parent, fixture); expect(fixture.innerHTML).toBe("
A0A1
"); await nextTick(); // wait for changes triggered in mounted to be applied expect(fixture.innerHTML).toBe("
B0B1
"); }); test("switch component position", async () => { const childInstances = []; class Child extends Component { static template = xml`
`; setup() { childInstances.push(this); } } class Parent extends Component { static components = { Child }; static template = xml` `; clist = [1, 2]; } const parent = await mount(Parent, fixture); expect(fixture.innerHTML).toBe("
1
2
"); parent.clist = [2, 1]; parent.render(); await nextTick(); expect(fixture.innerHTML).toBe("
2
1
"); expect(childInstances.length).toBe(2); }); test("crash on duplicate key in dev mode", async () => { const consoleInfo = console.info; console.info = jest.fn(); class Child extends Component { static template = xml``; } class Parent extends Component { static template = xml` `; static components = { Child }; } const app = new App(Parent, { test: true }); const mountProm = expect(app.mount(fixture)).rejects.toThrow( "Got duplicate key in t-foreach: child" ); await expect(nextAppError(app)).resolves.toThrow("Got duplicate key in t-foreach: child"); await mountProm; console.info = consoleInfo; expect(mockConsoleWarn).toBeCalledTimes(1); }); test("crash when using object as keys that serialize to the same string", async () => { const consoleInfo = console.info; console.info = jest.fn(); class Child extends Component { static template = xml``; } class Parent extends Component { static template = xml` `; static components = { Child }; } const app = new App(Parent, { test: true }); const mountProm = expect(app.mount(fixture)).rejects.toThrow( "Got duplicate key in t-foreach: [object Object]" ); await expect(nextAppError(app)).resolves.toThrow( "Got duplicate key in t-foreach: [object Object]" ); await mountProm; console.info = consoleInfo; expect(mockConsoleWarn).toBeCalledTimes(1); }); test("order is correct when slots are not of same type", async () => { class Child extends Component { static template = xml` `; get slotNames() { return Object.entries(this.props.slots) .filter((entry: any) => entry[1].active) .map((entry) => entry[0]); } } class Parent extends Component { static template = xml`
A
B C
`; static components = { Child }; state = useState({ active: false }); } const parent = await mount(Parent, fixture); expect(fixture.textContent).toBe("AB"); parent.state.active = true; await nextTick(); expect(fixture.textContent).toBe("BC"); }); });