Zum Inhalt

tests/stores/reorderCustom.test.ts

// 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);
  });
});