This commit is contained in:
2026-07-12 21:26:08 +08:00
commit 9dd41afd48
502 changed files with 129901 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { buildRenderableGraph, screenPointToWorldPoint, worldPointToScreenPoint } from "../src/render";
import {
constrainDragTargetToLayoutBounds,
createLiveGraphSimulation,
pinsToPositions,
PinState
} from "../src/sim";
import type { GraphData, PinMap } from "../src/types";
describe("LiveGraphSimulation", () => {
it("starts from cold-start graph coordinates and pinned positions", () => {
const graph = buildRenderableGraph(sampleGraph(), {
theme: "shan-shui",
pins: {
"wiki/near.md": { x: 800, y: 340, coordinateSpace: "world" }
}
});
const simulation = createLiveGraphSimulation(graph);
const snapshot = simulation.snapshot();
assert.deepEqual(snapshot.positions.drag, { x: 200, y: 340 });
assert.deepEqual(snapshot.positions.near, { x: 800, y: 340 });
assert.deepEqual(snapshot.positions.far, { x: 850, y: 340 });
simulation.destroy();
});
it("settles to alpha zero instead of moving forever", () => {
const graph = buildRenderableGraph(sampleGraph(), { theme: "shan-shui" });
const simulation = createLiveGraphSimulation(graph, { coldStartAlpha: 0.09, alphaMin: 0.004 });
const settled = simulation.settle();
assert.equal(settled.alpha, 0);
assert.equal(simulation.alpha, 0);
simulation.destroy();
});
it("moves direct neighbors during low-heat drag while keeping far nodes fixed", () => {
const graph = buildRenderableGraph(sampleGraph(), { theme: "shan-shui" });
const simulation = createLiveGraphSimulation(graph, { lowHeatAlphaTarget: 0.15 });
simulation.settle();
const before = simulation.snapshot();
simulation.beginDrag("drag");
simulation.dragTo("drag", { x: 460, y: 340 });
const during = simulation.tick(36);
assert.ok(during.positions.near.x > before.positions.near.x + 4, "direct neighbor should slide toward the dragged node");
assert.deepEqual(during.positions.far, before.positions.far, "far node should remain fixed during the drag");
assert.equal(during.positions.drag.x, 460);
simulation.endDrag();
const settled = simulation.settle();
assert.equal(settled.alpha, 0);
simulation.destroy();
});
it("keeps drag bounds as an explicit layout constraint outside projection helpers", () => {
assert.deepEqual(
constrainDragTargetToLayoutBounds({ x: -120, y: 940 }, { fallback: { x: 200, y: 340 } }),
{ x: 0, y: 680 }
);
const graph = buildRenderableGraph(sampleGraph(), { theme: "shan-shui" });
const simulation = createLiveGraphSimulation(graph);
simulation.beginDrag("drag");
const dragged = simulation.dragTo("drag", { x: 1200, y: -250 });
assert.equal(dragged.fx, 1200);
assert.equal(dragged.fy, -250);
simulation.destroy();
const boundedSimulation = createLiveGraphSimulation(graph, {
dragBounds: { minX: 0, minY: 0, maxX: 1000, maxY: 680 }
});
boundedSimulation.beginDrag("drag");
const boundedDrag = boundedSimulation.dragTo("drag", { x: 1200, y: -250 });
assert.equal(boundedDrag.fx, 1000);
assert.equal(boundedDrag.fy, 0);
boundedSimulation.destroy();
});
});
describe("PinState", () => {
it("pins, unpins, and resets by wiki-relative node path", () => {
const graph = buildRenderableGraph(sampleGraph(), { theme: "shan-shui" });
const pins = new PinState(graph);
const pinned = pins.pin("drag", { x: 412.5, y: -88.2 });
assert.equal(pins.isPinned("drag"), true);
assert.deepEqual(pinned.pins, {
"wiki/drag.md": { x: 412.5, y: -88.2, coordinateSpace: "world" }
});
assert.deepEqual(pinned.pinnedNodeIds, ["drag"]);
const unpinned = pins.unpin("drag");
assert.equal(pins.isPinned("drag"), false);
assert.deepEqual(unpinned.pins, {});
pins.pin("near", { x: 130, y: 245.7 });
assert.deepEqual(pins.reset().pins, {});
});
it("normalizes initial pins and exposes render positions for known graph nodes only", () => {
const graph = buildRenderableGraph(sampleGraph(), { theme: "shan-shui" });
const pins = {
"wiki/near.md": { x: 130, y: 245.7, coordinateSpace: "world" as const },
"wiki/missing.md": { x: 999, y: 999 }
};
const state = new PinState(graph, pins).snapshot();
assert.deepEqual(state.pins, {
"wiki/near.md": { x: 130, y: 245.7, coordinateSpace: "world" }
});
assert.deepEqual(pinsToPositions(graph, state.pins), {
near: { x: 130, y: 245.7 }
});
});
it("keeps wiki-relative pin format readable and separate from projection math", () => {
const graph = buildRenderableGraph(sampleGraph(), { theme: "shan-shui" });
const pins: PinMap = {
"wiki/drag.md": { x: -42.5, y: 812.75, coordinateSpace: "world" },
"wiki/near.md": { x: Number.NaN, y: 245.7 }
};
const beforeProjection = structuredClone(pins);
const projected = worldPointToScreenPoint(pins["wiki/drag.md"], { x: -180, y: 64, scale: 1.25 }, { width: 1170, height: 856 });
const roundTrip = screenPointToWorldPoint(projected, { x: -180, y: 64, scale: 1.25 }, { width: 1170, height: 856 });
const state = new PinState(graph, pins).snapshot();
assert.deepEqual(pins, beforeProjection, "projection helpers must not rewrite the host pin map");
assert.ok(roundTrip.x < 0, "projection round trip should preserve an out-of-world finite pin x");
assert.ok(roundTrip.y > 680, "projection round trip should preserve an out-of-world finite pin y");
assert.deepEqual(state.pins, {
"wiki/drag.md": { x: -42.5, y: 812.75, coordinateSpace: "world" },
"wiki/near.md": { x: 0, y: 245.7 }
});
assert.deepEqual(pinsToPositions(graph, state.pins), {
drag: { x: -42.5, y: 812.75 },
near: { x: 0, y: 245.7 }
});
});
it("separates old percent pins from new world pins", () => {
const graph = buildRenderableGraph(sampleGraph(), { theme: "shan-shui" });
const pins = new PinState(graph, {
"wiki/drag.md": { x: 8, y: 12, coordinateSpace: "world" },
"wiki/near.md": { x: 13, y: 50, coordinateSpace: "legacy-percent" }
}).snapshot();
assert.deepEqual(pins.pins, {
"wiki/drag.md": { x: 8, y: 12, coordinateSpace: "world" },
"wiki/near.md": { x: 13, y: 50, coordinateSpace: "legacy-percent" }
});
assert.deepEqual(pinsToPositions(graph, pins.pins), {
drag: { x: 8, y: 12 },
near: { x: 130, y: 340 }
});
});
it("treats unmarked small pin values as world coordinates instead of guessing percent", () => {
const graph = buildRenderableGraph(sampleGraph(), { theme: "shan-shui" });
const pins = new PinState(graph, {
"wiki/drag.md": { x: 8, y: 12 }
}).snapshot();
assert.deepEqual(pinsToPositions(graph, pins.pins), {
drag: { x: 8, y: 12 }
});
});
});
function sampleGraph(): GraphData {
return {
meta: {
build_date: "2026-06-12T00:00:00.000Z",
wiki_title: "Simulation Fixture",
total_nodes: 3,
total_edges: 1
},
nodes: [
{ id: "drag", label: "Drag", type: "topic", community: "c1", source_path: "wiki/drag.md", weight: 80, x: 20, y: 50 },
{ id: "near", label: "Near", type: "entity", community: "c1", source_path: "wiki/near.md", weight: 50, x: 32, y: 50 },
{ id: "far", label: "Far", type: "entity", community: "c2", source_path: "wiki/far.md", weight: 20, x: 85, y: 50 }
],
edges: [
{ id: "drag-near", from: "drag", to: "near", type: "EXTRACTED", weight: 1 }
],
learning: {
version: 1,
entry: { recommended_start_node_id: "drag", recommended_start_reason: "community_hub", default_mode: "global" },
views: {
path: { enabled: false, start_node_id: null, node_ids: [], degraded: true },
community: { enabled: false, community_id: null, label: null, node_ids: [], is_weak: false, degraded: true },
global: { enabled: true, node_ids: ["drag", "near", "far"], degraded: false }
},
communities: [
{ id: "c1", label: "Core", node_count: 2, color_index: 0, recommended_start_node_id: "drag" },
{ id: "c2", label: "Edge", node_count: 1, color_index: 1 }
]
}
};
}