first
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { createLiveGraphSimulation, PinState } from "../src/sim";
|
||||
import { buildRenderableGraph } from "../src/render";
|
||||
import { cancelGraphNodeDrag, commitGraphNodeDrag, type GraphNodeDragSession } from "../src/render/node-drag-lifecycle";
|
||||
import type { GraphData, PinMap } from "../src/types";
|
||||
|
||||
describe("graph node drag lifecycle", () => {
|
||||
it("commits a real drag by pinning the final world position", () => {
|
||||
const graph = buildRenderableGraph(sampleGraph(), { theme: "shan-shui" });
|
||||
const simulation = createLiveGraphSimulation(graph);
|
||||
const pinState = new PinState(graph);
|
||||
|
||||
simulation.beginDrag("drag");
|
||||
simulation.dragTo("drag", { x: 460, y: 300 });
|
||||
|
||||
const result = commitGraphNodeDrag({ nodeId: "drag", simulation, pinState });
|
||||
|
||||
assert.deepEqual(result.pinPosition, { x: 460, y: 300 });
|
||||
assert.deepEqual(result.pins, {
|
||||
"wiki/drag.md": { x: 460, y: 300, coordinateSpace: "world" }
|
||||
});
|
||||
assert.deepEqual(result.pinnedNodeIds, ["drag"]);
|
||||
assert.equal(simulation.nodes.find((node) => node.id === "drag")?.fx, 460);
|
||||
assert.equal(simulation.nodes.find((node) => node.id === "drag")?.fy, 300);
|
||||
simulation.destroy();
|
||||
});
|
||||
|
||||
it("commits a fast release by applying the final pointer target before pinning", () => {
|
||||
const graph = buildRenderableGraph(sampleGraph(), { theme: "shan-shui" });
|
||||
const simulation = createLiveGraphSimulation(graph);
|
||||
const pinState = new PinState(graph);
|
||||
|
||||
simulation.beginDrag("drag");
|
||||
simulation.dragTo("drag", { x: 460, y: 300 });
|
||||
|
||||
const result = commitGraphNodeDrag({
|
||||
nodeId: "drag",
|
||||
simulation,
|
||||
pinState,
|
||||
finalWorldPoint: { x: 620, y: 410 }
|
||||
});
|
||||
|
||||
assert.deepEqual(result.pinPosition, { x: 620, y: 410 });
|
||||
assert.deepEqual(result.pins, {
|
||||
"wiki/drag.md": { x: 620, y: 410, coordinateSpace: "world" }
|
||||
});
|
||||
assert.equal(simulation.nodes.find((node) => node.id === "drag")?.fx, 620);
|
||||
assert.equal(simulation.nodes.find((node) => node.id === "drag")?.fy, 410);
|
||||
simulation.destroy();
|
||||
});
|
||||
|
||||
it("commits a drag outside the old default world instead of clamping it back", () => {
|
||||
const graph = buildRenderableGraph(sampleGraph(), { theme: "shan-shui" });
|
||||
const simulation = createLiveGraphSimulation(graph);
|
||||
const pinState = new PinState(graph);
|
||||
|
||||
simulation.beginDrag("drag");
|
||||
simulation.dragTo("drag", { x: 1240, y: 816 });
|
||||
|
||||
const result = commitGraphNodeDrag({ nodeId: "drag", simulation, pinState });
|
||||
|
||||
assert.deepEqual(result.pinPosition, { x: 1240, y: 816 });
|
||||
assert.deepEqual(result.pins, {
|
||||
"wiki/drag.md": { x: 1240, y: 816, coordinateSpace: "world" }
|
||||
});
|
||||
assert.equal(simulation.nodes.find((node) => node.id === "drag")?.fx, 1240);
|
||||
assert.equal(simulation.nodes.find((node) => node.id === "drag")?.fy, 816);
|
||||
simulation.destroy();
|
||||
});
|
||||
|
||||
it("cancels an unpinned drag by restoring the start position without writing a pin", () => {
|
||||
const graph = buildRenderableGraph(sampleGraph(), { theme: "shan-shui" });
|
||||
const simulation = createLiveGraphSimulation(graph);
|
||||
const pinState = new PinState(graph);
|
||||
const startWorldPoint = graph.nodes.find((node) => node.id === "drag")?.point;
|
||||
assert.ok(startWorldPoint);
|
||||
|
||||
simulation.beginDrag("drag");
|
||||
simulation.dragTo("drag", { x: 540, y: 420 });
|
||||
const result = cancelGraphNodeDrag({
|
||||
simulation,
|
||||
pinState,
|
||||
session: session({ startWorldPoint, wasPinned: false })
|
||||
});
|
||||
|
||||
assert.deepEqual(result.pins, {});
|
||||
assert.deepEqual(result.pinnedNodeIds, []);
|
||||
assert.deepEqual(result.restoredPosition, startWorldPoint);
|
||||
assert.equal(result.restoredFixed, false);
|
||||
assert.deepEqual(result.positions.drag, startWorldPoint);
|
||||
assert.equal(simulation.nodes.find((node) => node.id === "drag")?.fx, null);
|
||||
assert.equal(simulation.nodes.find((node) => node.id === "drag")?.fy, null);
|
||||
simulation.destroy();
|
||||
});
|
||||
|
||||
it("cancels a pinned drag by restoring the previous pin and keeping it fixed", () => {
|
||||
const pins: PinMap = {
|
||||
"wiki/drag.md": { x: 320, y: 240, coordinateSpace: "world" }
|
||||
};
|
||||
const graph = buildRenderableGraph(sampleGraph(), { theme: "shan-shui", pins });
|
||||
const simulation = createLiveGraphSimulation(graph);
|
||||
const pinState = new PinState(graph, pins);
|
||||
const startWorldPoint = pins["wiki/drag.md"];
|
||||
assert.ok(startWorldPoint);
|
||||
assert.deepEqual(startWorldPoint, { x: 320, y: 240, coordinateSpace: "world" });
|
||||
|
||||
simulation.beginDrag("drag");
|
||||
simulation.dragTo("drag", { x: 700, y: 500 });
|
||||
const result = cancelGraphNodeDrag({
|
||||
simulation,
|
||||
pinState,
|
||||
session: session({ startWorldPoint, wasPinned: true })
|
||||
});
|
||||
|
||||
assert.deepEqual(result.pins, pins);
|
||||
assert.deepEqual(result.pinnedNodeIds, ["drag"]);
|
||||
assert.deepEqual(result.positions.drag, { x: 320, y: 240 });
|
||||
assert.equal(result.restoredFixed, true);
|
||||
assert.equal(simulation.nodes.find((node) => node.id === "drag")?.fx, 320);
|
||||
assert.equal(simulation.nodes.find((node) => node.id === "drag")?.fy, 240);
|
||||
simulation.destroy();
|
||||
});
|
||||
});
|
||||
|
||||
function session(options: { startWorldPoint: { x: number; y: number }; wasPinned: boolean }): GraphNodeDragSession {
|
||||
return {
|
||||
pointerId: 1,
|
||||
nodeId: "drag",
|
||||
startWorldPoint: options.startWorldPoint,
|
||||
wasPinned: options.wasPinned
|
||||
};
|
||||
}
|
||||
|
||||
function sampleGraph(): GraphData {
|
||||
return {
|
||||
meta: {
|
||||
build_date: "2026-06-12T00:00:00.000Z",
|
||||
wiki_title: "Drag Lifecycle 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 }
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user