mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-17 13:13:35 +08:00
PR #428 added a General Settings zoom slider after the desktop shortcut work already introduced a native-first app zoom controller. Keeping both paths would create double scaling and stale UI state, so the slider now routes through the existing controller and store state while the app shell no longer applies a second CSS zoom. Constraint: UI zoom is device-local display state and should not be written into shared user settings. Rejected: Keep cc-haha-ui-zoom plus AppShell style.zoom | it conflicts with shortcut zoom and multiplies visual scale. Rejected: Persist zoom through /api/settings/user | it would sync display-specific state across machines. Confidence: high Scope-risk: moderate Directive: Keep app zoom behind desktop/src/lib/appZoom.ts; do not add another storage key or DOM zoom application point without migration and shortcut sync tests. Tested: cd desktop && bun run test -- --run src/lib/appZoom.test.ts src/hooks/useKeyboardShortcuts.test.tsx src/stores/settingsStore.test.ts src/__tests__/generalSettings.test.tsx src/lib/persistenceMigrations.test.ts src/lib/doctorRepair.test.ts Tested: bun run check:desktop Not-tested: Real Windows/Linux desktop runtime smoke.
138 lines
5.0 KiB
TypeScript
138 lines
5.0 KiB
TypeScript
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,
|
|
]))
|
|
})
|
|
})
|