import { beforeEach, describe, expect, test } from 'vitest' import { CURRENT_DESKTOP_PERSISTENCE_SCHEMA_VERSION, DESKTOP_PERSISTENCE_VERSION_KEY, runDesktopPersistenceMigrations, } from './persistenceMigrations' describe('desktop persistence migrations', () => { beforeEach(() => { window.localStorage.clear() }) test('migrates legacy open-tab arrays into the current tab persistence shape', () => { window.localStorage.setItem('cc-haha-open-tabs', JSON.stringify([ { sessionId: 'session-1', title: 'Old tab' }, { sessionId: '__terminal__legacy', title: 'Terminal 1', type: 'terminal' }, { sessionId: 123, title: 'bad' }, ])) const report = runDesktopPersistenceMigrations() expect(report.migratedKeys).toContain('cc-haha-open-tabs') expect(JSON.parse(window.localStorage.getItem('cc-haha-open-tabs') || '{}')).toEqual({ openTabs: [{ sessionId: 'session-1', title: 'Old tab', type: 'session' }], activeTabId: 'session-1', }) expect(window.localStorage.getItem(DESKTOP_PERSISTENCE_VERSION_KEY)).toBe(String(CURRENT_DESKTOP_PERSISTENCE_SCHEMA_VERSION)) }) test('filters stale session runtime selections without clearing unrelated keys', () => { window.localStorage.setItem('unrelated-user-key', 'keep') window.localStorage.setItem('cc-haha-session-runtime', JSON.stringify({ good: { providerId: null, modelId: 'claude-sonnet' }, alsoGood: { providerId: 'provider-1', modelId: 'gpt-5.4' }, bad: { providerId: 'provider-2' }, })) runDesktopPersistenceMigrations() expect(JSON.parse(window.localStorage.getItem('cc-haha-session-runtime') || '{}')).toEqual({ alsoGood: { providerId: 'provider-1', modelId: 'gpt-5.4' }, good: { providerId: null, modelId: 'claude-sonnet' }, }) expect(window.localStorage.getItem('unrelated-user-key')).toBe('keep') }) test('removes malformed known keys without throwing during startup', () => { window.localStorage.setItem('cc-haha-open-tabs', '{"openTabs":') window.localStorage.setItem('cc-haha-theme', 'sepia') const report = runDesktopPersistenceMigrations() expect(report.migratedKeys).toContain('cc-haha-open-tabs') expect(report.migratedKeys).toContain('cc-haha-theme') expect(window.localStorage.getItem('cc-haha-open-tabs')).toBeNull() expect(window.localStorage.getItem('cc-haha-theme')).toBeNull() }) test('preserves the pure white theme as a valid persisted theme', () => { window.localStorage.setItem('cc-haha-theme', 'white') const report = runDesktopPersistenceMigrations() expect(report.migratedKeys).not.toContain('cc-haha-theme') expect(window.localStorage.getItem('cc-haha-theme')).toBe('white') }) test('preserves valid app zoom and removes invalid app zoom values', () => { window.localStorage.setItem('cc-haha-app-zoom', '1.2') const validReport = runDesktopPersistenceMigrations() expect(validReport.migratedKeys).not.toContain('cc-haha-app-zoom') expect(window.localStorage.getItem('cc-haha-app-zoom')).toBe('1.2') window.localStorage.setItem('cc-haha-app-zoom', '4') const invalidReport = runDesktopPersistenceMigrations() expect(invalidReport.migratedKeys).toContain('cc-haha-app-zoom') expect(window.localStorage.getItem('cc-haha-app-zoom')).toBeNull() }) test('migrates the legacy UI zoom key into app zoom storage', () => { window.localStorage.setItem('cc-haha-ui-zoom', '1.25') const report = runDesktopPersistenceMigrations() expect(report.migratedKeys).toEqual(expect.arrayContaining([ 'cc-haha-app-zoom', 'cc-haha-ui-zoom', ])) expect(window.localStorage.getItem('cc-haha-app-zoom')).toBe('1.25') expect(window.localStorage.getItem('cc-haha-ui-zoom')).toBeNull() }) test('does not throw if schema version persistence is blocked', () => { const storage = { getItem: window.localStorage.getItem.bind(window.localStorage), removeItem: window.localStorage.removeItem.bind(window.localStorage), setItem: (key: string, value: string) => { if (key === DESKTOP_PERSISTENCE_VERSION_KEY) { throw new Error('storage blocked') } window.localStorage.setItem(key, value) }, } expect(() => runDesktopPersistenceMigrations(storage)).not.toThrow() expect(runDesktopPersistenceMigrations(storage).migratedKeys).toContain(DESKTOP_PERSISTENCE_VERSION_KEY) }) test('does not throw if storage reads and writes are blocked', () => { const storage = { getItem: () => { throw new Error('storage unavailable') }, removeItem: () => { throw new Error('storage unavailable') }, setItem: () => { throw new Error('storage unavailable') }, } const report = runDesktopPersistenceMigrations(storage) expect(report.migratedKeys).toEqual(expect.arrayContaining([ 'cc-haha-open-tabs', 'cc-haha-session-runtime', 'cc-haha-theme', 'cc-haha-locale', 'cc-haha-app-zoom', DESKTOP_PERSISTENCE_VERSION_KEY, ])) }) })