Merge feat/value-hash-detection: add Value.Hash O(1) change detection with commitHashes (clean merge, no Value.Diff overlap)
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,4 +7,4 @@ dist/
|
||||
.idea/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
coverage/
|
||||
coverage/.worktrees/
|
||||
|
||||
@@ -81,15 +81,15 @@ describe("Root.render() re-renderable", () => {
|
||||
expect(divFiber.props.class).toBe("b");
|
||||
});
|
||||
|
||||
it("prepareUpdate is called for changed props on re-render", () => {
|
||||
const { host, prepareUpdateCalls } = makeHost();
|
||||
it("commitUpdate is called for changed props on re-render", () => {
|
||||
const { host, commitUpdateCalls } = makeHost();
|
||||
const root = createHostRoot(host, {});
|
||||
root.render(h("div", { class: "old" }, "hello"));
|
||||
prepareUpdateCalls.length = 0;
|
||||
commitUpdateCalls.length = 0;
|
||||
|
||||
root.render(h("div", { class: "new" }, "hello"));
|
||||
expect(prepareUpdateCalls.length).toBeGreaterThanOrEqual(1);
|
||||
const divCall = prepareUpdateCalls.find((c) => c.tag === "div");
|
||||
expect(commitUpdateCalls.length).toBeGreaterThanOrEqual(1);
|
||||
const divCall = commitUpdateCalls.find((c) => c.tag === "div");
|
||||
expect(divCall).toBeDefined();
|
||||
expect(divCall!.prevProps.class).toBe("old");
|
||||
expect(divCall!.nextProps.class).toBe("new");
|
||||
|
||||
@@ -60,8 +60,8 @@ describe("signal-driven-updates", () => {
|
||||
});
|
||||
|
||||
describe("wireSignalToFiber", () => {
|
||||
it("signal change triggers prepareUpdate + commitUpdate", async () => {
|
||||
const { host, prepareUpdateCalls, commitUpdateCalls } = makeTrackingHost();
|
||||
it("signal change triggers commitUpdate", async () => {
|
||||
const { host, commitUpdateCalls } = makeTrackingHost();
|
||||
const color = signal("red");
|
||||
|
||||
const root = createHostRoot(host, {});
|
||||
@@ -77,8 +77,7 @@ describe("signal-driven-updates", () => {
|
||||
{},
|
||||
);
|
||||
|
||||
expect(prepareUpdateCalls.length).toBe(0);
|
||||
expect(commitUpdateCalls.length).toBe(0);
|
||||
commitUpdateCalls.length = 0;
|
||||
|
||||
color.value = "blue";
|
||||
|
||||
@@ -86,16 +85,9 @@ describe("signal-driven-updates", () => {
|
||||
queueMicrotask(() => resolve());
|
||||
});
|
||||
|
||||
expect(prepareUpdateCalls.length).toBeGreaterThanOrEqual(1);
|
||||
const lastPrepare = prepareUpdateCalls[prepareUpdateCalls.length - 1]!;
|
||||
expect(lastPrepare.tag).toBe("div");
|
||||
expect(lastPrepare.prevProps.color).toBe("red");
|
||||
expect(lastPrepare.nextProps.color).toBe("blue");
|
||||
|
||||
expect(commitUpdateCalls.length).toBeGreaterThanOrEqual(1);
|
||||
const lastCommit = commitUpdateCalls[commitUpdateCalls.length - 1]!;
|
||||
expect(lastCommit.instance).toBe(divFiber.instance);
|
||||
expect(lastCommit.payload).toEqual({ color: "blue" });
|
||||
});
|
||||
|
||||
it("signal effect disposer stored in fiber.signalDisposers", () => {
|
||||
@@ -120,7 +112,7 @@ describe("signal-driven-updates", () => {
|
||||
});
|
||||
|
||||
it("disposing signal via signalDisposers stops updates", async () => {
|
||||
const { host, prepareUpdateCalls } = makeTrackingHost();
|
||||
const { host, commitUpdateCalls } = makeTrackingHost();
|
||||
const color = signal("red");
|
||||
|
||||
const root = createHostRoot(host, {});
|
||||
@@ -142,20 +134,20 @@ describe("signal-driven-updates", () => {
|
||||
const disposer = divFiber.signalDisposers.pop()!;
|
||||
disposer();
|
||||
|
||||
const callCountBefore = prepareUpdateCalls.filter((c) => c.tag === "div").length;
|
||||
const callCountBefore = commitUpdateCalls.filter((c) => c.tag === "div").length;
|
||||
color.value = "green";
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
queueMicrotask(() => resolve());
|
||||
});
|
||||
|
||||
expect(prepareUpdateCalls.filter((c) => c.tag === "div").length).toBe(callCountBefore);
|
||||
expect(commitUpdateCalls.filter((c) => c.tag === "div").length).toBe(callCountBefore);
|
||||
});
|
||||
});
|
||||
|
||||
describe("batch of signal changes", () => {
|
||||
it("batch of signal changes results in single reconciliation pass", async () => {
|
||||
const { host, prepareUpdateCalls, commitUpdateCalls } = makeTrackingHost();
|
||||
const { host, commitUpdateCalls } = makeTrackingHost();
|
||||
const color = signal("red");
|
||||
const size = signal("small");
|
||||
|
||||
@@ -175,7 +167,6 @@ describe("signal-driven-updates", () => {
|
||||
queueMicrotask(() => resolve());
|
||||
});
|
||||
resetUpdateQueue();
|
||||
prepareUpdateCalls.length = 0;
|
||||
commitUpdateCalls.length = 0;
|
||||
|
||||
batch(() => {
|
||||
@@ -187,14 +178,13 @@ describe("signal-driven-updates", () => {
|
||||
queueMicrotask(() => resolve());
|
||||
});
|
||||
|
||||
expect(prepareUpdateCalls.filter((c) => c.tag === "div").length).toBe(1);
|
||||
expect(commitUpdateCalls.filter((c) => c.tag === "div").length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("render() re-renderable", () => {
|
||||
it("second render reconciles props against existing fiber tree", () => {
|
||||
const { host, prepareUpdateCalls, commitUpdateCalls, instances } = makeTrackingHost();
|
||||
const { host, commitUpdateCalls, instances } = makeTrackingHost();
|
||||
const root = createHostRoot(host, {});
|
||||
root.render(h("div", { color: "red" }, "hello"));
|
||||
|
||||
@@ -205,14 +195,10 @@ describe("signal-driven-updates", () => {
|
||||
|
||||
expect(instances.length).toBe(instanceCountBefore);
|
||||
|
||||
const divPrepareCalls = prepareUpdateCalls.filter((c) => c.tag === "div");
|
||||
expect(divPrepareCalls.length).toBe(1);
|
||||
expect(divPrepareCalls[0]!.prevProps.color).toBe("red");
|
||||
expect(divPrepareCalls[0]!.nextProps.color).toBe("blue");
|
||||
|
||||
const divCommitCalls = commitUpdateCalls.filter((c) => c.tag === "div");
|
||||
expect(divCommitCalls.length).toBe(1);
|
||||
expect(divCommitCalls[0]!.payload).toEqual({ color: "blue" });
|
||||
expect(divCommitCalls[0]!.prevProps.color).toBe("red");
|
||||
expect(divCommitCalls[0]!.nextProps.color).toBe("blue");
|
||||
});
|
||||
|
||||
it("second render does not create duplicate instances", () => {
|
||||
@@ -225,16 +211,16 @@ describe("signal-driven-updates", () => {
|
||||
expect(instances.length).toBe(1);
|
||||
});
|
||||
|
||||
it("prepareUpdate is called for changed props on re-render", () => {
|
||||
const { host, prepareUpdateCalls } = makeTrackingHost();
|
||||
it("commitUpdate is called for changed props on re-render", () => {
|
||||
const { host, commitUpdateCalls } = makeTrackingHost();
|
||||
const root = createHostRoot(host, {});
|
||||
root.render(h("span", { label: "a" }));
|
||||
root.render(h("span", { label: "b" }));
|
||||
|
||||
const spanPrepares = prepareUpdateCalls.filter((c) => c.tag === "span");
|
||||
expect(spanPrepares.length).toBe(1);
|
||||
expect(spanPrepares[0]!.prevProps.label).toBe("a");
|
||||
expect(spanPrepares[0]!.nextProps.label).toBe("b");
|
||||
const spanCommits = commitUpdateCalls.filter((c) => c.tag === "span");
|
||||
expect(spanCommits.length).toBe(1);
|
||||
expect(spanCommits[0]!.prevProps.label).toBe("a");
|
||||
expect(spanCommits[0]!.nextProps.label).toBe("b");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -88,33 +88,33 @@ describe("Value.Equal bail-out", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("changed prop still triggers prepareUpdate", () => {
|
||||
it("changed prop on parent triggers prepareUpdate", () => {
|
||||
const { host, prepareUpdateCalls } = makeTrackingHost();
|
||||
describe("changed prop still triggers update", () => {
|
||||
it("changed prop on parent triggers commitUpdate", () => {
|
||||
const { host, commitUpdateCalls } = makeTrackingHost();
|
||||
const root = createHostRoot(host, {});
|
||||
root.render(h("div", { color: "red" }));
|
||||
|
||||
prepareUpdateCalls.length = 0;
|
||||
commitUpdateCalls.length = 0;
|
||||
|
||||
root.render(h("div", { color: "blue" }));
|
||||
|
||||
expect(prepareUpdateCalls.length).toBeGreaterThanOrEqual(1);
|
||||
const divCall = prepareUpdateCalls.find((c) => c.tag === "div");
|
||||
expect(commitUpdateCalls.length).toBeGreaterThanOrEqual(1);
|
||||
const divCall = commitUpdateCalls.find((c) => c.tag === "div");
|
||||
expect(divCall).toBeDefined();
|
||||
expect(divCall!.prevProps.color).toBe("red");
|
||||
expect(divCall!.nextProps.color).toBe("blue");
|
||||
});
|
||||
|
||||
it("changed child prop triggers prepareUpdate only for child", () => {
|
||||
const { host, prepareUpdateCalls } = makeTrackingHost();
|
||||
it("changed child prop triggers commitUpdate only for child", () => {
|
||||
const { host, commitUpdateCalls } = makeTrackingHost();
|
||||
const root = createHostRoot(host, {});
|
||||
root.render(h("div", { color: "red" }, h("span", { label: "a" })));
|
||||
|
||||
prepareUpdateCalls.length = 0;
|
||||
commitUpdateCalls.length = 0;
|
||||
|
||||
root.render(h("div", { color: "red" }, h("span", { label: "b" })));
|
||||
|
||||
const spanCalls = prepareUpdateCalls.filter((c) => c.tag === "span");
|
||||
const spanCalls = commitUpdateCalls.filter((c) => c.tag === "span");
|
||||
expect(spanCalls.length).toBe(1);
|
||||
expect(spanCalls[0]!.prevProps.label).toBe("a");
|
||||
expect(spanCalls[0]!.nextProps.label).toBe("b");
|
||||
@@ -214,17 +214,17 @@ describe("Value.Equal bail-out", () => {
|
||||
expect(divFiber.effect).toBeNull();
|
||||
});
|
||||
|
||||
it("reconcileProps with changed node calls prepareUpdate", () => {
|
||||
const { host, prepareUpdateCalls } = makeTrackingHost();
|
||||
it("reconcileProps with changed node produces an update effect", () => {
|
||||
const { host } = makeTrackingHost();
|
||||
const root = createHostRoot(host, {});
|
||||
root.render(h("div", { color: "red" }));
|
||||
|
||||
const divFiber = root.rootFiber!.children[0]!;
|
||||
prepareUpdateCalls.length = 0;
|
||||
|
||||
reconcileProps(divFiber, h("div", { color: "blue" }), host as HostConfig<string, string, unknown>, {});
|
||||
|
||||
expect(prepareUpdateCalls.length).toBe(1);
|
||||
expect(divFiber.effect).not.toBeNull();
|
||||
expect(divFiber.props.color).toBe("blue");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user