Files
PlantAI-rag-new/tests/frontend/unit/b3/graphLayout.test.js
T
jerryandClaude Fable 5 1e94de7464 PlantAI-rag-new 首次提交:文献知识工作台
- BFF 薄适配层(FastAPI,8090):QA 代理/SSE 中继/Neo4j 直连/抽取代理/会话持久化
- Vue3 前端:五相位检索流水线、文献来源与证据链、知识图谱、提取任务四 tab
- 四档响应式(桌面/笔记本/平板/手机)、深浅主题
- 81 个 FR 全部实现,测试追溯矩阵 81/81,后端 121 测/前端 156 测
- 真实凭据已脱敏,运行时配置在 backend/.env(不入 git)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 20:47:52 +08:00

87 lines
3.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// graphLayout.test.js —— B3 单测:手写力导向布局(graphLayout.js)
import { describe, it, expect } from 'vitest';
import { forceLayout, buildPalette, hashString } from '../../../../frontend/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'));
});
});