import { describe, expect, it } from 'vitest'; import { assertNever, groupBy, isDefined, sortedEntries, topoOrder } from '../src/index.ts'; describe('@utils', () => { it('isDefined 滤掉 null 与 undefined', () => { expect([1, null, 2, undefined].filter(isDefined)).toEqual([1, 2]); }); it('groupBy 按键分组', () => { const rows = [ { pn: 'A', type: 'A' as const }, { pn: 'B', type: 'B' as const }, { pn: 'C', type: 'A' as const }, ]; expect(groupBy(rows, (r) => r.type)).toEqual({ A: [rows[0], rows[2]], B: [rows[1]], }); }); it('sortedEntries 稳定排序', () => { expect(sortedEntries({ b: 2, a: 1 })).toEqual([ ['a', 1], ['b', 2], ]); }); it('assertNever 抛错', () => { expect(() => assertNever('x' as never)).toThrow('未覆盖的分支'); }); }); describe('topoOrder', () => { it('无依赖:随便什么序都放行,谁都不欠谁', () => { const result = topoOrder(['b', 'a', 'c'], () => []); expect(result.ok).toBe(true); expect(result.ok && [...result.order].sort()).toEqual(['a', 'b', 'c']); }); /* 入参故意按「被依赖的排在后面」给 —— 证明排序看的是依赖关系,不是输入数组的次序 */ it('链式依赖:按「被依赖的排前面」重排,不管输入次序', () => { const deps: Record = { c: ['b'], b: ['a'], a: [] }; const result = topoOrder(['c', 'b', 'a'], (n) => deps[n] ?? []); expect(result).toEqual({ ok: true, order: ['a', 'b', 'c'] }); }); it('菱形依赖:d 依赖 b 和 c,b 与 c 都依赖 a —— a 最先,d 最后,b/c 顺序不拘', () => { const deps: Record = { a: [], b: ['a'], c: ['a'], d: ['b', 'c'] }; const result = topoOrder(['a', 'b', 'c', 'd'], (n) => deps[n] ?? []); expect(result.ok).toBe(true); const order = result.ok ? result.order : []; expect(order.indexOf('a')).toBeLessThan(order.indexOf('b')); expect(order.indexOf('a')).toBeLessThan(order.indexOf('c')); expect(order.indexOf('b')).toBeLessThan(order.indexOf('d')); expect(order.indexOf('c')).toBeLessThan(order.indexOf('d')); }); it('真正的环(a 依赖 b、b 依赖 a):当场报出来,不默默兜底', () => { const deps: Record = { a: ['b'], b: ['a'] }; const result = topoOrder(['a', 'b'], (n) => deps[n] ?? []); expect(result.ok).toBe(false); expect(!result.ok && [...result.cycle].sort()).toEqual(['a', 'b']); }); it('自依赖(a 依赖自己)同样算环', () => { const result = topoOrder(['a'], () => ['a']); expect(result).toEqual({ ok: false, cycle: ['a'] }); }); it('三元环:a 依赖 c、c 依赖 b、b 依赖 a,三个一起报出来', () => { const deps: Record = { a: ['c'], b: ['a'], c: ['b'] }; const result = topoOrder(['a', 'b', 'c'], (n) => deps[n] ?? []); expect(result.ok).toBe(false); expect(!result.ok && [...result.cycle].sort()).toEqual(['a', 'b', 'c']); }); it('环之外互不相干的节点不受连累 —— 只有环上那两个排不出来', () => { const deps: Record = { y: [], a: ['b'], b: ['a'] }; const result = topoOrder(['y', 'a', 'b'], (n) => deps[n] ?? []); expect(result.ok).toBe(false); expect(!result.ok && [...result.cycle].sort()).toEqual(['a', 'b']); }); it('依赖了一个不在 nodes 里的名字:当没有这条依赖,不误报成环', () => { const result = topoOrder(['a'], () => ['not-a-node']); expect(result).toEqual({ ok: true, order: ['a'] }); }); });