- PCHIP для предпросмотра и серверного рендера - пресеты всех каналов в профиле, импорт и экспорт - мерж настроек auth без потери разделов - сохранение вкладки и правок текущего фото при переключении
167 lines
5.5 KiB
TypeScript
167 lines
5.5 KiB
TypeScript
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);
|
|
});
|
|
});
|