72 lines
3.3 KiB
TypeScript
72 lines
3.3 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
function deferred<T>() {
|
|
let resolve!: (value: T) => void
|
|
const promise = new Promise<T>((done) => {
|
|
resolve = done
|
|
})
|
|
return { promise, resolve }
|
|
}
|
|
|
|
let store: typeof import('../../src/stores/settingsStore').useSettingsStore
|
|
beforeEach(async () => {
|
|
vi.resetModules()
|
|
vi.mocked(window.opsxApi.settingsGet).mockReset().mockResolvedValue({ success: true })
|
|
vi.mocked(window.opsxApi.settingsSet).mockReset().mockResolvedValue({ success: true })
|
|
store = (await import('../../src/stores/settingsStore')).useSettingsStore
|
|
})
|
|
|
|
describe('settings persistence', () => {
|
|
it('serializes writes while keeping later edits visible', async () => {
|
|
const first = deferred<{ success: boolean }>()
|
|
vi.mocked(window.opsxApi.settingsSet).mockImplementationOnce(() => first.promise)
|
|
const saveTheme = store.getState().setSetting('theme', 'light')
|
|
const saveFont = store.getState().setSetting('fontSize', 20)
|
|
expect(store.getState().settings).toMatchObject({ theme: 'light', fontSize: 20 })
|
|
await Promise.resolve()
|
|
expect(window.opsxApi.settingsSet).toHaveBeenCalledTimes(1)
|
|
first.resolve({ success: true })
|
|
await Promise.all([saveTheme, saveFont])
|
|
expect(window.opsxApi.settingsSet).toHaveBeenLastCalledWith(
|
|
'app-settings',
|
|
expect.objectContaining({ theme: 'light', fontSize: 20 })
|
|
)
|
|
})
|
|
|
|
it('rolls back a rejected envelope without persisting the failed patch in the next write', async () => {
|
|
vi.mocked(window.opsxApi.settingsSet).mockResolvedValueOnce({ success: false, error: 'disk full' })
|
|
await Promise.all([store.getState().setSetting('theme', 'light'), store.getState().setSetting('fontSize', 20)])
|
|
expect(store.getState().settings).toMatchObject({ theme: 'dark', fontSize: 20 })
|
|
expect(window.opsxApi.settingsSet).toHaveBeenLastCalledWith(
|
|
'app-settings',
|
|
expect.objectContaining({ theme: 'dark', fontSize: 20 })
|
|
)
|
|
const { useToastStore } = await import('../../src/stores/toastStore')
|
|
expect(useToastStore.getState().toasts.at(-1)).toMatchObject({
|
|
type: 'error',
|
|
message: expect.stringContaining('disk full'),
|
|
})
|
|
})
|
|
|
|
it('continues saving after a thrown IPC failure, including edits to the same field', async () => {
|
|
vi.mocked(window.opsxApi.settingsSet).mockRejectedValueOnce(new Error('IPC unavailable'))
|
|
await Promise.all([store.getState().setSetting('fontSize', 18), store.getState().setSetting('fontSize', 22)])
|
|
expect(store.getState().settings.fontSize).toBe(22)
|
|
expect(window.opsxApi.settingsSet).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('merges edits made while initial settings are loading into the persisted snapshot', async () => {
|
|
const read = deferred<{ success: boolean; value: unknown }>()
|
|
vi.mocked(window.opsxApi.settingsGet).mockReturnValueOnce(read.promise)
|
|
const load = store.getState().loadSettings()
|
|
const save = store.getState().setSetting('fontSize', 22)
|
|
read.resolve({ success: true, value: { theme: 'light', agentSafetyMode: 'relaxed' } })
|
|
await Promise.all([load, save])
|
|
expect(store.getState().settings).toMatchObject({ theme: 'light', fontSize: 22 })
|
|
expect(window.opsxApi.settingsSet).toHaveBeenLastCalledWith(
|
|
'app-settings',
|
|
expect.objectContaining({ theme: 'light', fontSize: 22 })
|
|
)
|
|
})
|
|
})
|