Files
MyGoNavi/frontend/src/hooks/useAppUpdateManager.test.tsx
Syngnat 2f39767211 🐛 fix(update): 修复下载进度标题显示内部键并消除通道切换竞态
- 下载进度状态拆分 key 与 version 字段,弹窗标题恢复显示纯版本号
- SetUpdateChannel 将检查、持久化与缓存清理并入同一临界区,消除下载窗口期竞态
- 通道配置文件改为临时文件加重命名的原子写入
- 移除 useAppUpdateManager 未使用的 isMacRuntime 参数
2026-07-05 17:13:28 +08:00

187 lines
5.2 KiB
TypeScript

import React from 'react';
import { act, create, type ReactTestRenderer } from 'react-test-renderer';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useAppUpdateManager } from './useAppUpdateManager';
const runtimeApi = vi.hoisted(() => ({
EventsOn: vi.fn(() => vi.fn()),
}));
const messageApi = vi.hoisted(() => ({
info: vi.fn(),
success: vi.fn(),
error: vi.fn(),
}));
vi.mock('../../wailsjs/runtime', () => runtimeApi);
vi.mock('antd', () => ({
message: messageApi,
}));
type BackendAppMock = {
CheckForUpdates: ReturnType<typeof vi.fn>;
CheckForUpdatesSilently: ReturnType<typeof vi.fn>;
DownloadUpdate: ReturnType<typeof vi.fn>;
GetUpdateChannel: ReturnType<typeof vi.fn>;
InstallUpdateAndRestart: ReturnType<typeof vi.fn>;
OpenDownloadedUpdateDirectory: ReturnType<typeof vi.fn>;
SetUpdateChannel: ReturnType<typeof vi.fn>;
GetAppInfo: ReturnType<typeof vi.fn>;
};
const createBackendAppMock = (): BackendAppMock => ({
CheckForUpdates: vi.fn(),
CheckForUpdatesSilently: vi.fn(),
DownloadUpdate: vi.fn(),
GetUpdateChannel: vi.fn(async () => ({ success: true, data: { channel: 'latest' } })),
InstallUpdateAndRestart: vi.fn(),
OpenDownloadedUpdateDirectory: vi.fn(),
SetUpdateChannel: vi.fn(async (channel: string) => ({ success: true, data: { channel } })),
GetAppInfo: vi.fn(async () => ({ success: true, data: { version: '0.8.1', author: 'Syngnat' } })),
});
describe('useAppUpdateManager', () => {
let backendApp: BackendAppMock;
let hook: ReturnType<typeof useAppUpdateManager> | null = null;
let renderer: ReactTestRenderer | null = null;
const t = (key: string, params?: Record<string, any>) => {
if (params?.version) return `${key}:${params.version}`;
if (params?.path) return `${key}:${params.path}`;
if (params?.error) return `${key}:${params.error}`;
return key;
};
const renderHook = () => {
const Harness = () => {
hook = useAppUpdateManager({
runtimeBuildType: 'release',
t,
});
return null;
};
act(() => {
renderer = create(<Harness />);
});
};
beforeEach(() => {
backendApp = createBackendAppMock();
hook = null;
renderer = null;
runtimeApi.EventsOn.mockClear();
messageApi.info.mockReset();
messageApi.success.mockReset();
messageApi.error.mockReset();
vi.useFakeTimers();
vi.stubGlobal('window', {
setTimeout,
clearTimeout,
setInterval,
clearInterval,
go: {
app: {
App: backendApp,
},
},
});
});
afterEach(() => {
act(() => {
renderer?.unmount();
});
vi.useRealTimers();
vi.unstubAllGlobals();
});
it('uses InstallUpdateAndRestart for downloaded macOS updates', async () => {
backendApp.CheckForUpdates.mockResolvedValue({
success: true,
data: {
hasUpdate: true,
currentVersion: '0.8.1',
latestVersion: '0.8.2',
downloaded: true,
assetSize: 1024,
},
});
backendApp.InstallUpdateAndRestart.mockResolvedValue({ success: true });
backendApp.OpenDownloadedUpdateDirectory.mockResolvedValue({ success: true });
renderHook();
await act(async () => {
await hook?.checkForUpdates(false);
});
await act(async () => {
await hook?.handleInstallFromProgress();
});
expect(backendApp.InstallUpdateAndRestart).toHaveBeenCalledTimes(1);
expect(backendApp.OpenDownloadedUpdateDirectory).not.toHaveBeenCalled();
});
it('does not auto-open the downloaded macOS package directory after download succeeds', async () => {
backendApp.CheckForUpdates.mockResolvedValue({
success: true,
data: {
hasUpdate: true,
currentVersion: '0.8.1',
latestVersion: '0.8.2',
downloaded: false,
assetSize: 2048,
},
});
backendApp.DownloadUpdate.mockResolvedValue({
success: true,
data: {
downloadPath: '/Users/test/Desktop/GoNavi-0.8.2-MacOS-Arm64.dmg',
},
});
backendApp.OpenDownloadedUpdateDirectory.mockResolvedValue({ success: true });
renderHook();
await act(async () => {
await hook?.checkForUpdates(false);
});
await act(async () => {
await hook?.downloadUpdate(hook?.lastUpdateInfo!, false);
});
expect(backendApp.DownloadUpdate).toHaveBeenCalledTimes(1);
expect(backendApp.OpenDownloadedUpdateDirectory).not.toHaveBeenCalled();
expect(hook?.lastUpdateInfo?.downloaded).toBe(true);
});
it('switches update channel and re-checks against the selected channel', async () => {
backendApp.SetUpdateChannel.mockResolvedValue({ success: true, data: { channel: 'dev' } });
backendApp.CheckForUpdates.mockResolvedValue({
success: true,
data: {
hasUpdate: false,
channel: 'dev',
currentVersion: '0.8.1',
latestVersion: 'dev-a1b2c3d',
},
});
renderHook();
await act(async () => {
await hook?.changeUpdateChannel('dev');
});
expect(backendApp.SetUpdateChannel).toHaveBeenCalledWith('dev');
expect(backendApp.CheckForUpdates).toHaveBeenCalledTimes(1);
expect(hook?.updateChannel).toBe('dev');
expect(hook?.lastUpdateInfo?.channel).toBe('dev');
});
});