This commit is contained in:
2026-07-12 21:26:08 +08:00
commit 9dd41afd48
502 changed files with 129901 additions and 0 deletions
@@ -0,0 +1,106 @@
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import {
classifyGraphEventTarget,
classifyGraphPointerDownTarget,
classifyGraphPointerDownTargetFromGraphTarget,
classifyGraphWheelTargetFromGraphTarget,
createGraphSpatialIndex,
graphSpatialHitToGestureTarget,
type GraphGestureTargetLike
} from "../src";
class FakeTarget implements GraphGestureTargetLike {
readonly dataset: Record<string, string | undefined>;
private readonly matchesBySelector = new Map<string, FakeTarget | null>();
constructor(options: { dataset?: Record<string, string | undefined>; closest?: Record<string, FakeTarget | null> } = {}) {
this.dataset = options.dataset || {};
for (const [selector, target] of Object.entries(options.closest || {})) {
this.matchesBySelector.set(selector, target);
}
}
closest(selector: string): GraphGestureTargetLike | null {
return this.matchesBySelector.get(selector) || null;
}
}
describe("graph interaction contract", () => {
it("lets SpatialIndex, not DOM stacking order, choose the graph object intent", () => {
const index = createGraphSpatialIndex({
nodes: [
{ id: "node-a", point: { x: 200, y: 200 }, hitBounds: { x: 175, y: 180, width: 50, height: 40 } },
{ id: "left", point: { x: 120, y: 200 }, hitBounds: { x: 110, y: 190, width: 20, height: 20 } },
{ id: "right", point: { x: 280, y: 200 }, hitBounds: { x: 270, y: 190, width: 20, height: 20 } }
],
edges: [{ id: "left-right", source: "left", target: "right", curveOffset: 0 }],
communities: [{ id: "community-a", wash: { cx: 200, cy: 200, rx: 120, ry: 80 } }]
});
const stackedCommunityDomTarget = communityWashDomTarget("community-a");
assert.deepEqual(classifyGraphEventTarget(stackedCommunityDomTarget), { kind: "community-wash", id: "community-a" });
const spatialTarget = graphSpatialHitToGestureTarget(index.hitTest({ x: 200, y: 200 }));
assert.deepEqual(spatialTarget, { kind: "node", id: "node-a" });
assert.deepEqual(classifyGraphPointerDownTargetFromGraphTarget(spatialTarget), {
intent: "node-drag-candidate",
target: { kind: "node", id: "node-a" }
});
assert.deepEqual(classifyGraphWheelTargetFromGraphTarget(spatialTarget), {
intent: "zoom",
target: { kind: "node", id: "node-a" }
});
});
it("keeps edge, community, and blank intents available through the same spatial target path", () => {
const index = createGraphSpatialIndex({
nodes: [
{ id: "node-a", point: { x: 100, y: 100 }, hitBounds: { x: 80, y: 80, width: 40, height: 40 } },
{ id: "node-b", point: { x: 220, y: 100 }, hitBounds: { x: 200, y: 80, width: 40, height: 40 } }
],
edges: [{ id: "edge-a", source: "node-a", target: "node-b" }],
communities: [{ id: "community-a", wash: { cx: 260, cy: 180, rx: 80, ry: 44 } }]
});
assert.deepEqual(
classifyGraphPointerDownTargetFromGraphTarget(graphSpatialHitToGestureTarget(index.hitTest({ x: 160, y: 90 }))),
{
intent: "blank-pan-candidate",
target: { kind: "edge", id: "edge-a" }
}
);
assert.deepEqual(
classifyGraphPointerDownTargetFromGraphTarget(graphSpatialHitToGestureTarget(index.hitTest({ x: 260, y: 180 }))),
{
intent: "community-click-candidate",
target: { kind: "community-wash", id: "community-a" }
}
);
assert.deepEqual(
classifyGraphPointerDownTargetFromGraphTarget(graphSpatialHitToGestureTarget(index.hitTest({ x: 500, y: 500 }))),
{
intent: "blank-pan-candidate",
target: { kind: "graph-blank" }
}
);
});
it("keeps UI blockers as the DOM boundary before graph-object hit testing", () => {
assert.deepEqual(classifyGraphPointerDownTarget(toolbarDomTarget()), {
intent: "blocked",
target: { kind: "toolbar" }
});
});
});
function communityWashDomTarget(id: string): FakeTarget {
const community = new FakeTarget({ dataset: { communityId: id } });
return new FakeTarget({ closest: { ".community-wash": community } });
}
function toolbarDomTarget(): FakeTarget {
const toolbar = new FakeTarget();
return new FakeTarget({ closest: { ".graph-toolbar": toolbar } });
}