import { describe, it, expect } from "vitest"; import { h, createComponent, createRoot } from "../src/core/h.js"; import { createRoot as createHostRoot } from "../src/host/config.js"; import type { HostConfig } from "../src/host/config.js"; import type { Fiber } from "../src/host/fiber.js"; function makeHost(): { host: HostConfig>; instances: { tag: string; props: Record }[]; texts: string[]; appends: { parent: string; child: string }[]; } { const instances: { tag: string; props: Record }[] = []; const texts: string[] = []; const appends: { parent: string; child: string }[] = []; const host: HostConfig> = { name: "test", createRootContext: () => ({}), createInstance: (tag, props) => { instances.push({ tag, props }); return tag; }, createTextInstance: (text) => { texts.push(text); return text; }, appendChild: (parent, child) => { appends.push({ parent, child }); }, }; return { host, instances, texts, appends }; } describe("mountWithFibers: rootFiber after render()", () => { it("rootFiber is null before render", () => { const { host } = makeHost(); const root = createHostRoot(host, {}); expect(root.rootFiber).toBeNull(); }); it("rootFiber is set after render", () => { const { host } = makeHost(); const root = createHostRoot(host, {}); root.render(h("div", null, "hello")); expect(root.rootFiber).not.toBeNull(); }); it("rootFiber has correct children for simple element with text", () => { const { host } = makeHost(); const root = createHostRoot(host, {}); root.render(h("div", { class: "outer" }, "hello")); const rf = root.rootFiber!; expect(rf.tag).toBe("#root"); expect(rf.parent).toBeNull(); expect(rf.children.length).toBe(1); const divFiber = rf.children[0]!; expect(divFiber.tag).toBe("div"); expect(divFiber.props.class).toBe("outer"); expect(divFiber.parent).toBe(rf); expect(divFiber.children.length).toBe(1); const textFiber = divFiber.children[0]!; expect(textFiber.tag).toBe("#text"); expect(textFiber.props.text).toBe("hello"); expect(textFiber.parent).toBe(divFiber); expect(textFiber.children.length).toBe(0); }); it("primitives get text fibers with tag #text", () => { const { host } = makeHost(); const root = createHostRoot(host, {}); root.render(h("p", null, "world")); const textFiber = root.rootFiber!.children[0]!.children[0]!; expect(textFiber.tag).toBe("#text"); expect(textFiber.instance).toBe("world"); }); it("fiber tree linked with parent and children", () => { const { host } = makeHost(); const root = createHostRoot(host, {}); root.render(h("div", null, h("span", null, "a"), h("em", null, "b"))); const divFiber = root.rootFiber!.children[0]!; expect(divFiber.parent).toBe(root.rootFiber); expect(divFiber.children.length).toBe(2); const spanFiber = divFiber.children[0]!; const emFiber = divFiber.children[1]!; expect(spanFiber.parent).toBe(divFiber); expect(emFiber.parent).toBe(divFiber); expect(spanFiber.tag).toBe("span"); expect(emFiber.tag).toBe("em"); }); it("function components are transparent — no fiber for component", () => { const { host } = makeHost(); const MyComp = createComponent("MyComp", (props) => h("section", null, props.text as string)); const root = createHostRoot(host, {}); root.render(h(MyComp, { text: "hi" })); const rf = root.rootFiber!; expect(rf.children.length).toBe(1); expect(rf.children[0]!.tag).toBe("section"); expect(rf.children[0]!.parent).toBe(rf); }); it("URoot children are mounted into parent fiber (root is transparent)", () => { const { host } = makeHost(); const root = createHostRoot(host, {}); const uRoot = createRoot("r", h("div", null, "x"), h("span", null, "y")); root.render(uRoot); const rf = root.rootFiber!; expect(rf.children.length).toBe(2); expect(rf.children[0]!.tag).toBe("div"); expect(rf.children[1]!.tag).toBe("span"); expect(rf.children[0]!.parent).toBe(rf); expect(rf.children[1]!.parent).toBe(rf); }); it("key is propagated to fiber", () => { const { host } = makeHost(); const root = createHostRoot(host, {}); root.render(h("li", { key: "a" }, "item")); const liFiber = root.rootFiber!.children[0]!; expect(liFiber.key).toBe("a"); }); it("signalDisposers initialized as empty array on each fiber", () => { const { host } = makeHost(); const root = createHostRoot(host, {}); root.render(h("div", null, "x")); const rf = root.rootFiber!; expect(rf.signalDisposers).toEqual([]); expect(rf.children[0]!.signalDisposers).toEqual([]); expect(rf.children[0]!.children[0]!.signalDisposers).toEqual([]); }); it("effect initialized as null on each fiber", () => { const { host } = makeHost(); const root = createHostRoot(host, {}); root.render(h("div", null, "x")); const rf = root.rootFiber!; expect(rf.effect).toBeNull(); expect(rf.children[0]!.effect).toBeNull(); expect(rf.children[0]!.children[0]!.effect).toBeNull(); }); it("existing mount behavior preserved — hosts receive same createInstance/appendChild calls", () => { const { host, instances, texts, appends } = makeHost(); const root = createHostRoot(host, {}); root.render(h("div", { class: "outer" }, "hello", h("span", null, "world"))); expect(instances.length).toBe(2); expect(instances[0]!.tag).toBe("div"); expect(instances[1]!.tag).toBe("span"); expect(texts.sort()).toEqual(["hello", "world"]); expect(appends.length).toBe(3); }); });