// Behavioural tests for custom-preset reordering. Covers the moveCustom
// store action (boundary handling, persistence to localStorage) and the
// side-effect that saveOverwrite now preserves position instead of
// bouncing the preset to the end of the list.
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { DEFAULT_PRESET } from '~/presets/builtIn';
import { loadCustomPresets } from '~/presets/storage';
import {
moveCustom,
presetState,
saveAsNew,
saveOverwrite,
selectPreset,
updateEdited,
} from '~/stores/presetStore';
beforeEach(() => {
const store = new Map<string, string>();
vi.stubGlobal('localStorage', {
getItem: (k: string) => store.get(k) ?? null,
setItem: (k: string, v: string) => {
store.set(k, v);
},
removeItem: (k: string) => {
store.delete(k);
},
clear: () => store.clear(),
key: () => null,
length: 0,
});
selectPreset(DEFAULT_PRESET);
});
afterEach(() => {
vi.unstubAllGlobals();
});
const createThree = () => {
// Helper: create A, B, C in that order. Returns their saved ids.
updateEdited('exposure', 1);
const a = saveAsNew('Alpha');
updateEdited('exposure', 2);
const b = saveAsNew('Bravo');
updateEdited('exposure', 3);
const c = saveAsNew('Charlie');
return { a, b, c };
};
describe('moveCustom', () => {
it('moves a preset up by one slot', () => {
const { a, b, c } = createThree();
moveCustom(b.id, 'up');
expect(presetState.customPresets.map((p) => p.id)).toEqual([b.id, a.id, c.id]);
});
it('moves a preset down by one slot', () => {
const { a, b, c } = createThree();
moveCustom(b.id, 'down');
expect(presetState.customPresets.map((p) => p.id)).toEqual([a.id, c.id, b.id]);
});
it('is a no-op at the top boundary (up)', () => {
const { a, b, c } = createThree();
moveCustom(a.id, 'up');
expect(presetState.customPresets.map((p) => p.id)).toEqual([a.id, b.id, c.id]);
});
it('is a no-op at the bottom boundary (down)', () => {
const { a, b, c } = createThree();
moveCustom(c.id, 'down');
expect(presetState.customPresets.map((p) => p.id)).toEqual([a.id, b.id, c.id]);
});
it('is a no-op for an unknown id', () => {
const { a, b, c } = createThree();
moveCustom('does-not-exist', 'up');
expect(presetState.customPresets.map((p) => p.id)).toEqual([a.id, b.id, c.id]);
});
it('persists the new order so it survives a reload', () => {
const { a, b, c } = createThree();
moveCustom(b.id, 'up');
const reloaded = loadCustomPresets();
expect(reloaded.map((p) => p.id)).toEqual([b.id, a.id, c.id]);
});
});
describe('saveOverwrite preserves list position', () => {
it('re-saving an existing preset does NOT bounce it to the end', () => {
const { a, b, c } = createThree();
// Select the middle preset and tweak it.
selectPreset(b);
updateEdited('decayRate', 0.5);
const updated = saveOverwrite();
expect(updated).not.toBeNull();
// Position is still index 1, not pushed to the end.
expect(presetState.customPresets.map((p) => p.id)).toEqual([a.id, b.id, c.id]);
expect(presetState.customPresets[1]?.params.decayRate).toBe(0.5);
});
});