Files
PlantAI-rag-new/tests/frontend/unit/b3/graphLayout.test.js
T

87 lines
3.1 KiB
JavaScript
Raw Normal View History

// graphLayout.test.js —— B3 单测:手写力导向布局(graphLayout.js)
import { describe, it, expect } from 'vitest';
import { forceLayout, buildPalette, hashString } from '../../../../ui/src/components/bench/graphLayout.js';
describe('forceLayout 手写力导向(FR-KG-001)', () => {
const small = {
nodes: [
{ id: 'a', label: 'A', type: 'gene' },
{ id: 'b', label: 'B', type: 'tissue' },
{ id: 'c', label: 'C', type: 'process' },
],
edges: [
{ source: 'a', target: 'b', relation: 'expressed_in' },
{ source: 'a', target: 'c', relation: 'involved_in' },
],
};
it('输出节点带有限位置(无 NaN/Infinity),且包围盒适配画布', () => {
const { nodes } = forceLayout(small, { width: 800, height: 600 });
expect(nodes).toHaveLength(3);
for (const n of nodes) {
expect(Number.isFinite(n.x)).toBe(true);
expect(Number.isFinite(n.y)).toBe(true);
expect(n.x).toBeGreaterThanOrEqual(0);
expect(n.x).toBeLessThanOrEqual(800);
expect(n.y).toBeGreaterThanOrEqual(0);
expect(n.y).toBeLessThanOrEqual(600);
}
});
it('边端点引用不存在的节点 → 被过滤,不抛错', () => {
const { nodes, edges } = forceLayout({
nodes: [{ id: 'a' }, { id: 'b' }],
edges: [{ source: 'a', target: 'ghost' }, { source: 'a', target: 'b' }],
});
expect(nodes).toHaveLength(2);
expect(edges).toHaveLength(1);
});
it('空图 → 空结果', () => {
const { nodes } = forceLayout({ nodes: [], edges: [] });
expect(nodes).toHaveLength(0);
});
it('单节点不崩溃', () => {
const { nodes } = forceLayout({ nodes: [{ id: 'x', type: 'gene' }], edges: [] });
expect(nodes).toHaveLength(1);
expect(Number.isFinite(nodes[0].x)).toBe(true);
});
it('大图(60 节点)也能稳定布局(供降级前渲染兜底)', () => {
const nodes = [];
const edges = [];
for (let i = 0; i < 60; i += 1) {
nodes.push({ id: `n${i}`, type: `t${i % 5}` });
if (i > 0) edges.push({ source: `n${i}`, target: `n${i - 1}` });
}
const out = forceLayout({ nodes, edges }, { width: 800, height: 600, iterations: 120 });
expect(out.nodes).toHaveLength(60);
for (const n of out.nodes) {
expect(Number.isFinite(n.x)).toBe(true);
expect(Number.isFinite(n.y)).toBe(true);
}
});
it('相同输入两次运行结果一致(确定性布局)', () => {
const a = forceLayout(small, { width: 800, height: 600 });
const b = forceLayout(small, { width: 800, height: 600 });
for (let i = 0; i < a.nodes.length; i += 1) {
expect(a.nodes[i].x).toBeCloseTo(b.nodes[i].x, 6);
expect(a.nodes[i].y).toBeCloseTo(b.nodes[i].y, 6);
}
});
});
describe('buildPalette 稳定映射', () => {
it('hashString 稳定', () => {
expect(hashString('gene')).toBe(hashString('gene'));
expect(hashString('gene')).not.toBe(hashString('tissue'));
});
it('调色板大小写敏感(Gene 与 gene 视为不同 type,与后端一致)', () => {
const p = buildPalette(['Gene', 'gene']);
expect(p.get('Gene')).not.toBe(p.get('gene'));
});
});