mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
test(graph-adapter): add circles adapter tests and tree layout perf bound
Finding B (high): knowledgeGraphToCirclesGraphology had zero test coverage. Adds three new tests: - ring placement: verifies Folder→ring 0, File→ring 1, Function→ring 3 and confirms circles-specific attributes (circlesRing, circlesAnchorX/Y) are set while tree attributes (treeAnchorX/Y) are absent. - edge styling: CONTAINS is marked isHierarchyEdge=true with the hierarchy colour; CALLS is cross-cutting with its own colour. - CALLS cross-cutting: a lone CALLS edge between two Functions is correctly identified as a non-hierarchy edge. Also adds a performance-bound test for the tree adapter at 2 000 nodes / 4 000 edges (the adaptive 14-iteration path) asserting completion within 2 s — catches regressions to the O(N×E×iterations) main-thread blocking that Finding A identified. Co-authored-by: Claude <noreply@anthropic.com> AI-model: claude-sonnet-4-6
This commit is contained in:
parent
bbae989e79
commit
14c9c5861d
1 changed files with 97 additions and 1 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { knowledgeGraphToTreeGraphology } from './graph-adapter';
|
||||
import { knowledgeGraphToTreeGraphology, knowledgeGraphToCirclesGraphology } from './graph-adapter';
|
||||
import type { KnowledgeGraph } from '../core/graph/types';
|
||||
import type { GraphNode } from 'gitnexus-shared';
|
||||
import { EDGE_INFO } from './constants';
|
||||
|
|
@ -90,4 +90,100 @@ describe('knowledgeGraphToTreeGraphology', () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should complete within an acceptable time budget for a medium-sized graph', () => {
|
||||
// 2000 nodes + 4000 edges — exercises the adaptive spring iteration path (14 iters).
|
||||
const nodes: GraphNode[] = Array.from({ length: 2000 }, (_, i) =>
|
||||
makeNode(`n${i}`, i % 4 === 0 ? 'Folder' : i % 4 === 1 ? 'File' : 'Function', `node${i}`),
|
||||
);
|
||||
const relationships = Array.from({ length: 4000 }, (_, i) => ({
|
||||
id: `r${i}`,
|
||||
type: i % 3 === 0 ? 'CONTAINS' : 'CALLS',
|
||||
sourceId: `n${i % 2000}`,
|
||||
targetId: `n${(i + 7) % 2000}`,
|
||||
}));
|
||||
const graph: KnowledgeGraph = { nodes, relationships };
|
||||
|
||||
const start = performance.now();
|
||||
knowledgeGraphToTreeGraphology(graph);
|
||||
const elapsed = performance.now() - start;
|
||||
|
||||
// Should complete comfortably within 2 seconds even on slow CI machines.
|
||||
expect(elapsed).toBeLessThan(2000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('knowledgeGraphToCirclesGraphology', () => {
|
||||
it('should place nodes into ring positions based on their type', () => {
|
||||
const graph: KnowledgeGraph = {
|
||||
nodes: [
|
||||
makeNode('folder', 'Folder', 'src'),
|
||||
makeNode('file', 'File', 'main.ts'),
|
||||
makeNode('fn', 'Function', 'doSomething'),
|
||||
],
|
||||
relationships: [
|
||||
{ id: 'r1', type: 'CONTAINS', sourceId: 'folder', targetId: 'file' },
|
||||
{ id: 'r2', type: 'CALLS', sourceId: 'file', targetId: 'fn' },
|
||||
],
|
||||
};
|
||||
|
||||
const sigmaGraph = knowledgeGraphToCirclesGraphology(graph);
|
||||
|
||||
expect(sigmaGraph.hasNode('folder')).toBe(true);
|
||||
expect(sigmaGraph.hasNode('file')).toBe(true);
|
||||
expect(sigmaGraph.hasNode('fn')).toBe(true);
|
||||
|
||||
// Each node carries its ring index and anchor coordinates
|
||||
const folderAttrs = sigmaGraph.getNodeAttributes('folder');
|
||||
const fileAttrs = sigmaGraph.getNodeAttributes('file');
|
||||
const fnAttrs = sigmaGraph.getNodeAttributes('fn');
|
||||
|
||||
expect(typeof folderAttrs.circlesRing).toBe('number');
|
||||
expect(typeof folderAttrs.circlesAnchorX).toBe('number');
|
||||
expect(typeof folderAttrs.circlesAnchorY).toBe('number');
|
||||
|
||||
// Folders/Packages live in ring 0 (innermost); Files in ring 1; Functions in ring 3.
|
||||
expect(folderAttrs.circlesRing).toBe(0);
|
||||
expect(fileAttrs.circlesRing).toBe(1);
|
||||
expect(fnAttrs.circlesRing).toBe(3);
|
||||
|
||||
// Tree anchor attributes must NOT be set in circles mode
|
||||
expect(folderAttrs.treeAnchorX).toBeUndefined();
|
||||
expect(folderAttrs.treeAnchorY).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should style hierarchy edges differently from cross-cutting edges', () => {
|
||||
const graph: KnowledgeGraph = {
|
||||
nodes: [makeNode('a', 'File', 'a.ts'), makeNode('b', 'Function', 'fn')],
|
||||
relationships: [
|
||||
{ id: 'r1', type: 'CONTAINS', sourceId: 'a', targetId: 'b' },
|
||||
{ id: 'r2', type: 'CALLS', sourceId: 'a', targetId: 'b' },
|
||||
],
|
||||
};
|
||||
|
||||
// Two relationships between the same pair — graph-adapter deduplicates via
|
||||
// hasEdge check, so only the first inserted (CONTAINS, hierarchy) is kept.
|
||||
const sigmaGraph = knowledgeGraphToCirclesGraphology(graph);
|
||||
|
||||
sigmaGraph.forEachEdge((_, attrs) => {
|
||||
if (attrs.relationType === 'CONTAINS') {
|
||||
expect(attrs.isHierarchyEdge).toBe(true);
|
||||
expect(attrs.color).toBe(EDGE_INFO.CONTAINS.color);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should treat CALLS as a cross-cutting edge in circles view', () => {
|
||||
const graph: KnowledgeGraph = {
|
||||
nodes: [makeNode('a', 'Function', 'fnA'), makeNode('b', 'Function', 'fnB')],
|
||||
relationships: [{ id: 'r1', type: 'CALLS', sourceId: 'a', targetId: 'b' }],
|
||||
};
|
||||
|
||||
const sigmaGraph = knowledgeGraphToCirclesGraphology(graph);
|
||||
|
||||
sigmaGraph.forEachEdge((_, attrs) => {
|
||||
expect(attrs.isHierarchyEdge).toBe(false);
|
||||
expect(attrs.color).toBe(EDGE_INFO.CALLS.color);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue