mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-30 04:40:16 +00:00
- Fix `import.meta.env.DEV` crash in graphSceneState.ts that broke the entire test:graph-workspace suite (module load fails in Node.js/tsx) - Export `resolveGroupedDisplayNodeId` from graphSceneState.ts and remove the identical copy in GraphWorkspace.tsx - Add `checkGroupedViewAvailability` helper (Louvain only, no centrality) so grouped view availability can be checked cheaply on every graph change - Gate full community graph build (`groupedDisplayCandidate`) on `viewMode === 'grouped'` to avoid running Louvain + centrality on every graph version tick when the user is not in grouped view - Remove dead ternary in `focusNode` where both branches returned `nodeId` - Add 7 new tests covering resolveGroupedDisplayNodeId, resolveGroupedDisplayStateSnapshot, and checkGroupedViewAvailability Co-Authored-By: ZohaibHassan16 <109234410+ZohaibHassan16@users.noreply.github.com> Co-Authored-By: KaifAhmad1 <mohammadk78600@gmail.com>
260 lines
8.7 KiB
TypeScript
260 lines
8.7 KiB
TypeScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import {
|
|
batchMergeEdges,
|
|
batchMergeNodes,
|
|
clearGraph,
|
|
} from "../src/store/graphStore.ts";
|
|
import {
|
|
checkGroupedViewAvailability,
|
|
resolveDisplayGraph,
|
|
resolveGroupedDisplayNodeId,
|
|
resolveGroupedDisplayStateSnapshot,
|
|
} from "../src/workspaces/GraphWorkspace/graphSceneState.ts";
|
|
|
|
function addNode(id: string, semanticGroup = "entity") {
|
|
batchMergeNodes([
|
|
{
|
|
id,
|
|
attributes: {
|
|
label: id,
|
|
content: id,
|
|
x: 0,
|
|
y: 0,
|
|
size: 8,
|
|
color: "#63E6FF",
|
|
baseColor: "#63E6FF",
|
|
nodeType: semanticGroup,
|
|
semanticGroup,
|
|
properties: {},
|
|
},
|
|
},
|
|
]);
|
|
}
|
|
|
|
function addEdge(id: string, source: string, target: string, weight = 1) {
|
|
batchMergeEdges([
|
|
{
|
|
id,
|
|
source,
|
|
target,
|
|
attributes: {
|
|
edgeType: "related_to",
|
|
weight,
|
|
properties: {},
|
|
},
|
|
},
|
|
]);
|
|
}
|
|
|
|
test.beforeEach(() => {
|
|
clearGraph();
|
|
});
|
|
|
|
test.after(() => {
|
|
clearGraph();
|
|
});
|
|
|
|
test("resolveDisplayGraph bundles parallel edges in full view", () => {
|
|
addNode("a");
|
|
addNode("b");
|
|
addEdge("e1", "a", "b", 1);
|
|
addEdge("e2", "a", "b", 2);
|
|
|
|
const { graph } = resolveDisplayGraph("", [], [], "full", { aggregationEnabled: true });
|
|
assert.equal(graph.size, 1);
|
|
|
|
const edgeId = graph.edges()[0];
|
|
const attrs = graph.getEdgeAttributes(edgeId) as {
|
|
isAggregated?: boolean;
|
|
aggregateCount?: number;
|
|
rawEdgeIds?: string[];
|
|
bundleKind?: string;
|
|
};
|
|
|
|
assert.equal(attrs.isAggregated, true);
|
|
assert.equal(attrs.aggregateCount, 2);
|
|
assert.deepEqual(new Set(attrs.rawEdgeIds ?? []), new Set(["e1", "e2"]));
|
|
assert.equal(attrs.bundleKind, "parallel");
|
|
});
|
|
|
|
test("resolveDisplayGraph collapse keeps path neighbor visible", () => {
|
|
addNode("center");
|
|
for (let index = 0; index < 10; index += 1) {
|
|
const neighbor = `n${index}`;
|
|
addNode(neighbor);
|
|
addEdge(`edge-${index}`, "center", neighbor, 1);
|
|
}
|
|
|
|
const { state } = resolveDisplayGraph("center", ["center", "n9"], [], "full", {
|
|
aggregationEnabled: false,
|
|
collapsedNeighborhoodNodeIds: ["center"],
|
|
});
|
|
|
|
assert.equal(state.selectedRootNodeId, "center");
|
|
assert.equal(state.selectedVisibleNeighborIds.includes("n9"), true);
|
|
assert.equal(state.selectedVisibleNeighborIds.length, 9);
|
|
assert.equal(state.selectedCollapsedNeighborIds.length, 1);
|
|
});
|
|
|
|
test("resolveDisplayGraph grouped view emits community nodes and edges", () => {
|
|
const left = ["a1", "a2", "a3", "a4"];
|
|
const right = ["b1", "b2", "b3", "b4"];
|
|
|
|
[...left, ...right].forEach((nodeId, index) => {
|
|
addNode(nodeId, index < left.length ? "left" : "right");
|
|
});
|
|
|
|
let edgeIndex = 0;
|
|
for (let i = 0; i < left.length; i += 1) {
|
|
for (let j = 0; j < left.length; j += 1) {
|
|
if (i !== j) {
|
|
addEdge(`l-${edgeIndex++}`, left[i], left[j], 3);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < right.length; i += 1) {
|
|
for (let j = 0; j < right.length; j += 1) {
|
|
if (i !== j) {
|
|
addEdge(`r-${edgeIndex++}`, right[i], right[j], 3);
|
|
}
|
|
}
|
|
}
|
|
|
|
addEdge("bridge-1", "a1", "b1", 0.1);
|
|
addEdge("bridge-2", "a2", "b2", 0.1);
|
|
|
|
const { graph, state } = resolveDisplayGraph("", [], [], "grouped", { aggregationEnabled: true });
|
|
|
|
assert.equal(state.groupedViewAvailable, true);
|
|
|
|
const communityNodes = graph.nodes().filter((nodeId) => nodeId.startsWith("__community__"));
|
|
assert.ok(communityNodes.length >= 2);
|
|
|
|
const hasCommunityEdge = graph
|
|
.edges()
|
|
.map((edgeId) => graph.getEdgeAttributes(edgeId) as { bundleKind?: string; isAggregated?: boolean; aggregateCount?: number })
|
|
.some((attrs) => attrs.bundleKind === "community" && attrs.isAggregated === true && Number(attrs.aggregateCount ?? 0) > 0);
|
|
|
|
assert.equal(hasCommunityEdge, true);
|
|
});
|
|
|
|
// ── resolveGroupedDisplayNodeId ──────────────────────────────────────────────
|
|
|
|
test("resolveGroupedDisplayNodeId returns null for empty nodeId", () => {
|
|
const { graph: displayGraph } = resolveDisplayGraph("", [], [], "full", { aggregationEnabled: false });
|
|
assert.equal(resolveGroupedDisplayNodeId(displayGraph, ""), null);
|
|
});
|
|
|
|
test("resolveGroupedDisplayNodeId returns nodeId when it exists directly in display graph", () => {
|
|
addNode("x");
|
|
const { graph: displayGraph } = resolveDisplayGraph("", [], [], "full", { aggregationEnabled: false });
|
|
assert.equal(resolveGroupedDisplayNodeId(displayGraph, "x"), "x");
|
|
});
|
|
|
|
test("resolveGroupedDisplayNodeId resolves base node to its community node", () => {
|
|
const left = ["a1", "a2", "a3", "a4"];
|
|
const right = ["b1", "b2", "b3", "b4"];
|
|
[...left, ...right].forEach((nodeId, index) => addNode(nodeId, index < left.length ? "left" : "right"));
|
|
|
|
let edgeIndex = 0;
|
|
for (let i = 0; i < left.length; i += 1) {
|
|
for (let j = 0; j < left.length; j += 1) {
|
|
if (i !== j) addEdge(`l-${edgeIndex++}`, left[i], left[j], 3);
|
|
}
|
|
}
|
|
for (let i = 0; i < right.length; i += 1) {
|
|
for (let j = 0; j < right.length; j += 1) {
|
|
if (i !== j) addEdge(`r-${edgeIndex++}`, right[i], right[j], 3);
|
|
}
|
|
}
|
|
addEdge("bridge-1", "a1", "b1", 0.1);
|
|
|
|
const { graph: displayGraph } = resolveDisplayGraph("", [], [], "grouped", { aggregationEnabled: true });
|
|
const communityNodes = displayGraph.nodes().filter((n) => n.startsWith("__community__"));
|
|
assert.ok(communityNodes.length >= 2, "expected community nodes");
|
|
|
|
const resolved = resolveGroupedDisplayNodeId(displayGraph, "a1");
|
|
assert.ok(resolved !== null, "should resolve a1 to a community node");
|
|
assert.ok(resolved!.startsWith("__community__"), "resolved id should be a community node");
|
|
});
|
|
|
|
// ── resolveGroupedDisplayStateSnapshot ──────────────────────────────────────
|
|
|
|
test("resolveGroupedDisplayStateSnapshot returns none-kind when no node selected", () => {
|
|
addNode("p");
|
|
addNode("q");
|
|
addEdge("e1", "p", "q");
|
|
const { graph: displayGraph } = resolveDisplayGraph("", [], [], "full", { aggregationEnabled: false });
|
|
const state = resolveGroupedDisplayStateSnapshot(displayGraph, "", {
|
|
groupedViewAvailable: true,
|
|
groupedViewReason: null,
|
|
});
|
|
assert.equal(state.selectedNodeKind, "none");
|
|
assert.equal(state.selectedRootNodeId, null);
|
|
});
|
|
|
|
test("resolveGroupedDisplayStateSnapshot maps selected base node to community in grouped graph", () => {
|
|
const left = ["c1", "c2", "c3", "c4"];
|
|
const right = ["d1", "d2", "d3", "d4"];
|
|
[...left, ...right].forEach((nodeId, index) => addNode(nodeId, index < left.length ? "left" : "right"));
|
|
|
|
let edgeIndex = 0;
|
|
for (let i = 0; i < left.length; i += 1) {
|
|
for (let j = 0; j < left.length; j += 1) {
|
|
if (i !== j) addEdge(`lc-${edgeIndex++}`, left[i], left[j], 3);
|
|
}
|
|
}
|
|
for (let i = 0; i < right.length; i += 1) {
|
|
for (let j = 0; j < right.length; j += 1) {
|
|
if (i !== j) addEdge(`rc-${edgeIndex++}`, right[i], right[j], 3);
|
|
}
|
|
}
|
|
addEdge("bridge-c1", "c1", "d1", 0.1);
|
|
addEdge("bridge-c2", "c2", "d2", 0.1);
|
|
|
|
const { graph: displayGraph } = resolveDisplayGraph("", [], [], "grouped", { aggregationEnabled: true });
|
|
const state = resolveGroupedDisplayStateSnapshot(displayGraph, "c1", {
|
|
groupedViewAvailable: true,
|
|
groupedViewReason: null,
|
|
selectedNodeKind: "grouped",
|
|
});
|
|
|
|
assert.ok(state.selectedRootNodeId !== null, "should resolve to a community node");
|
|
assert.ok(state.selectedRootNodeId!.startsWith("__community__"), "root should be a community node");
|
|
assert.equal(state.groupedViewAvailable, true);
|
|
});
|
|
|
|
// ── checkGroupedViewAvailability ─────────────────────────────────────────────
|
|
|
|
test("checkGroupedViewAvailability returns unavailable on empty graph", () => {
|
|
const result = checkGroupedViewAvailability();
|
|
assert.equal(result.available, false);
|
|
assert.ok(typeof result.reason === "string" && result.reason.length > 0);
|
|
});
|
|
|
|
test("checkGroupedViewAvailability returns available when communities exist", () => {
|
|
const left = ["e1", "e2", "e3", "e4"];
|
|
const right = ["f1", "f2", "f3", "f4"];
|
|
[...left, ...right].forEach((nodeId, index) => addNode(nodeId, index < left.length ? "left" : "right"));
|
|
|
|
let edgeIndex = 0;
|
|
for (let i = 0; i < left.length; i += 1) {
|
|
for (let j = 0; j < left.length; j += 1) {
|
|
if (i !== j) addEdge(`le-${edgeIndex++}`, left[i], left[j], 3);
|
|
}
|
|
}
|
|
for (let i = 0; i < right.length; i += 1) {
|
|
for (let j = 0; j < right.length; j += 1) {
|
|
if (i !== j) addEdge(`re-${edgeIndex++}`, right[i], right[j], 3);
|
|
}
|
|
}
|
|
addEdge("bridge-e1", "e1", "f1", 0.1);
|
|
|
|
const result = checkGroupedViewAvailability();
|
|
assert.equal(result.available, true);
|
|
assert.equal(result.reason, null);
|
|
});
|