Files
makelore/tests/unit/update-store.test.ts
2026-07-29 17:22:35 +08:00

100 lines
3.3 KiB
TypeScript

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<string>((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);
});
});