/
GreyCat
/
openhuman
Обзор
Документация
Войти
/
GreyCat
/
openhuman
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
app/src/components/intelligence/memoryGraphLayout.test.ts
114 строк
5 KB
Steven Enamakel
refactor: rename tabs, flatten composer, polish UI (#3611)
12 июн 2026, 11:05
Не верифицирован
12 июн 2026, 11:05
5bf7dae
Код
Авторство
О чём код?
import { describe, expect, it } from 'vitest'; import type { GraphEdge, GraphNode } from '../../utils/tauriCommands'; import { buildGraph, CONTACT_COLOR, createSimulation, LEAF_COLOR, LEVEL_COLOR, levelColor, nodeColor, nodeGlows, nodeRadius, pickNode, type SimNode, supportsWebGL, } from './memoryGraphLayout'; function summary(overrides: Partial<GraphNode> = {}): GraphNode { return { kind: 'summary', id: 's', label: 'S', level: 0, parent_id: null, ...overrides }; } function chunk(overrides: Partial<GraphNode> = {}): GraphNode { return { kind: 'chunk', id: 'c', label: 'C', ...overrides }; } function contact(overrides: Partial<GraphNode> = {}): GraphNode { return { kind: 'contact', id: 'p', label: 'P', entity_kind: 'person', ...overrides }; } describe('memoryGraphLayout', () => { it('colours summaries by level, wrapping the palette', () => { expect(levelColor(0)).toBe(LEVEL_COLOR[0]); expect(levelColor(2)).toBe(LEVEL_COLOR[2]); expect(levelColor(LEVEL_COLOR.length)).toBe(LEVEL_COLOR[0]); // wraps expect(levelColor(null)).toBe(LEAF_COLOR); expect(levelColor(-5)).toBe(LEVEL_COLOR[0]); // clamped to 0 }); it('nodeColor branches on kind', () => { expect(nodeColor(summary({ level: 1 }))).toBe(LEVEL_COLOR[1]); expect(nodeColor(chunk())).toBe(LEAF_COLOR); expect(nodeColor(contact())).toBe(CONTACT_COLOR); }); it('nodeRadius grows with level (capped) and is fixed for chunk/contact', () => { expect(nodeRadius(summary({ level: 0 }))).toBe(5); expect(nodeRadius(summary({ level: 3 }))).toBe(12.5); // Capped at 14: document merge-tier nodes live at a large synthetic level // (MERGE_LEVEL_BASE = 1000+), so the raw `5 + level*2.5` is clamped to keep // the d3 layout/collision sane instead of rendering giant discs. expect(nodeRadius(summary({ level: 4 }))).toBe(14); // 5 + 4*2.5 = 15 → capped expect(nodeRadius(summary({ level: 99 }))).toBe(14); expect(nodeRadius(summary({ level: 1001 }))).toBe(14); expect(nodeRadius(contact())).toBe(9); expect(nodeRadius(chunk())).toBe(3); }); it('only summary/contact nodes glow', () => { expect(nodeGlows(summary())).toBe(true); expect(nodeGlows(contact())).toBe(true); expect(nodeGlows(chunk())).toBe(false); }); it('buildGraph derives parent_id edges in tree mode and drops danglers', () => { const nodes = [ summary({ id: 'root', parent_id: null }), summary({ id: 'child', level: 1, parent_id: 'root' }), chunk({ id: 'leaf', parent_id: 'child' }), chunk({ id: 'orphan', parent_id: 'missing' }), // dangling → dropped ]; const { simNodes, links } = buildGraph(nodes, [], 'tree'); expect(simNodes).toHaveLength(5); // 4 data nodes + synthetic root hub expect(simNodes.every(n => typeof n.x === 'number' && typeof n.y === 'number')).toBe(true); const pairs = links.map(l => `${String(l.source)}->${String(l.target)}`); expect(pairs).toContain('child->root'); expect(pairs).toContain('leaf->child'); expect(pairs).not.toContain('orphan->missing'); }); it('buildGraph uses explicit edges in contacts mode and drops danglers', () => { const nodes = [chunk({ id: 'c1' }), contact({ id: 'p1' })]; const edges: GraphEdge[] = [ { from: 'c1', to: 'p1' }, { from: 'c1', to: 'ghost' }, // dangling endpoint → dropped ]; const { links } = buildGraph(nodes, edges, 'contacts'); expect(links).toHaveLength(1); // no source nodes → no root links expect(String(links[0].source)).toBe('c1'); expect(String(links[0].target)).toBe('p1'); }); it('createSimulation resolves link ids to node objects and converges', () => { const nodes = [summary({ id: 'root' }), summary({ id: 'child', parent_id: 'root' })]; const { simNodes, links } = buildGraph(nodes, [], 'tree'); const sim = createSimulation(simNodes, links); for (let i = 0; i < 50; i++) sim.tick(); // forceLink replaces string ids with the actual node objects. expect((links[0].source as SimNode).id).toBe('child'); expect((links[0].target as SimNode).id).toBe('root'); expect(Number.isFinite(simNodes[0].x)).toBe(true); sim.stop(); }); it('pickNode returns the nearest node within its disc, else null', () => { const a: SimNode = { ...summary({ id: 'a', level: 0 }), x: 0, y: 0 }; const b: SimNode = { ...summary({ id: 'b', level: 0 }), x: 100, y: 0 }; expect(pickNode([a, b], 1, 1)?.id).toBe('a'); expect(pickNode([a, b], 99, 0)?.id).toBe('b'); expect(pickNode([a, b], 50, 50)).toBeNull(); // outside both discs }); it('supportsWebGL is false under jsdom (no GL context)', () => { expect(supportsWebGL()).toBe(false); }); });