Add TypeBox Value.Equal deep-comparison as first optimization layer in reconcileProps. When a fiber's cached node is deep-equal to the next node, skip prepareUpdate, commitUpdate, and children reconciliation entirely. New cachedNode field on Fiber stores the last reconciled node for comparison.
170 lines
4.6 KiB
TypeScript
170 lines
4.6 KiB
TypeScript
import { describe, it, expect, expectTypeOf } from "vitest";
|
|
import type { Fiber, Effect } from "../src/host/fiber.js";
|
|
|
|
const baseFiber: Omit<Fiber<string>, "instance" | "tag" | "props" | "key"> = {
|
|
children: [],
|
|
parent: null,
|
|
effect: null,
|
|
signalDisposers: [],
|
|
prevProps: null,
|
|
cachedNode: null,
|
|
};
|
|
|
|
describe("Fiber<I> interface", () => {
|
|
it("has all required fields", () => {
|
|
const fiber: Fiber<string> = {
|
|
instance: "inst-1",
|
|
tag: "div",
|
|
props: { class: "test" },
|
|
key: "a",
|
|
...baseFiber,
|
|
};
|
|
expect(fiber.instance).toBe("inst-1");
|
|
expect(fiber.tag).toBe("div");
|
|
expect(fiber.props.class).toBe("test");
|
|
expect(fiber.key).toBe("a");
|
|
expect(fiber.children).toEqual([]);
|
|
expect(fiber.parent).toBeNull();
|
|
expect(fiber.effect).toBeNull();
|
|
expect(fiber.signalDisposers).toEqual([]);
|
|
expect(fiber.prevProps).toBeNull();
|
|
expect(fiber.cachedNode).toBeNull();
|
|
});
|
|
|
|
it("supports key as undefined", () => {
|
|
const fiber: Fiber<string> = {
|
|
instance: "inst-2",
|
|
tag: "span",
|
|
props: {},
|
|
key: undefined,
|
|
...baseFiber,
|
|
};
|
|
expect(fiber.key).toBeUndefined();
|
|
});
|
|
|
|
it("supports parent reference", () => {
|
|
const parent: Fiber<string> = {
|
|
instance: "parent-inst",
|
|
tag: "div",
|
|
props: {},
|
|
key: undefined,
|
|
...baseFiber,
|
|
};
|
|
const child: Fiber<string> = {
|
|
instance: "child-inst",
|
|
tag: "span",
|
|
props: {},
|
|
key: "child-1",
|
|
...baseFiber,
|
|
parent,
|
|
};
|
|
parent.children.push(child);
|
|
expect(child.parent).toBe(parent);
|
|
expect(parent.children[0]).toBe(child);
|
|
});
|
|
|
|
it("supports signalDisposers array", () => {
|
|
const disposed: string[] = [];
|
|
const fiber: Fiber<string> = {
|
|
instance: "inst",
|
|
tag: "div",
|
|
props: {},
|
|
key: undefined,
|
|
...baseFiber,
|
|
signalDisposers: [() => disposed.push("a"), () => disposed.push("b")],
|
|
};
|
|
fiber.signalDisposers.forEach((d) => d());
|
|
expect(disposed).toEqual(["a", "b"]);
|
|
});
|
|
});
|
|
|
|
describe("Effect<I> union type", () => {
|
|
it("update effect", () => {
|
|
const effect: Effect<string> = { type: "update", payload: { class: "new" } };
|
|
expect(effect.type).toBe("update");
|
|
expect((effect as { type: "update"; payload: unknown }).payload).toEqual({ class: "new" });
|
|
});
|
|
|
|
it("insert effect with before=null (append)", () => {
|
|
const effect: Effect<string> = { type: "insert", before: null };
|
|
expect(effect.type).toBe("insert");
|
|
});
|
|
|
|
it("insert effect with before=fiber", () => {
|
|
const before: Fiber<string> = {
|
|
instance: "existing",
|
|
tag: "span",
|
|
props: {},
|
|
key: undefined,
|
|
...baseFiber,
|
|
};
|
|
const effect: Effect<string> = { type: "insert", before };
|
|
expect(effect.type).toBe("insert");
|
|
expect(effect.before).toBe(before);
|
|
});
|
|
|
|
it("move effect with before=fiber", () => {
|
|
const before: Fiber<string> = {
|
|
instance: "target",
|
|
tag: "span",
|
|
props: {},
|
|
key: undefined,
|
|
...baseFiber,
|
|
};
|
|
const effect: Effect<string> = { type: "move", before };
|
|
expect(effect.type).toBe("move");
|
|
expect(effect.before).toBe(before);
|
|
});
|
|
|
|
it("move effect with before=null (append at end)", () => {
|
|
const effect: Effect<string> = { type: "move", before: null };
|
|
expect(effect.type).toBe("move");
|
|
});
|
|
|
|
it("remove effect", () => {
|
|
const effect: Effect<string> = { type: "remove" };
|
|
expect(effect.type).toBe("remove");
|
|
});
|
|
|
|
it("effect field on Fiber accepts all variants", () => {
|
|
const fiber: Fiber<number> = {
|
|
instance: 1,
|
|
tag: "div",
|
|
props: {},
|
|
key: undefined,
|
|
...baseFiber,
|
|
effect: { type: "update", payload: "x" },
|
|
prevProps: {},
|
|
};
|
|
expect(fiber.effect!.type).toBe("update");
|
|
|
|
fiber.effect = { type: "remove" };
|
|
expect(fiber.effect.type).toBe("remove");
|
|
|
|
fiber.effect = { type: "insert", before: null };
|
|
expect(fiber.effect.type).toBe("insert");
|
|
|
|
fiber.effect = { type: "move", before: null };
|
|
expect(fiber.effect.type).toBe("move");
|
|
|
|
fiber.effect = null;
|
|
expect(fiber.effect).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("Fiber re-export from barrel", () => {
|
|
it("Fiber and Effect types are importable from src/mod.ts", async () => {
|
|
type _FiberCheck = import("../src/mod.js").Fiber<string>;
|
|
type _EffectCheck = import("../src/mod.js").Effect<string>;
|
|
const _fiber: _FiberCheck = {
|
|
instance: "inst",
|
|
tag: "div",
|
|
props: {},
|
|
key: undefined,
|
|
...baseFiber,
|
|
};
|
|
const _effect: _EffectCheck = { type: "remove" };
|
|
expect(_fiber.instance).toBe("inst");
|
|
expect(_effect.type).toBe("remove");
|
|
});
|
|
}); |