import { describe, it, expect, vi, beforeEach } from 'vitest'; // Mock @tauri-apps/api/core vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn(), })); // Mock i18n vi.mock('@/i18n', () => ({ default: { t: vi.fn((key: string, opts?: Record) => { if (key === 'status.loaded') return `已加载 ${opts?.sysCount} 条系统 PATH,${opts?.userCount} 条用户 PATH`; if (key === 'status.error') return '加载失败'; if (key === 'status.saving') return '正在保存...'; if (key === 'status.saved') return '保存成功'; if (key === 'status.warning_backup') return '备份失败,但保存继续'; if (key === 'status.readonly') return '只读模式'; if (key === 'status.deleted') return `已删除 ${opts?.count} 条路径`; return key; }) }, })); import { useAppStore } from '@/store/app-store'; import { UndoRedoManager, TargetType } from '@/core/undo-redo'; import { invoke } from '@tauri-apps/api/core'; const mockedInvoke = vi.mocked(invoke); function resetStore() { useAppStore.setState({ sysPaths: [], userPaths: [], undoRedo: new UndoRedoManager(50), _savedSys: [], _savedUser: [], isModified: false, isLoading: false, isSaving: false, selectedIndices: [], searchQuery: '', statusMessage: '', }); } describe('app-store CRUD', () => { beforeEach(() => { vi.clearAllMocks(); resetStore(); }); it('addPath 追加到 sysPaths', () => { useAppStore.getState().addPath('C:\\test', TargetType.SYSTEM); const s = useAppStore.getState(); expect(s.sysPaths).toEqual(['C:\\test']); expect(s.isModified).toBe(true); expect(s.undoRedo.historyLength).toBe(1); }); it('addPath 追加到 userPaths', () => { useAppStore.getState().addPath('D:\\user', TargetType.USER); const s = useAppStore.getState(); expect(s.userPaths).toEqual(['D:\\user']); expect(s.sysPaths).toEqual([]); }); it('editPath 替换正确位置', () => { const store = useAppStore.getState(); store.addPath('C:\\old', TargetType.SYSTEM); store.editPath(0, 'C:\\new', TargetType.SYSTEM); expect(useAppStore.getState().sysPaths).toEqual(['C:\\new']); }); it('editPath 越界 index 无崩溃', () => { expect(() => { useAppStore.getState().editPath(99, 'X', TargetType.SYSTEM); }).not.toThrow(); }); it('deletePaths 单选删除', () => { const store = useAppStore.getState(); store.addPath('A', TargetType.SYSTEM); store.addPath('B', TargetType.SYSTEM); store.addPath('C', TargetType.SYSTEM); store.deletePaths([1], TargetType.SYSTEM); expect(useAppStore.getState().sysPaths).toEqual(['A', 'C']); expect(useAppStore.getState().selectedIndices).toEqual([]); }); it('deletePaths 多选删除(逆序排序一次 undo 覆盖)', () => { const store = useAppStore.getState(); store.addPath('A', TargetType.USER); store.addPath('B', TargetType.USER); store.addPath('C', TargetType.USER); store.addPath('D', TargetType.USER); store.deletePaths([1, 3], TargetType.USER); expect(useAppStore.getState().userPaths).toEqual(['A', 'C']); }); it('deletePaths 非连续多选删除后可 undo 恢复到正确位置', () => { const store = useAppStore.getState(); store.addPath('A', TargetType.SYSTEM); store.addPath('B', TargetType.SYSTEM); store.addPath('C', TargetType.SYSTEM); store.addPath('D', TargetType.SYSTEM); store.deletePaths([1, 3], TargetType.SYSTEM); expect(useAppStore.getState().sysPaths).toEqual(['A', 'C']); useAppStore.getState().undo(); expect(useAppStore.getState().sysPaths).toEqual(['A', 'B', 'C', 'D']); }); it('moveUp index=0 无操作', () => { const store = useAppStore.getState(); store.addPath('A', TargetType.SYSTEM); store.moveUp(0, TargetType.SYSTEM); expect(useAppStore.getState().sysPaths).toEqual(['A']); }); it('moveUp 正常交换位置', () => { const store = useAppStore.getState(); store.addPath('A', TargetType.SYSTEM); store.addPath('B', TargetType.SYSTEM); store.moveUp(1, TargetType.SYSTEM); expect(useAppStore.getState().sysPaths).toEqual(['B', 'A']); expect(useAppStore.getState().selectedIndices).toEqual([0]); }); it('moveDown 末位无操作', () => { const store = useAppStore.getState(); store.addPath('A', TargetType.USER); store.moveDown(0, TargetType.USER); expect(useAppStore.getState().userPaths).toEqual(['A']); }); it('cleanPaths 移除无效路径并返回 removed', () => { const store = useAppStore.getState(); store.addPath('C:\\valid', TargetType.SYSTEM); store.addPath(':::invalid:::', TargetType.SYSTEM); // is_valid_path_format 拒绝全标点路径 const removed = store.cleanPaths(TargetType.SYSTEM, (p) => !p.includes(':::')); expect(removed).toEqual([':::invalid:::']); expect(useAppStore.getState().sysPaths).toEqual(['C:\\valid']); }); it('replacePaths 整体替换列表', () => { const store = useAppStore.getState(); store.addPath('old1', TargetType.USER); store.addPath('old2', TargetType.USER); store.replacePaths(TargetType.USER, ['new1', 'new2', 'new3']); expect(useAppStore.getState().userPaths).toEqual(['new1', 'new2', 'new3']); }); it('clearPaths 清空列表', () => { const store = useAppStore.getState(); store.addPath('A', TargetType.SYSTEM); store.addPath('B', TargetType.SYSTEM); store.clearPaths(TargetType.SYSTEM); expect(useAppStore.getState().sysPaths).toEqual([]); }); it('clearPaths 空列表无操作', () => { const store = useAppStore.getState(); store.clearPaths(TargetType.USER); expect(useAppStore.getState().undoRedo.historyLength).toBe(0); }); }); describe('undo/redo', () => { beforeEach(() => { vi.clearAllMocks(); resetStore(); }); it('undo 恢复操作前状态', () => { useAppStore.getState().addPath('test', TargetType.SYSTEM); expect(useAppStore.getState().sysPaths.length).toBe(1); useAppStore.getState().undo(); expect(useAppStore.getState().sysPaths).toEqual([]); }); it('redo 回到操作后状态', () => { const store = useAppStore.getState(); store.addPath('test', TargetType.SYSTEM); store.undo(); store.redo(); expect(useAppStore.getState().sysPaths).toEqual(['test']); }); it('undo/redo 正确更新 isModified', () => { const store = useAppStore.getState(); // 设置已保存快照 useAppStore.setState({ _savedSys: [], _savedUser: [] }); store.addPath('test', TargetType.SYSTEM); expect(useAppStore.getState().isModified).toBe(true); store.undo(); expect(useAppStore.getState().isModified).toBe(false); store.redo(); expect(useAppStore.getState().isModified).toBe(true); }); }); describe('loadPaths', () => { beforeEach(() => { vi.clearAllMocks(); resetStore(); }); it('成功加载', async () => { mockedInvoke.mockResolvedValueOnce(['C:\\sys1', 'C:\\sys2']); mockedInvoke.mockResolvedValueOnce(['D:\\usr1']); await useAppStore.getState().loadPaths(); const s = useAppStore.getState(); expect(s.sysPaths).toEqual(['C:\\sys1', 'C:\\sys2']); expect(s.userPaths).toEqual(['D:\\usr1']); expect(s.isLoading).toBe(false); expect(s.isModified).toBe(false); }); it('加载失败时 isLoading 重置', async () => { mockedInvoke.mockRejectedValueOnce(new Error('reg error')); mockedInvoke.mockResolvedValueOnce([]); await useAppStore.getState().loadPaths(); const s = useAppStore.getState(); expect(s.isLoading).toBe(false); expect(s.statusMessage).toContain('加载失败'); }); }); describe('savePaths', () => { beforeEach(() => { vi.clearAllMocks(); resetStore(); useAppStore.setState({ sysPaths: ['A'], userPaths: ['B'] }); }); it('保存成功', async () => { mockedInvoke.mockResolvedValue(undefined); await useAppStore.getState().savePaths(); const s = useAppStore.getState(); expect(s.isSaving).toBe(false); expect(s.isModified).toBe(false); expect(s.statusMessage).toBe('保存成功'); }); it('部分失败时报告具体 hive', async () => { mockedInvoke .mockResolvedValueOnce(undefined) // backup_registry .mockResolvedValueOnce(undefined) // save_system_paths .mockRejectedValueOnce('权限不足'); // save_user_paths await useAppStore.getState().savePaths(); const s = useAppStore.getState(); expect(s.isSaving).toBe(false); expect(s.statusMessage).toContain('用户 PATH 保存失败'); }); it('isSaving 守卫:并发第二次调用直接返回', async () => { let resolveAll: (v: unknown) => void; const pending = new Promise((r) => { resolveAll = r; }); mockedInvoke.mockReturnValue(pending as any); // 第一次调用(不等它完成,停在 Promise.allSettled) const p1 = useAppStore.getState().savePaths(); // 第二次调用应被 isSaving 守卫拦截(此时 isSaving=true) const r2 = useAppStore.getState().savePaths(); // 第二次调用同步返回 undefined(被守卫拦截) await expect(r2).resolves.toBeUndefined(); // 放行第一次调用的所有 invoke resolveAll!(undefined); await p1; }); }); describe('initialize', () => { beforeEach(() => { vi.clearAllMocks(); resetStore(); }); it('管理员模式初始化', async () => { mockedInvoke .mockResolvedValueOnce(true) // check_admin .mockResolvedValueOnce(['S1']) // load_system_paths .mockResolvedValueOnce(['U1']); // load_user_paths await useAppStore.getState().initialize(); const s = useAppStore.getState(); expect(s.isAdmin).toBe(true); expect(s.sysPaths).toEqual(['S1']); expect(s.userPaths).toEqual(['U1']); }); it('非管理员初始化进入只读模式', async () => { mockedInvoke .mockResolvedValueOnce(false) // check_admin .mockResolvedValueOnce([]) // load_system_paths .mockResolvedValueOnce([]); // load_user_paths await useAppStore.getState().initialize(); expect(useAppStore.getState().isAdmin).toBe(false); // statusMessage 被后续 loadPaths 覆盖为加载完成消息,但 isAdmin=false 不变 }); });