import { describe, it, expect } from 'vitest'; import { buildDecisionGraph, validateSections, dependentsOf, impactedBy, buildValidatedGraph, } from './decision-graph.mjs'; const SECTIONS = [ { id: 'отступ', dependsOn: [] }, { id: 'вёрстка', dependsOn: ['отступ'] }, { id: 'мобильный', dependsOn: ['вёрстка', 'отступ'] }, ]; describe('buildDecisionGraph (узлы=секции, рёбра=зависит-от)', () => { it('узлы = id секций', () => { expect(buildDecisionGraph(SECTIONS).nodes).toEqual(['отступ', 'вёрстка', 'мобильный']); }); it('ребро from зависит от to', () => { expect(buildDecisionGraph(SECTIONS).edges).toContainEqual({ from: 'вёрстка', to: 'отступ' }); }); }); describe('validateSections', () => { it('валидные секции → ok', () => { expect(validateSections(SECTIONS).ok).toBe(true); }); it('дубль id → error', () => { expect(validateSections([{ id: 'a', dependsOn: [] }, { id: 'a', dependsOn: [] }]).ok).toBe(false); }); it('зависимость от несуществующей секции → error', () => { const r = validateSections([{ id: 'a', dependsOn: ['нет-такой'] }]); expect(r.ok).toBe(false); expect(r.errors.some((e) => /несуществ/i.test(e))).toBe(true); }); it('пустой id → error', () => { expect(validateSections([{ id: '', dependsOn: [] }]).ok).toBe(false); }); }); describe('dependentsOf (прямые зависимые)', () => { it('кто зависит от отступа', () => { expect(dependentsOf(SECTIONS, 'отступ').sort()).toEqual(['вёрстка', 'мобильный']); }); it('от кого никто не зависит → []', () => { expect(dependentsOf(SECTIONS, 'мобильный')).toEqual([]); }); }); describe('impactedBy (транзитивное наведение внимания; cycle-safe; без самого changedId)', () => { it('сменил отступ → перепроверить вёрстку и мобильный', () => { expect(impactedBy(SECTIONS, 'отступ').sort()).toEqual(['вёрстка', 'мобильный']); }); it('сменил вёрстку → мобильный', () => { expect(impactedBy(SECTIONS, 'вёрстка')).toEqual(['мобильный']); }); it('цикл не зацикливает', () => { const cyc = [{ id: 'a', dependsOn: ['b'] }, { id: 'b', dependsOn: ['a'] }]; expect(impactedBy(cyc, 'a')).toEqual(['b']); }); it('сам changedId не в результате', () => { expect(impactedBy(SECTIONS, 'отступ')).not.toContain('отступ'); }); }); describe('buildValidatedGraph (validate→build; бросает на невалидном — аудит footgun)', () => { it('валидные секции → тот же граф, что buildDecisionGraph', () => { expect(buildValidatedGraph(SECTIONS)).toEqual(buildDecisionGraph(SECTIONS)); }); it('висящая зависимость → бросает (не строит молча рёбра на несуществующий узел)', () => { expect(() => buildValidatedGraph([{ id: 'a', dependsOn: ['нет'] }])).toThrow(/несуществ|невалид/i); }); it('дубль id → бросает', () => { expect(() => buildValidatedGraph([{ id: 'a', dependsOn: [] }, { id: 'a', dependsOn: [] }])).toThrow(); }); });