import { describe, expect, it } from 'vitest'; import { BUILTIN_CURVE_PRESETS, MAX_CURVE_PRESETS, exportCurvePresetFile, findActiveCurvePreset, normalizeCurvePreset, normalizeCurvePresets, parseCurvePresetFile, presetPointsEqual, upsertCurvePresets } from './curve-presets'; import type { CurvePreset } from './types'; const linear = () => [ { x: 0, y: 0 }, { x: 1, y: 1 } ]; const medium = BUILTIN_CURVE_PRESETS.find((preset) => preset.name === 'Средний контраст')!; const custom: CurvePreset = { name: 'Мой', channels: { rgb: [ { x: 0, y: 0 }, { x: 0.5, y: 0.6 }, { x: 1, y: 1 } ], red: linear(), green: linear(), blue: linear() } }; describe('пресеты кривой', () => { it('содержит встроенные пресеты со всеми четырьмя каналами', () => { expect(BUILTIN_CURVE_PRESETS.map((preset) => preset.name)).toEqual([ 'Линейный', 'Средний контраст', 'Сильный контраст' ]); for (const preset of BUILTIN_CURVE_PRESETS) { expect(Object.keys(preset.channels).sort()).toEqual(['blue', 'green', 'red', 'rgb']); for (const channel of ['rgb', 'red', 'green', 'blue'] as const) { expect(preset.channels[channel].length).toBeGreaterThanOrEqual(2); } } // Контраст задаётся только мастер-кривой, остальные каналы линейные. expect(medium.channels.rgb).not.toEqual(linear()); expect(medium.channels.red).toEqual(linear()); }); it('валидирует и нормализует пресет со всеми каналами', () => { expect(normalizeCurvePreset(custom)).toEqual(custom); expect(normalizeCurvePreset({ ...custom, name: ' Мой ' })).toEqual(custom); expect(normalizeCurvePreset({ name: 'x', channels: { rgb: linear() } })).toBeNull(); expect( normalizeCurvePreset({ ...custom, channels: { ...custom.channels, green: [{ x: 0, y: 0 }] } }) ).toBeNull(); expect( normalizeCurvePreset({ ...custom, channels: { ...custom.channels, blue: [{ x: 0, y: 0 }, { x: 1.2, y: 1 }] } }) ).toBeNull(); expect(normalizeCurvePreset({ ...custom, name: 'a'.repeat(65) })).toBeNull(); }); it('конвертирует старый формат одного канала', () => { const legacy = normalizeCurvePreset({ name: 'Старый', channel: 'red', points: [ { x: 0, y: 0 }, { x: 1, y: 0.4 } ] }); expect(legacy?.channels.rgb).toEqual(linear()); expect(legacy?.channels.red).toEqual([ { x: 0, y: 0 }, { x: 1, y: 0.4 } ]); expect(legacy?.channels.green).toEqual(linear()); expect(legacy?.channels.blue).toEqual(linear()); }); it('сортирует точки и отбрасывает дубликаты', () => { const preset = normalizeCurvePreset({ ...custom, channels: { ...custom.channels, rgb: [ { x: 1, y: 1 }, { x: 0, y: 0 }, { x: 0.5, y: 0.6 } ] } }); expect(preset?.channels.rgb.map((point) => point.x)).toEqual([0, 0.5, 1]); }); it('нормализует список из профиля и убирает повторы имён', () => { expect(normalizeCurvePresets([custom, custom, { name: 'bad' }])).toEqual([custom]); expect(normalizeCurvePresets('nope')).toEqual([]); }); it('разбирает и сериализует файл экспорта', () => { const text = exportCurvePresetFile([custom]); expect(parseCurvePresetFile(text)).toEqual([custom]); expect(() => parseCurvePresetFile('not json')).toThrow(); expect(() => parseCurvePresetFile(JSON.stringify({ version: 2, presets: [] }))).toThrow(); expect(() => parseCurvePresetFile(JSON.stringify({ version: 1, presets: [{ name: 'x' }] }))).toThrow(); expect(() => parseCurvePresetFile(JSON.stringify({ version: 1, presets: [custom, custom] })) ).toThrow(); }); it('добавляет и перезаписывает пресеты по имени', () => { const changed: CurvePreset = { name: 'Мой', channels: { ...custom.channels, blue: [{ x: 0, y: 0 }, { x: 1, y: 0.5 }] } }; const next = upsertCurvePresets([custom], [changed]); expect(next).toHaveLength(1); expect(next[0].channels.blue).toEqual(changed.channels.blue); // Копии не должны делить массивы с исходным пресетом. expect(next[0].channels.red).not.toBe(custom.channels.red); const other: CurvePreset = { ...custom, name: 'Другой' }; expect(upsertCurvePresets([custom], [other]).map((preset) => preset.name)).toEqual([ 'Мой', 'Другой' ]); expect(() => upsertCurvePresets( Array.from({ length: MAX_CURVE_PRESETS }, (_, i) => ({ ...custom, name: `p${i}` })), [other] ) ).toThrow(); }); it('находит активный пресет по кривым всех каналов', () => { expect(findActiveCurvePreset(medium.channels, [])?.name).toBe('Средний контраст'); expect(findActiveCurvePreset(custom.channels, [custom])?.name).toBe('Мой'); expect( findActiveCurvePreset( { ...custom.channels, blue: [{ x: 0, y: 0 }, { x: 1, y: 0.5 }] }, [custom] ) ).toBeUndefined(); expect( findActiveCurvePreset( { rgb: linear(), red: linear(), green: linear(), blue: linear() }, [] )?.name ).toBe('Линейный'); }); it('сравнивает точки с допуском', () => { expect(presetPointsEqual([{ x: 0, y: 0 }], [{ x: 1e-9, y: -1e-9 }])).toBe(true); expect(presetPointsEqual([{ x: 0, y: 0 }], [{ x: 0.1, y: 0 }])).toBe(false); }); });