// tools/graph-radar.test.mjs import { describe, it, expect } from 'vitest'; import { buildGraphRadar } from './graph-radar.mjs'; const graph = { nodes: [ { id: 'x', label: 'X', source_file: 'tools/x.mjs' }, { id: 'y', label: 'Y', source_file: 'tools/y.mjs' }, { id: 'z', label: 'Z', source_file: 'tools/z.mjs' }, ], links: [ { source: 'x', target: 'y', relation: 'references' }, { source: 'z', target: 'x', relation: 'references' }, ], }; describe('buildGraphRadar (§5.7 граф-радар)', () => { it('тронут x → соседи y и z в чек-листе (addressed:false)', () => { const r = buildGraphRadar({ touchedFiles: ['tools/x.mjs'], graph }); const ids = r.neighbors.map((n) => n.node).sort(); expect(ids).toEqual(['y', 'z']); expect(r.neighbors.every((n) => n.addressed === false)).toBe(true); expect(r.neighbors.find((n) => n.node === 'y').viaTouched).toBe('x'); }); it('сосед, который САМ тронут, не попадает в чек-лист (уже адресован)', () => { const r = buildGraphRadar({ touchedFiles: ['tools/x.mjs', 'tools/y.mjs'], graph }); expect(r.neighbors.map((n) => n.node)).toEqual(['z']); // y тронут → не флагуем }); it('нет тронутых узлов в графе → пустой чек-лист', () => { expect(buildGraphRadar({ touchedFiles: ['tools/nope.mjs'], graph }).neighbors).toEqual([]); }); it('битый граф/входы → пусто, не крашит', () => { expect(buildGraphRadar({ touchedFiles: null, graph: null }).neighbors).toEqual([]); expect(buildGraphRadar({ touchedFiles: ['a'], graph: { nodes: 'x', links: 1 } }).neighbors).toEqual([]); }); // F-F1: слепота радара видна — подано файлов > 0, найдено узлов 0 it('F-F1: summary.touchedFiles показывает поданное; слепота = touchedFiles>0 ∧ touchedNodes=0', () => { const blind = buildGraphRadar({ touchedFiles: ['tools/nope.mjs'], graph }); expect(blind.summary.touchedFiles).toBe(1); expect(blind.summary.touchedNodes).toBe(0); const ok = buildGraphRadar({ touchedFiles: ['tools/x.mjs'], graph }); expect(ok.summary.touchedFiles).toBe(1); expect(ok.summary.touchedNodes).toBe(1); }); // F-F2: нормализация путей — backslash и ./-префикс не дают тихий промах it('F-F2: windows-backslash и ./-префикс в touchedFiles находят узел', () => { const r1 = buildGraphRadar({ touchedFiles: ['tools\\x.mjs'], graph }); expect(r1.summary.touchedNodes).toBe(1); expect(r1.neighbors.map((n) => n.node).sort()).toEqual(['y', 'z']); const r2 = buildGraphRadar({ touchedFiles: ['./tools/x.mjs'], graph }); expect(r2.summary.touchedNodes).toBe(1); }); it('F-F2: backslash в source_file графа тоже нормализуется', () => { const g = { nodes: [ { id: 'a', label: 'A', source_file: 'tools\\a.mjs' }, { id: 'b', label: 'B', source_file: 'tools/b.mjs' }, ], links: [{ source: 'a', target: 'b' }], }; const r = buildGraphRadar({ touchedFiles: ['tools/a.mjs'], graph: g }); expect(r.neighbors.map((n) => n.node)).toEqual(['b']); }); // F-F4: выходные элементы заморожены — машинное addressed:true бросает (L2-пол) it('F-F4: элементы neighbors заморожены, мутация addressed бросает TypeError', () => { const r = buildGraphRadar({ touchedFiles: ['tools/x.mjs'], graph }); expect(r.neighbors.length).toBeGreaterThan(0); expect(r.neighbors.every((n) => Object.isFrozen(n))).toBe(true); expect(() => { r.neighbors[0].addressed = true; }).toThrow(TypeError); }); });