import { describe, it, expect } from 'vitest' import { applyTransforms, upsertTransform, removeTransform } from './transforms' import type { Transform } from './types' const t = ( over: Partial & { id: string; fn: Transform['fn']; order: number }, ): Transform => ({ type: 'data', enabled: true, ...over, }) describe('applyTransforms', () => { it('returns input when transforms array is empty', () => { expect(applyTransforms({ a: 1 }, [])).toEqual({ a: 1 }) }) it('applies a single enabled transform', () => { const out = applyTransforms(2, [ t({ id: 'x2', order: 1, fn: (n) => (n as number) * 2 }), ]) expect(out).toBe(4) }) it('chains transforms in order', () => { const xs = [ t({ id: 'a', order: 1, fn: (n) => (n as number) + 1 }), t({ id: 'b', order: 2, fn: (n) => (n as number) * 10 }), ] expect(applyTransforms(1, xs)).toBe(20) }) it('skips disabled transforms', () => { const xs = [ t({ id: 'a', order: 1, fn: (n) => (n as number) + 1 }), t({ id: 'b', order: 2, fn: (n) => (n as number) * 10, enabled: false }), ] expect(applyTransforms(1, xs)).toBe(2) }) it('respects disables map: enabled transform disables another', () => { const xs = [ t({ id: 'a', order: 1, fn: (n) => (n as number) + 1, disables: ['b'] }), t({ id: 'b', order: 2, fn: (n) => (n as number) * 10 }), ] expect(applyTransforms(1, xs)).toBe(2) }) it('does not apply disables when the disabling transform itself is disabled', () => { const xs = [ t({ id: 'a', order: 1, fn: (n) => (n as number) + 1, disables: ['b'], enabled: false, }), t({ id: 'b', order: 2, fn: (n) => (n as number) * 10 }), ] expect(applyTransforms(1, xs)).toBe(10) }) it('captures the first error and continues with remaining transforms', () => { const errors: { id: string; msg: string }[] = [] const xs = [ t({ id: 'bad', order: 1, fn: () => { throw new Error('boom') }, }), t({ id: 'good', order: 2, fn: (n) => (n as number) * 2 }), ] const out = applyTransforms(3, xs, (id, err) => errors.push({ id, msg: err.message }), ) expect(out).toBe(6) expect(errors).toEqual([{ id: 'bad', msg: 'boom' }]) }) it('wraps non-Error throws as Error', () => { const errors: Error[] = [] applyTransforms( 1, [ t({ id: 'x', order: 1, fn: () => { // eslint-disable-next-line @typescript-eslint/only-throw-error -- exercising non-Error wrap throw 'string-throw' }, }), ], (_id, err) => errors.push(err), ) expect(errors[0]).toBeInstanceOf(Error) expect(errors[0]?.message).toBe('string-throw') }) }) describe('upsertTransform', () => { it('inserts new transforms in ascending order', () => { const a = t({ id: 'a', order: 10, fn: (n) => n }) const b = t({ id: 'b', order: 5, fn: (n) => n }) const c = t({ id: 'c', order: 20, fn: (n) => n }) const out = upsertTransform(upsertTransform(upsertTransform([], a), b), c) expect(out.map((x) => x.id)).toEqual(['b', 'a', 'c']) }) it('returns the same array reference when transform is identical', () => { const fn = (n: unknown) => n const a = t({ id: 'a', order: 1, fn }) const arr = upsertTransform([], a) const next = upsertTransform(arr, t({ id: 'a', order: 1, fn })) expect(next).toBe(arr) }) it('replaces an existing transform when fn changes', () => { const a1 = t({ id: 'a', order: 1, fn: (n) => n }) const a2 = t({ id: 'a', order: 1, fn: (n) => (n as number) + 1 }) const arr = upsertTransform([], a1) const next = upsertTransform(arr, a2) expect(next).not.toBe(arr) expect(next.find((x) => x.id === 'a')?.fn).toBe(a2.fn) }) it('reorders when order changes', () => { const a = t({ id: 'a', order: 10, fn: (n) => n }) const b = t({ id: 'b', order: 20, fn: (n) => n }) const arr = [a, b] const next = upsertTransform(arr, t({ id: 'a', order: 30, fn: a.fn })) expect(next.map((x) => x.id)).toEqual(['b', 'a']) }) it('treats different disables as a structural change', () => { const a1 = t({ id: 'a', order: 1, fn: (n) => n, disables: ['b'] }) const a2 = t({ id: 'a', order: 1, fn: a1.fn, disables: ['c'] }) const arr = upsertTransform([], a1) const next = upsertTransform(arr, a2) expect(next).not.toBe(arr) expect(next.find((x) => x.id === 'a')?.disables).toEqual(['c']) }) }) describe('removeTransform', () => { it('removes a transform by id', () => { const arr = [ t({ id: 'a', order: 1, fn: (n) => n }), t({ id: 'b', order: 2, fn: (n) => n }), ] const next = removeTransform(arr, 'a') expect(next.map((x) => x.id)).toEqual(['b']) }) it('returns the same array reference when id is absent', () => { const arr = [t({ id: 'a', order: 1, fn: (n) => n })] const next = removeTransform(arr, 'missing') expect(next).toBe(arr) }) })