import { describe, test, expect, vi, beforeEach } from 'vitest'; import { UserSettingsStore } from '../src/lib/stores/UserSettingsStore.svelte'; import { AppStateStore } from '../src/lib/stores/AppStateStore.svelte'; import type { Scenario } from '../types'; describe('Svelte 5 Reactive Stores', () => { const mockStorage: Record = {}; beforeEach(() => { for (const key in mockStorage) { delete mockStorage[key]; } vi.stubGlobal('localStorage', { getItem: (key: string) => mockStorage[key] || null, setItem: (key: string, val: string) => { mockStorage[key] = val; }, removeItem: (key: string) => { delete mockStorage[key]; }, clear: () => { for (const key in mockStorage) { delete mockStorage[key]; } } }); }); test('UserSettingsStore initializes with 3.8 models and correct defaults', () => { const store = new UserSettingsStore(); expect(store.voiceName).toBe('Zephyr'); expect(store.roleplayModel).toBe('google/gemini-3.8-flash'); expect(store.supervisorModel).toBe('google/gemini-3.8-flash'); expect(store.genderPreference).toBe('Neutral'); }); test('UserSettingsStore updates properties cleanly', () => { const store = new UserSettingsStore(); store.update({ voiceName: 'Kore', openRouterApiKey: 'sk-or-v1-svelte-test-key', roleplayModel: 'google/gemini-3.7-flash' }); expect(store.voiceName).toBe('Kore'); expect(store.openRouterApiKey).toBe('sk-or-v1-svelte-test-key'); expect(store.roleplayModel).toBe('google/gemini-3.7-flash'); const plain = store.toUserSettings(); expect(plain.voiceName).toBe('Kore'); expect(plain.openRouterApiKey).toBe('sk-or-v1-svelte-test-key'); }); test('UserSettingsStore recovers gracefully from corrupted localStorage', () => { mockStorage['cognitive-clarity-settings'] = 'MALFORMED_JSON_STRING'; const store = new UserSettingsStore(); expect(store.voiceName).toBe('Zephyr'); expect(store.roleplayModel).toBe('google/gemini-3.8-flash'); }); test('AppStateStore manages scenario catalog and filtering', () => { const app = new AppStateStore(); expect(app.allScenarios.length).toBe(14); app.selectedDifficulty = 'Beginner'; expect(app.filteredScenarios.every((s) => s.difficulty === 'Beginner')).toBe(true); app.selectedDifficulty = 'Advanced'; expect(app.filteredScenarios.every((s) => s.difficulty === 'Advanced')).toBe(true); app.selectedDifficulty = 'All'; expect(app.filteredScenarios.length).toBe(14); }); test('AppStateStore adds and removes custom scenarios', () => { const app = new AppStateStore(); const customScenario: Scenario = { id: 'custom-test-1', title: 'Custom Job Negotiation', description: 'Negotiating offer', category: 'Workplace', difficulty: 'Intermediate', systemInstruction: 'INTERNAL CBT LOGIC: Be firm.', initialMessage: 'Here is our final offer.', cbtFocus: ['All-or-Nothing Thinking'], characterName: 'Hiring Lead' }; app.addCustomScenario(customScenario); expect(app.customScenarios.length).toBe(1); expect(app.allScenarios.length).toBe(15); expect(app.allScenarios[0].id).toBe('custom-test-1'); app.deleteCustomScenario('custom-test-1'); expect(app.customScenarios.length).toBe(0); expect(app.allScenarios.length).toBe(14); }); test('AppStateStore selectScenario and exitSession lifecycle', async () => { vi.useFakeTimers(); const app = new AppStateStore(); const targetScenario = app.allScenarios[0]; app.selectScenario(targetScenario, 'voice'); expect(app.isPreparing).toBe(true); expect(app.activeScenario).toBe(null); vi.advanceTimersByTime(1400); expect(app.isPreparing).toBe(false); expect(app.activeScenario?.id).toBe(targetScenario.id); expect(app.sessionMode).toBe('voice'); app.exitSession(); expect(app.activeScenario).toBe(null); vi.useRealTimers(); }); });