Zum Inhalt

tests/stores/saveAsNew.test.ts

// Regression test for the "Save preset does nothing" bug. After the engine
// init-race fix made presetState.edited a live Solid store proxy, the
// `structuredClone(presetState.edited)` in saveAsNew was throwing
// DataCloneError silently — the dialog's submit() swallowed it and never
// reached closePanel().

import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { DEFAULT_PRESET } from '~/presets/builtIn';
import {
  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();
});

describe('saveAsNew with a live reactive edited proxy', () => {
  it('clones the reactive edited proxy without throwing', () => {
    updateEdited('decayRate', 0.88);
    expect(() => saveAsNew('Reactive Save Test')).not.toThrow();
  });

  it('captures the latest edited values into the saved preset', () => {
    updateEdited('decayRate', 0.91);
    updateEdited('exposure', 4.2);
    const saved = saveAsNew('Edits Captured');
    expect(saved.params.decayRate).toBe(0.91);
    expect(saved.params.exposure).toBe(4.2);
    expect(saved.builtIn).toBe(false);
    expect(saved.id).toMatch(/^custom-edits-captured-/);
  });

  it('persists to localStorage and reloads via customPresets', () => {
    updateEdited('contrast', 1.3);
    saveAsNew('Persistence Test');
    expect(presetState.customPresets.find((p) => p.name === 'Persistence Test')).toBeDefined();
    expect(presetState.current.name).toBe('Persistence Test');
  });
});

describe('saveOverwrite', () => {
  it('updates the existing custom preset in place', () => {
    updateEdited('exposure', 3);
    const created = saveAsNew('To Be Updated');
    const originalId = created.id;

    updateEdited('exposure', 6);
    const updated = saveOverwrite();
    expect(updated).not.toBeNull();
    expect(updated?.id).toBe(originalId);
    expect(updated?.params.exposure).toBe(6);
    expect(presetState.customPresets.filter((p) => p.id === originalId)).toHaveLength(1);
    expect(presetState.isDirty).toBe(false);
  });

  it('returns null and does nothing for a built-in preset', () => {
    updateEdited('exposure', 7);
    const result = saveOverwrite();
    expect(result).toBeNull();
    expect(presetState.current.builtIn).toBe(true);
    expect(presetState.isDirty).toBe(true);
  });
});