Files
brain/tools/decision-graph.test.mjs

71 lines
3.4 KiB
JavaScript

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();
});
});