Refactor mountNode to build fiber tree alongside host instances
This commit is contained in:
166
test/mount-fibers.test.ts
Normal file
166
test/mount-fibers.test.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
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<string, string, Record<string, unknown>>;
|
||||
instances: { tag: string; props: Record<string, unknown> }[];
|
||||
texts: string[];
|
||||
appends: { parent: string; child: string }[];
|
||||
} {
|
||||
const instances: { tag: string; props: Record<string, unknown> }[] = [];
|
||||
const texts: string[] = [];
|
||||
const appends: { parent: string; child: string }[] = [];
|
||||
const host: HostConfig<string, string, Record<string, unknown>> = {
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user