Files
llm-wiki/packages/graph-engine/test/renderer-adapter-contract.test.ts
T
2026-07-12 21:26:08 +08:00

313 lines
11 KiB
TypeScript

import { describe, it } from "node:test";
import assert from "node:assert/strict";
import {
GRAPH_RENDERER_ADAPTER_ROUTES,
buildGraphRendererAdapterData,
buildGraphRendererBehaviorContract
} from "../src/render";
import { UNGROUPED_COMMUNITY_ID } from "../src/types";
import type {
GraphAggregationMarker,
GraphData,
GraphRendererBehaviorContract,
PinMap
} from "../src";
describe("graph renderer adapter contract", () => {
it("preserves shared object ids, selected state, search hits, Pin hints, aggregations, and drawer targets", () => {
const adapter = buildGraphRendererAdapterData(graphFixture(), adapterOptions());
assert.deepEqual(adapter.nodes.map((node) => node.id), ["a", "b", "c", "d"]);
assert.deepEqual(adapter.nodes.find((node) => node.id === "a")?.object, { kind: "node", nodeId: "a" });
assert.deepEqual(adapter.nodes.find((node) => node.id === "a")?.drawerTarget, {
summaryKind: "node-summary",
object: { kind: "node", nodeId: "a" }
});
assert.equal(adapter.nodes.find((node) => node.id === "a")?.selected, true);
assert.equal(adapter.nodes.find((node) => node.id === "b")?.searchHit, true);
assert.deepEqual(adapter.nodes.find((node) => node.id === "a")?.pinHint, {
nodeId: "a",
wikiPath: "wiki/alpha/a.md",
pinned: true,
position: { x: 12, y: 34, coordinateSpace: "world" }
});
assert.deepEqual(adapter.nodes.find((node) => node.id === "a")?.aggregationIds, ["agg-alpha"]);
const alpha = adapter.communities.find((community) => community.id === "alpha");
assert.ok(alpha);
assert.deepEqual(alpha.object, { kind: "community", communityId: "alpha" });
assert.equal(alpha.selected, true);
assert.deepEqual(alpha.searchResultIds, ["b"]);
assert.deepEqual(alpha.pinHints.map((hint) => hint.nodeId), ["a"]);
assert.deepEqual(alpha.drawerTarget, {
summaryKind: "community-summary",
object: { kind: "community", communityId: "alpha" }
});
assert.deepEqual(alpha.commands, [{ kind: "enter-community", communityId: "alpha", label: "进入社区" }]);
const aggregation = adapter.aggregations.find((item) => item.id === "agg-alpha");
assert.ok(aggregation);
assert.deepEqual(aggregation.object, {
kind: "aggregation",
aggregationId: "agg-alpha",
nodeIds: ["a", "b"],
communityId: "alpha"
});
assert.equal(aggregation.selected, true);
assert.deepEqual(aggregation.selectedNodeIds, ["a"]);
assert.deepEqual(aggregation.searchResultIds, ["b"]);
assert.deepEqual(aggregation.pinnedNodeIds, ["a"]);
assert.deepEqual(aggregation.drawerTarget, {
summaryKind: "community-summary",
object: { kind: "community", communityId: "alpha" }
});
});
it("defines identical behavior semantics for DOM/SVG, candidate global renderers, and aggregation fallback", () => {
const contracts = contractsForAllRoutes();
const domSvg = contracts.find((contract) => contract.route === "dom-svg");
assert.ok(domSvg);
for (const contract of contracts) {
assert.deepEqual(stripRoute(contract), stripRoute(domSvg), `${contract.route} must keep the same graph semantics`);
}
});
it("defines point select, container select, search highlight, selected aggregation, Pin aggregation, and enter-community output", () => {
const contract = buildGraphRendererBehaviorContract(buildGraphRendererAdapterData(graphFixture(), adapterOptions()), "candidate-global");
assert.deepEqual(contract.pointSelect.find((item) => item.nodeId === "a"), {
nodeId: "a",
object: { kind: "node", nodeId: "a" },
drawerTarget: {
summaryKind: "node-summary",
object: { kind: "node", nodeId: "a" }
},
selected: true,
searchHit: false,
pinHint: {
nodeId: "a",
wikiPath: "wiki/alpha/a.md",
pinned: true,
position: { x: 12, y: 34, coordinateSpace: "world" }
},
aggregationIds: ["agg-alpha"]
});
assert.deepEqual(contract.containerSelect.find((item) => item.containerId === "alpha"), {
containerId: "alpha",
object: { kind: "community", communityId: "alpha" },
drawerTarget: {
summaryKind: "community-summary",
object: { kind: "community", communityId: "alpha" }
},
selected: true,
searchResultIds: ["b"],
pinHintNodeIds: ["a"]
});
assert.deepEqual(contract.containerSelect.find((item) => item.containerId === "agg-alpha"), {
containerId: "agg-alpha",
object: {
kind: "aggregation",
aggregationId: "agg-alpha",
nodeIds: ["a", "b"],
communityId: "alpha"
},
drawerTarget: {
summaryKind: "community-summary",
object: { kind: "community", communityId: "alpha" }
},
selected: true,
searchResultIds: ["b"],
pinHintNodeIds: ["a"]
});
assert.deepEqual(contract.searchHighlight, [
{
nodeId: "b",
object: { kind: "node", nodeId: "b" },
aggregationIds: ["agg-alpha"],
drawerTarget: {
summaryKind: "node-summary",
object: { kind: "node", nodeId: "b" }
}
},
{
nodeId: "d",
object: { kind: "node", nodeId: "d" },
aggregationIds: ["agg-beta"],
drawerTarget: {
summaryKind: "node-summary",
object: { kind: "node", nodeId: "d" }
}
}
]);
assert.deepEqual(contract.selectedObjectInsideAggregation, [
{
aggregationId: "agg-alpha",
object: {
kind: "aggregation",
aggregationId: "agg-alpha",
nodeIds: ["a", "b"],
communityId: "alpha"
},
selectedNodeIds: ["a"],
selected: true,
drawerTarget: {
summaryKind: "community-summary",
object: { kind: "community", communityId: "alpha" }
}
}
]);
assert.deepEqual(contract.pinInsideAggregation, [
{
aggregationId: "agg-alpha",
pinnedNodeIds: ["a"],
pinHints: [
{
nodeId: "a",
wikiPath: "wiki/alpha/a.md",
pinned: true,
position: { x: 12, y: 34, coordinateSpace: "world" }
}
],
drawerTarget: {
summaryKind: "community-summary",
object: { kind: "community", communityId: "alpha" }
}
}
]);
assert.deepEqual(contract.enterCommunity.find((item) => item.communityId === "alpha"), {
communityId: "alpha",
command: { kind: "enter-community", communityId: "alpha", label: "进入社区" }
});
});
it("exposes ungrouped adapter community node ids and drawer target", () => {
const adapter = buildGraphRendererAdapterData(graphFixtureWithUngroupedNodes(), {
selection: { kind: "community", id: UNGROUPED_COMMUNITY_ID }
});
const ungrouped = adapter.communities.find((community) => community.id === UNGROUPED_COMMUNITY_ID);
assert.deepEqual(ungrouped?.nodeIds, ["loose-a", "loose-b"]);
assert.equal(ungrouped?.selected, true);
assert.deepEqual(ungrouped?.drawerTarget, {
summaryKind: "community-summary",
object: { kind: "community", communityId: UNGROUPED_COMMUNITY_ID }
});
assert.deepEqual(ungrouped?.commands, []);
});
it("omits enter-community behavior for the ungrouped virtual community", () => {
const contract = buildGraphRendererBehaviorContract(
buildGraphRendererAdapterData(graphFixtureWithUngroupedNodes()),
"candidate-global"
);
assert.equal(contract.enterCommunity.some((item) => item.communityId === UNGROUPED_COMMUNITY_ID), false);
});
});
function contractsForAllRoutes(): GraphRendererBehaviorContract[] {
return GRAPH_RENDERER_ADAPTER_ROUTES.map((route) => buildGraphRendererBehaviorContract(
buildGraphRendererAdapterData(graphFixture(), adapterOptions()),
route
));
}
function stripRoute(contract: GraphRendererBehaviorContract): Omit<GraphRendererBehaviorContract, "route"> {
const { route: _route, ...semanticContract } = contract;
return semanticContract;
}
function adapterOptions(): {
pins: PinMap;
selection: { kind: "node"; id: "a" };
searchResultIds: string[];
aggregationMarkers: GraphAggregationMarker[];
} {
return {
pins: {
"wiki/alpha/a.md": { x: 12, y: 34, coordinateSpace: "world" }
},
selection: { kind: "node", id: "a" },
searchResultIds: ["b", "d"],
aggregationMarkers: [
{
id: "agg-alpha",
label: "Alpha overflow",
communityId: "alpha",
nodeIds: ["a", "b"],
selectedNodeIds: ["a"],
searchResultIds: ["b"],
pinnedNodeIds: ["a"],
totalCount: 2
},
{
id: "agg-beta",
label: "Beta overflow",
communityId: "beta",
nodeIds: ["c", "d"],
searchResultIds: ["d"],
totalCount: 2
}
]
};
}
function graphFixture(): GraphData {
return {
meta: {
build_date: "2026-06-18T00:00:00.000Z",
wiki_title: "Renderer adapter contract graph",
total_nodes: 4,
total_edges: 3
},
nodes: [
{ id: "a", label: "Alpha hub", type: "topic", community: "alpha", source_path: "wiki/alpha/a.md", score: 3, x: 10, y: 20 },
{ id: "b", label: "Alpha leaf", type: "entity", community: "alpha", source_path: "wiki/alpha/b.md", weight: 2, x: 30, y: 40 },
{ id: "c", label: "Beta bridge", type: "topic", community: "beta", source_path: "wiki/beta/c.md", weight: 1, x: 60, y: 70 },
{ id: "d", label: "Beta detail", type: "source", community: "beta", source_path: "wiki/beta/d.md", x: 90, y: 80 }
],
edges: [
{ id: "a-b", from: "a", to: "b", type: "EXTRACTED", relation_type: "实现", weight: 0.6 },
{ id: "a-c", from: "a", to: "c", type: "INFERRED", relation_type: "依赖", weight: 0.9 },
{ id: "c-d", from: "c", to: "d", type: "EXTRACTED", relation_type: "衍生", weight: 0.8 }
],
learning: {
version: 1,
entry: { recommended_start_node_id: "a", recommended_start_reason: "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: ["a", "b", "c", "d"], degraded: false }
},
communities: [
{ id: "alpha", label: "Alpha", node_count: 2, color_index: 0, members: ["a", "b"] },
{ id: "beta", label: "Beta", node_count: 2, color_index: 1, members: ["c", "d"] }
]
}
};
}
function graphFixtureWithUngroupedNodes(): GraphData {
const base = graphFixture();
return {
...base,
meta: {
...base.meta,
total_nodes: base.meta.total_nodes + 2
},
nodes: [
...base.nodes,
{ id: "loose-a", label: "Loose A", type: "topic", community: null, source_path: "wiki/loose/a.md", score: 2, x: 20, y: 50 },
{ id: "loose-b", label: "Loose B", type: "entity", source_path: "wiki/loose/b.md", weight: 1, x: 80, y: 50 }
]
};
}