codev / desktop /src /stores /updateStore.test.ts
chenbhao's picture
feat: desktop
1f21206
Raw History Blame
7.85 kB
import { beforeEach, describe, expect, it, vi } from 'vitest'
const check = vi.fn()
const relaunch = vi.fn()
const invoke = vi.fn()
vi.mock('@tauri-apps/plugin-updater', () => ({
check,
}))
vi.mock('@tauri-apps/plugin-process', () => ({
relaunch,
}))
vi.mock('@tauri-apps/api/core', () => ({
invoke,
}))
describe('updateStore', () => {
beforeEach(() => {
check.mockReset()
relaunch.mockReset()
invoke.mockReset()
window.localStorage.clear()
Object.defineProperty(window, '__TAURI_INTERNALS__', {
configurable: true,
value: {},
})
})
it('stores available update metadata after a successful check', async () => {
const update = {
version: '0.2.0',
body: 'Bug fixes and performance improvements',
close: vi.fn().mockResolvedValue(undefined),
}
check.mockResolvedValue(update)
vi.resetModules()
const { useUpdateStore } = await import('./updateStore')
const result = await useUpdateStore.getState().checkForUpdates()
expect(result).toBe(update)
expect(useUpdateStore.getState().status).toBe('available')
expect(useUpdateStore.getState().availableVersion).toBe('0.2.0')
expect(useUpdateStore.getState().releaseNotes).toBe('Bug fixes and performance improvements')
expect(useUpdateStore.getState().shouldPrompt).toBe(true)
})
it('passes the configured manual update proxy to update checks', async () => {
const update = {
version: '0.2.0',
body: 'Bug fixes and performance improvements',
close: vi.fn().mockResolvedValue(undefined),
}
check.mockResolvedValue(update)
vi.resetModules()
const { useSettingsStore } = await import('./settingsStore')
useSettingsStore.setState({
updateProxy: {
mode: 'manual',
url: 'http://127.0.0.1:7890',
},
})
const { useUpdateStore } = await import('./updateStore')
await useUpdateStore.getState().checkForUpdates()
expect(check).toHaveBeenCalledWith({ proxy: 'http://127.0.0.1:7890' })
})
it('does not re-prompt for the same version after dismissing once', async () => {
check.mockResolvedValue({
version: '0.2.0',
body: 'Bug fixes and performance improvements',
close: vi.fn().mockResolvedValue(undefined),
})
vi.resetModules()
const { useUpdateStore } = await import('./updateStore')
await useUpdateStore.getState().checkForUpdates()
useUpdateStore.getState().dismissPrompt()
expect(useUpdateStore.getState().shouldPrompt).toBe(false)
expect(window.localStorage.getItem('cc-haha-dismissed-update-version')).toBe('0.2.0')
await useUpdateStore.getState().checkForUpdates({ silent: true })
expect(useUpdateStore.getState().status).toBe('available')
expect(useUpdateStore.getState().availableVersion).toBe('0.2.0')
expect(useUpdateStore.getState().shouldPrompt).toBe(false)
})
it('prompts again when a newer version is available after dismissing an older one', async () => {
check
.mockResolvedValueOnce({
version: '0.2.0',
body: 'Bug fixes and performance improvements',
close: vi.fn().mockResolvedValue(undefined),
})
.mockResolvedValueOnce({
version: '0.3.0',
body: 'New release',
close: vi.fn().mockResolvedValue(undefined),
})
vi.resetModules()
const { useUpdateStore } = await import('./updateStore')
await useUpdateStore.getState().checkForUpdates()
useUpdateStore.getState().dismissPrompt()
await useUpdateStore.getState().checkForUpdates({ silent: true })
expect(useUpdateStore.getState().availableVersion).toBe('0.3.0')
expect(useUpdateStore.getState().shouldPrompt).toBe(true)
})
it('downloads, stops sidecars, installs, and relaunches', async () => {
const download = vi.fn(async (onEvent?: (event: unknown) => void) => {
onEvent?.({ event: 'Started', data: { contentLength: 200 } })
onEvent?.({ event: 'Progress', data: { chunkLength: 50 } })
onEvent?.({ event: 'Progress', data: { chunkLength: 150 } })
onEvent?.({ event: 'Finished' })
})
const install = vi.fn().mockResolvedValue(undefined)
check.mockResolvedValue({
version: '0.2.0',
body: 'Notes',
download,
install,
close: vi.fn().mockResolvedValue(undefined),
})
invoke.mockResolvedValue(undefined)
relaunch.mockResolvedValue(undefined)
vi.resetModules()
const { useUpdateStore } = await import('./updateStore')
await useUpdateStore.getState().checkForUpdates()
await useUpdateStore.getState().installUpdate()
expect(download).toHaveBeenCalledTimes(1)
expect(invoke).toHaveBeenCalledWith('prepare_for_update_install')
expect(install).toHaveBeenCalledTimes(1)
const prepareCallOrder = invoke.mock.invocationCallOrder[0]
const installCallOrder = install.mock.invocationCallOrder[0]
expect(prepareCallOrder).toBeDefined()
expect(installCallOrder).toBeDefined()
expect(prepareCallOrder!).toBeLessThan(installCallOrder!)
expect(useUpdateStore.getState().progressPercent).toBe(100)
expect(useUpdateStore.getState().status).toBe('restarting')
expect(relaunch).toHaveBeenCalledTimes(1)
})
it('refreshes the pending update when the proxy changes before install', async () => {
const staleClose = vi.fn().mockResolvedValue(undefined)
const freshDownload = vi.fn(async (onEvent?: (event: unknown) => void) => {
onEvent?.({ event: 'Started', data: { contentLength: 100 } })
onEvent?.({ event: 'Progress', data: { chunkLength: 100 } })
onEvent?.({ event: 'Finished' })
})
const freshInstall = vi.fn().mockResolvedValue(undefined)
check
.mockResolvedValueOnce({
version: '0.2.0',
body: 'Notes',
close: staleClose,
})
.mockResolvedValueOnce({
version: '0.2.0',
body: 'Notes',
download: freshDownload,
install: freshInstall,
close: vi.fn().mockResolvedValue(undefined),
})
invoke.mockResolvedValue(undefined)
relaunch.mockResolvedValue(undefined)
vi.resetModules()
const { useSettingsStore } = await import('./settingsStore')
const { useUpdateStore } = await import('./updateStore')
await useUpdateStore.getState().checkForUpdates()
useSettingsStore.setState({
updateProxy: {
mode: 'manual',
url: 'http://127.0.0.1:7890',
},
})
await useUpdateStore.getState().installUpdate()
expect(staleClose).toHaveBeenCalledTimes(1)
expect(check).toHaveBeenNthCalledWith(2, { proxy: 'http://127.0.0.1:7890' })
expect(freshDownload).toHaveBeenCalledTimes(1)
expect(freshInstall).toHaveBeenCalledTimes(1)
})
it('clears the native exit guard when install fails after sidecars stop', async () => {
const download = vi.fn(async (onEvent?: (event: unknown) => void) => {
onEvent?.({ event: 'Started', data: { contentLength: 100 } })
onEvent?.({ event: 'Finished' })
})
const install = vi.fn().mockRejectedValue(new Error('installer failed'))
check.mockResolvedValue({
version: '0.2.0',
body: 'Notes',
download,
install,
close: vi.fn().mockResolvedValue(undefined),
})
invoke.mockResolvedValue(undefined)
vi.resetModules()
const { useUpdateStore } = await import('./updateStore')
await useUpdateStore.getState().checkForUpdates()
await useUpdateStore.getState().installUpdate()
expect(invoke).toHaveBeenNthCalledWith(1, 'prepare_for_update_install')
expect(invoke).toHaveBeenNthCalledWith(2, 'cancel_update_install')
expect(useUpdateStore.getState().status).toBe('available')
expect(useUpdateStore.getState().error).toContain('installer failed')
expect(useUpdateStore.getState().shouldPrompt).toBe(true)
})
})