import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { invokeIpc } from '@/lib/api-client'; import { useSettingsStore } from '@/stores/settings'; import { useUpdateStore } from '@/stores/update'; vi.mock('@/lib/api-client', () => ({ invokeIpc: vi.fn(), })); const invokeIpcMock = vi.mocked(invokeIpc); const ipcOn = vi.mocked(window.electron.ipcRenderer.on); describe('update store initialization', () => { beforeEach(() => { vi.useFakeTimers(); vi.clearAllMocks(); useSettingsStore.setState({ autoCheckUpdate: true, autoDownloadUpdate: false, }); useUpdateStore.setState({ status: 'idle', currentVersion: '0.0.0', updateInfo: null, progress: null, error: null, isInitialized: false, isInitializing: false, autoInstallCountdown: null, }); invokeIpcMock.mockImplementation(async (channel) => { if (channel === 'update:version') return '0.9.1'; if (channel === 'update:status') return { status: 'idle' }; if (channel === 'update:check') { return { success: true, status: { status: 'not-available' } }; } return undefined; }); }); afterEach(() => { vi.clearAllTimers(); vi.useRealTimers(); }); it('initializes concurrent callers once and schedules one delayed startup check', async () => { let resolveVersion!: (version: string) => void; const versionPromise = new Promise((resolve) => { resolveVersion = resolve; }); invokeIpcMock.mockImplementation(async (channel) => { if (channel === 'update:version') return versionPromise; if (channel === 'update:status') return { status: 'idle' }; if (channel === 'update:check') { return { success: true, status: { status: 'not-available' } }; } return undefined; }); const first = useUpdateStore.getState().init(); const second = useUpdateStore.getState().init(); resolveVersion('0.9.1'); await Promise.all([first, second]); expect(invokeIpcMock).toHaveBeenCalledWith('update:version'); expect(invokeIpcMock).toHaveBeenCalledWith('update:status'); expect(invokeIpcMock.mock.calls.filter(([channel]) => channel === 'update:version')).toHaveLength(1); expect(invokeIpcMock.mock.calls.filter(([channel]) => channel === 'update:status')).toHaveLength(1); expect(ipcOn).toHaveBeenCalledTimes(2); expect(vi.getTimerCount()).toBe(1); await vi.advanceTimersByTimeAsync(10_000); expect(invokeIpcMock).toHaveBeenCalledWith('update:check'); }); it('does not schedule a startup check when automatic checks are disabled', async () => { useSettingsStore.setState({ autoCheckUpdate: false }); await useUpdateStore.getState().init(); expect(ipcOn).toHaveBeenCalledTimes(2); expect(vi.getTimerCount()).toBe(0); await vi.advanceTimersByTimeAsync(10_000); expect(invokeIpcMock).not.toHaveBeenCalledWith('update:check'); }); it('ignores later initialization calls without adding listeners or timers', async () => { await useUpdateStore.getState().init(); expect(ipcOn).toHaveBeenCalledTimes(2); expect(vi.getTimerCount()).toBe(1); await useUpdateStore.getState().init(); expect(ipcOn).toHaveBeenCalledTimes(2); expect(vi.getTimerCount()).toBe(1); }); });