Files
2026-07-12 21:26:08 +08:00

122 lines
4.4 KiB
TypeScript

import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { GraphDiffQueue, mergeGraphDiffs } from "../src/anim";
import type { GraphDiff } from "../src/types";
describe("GraphDiffQueue", () => {
it("consumes immediately when graph is visible", () => {
const queue = new GraphDiffQueue({ visible: true });
const decision = queue.push(diff({ addedNodes: ["n1"] }));
assert.equal(decision.action, "consume");
assert.deepEqual(decision.diff?.addedNodes, ["n1"]);
assert.equal(decision.snapshot.isAnimating, true);
const done = queue.finishAnimation();
assert.equal(done.action, "queue");
assert.equal(done.snapshot.isAnimating, false);
});
it("queues while hidden and consumes the net diff when shown", () => {
const queue = new GraphDiffQueue({ visible: false });
const queued = queue.push(diff({ addedNodes: ["n1"], addedEdges: ["e1"] }));
assert.equal(queued.action, "queue");
assert.deepEqual(queued.snapshot.pending?.addedNodes, ["n1"]);
const shown = queue.setVisible(true);
assert.equal(shown.action, "consume");
assert.deepEqual(shown.diff?.addedNodes, ["n1"]);
assert.deepEqual(shown.diff?.addedEdges, ["e1"]);
assert.equal(shown.snapshot.pending, null);
});
it("holds diffs during drag and consumes after release", () => {
const queue = new GraphDiffQueue({ visible: true });
queue.setDragging(true);
const queued = queue.push(diff({ addedNodes: ["n1"] }));
assert.equal(queued.action, "queue");
assert.equal(queued.reason, "dragging");
const released = queue.setDragging(false);
assert.equal(released.action, "consume");
assert.deepEqual(released.diff?.addedNodes, ["n1"]);
});
it("does not replay a refresh diff until both drag and animation are idle", () => {
const queue = new GraphDiffQueue({ visible: true });
const first = queue.push(diff({ addedNodes: ["already-playing"] }));
assert.equal(first.action, "consume");
assert.equal(first.snapshot.isAnimating, true);
queue.setDragging(true);
const queued = queue.push(diff({ addedNodes: ["fresh"] }));
assert.equal(queued.action, "queue");
assert.equal(queued.reason, "dragging");
const released = queue.setDragging(false);
assert.equal(released.action, "queue");
assert.equal(released.snapshot.pending?.addedNodes.includes("fresh"), true);
const finished = queue.finishAnimation();
assert.equal(finished.action, "consume");
assert.deepEqual(finished.diff?.addedNodes, ["fresh"]);
});
it("folds multiple pending diffs into one net replay", () => {
const queue = new GraphDiffQueue({ visible: false });
queue.push(diff({
addedNodes: ["create-then-delete", "stay-new"],
addedEdges: ["edge-create-then-delete"],
recoloredNodes: [{ id: "color", from: "old", to: "mid" }],
stats: { nodeCount: 3, edgeCount: 1, communityCount: 2 }
}));
queue.push(diff({
removedNodes: ["create-then-delete"],
removedEdges: ["edge-create-then-delete"],
recoloredNodes: [{ id: "color", from: "mid", to: "new" }],
newCommunities: ["fresh"],
stats: { nodeCount: 2, edgeCount: 0, communityCount: 3 }
}));
const shown = queue.setVisible(true);
assert.equal(shown.action, "consume");
assert.deepEqual(shown.diff, diff({
addedNodes: ["stay-new"],
recoloredNodes: [{ id: "color", from: "old", to: "new" }],
newCommunities: ["fresh"],
stats: { nodeCount: 2, edgeCount: 0, communityCount: 3 }
}));
});
});
describe("mergeGraphDiffs", () => {
it("cancels add-then-remove nodes and keeps latest stats", () => {
const merged = mergeGraphDiffs(
diff({ addedNodes: ["a", "b"], stats: { nodeCount: 2, edgeCount: 0, communityCount: 1 } }),
diff({ removedNodes: ["a"], stats: { nodeCount: 1, edgeCount: 0, communityCount: 1 } })
);
assert.deepEqual(merged.addedNodes, ["b"]);
assert.deepEqual(merged.removedNodes, []);
assert.equal(merged.stats.nodeCount, 1);
});
});
function diff(partial: Partial<GraphDiff> = {}): GraphDiff {
return {
addedNodes: partial.addedNodes ?? [],
removedNodes: partial.removedNodes ?? [],
recoloredNodes: partial.recoloredNodes ?? [],
addedEdges: partial.addedEdges ?? [],
removedEdges: partial.removedEdges ?? [],
newCommunities: partial.newCommunities ?? [],
stats: partial.stats ?? { nodeCount: 0, edgeCount: 0, communityCount: 0 }
};
}