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