first
This commit is contained in:
@@ -0,0 +1,271 @@
|
||||
import { describe, it } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import type { GraphRendererAdapterData } from "../src";
|
||||
import {
|
||||
createSigmaGlobalHitProjector,
|
||||
gestureTargetFromSigmaRenderedObject,
|
||||
sigmaNodeIdFromPayload,
|
||||
sigmaScreenPointFromPayload
|
||||
} from "../src/render/sigma-hit-projector";
|
||||
|
||||
describe("Sigma hit projector", () => {
|
||||
it("projects known Sigma node ids before spatial fallback", () => {
|
||||
const projector = createSigmaGlobalHitProjector(projectorInput());
|
||||
|
||||
assert.deepEqual(
|
||||
projector.targetFromSigmaHit({ nodeId: "alpha", screenPoint: { x: 50, y: 50 } }),
|
||||
{ kind: "node", id: "alpha" }
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back from unknown Sigma node ids to rendered objects and screen points", () => {
|
||||
const projector = createSigmaGlobalHitProjector(projectorInput());
|
||||
|
||||
assert.deepEqual(
|
||||
projector.targetFromSigmaHit({ nodeId: "missing", renderedObject: { kind: "edge", id: "edge-a" } }),
|
||||
{ kind: "edge", id: "edge-a" }
|
||||
);
|
||||
assert.deepEqual(
|
||||
projector.targetFromSigmaHit({ nodeId: "missing", screenPoint: { x: 50, y: 30 } }),
|
||||
{ kind: "community-wash", id: "community-a" }
|
||||
);
|
||||
});
|
||||
|
||||
it("translates rendered objects into graph gesture targets", () => {
|
||||
const adapterData = adapterDataFixture();
|
||||
|
||||
assert.deepEqual(gestureTargetFromSigmaRenderedObject({ kind: "node", id: "alpha" }, adapterData), { kind: "node", id: "alpha" });
|
||||
assert.deepEqual(gestureTargetFromSigmaRenderedObject({ kind: "edge", id: "edge-a" }, adapterData), { kind: "edge", id: "edge-a" });
|
||||
assert.deepEqual(gestureTargetFromSigmaRenderedObject({ kind: "community-wash", id: "community-a" }, adapterData), { kind: "community-wash", id: "community-a" });
|
||||
assert.deepEqual(
|
||||
gestureTargetFromSigmaRenderedObject({ kind: "aggregation-container", id: "aggregation-a" }, adapterData),
|
||||
{ kind: "aggregation-container", id: "aggregation-a", communityId: "community-a" }
|
||||
);
|
||||
});
|
||||
|
||||
it("returns graph blank for invalid rendered objects through projector fallback", () => {
|
||||
const projector = createSigmaGlobalHitProjector(projectorInput());
|
||||
|
||||
assert.deepEqual(
|
||||
projector.targetFromSigmaHit({ renderedObject: { kind: "edge", id: "missing" } }),
|
||||
{ kind: "graph-blank" }
|
||||
);
|
||||
});
|
||||
|
||||
it("parses Sigma node and screen point payloads defensively", () => {
|
||||
assert.equal(sigmaNodeIdFromPayload({ node: "alpha" }), "alpha");
|
||||
assert.equal(sigmaNodeIdFromPayload({ node: 42 }), null);
|
||||
assert.deepEqual(sigmaScreenPointFromPayload({ x: 10, y: 20 }), { x: 10, y: 20 });
|
||||
assert.deepEqual(sigmaScreenPointFromPayload({ event: { x: 30, y: 40 } }), { x: 30, y: 40 });
|
||||
assert.equal(sigmaScreenPointFromPayload({ event: { x: "30", y: 40 } }), null);
|
||||
});
|
||||
|
||||
it("returns graph blank when no hit input can be projected", () => {
|
||||
const projector = createSigmaGlobalHitProjector(projectorInput());
|
||||
|
||||
assert.deepEqual(projector.targetFromSigmaHit({}), { kind: "graph-blank" });
|
||||
});
|
||||
});
|
||||
|
||||
function projectorInput() {
|
||||
return {
|
||||
adapterData: adapterDataFixture(),
|
||||
viewport: { x: 0, y: 0, scale: 1 },
|
||||
viewportSize: { width: 100, height: 100 },
|
||||
screenPointToWorldPoint: (point: { x: number; y: number }) => point
|
||||
};
|
||||
}
|
||||
|
||||
function adapterDataFixture(): GraphRendererAdapterData {
|
||||
return {
|
||||
counts: {
|
||||
nodes: 2,
|
||||
edges: 1,
|
||||
communities: 1,
|
||||
hidden: 0,
|
||||
renderedNodes: 2,
|
||||
renderedEdges: 1,
|
||||
aggregationContainers: 1
|
||||
},
|
||||
selection: {
|
||||
input: null,
|
||||
selectionId: null,
|
||||
selectedNodeIds: [],
|
||||
selectedCommunityIds: [],
|
||||
containsCurrentObject: false
|
||||
},
|
||||
nodes: [
|
||||
nodeFixture("alpha", { x: 10, y: 10 }),
|
||||
nodeFixture("beta", { x: 90, y: 10 })
|
||||
],
|
||||
edges: [
|
||||
{
|
||||
id: "edge-a",
|
||||
sourceNodeId: "alpha",
|
||||
targetNodeId: "beta",
|
||||
sourceCommunityId: "community-a",
|
||||
targetCommunityId: "community-a",
|
||||
relationType: "depends-on",
|
||||
confidence: "EXTRACTED",
|
||||
weight: 1,
|
||||
render: { strokeWidth: 1, opacity: 1 }
|
||||
}
|
||||
],
|
||||
communities: [
|
||||
{
|
||||
id: "community-a",
|
||||
object: { kind: "community", communityId: "community-a" },
|
||||
label: "Community A",
|
||||
nodeIds: ["alpha", "beta"],
|
||||
nodeCount: 2,
|
||||
selected: false,
|
||||
searchResultIds: [],
|
||||
pinHints: [],
|
||||
aggregationIds: ["aggregation-a"],
|
||||
drawerTarget: communityDrawerTarget("community-a"),
|
||||
commands: [{ kind: "enter-community", communityId: "community-a", label: "进入社区" }]
|
||||
}
|
||||
],
|
||||
aggregations: [
|
||||
{
|
||||
id: "aggregation-a",
|
||||
object: { kind: "aggregation", aggregationId: "aggregation-a", nodeIds: ["alpha", "beta"], communityId: "community-a" },
|
||||
label: "Aggregation A",
|
||||
communityId: "community-a",
|
||||
nodeIds: ["alpha", "beta"],
|
||||
selectedNodeIds: [],
|
||||
searchResultIds: [],
|
||||
pinnedNodeIds: [],
|
||||
totalCount: 2,
|
||||
selected: false,
|
||||
pinHints: [],
|
||||
drawerTarget: communityDrawerTarget("community-a"),
|
||||
commands: []
|
||||
}
|
||||
],
|
||||
renderable: {
|
||||
nodes: [],
|
||||
edges: [{ id: "edge-a", sourceNodeId: "alpha", targetNodeId: "beta", curveOffset: 0 }],
|
||||
communities: [
|
||||
{
|
||||
id: "community-a",
|
||||
role: "community",
|
||||
label: "Community A",
|
||||
nodeCount: 2,
|
||||
selected: false,
|
||||
searchHitCount: 0,
|
||||
pinnedCount: 0,
|
||||
selectedCount: 0,
|
||||
color: "#64748b",
|
||||
x: 50,
|
||||
y: 50,
|
||||
radius: 30,
|
||||
wash: { cx: 50, cy: 50, rx: 30, ry: 30 },
|
||||
drawerTarget: communityDrawerTarget("community-a"),
|
||||
commands: [{ kind: "enter-community", communityId: "community-a", label: "进入社区" }]
|
||||
}
|
||||
],
|
||||
aggregationContainers: [
|
||||
{
|
||||
id: "aggregation-a",
|
||||
role: "aggregation-container",
|
||||
label: "Aggregation A",
|
||||
communityId: "community-a",
|
||||
nodeIds: ["alpha", "beta"],
|
||||
nodeCount: 2,
|
||||
searchHitCount: 0,
|
||||
pinnedCount: 0,
|
||||
selectedCount: 0,
|
||||
selected: false,
|
||||
searchResultIds: [],
|
||||
pinnedNodeIds: [],
|
||||
selectedNodeIds: [],
|
||||
pinHints: [],
|
||||
point: { x: 80, y: 80 },
|
||||
x: 80,
|
||||
y: 80,
|
||||
radius: 8,
|
||||
color: "#64748b"
|
||||
}
|
||||
],
|
||||
minimap: { path: "", nodes: [] },
|
||||
relationLegend: [],
|
||||
selectedNodeId: null,
|
||||
selectedCommunityId: null,
|
||||
selectedNodeIds: [],
|
||||
hiddenNodeIds: new Set(),
|
||||
searchResultIds: [],
|
||||
worldBounds: { minX: 0, maxX: 100, minY: 0, maxY: 100 },
|
||||
budgets: {
|
||||
limits: {
|
||||
maxNodes: 2,
|
||||
maxEdges: 1,
|
||||
maxLabels: 1,
|
||||
maxCards: 1,
|
||||
maxInteractionUpdates: 1,
|
||||
maxVisibleCommunities: 1
|
||||
},
|
||||
usage: {
|
||||
nodes: 2,
|
||||
edges: 1,
|
||||
labels: 1,
|
||||
cards: 1,
|
||||
interactionUpdate: 1,
|
||||
activeInteraction: 1,
|
||||
communities: 1,
|
||||
aggregationContainers: 1
|
||||
}
|
||||
},
|
||||
qualityNotice: null,
|
||||
communityFocus: null,
|
||||
communityQuality: {
|
||||
boundaryCertainty: "high",
|
||||
skeletonLabel: "stable",
|
||||
hiddenNodeCount: 0,
|
||||
hiddenEdgeCount: 0,
|
||||
stableCoreNodeIds: ["alpha"],
|
||||
stableSkeletonEdgeIds: ["edge-a"],
|
||||
temporaryBoostNodeIds: []
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function nodeFixture(id: string, point: { x: number; y: number }): GraphRendererAdapterData["nodes"][number] {
|
||||
return {
|
||||
id,
|
||||
object: { kind: "node", nodeId: id },
|
||||
label: id,
|
||||
type: "topic",
|
||||
communityId: "community-a",
|
||||
sourcePath: `${id}.md`,
|
||||
point,
|
||||
selected: false,
|
||||
searchHit: false,
|
||||
pinHint: {
|
||||
nodeId: id,
|
||||
wikiPath: `${id}.md`,
|
||||
pinned: false,
|
||||
position: null
|
||||
},
|
||||
aggregationIds: ["aggregation-a"],
|
||||
drawerTarget: {
|
||||
summaryKind: "node-summary",
|
||||
object: { kind: "node", nodeId: id }
|
||||
},
|
||||
render: {
|
||||
displayMode: "point",
|
||||
visualRole: "landmark",
|
||||
priority: 1,
|
||||
labelVisible: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function communityDrawerTarget(id: string): GraphRendererAdapterData["communities"][number]["drawerTarget"] {
|
||||
return {
|
||||
summaryKind: "community-summary",
|
||||
object: { kind: "community", communityId: id }
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user