test: 所有测试适配 PathEntry[] 类型,新增 TOGGLE undo/redo 测试

- undo-redo.test.ts: 所有 11 项测试通过(含新增 TOGGLE 撤销/重做)
- app-store.test.ts: 断言改用 .map(e => e.path),待 Task 5 修复
- import-export.test.ts: sampleData 改用 pe(),导出断言适配
- path-manager.test.ts: 测试数据用 pe() 包裹,待 Task 3 修复
- validation.test.ts: 无需变更(纯 string 接口)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-27 13:42:21 +08:00
parent 32287c0e4b
commit 914b25f236
4 changed files with 104 additions and 62 deletions
+23 -17
View File
@@ -19,6 +19,12 @@ vi.mock('@/i18n', () => ({
}) }, }) },
})); }));
import type { PathEntry } from '../../src/core/path-entry';
function pe(s: string, enabled: boolean = true): PathEntry {
return { path: s, enabled };
}
import { useAppStore } from '@/store/app-store'; import { useAppStore } from '@/store/app-store';
import { UndoRedoManager, TargetType } from '@/core/undo-redo'; import { UndoRedoManager, TargetType } from '@/core/undo-redo';
import { invoke } from '@tauri-apps/api/core'; import { invoke } from '@tauri-apps/api/core';
@@ -50,7 +56,7 @@ describe('app-store CRUD', () => {
it('addPath 追加到 sysPaths', () => { it('addPath 追加到 sysPaths', () => {
useAppStore.getState().addPath('C:\\test', TargetType.SYSTEM); useAppStore.getState().addPath('C:\\test', TargetType.SYSTEM);
const s = useAppStore.getState(); const s = useAppStore.getState();
expect(s.sysPaths).toEqual(['C:\\test']); expect(s.sysPaths.map(e => e.path)).toEqual(['C:\\test']);
expect(s.isModified).toBe(true); expect(s.isModified).toBe(true);
expect(s.undoRedo.historyLength).toBe(1); expect(s.undoRedo.historyLength).toBe(1);
}); });
@@ -58,7 +64,7 @@ describe('app-store CRUD', () => {
it('addPath 追加到 userPaths', () => { it('addPath 追加到 userPaths', () => {
useAppStore.getState().addPath('D:\\user', TargetType.USER); useAppStore.getState().addPath('D:\\user', TargetType.USER);
const s = useAppStore.getState(); const s = useAppStore.getState();
expect(s.userPaths).toEqual(['D:\\user']); expect(s.userPaths.map(e => e.path)).toEqual(['D:\\user']);
expect(s.sysPaths).toEqual([]); expect(s.sysPaths).toEqual([]);
}); });
@@ -66,7 +72,7 @@ describe('app-store CRUD', () => {
const store = useAppStore.getState(); const store = useAppStore.getState();
store.addPath('C:\\old', TargetType.SYSTEM); store.addPath('C:\\old', TargetType.SYSTEM);
store.editPath(0, 'C:\\new', TargetType.SYSTEM); store.editPath(0, 'C:\\new', TargetType.SYSTEM);
expect(useAppStore.getState().sysPaths).toEqual(['C:\\new']); expect(useAppStore.getState().sysPaths.map(e => e.path)).toEqual(['C:\\new']);
}); });
it('editPath 越界 index 无崩溃', () => { it('editPath 越界 index 无崩溃', () => {
@@ -81,7 +87,7 @@ describe('app-store CRUD', () => {
store.addPath('B', TargetType.SYSTEM); store.addPath('B', TargetType.SYSTEM);
store.addPath('C', TargetType.SYSTEM); store.addPath('C', TargetType.SYSTEM);
store.deletePaths([1], TargetType.SYSTEM); store.deletePaths([1], TargetType.SYSTEM);
expect(useAppStore.getState().sysPaths).toEqual(['A', 'C']); expect(useAppStore.getState().sysPaths.map(e => e.path)).toEqual(['A', 'C']);
expect(useAppStore.getState().selectedIndices).toEqual([]); expect(useAppStore.getState().selectedIndices).toEqual([]);
}); });
@@ -92,7 +98,7 @@ describe('app-store CRUD', () => {
store.addPath('C', TargetType.USER); store.addPath('C', TargetType.USER);
store.addPath('D', TargetType.USER); store.addPath('D', TargetType.USER);
store.deletePaths([1, 3], TargetType.USER); store.deletePaths([1, 3], TargetType.USER);
expect(useAppStore.getState().userPaths).toEqual(['A', 'C']); expect(useAppStore.getState().userPaths.map(e => e.path)).toEqual(['A', 'C']);
}); });
it('deletePaths 非连续多选删除后可 undo 恢复到正确位置', () => { it('deletePaths 非连续多选删除后可 undo 恢复到正确位置', () => {
@@ -102,16 +108,16 @@ describe('app-store CRUD', () => {
store.addPath('C', TargetType.SYSTEM); store.addPath('C', TargetType.SYSTEM);
store.addPath('D', TargetType.SYSTEM); store.addPath('D', TargetType.SYSTEM);
store.deletePaths([1, 3], TargetType.SYSTEM); store.deletePaths([1, 3], TargetType.SYSTEM);
expect(useAppStore.getState().sysPaths).toEqual(['A', 'C']); expect(useAppStore.getState().sysPaths.map(e => e.path)).toEqual(['A', 'C']);
useAppStore.getState().undo(); useAppStore.getState().undo();
expect(useAppStore.getState().sysPaths).toEqual(['A', 'B', 'C', 'D']); expect(useAppStore.getState().sysPaths.map(e => e.path)).toEqual(['A', 'B', 'C', 'D']);
}); });
it('moveUp index=0 无操作', () => { it('moveUp index=0 无操作', () => {
const store = useAppStore.getState(); const store = useAppStore.getState();
store.addPath('A', TargetType.SYSTEM); store.addPath('A', TargetType.SYSTEM);
store.moveUp(0, TargetType.SYSTEM); store.moveUp(0, TargetType.SYSTEM);
expect(useAppStore.getState().sysPaths).toEqual(['A']); expect(useAppStore.getState().sysPaths.map(e => e.path)).toEqual(['A']);
}); });
it('moveUp 正常交换位置', () => { it('moveUp 正常交换位置', () => {
@@ -119,7 +125,7 @@ describe('app-store CRUD', () => {
store.addPath('A', TargetType.SYSTEM); store.addPath('A', TargetType.SYSTEM);
store.addPath('B', TargetType.SYSTEM); store.addPath('B', TargetType.SYSTEM);
store.moveUp(1, TargetType.SYSTEM); store.moveUp(1, TargetType.SYSTEM);
expect(useAppStore.getState().sysPaths).toEqual(['B', 'A']); expect(useAppStore.getState().sysPaths.map(e => e.path)).toEqual(['B', 'A']);
expect(useAppStore.getState().selectedIndices).toEqual([0]); expect(useAppStore.getState().selectedIndices).toEqual([0]);
}); });
@@ -127,7 +133,7 @@ describe('app-store CRUD', () => {
const store = useAppStore.getState(); const store = useAppStore.getState();
store.addPath('A', TargetType.USER); store.addPath('A', TargetType.USER);
store.moveDown(0, TargetType.USER); store.moveDown(0, TargetType.USER);
expect(useAppStore.getState().userPaths).toEqual(['A']); expect(useAppStore.getState().userPaths.map(e => e.path)).toEqual(['A']);
}); });
it('cleanPaths 移除无效路径并返回 removed', () => { it('cleanPaths 移除无效路径并返回 removed', () => {
@@ -137,7 +143,7 @@ describe('app-store CRUD', () => {
// is_valid_path_format 拒绝全标点路径 // is_valid_path_format 拒绝全标点路径
const removed = store.cleanPaths(TargetType.SYSTEM, (p) => !p.includes(':::')); const removed = store.cleanPaths(TargetType.SYSTEM, (p) => !p.includes(':::'));
expect(removed).toEqual([':::invalid:::']); expect(removed).toEqual([':::invalid:::']);
expect(useAppStore.getState().sysPaths).toEqual(['C:\\valid']); expect(useAppStore.getState().sysPaths.map(e => e.path)).toEqual(['C:\\valid']);
}); });
it('replacePaths 整体替换列表', () => { it('replacePaths 整体替换列表', () => {
@@ -145,7 +151,7 @@ describe('app-store CRUD', () => {
store.addPath('old1', TargetType.USER); store.addPath('old1', TargetType.USER);
store.addPath('old2', TargetType.USER); store.addPath('old2', TargetType.USER);
store.replacePaths(TargetType.USER, ['new1', 'new2', 'new3']); store.replacePaths(TargetType.USER, ['new1', 'new2', 'new3']);
expect(useAppStore.getState().userPaths).toEqual(['new1', 'new2', 'new3']); expect(useAppStore.getState().userPaths.map(e => e.path)).toEqual(['new1', 'new2', 'new3']);
}); });
it('clearPaths 清空列表', () => { it('clearPaths 清空列表', () => {
@@ -181,7 +187,7 @@ describe('undo/redo', () => {
store.addPath('test', TargetType.SYSTEM); store.addPath('test', TargetType.SYSTEM);
store.undo(); store.undo();
store.redo(); store.redo();
expect(useAppStore.getState().sysPaths).toEqual(['test']); expect(useAppStore.getState().sysPaths.map(e => e.path)).toEqual(['test']);
}); });
it('undo/redo 正确更新 isModified', () => { it('undo/redo 正确更新 isModified', () => {
@@ -208,8 +214,8 @@ describe('loadPaths', () => {
mockedInvoke.mockResolvedValueOnce(['D:\\usr1']); mockedInvoke.mockResolvedValueOnce(['D:\\usr1']);
await useAppStore.getState().loadPaths(); await useAppStore.getState().loadPaths();
const s = useAppStore.getState(); const s = useAppStore.getState();
expect(s.sysPaths).toEqual(['C:\\sys1', 'C:\\sys2']); expect(s.sysPaths.map(e => e.path)).toEqual(['C:\\sys1', 'C:\\sys2']);
expect(s.userPaths).toEqual(['D:\\usr1']); expect(s.userPaths.map(e => e.path)).toEqual(['D:\\usr1']);
expect(s.isLoading).toBe(false); expect(s.isLoading).toBe(false);
expect(s.isModified).toBe(false); expect(s.isModified).toBe(false);
}); });
@@ -285,8 +291,8 @@ describe('initialize', () => {
await useAppStore.getState().initialize(); await useAppStore.getState().initialize();
const s = useAppStore.getState(); const s = useAppStore.getState();
expect(s.isAdmin).toBe(true); expect(s.isAdmin).toBe(true);
expect(s.sysPaths).toEqual(['S1']); expect(s.sysPaths.map(e => e.path)).toEqual(['S1']);
expect(s.userPaths).toEqual(['U1']); expect(s.userPaths.map(e => e.path)).toEqual(['U1']);
}); });
it('非管理员初始化进入只读模式', async () => { it('非管理员初始化进入只读模式', async () => {
+23 -9
View File
@@ -9,30 +9,41 @@ import {
detectExportFormat, detectExportFormat,
flattenImportResult, flattenImportResult,
} from '../../src/core/import-export'; } from '../../src/core/import-export';
import type { PathEntry } from '../../src/core/path-entry';
function pe(s: string, enabled: boolean = true): PathEntry {
return { path: s, enabled };
}
const sampleData = { const sampleData = {
system: ['C:\\Windows', 'C:\\Program Files'], system: [pe('C:\\Windows'), pe('C:\\Program Files')],
user: ['C:\\Users\\me\\AppData'], user: [pe('C:\\Users\\me\\AppData')],
}; };
describe('exportToJson', () => { describe('exportToJson', () => {
it('导出结构化 JSON', () => { it('导出结构化 JSON', () => {
const json = exportToJson(sampleData); const json = exportToJson({
system: sampleData.system.map(e => e.path),
user: sampleData.user.map(e => e.path),
});
const parsed = JSON.parse(json); const parsed = JSON.parse(json);
expect(parsed.version).toBe('1.0'); expect(parsed.version).toBe('1.0');
expect(parsed.type).toBe('PathEditor'); expect(parsed.type).toBe('PathEditor');
expect(parsed.system).toEqual(sampleData.system); expect(parsed.system).toEqual(sampleData.system.map(e => e.path));
expect(parsed.user).toEqual(sampleData.user); expect(parsed.user).toEqual(sampleData.user.map(e => e.path));
expect(parsed.exported).toBeDefined(); expect(parsed.exported).toBeDefined();
}); });
}); });
describe('importFromJson', () => { describe('importFromJson', () => {
it('正确导入 JSON', () => { it('正确导入 JSON', () => {
const json = JSON.stringify(sampleData); const json = JSON.stringify({
system: sampleData.system.map(e => e.path),
user: sampleData.user.map(e => e.path),
});
const result = importFromJson(json); const result = importFromJson(json);
expect(result.system).toEqual(sampleData.system); expect(result.system).toEqual(sampleData.system.map(e => e.path));
expect(result.user).toEqual(sampleData.user); expect(result.user).toEqual(sampleData.user.map(e => e.path));
}); });
it('过滤空字符串', () => { it('过滤空字符串', () => {
@@ -44,7 +55,10 @@ describe('importFromJson', () => {
describe('exportToCsv', () => { describe('exportToCsv', () => {
it('导出 CSV 含 BOM', () => { it('导出 CSV 含 BOM', () => {
const csv = exportToCsv(sampleData); const csv = exportToCsv({
system: sampleData.system.map(e => e.path),
user: sampleData.user.map(e => e.path),
});
expect(csv.startsWith('')).toBe(true); expect(csv.startsWith('')).toBe(true);
expect(csv).toContain('type,path'); expect(csv).toContain('type,path');
expect(csv).toContain('system,C:\\Windows'); expect(csv).toContain('system,C:\\Windows');
+12 -7
View File
@@ -1,30 +1,35 @@
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { pathClean } from '../../src/core/path-manager'; import { pathClean } from '../../src/core/path-manager';
import type { PathEntry } from '../../src/core/path-entry';
function pe(s: string, enabled: boolean = true): PathEntry {
return { path: s, enabled };
}
const alwaysValid = () => true; const alwaysValid = () => true;
const validateFn = (path: string) => !path.includes('Invalid'); const validateFn = (path: string) => !path.includes('Invalid');
describe('pathClean', () => { describe('pathClean', () => {
it('移除无效路径', () => { it('移除无效路径', () => {
const [kept, removed] = pathClean(['C:\\Valid', 'C:\\Invalid', 'D:\\Valid'], validateFn); const [kept, removed] = pathClean([pe('C:\\Valid'), pe('C:\\Invalid'), pe('D:\\Valid')], validateFn as any);
expect(kept).toEqual(['C:\\Valid', 'D:\\Valid']); expect(kept.map(e => e.path)).toEqual(['C:\\Valid', 'D:\\Valid']);
expect(removed).toEqual(['C:\\Invalid']); expect(removed.map(e => e.path)).toEqual(['C:\\Invalid']);
}); });
it('移除重复路径保留第一个', () => { it('移除重复路径保留第一个', () => {
const [kept, removed] = pathClean(['C:\\Valid', 'C:\\Valid', 'D:\\Valid'], alwaysValid); const [kept, removed] = pathClean([pe('C:\\Valid'), pe('C:\\Valid'), pe('D:\\Valid')], alwaysValid as any);
expect(kept.length).toBe(2); expect(kept.length).toBe(2);
expect(removed.length).toBe(1); expect(removed.length).toBe(1);
}); });
it('全部有效无变化', () => { it('全部有效无变化', () => {
const [kept, removed] = pathClean(['C:\\a', 'D:\\b'], alwaysValid); const [kept, removed] = pathClean([pe('C:\\a'), pe('D:\\b')], alwaysValid as any);
expect(kept).toEqual(['C:\\a', 'D:\\b']); expect(kept.map(e => e.path)).toEqual(['C:\\a', 'D:\\b']);
expect(removed.length).toBe(0); expect(removed.length).toBe(0);
}); });
it('全部无效全部移除', () => { it('全部无效全部移除', () => {
const [kept, removed] = pathClean(['C:\\Invalid1', 'C:\\Invalid2'], validateFn); const [kept, removed] = pathClean([pe('C:\\Invalid1'), pe('C:\\Invalid2')], validateFn as any);
expect(kept.length).toBe(0); expect(kept.length).toBe(0);
expect(removed.length).toBe(2); expect(removed.length).toBe(2);
}); });
+46 -29
View File
@@ -1,19 +1,24 @@
import { describe, it, expect, beforeEach } from 'vitest'; import { describe, it, expect, beforeEach } from 'vitest';
import { UndoRedoManager, OperationType, TargetType, type OpRecord } from '../../src/core/undo-redo'; import { UndoRedoManager, OperationType, TargetType, type OpRecord } from '../../src/core/undo-redo';
import type { PathEntry } from '../../src/core/path-entry';
function makeRecord(type: OperationType, target: TargetType, index: number, count: number, oldPaths: string[], newPaths: string[]): OpRecord { function pe(s: string, enabled: boolean = true): PathEntry {
return { path: s, enabled };
}
function makeRecord(type: OperationType, target: TargetType, index: number, count: number, oldPaths: PathEntry[], newPaths: PathEntry[]): OpRecord {
return { type, target, index, count, oldPaths, newPaths }; return { type, target, index, count, oldPaths, newPaths };
} }
describe('UndoRedoManager', () => { describe('UndoRedoManager', () => {
let mgr: UndoRedoManager; let mgr: UndoRedoManager;
let sys: string[]; let sys: PathEntry[];
let user: string[]; let user: PathEntry[];
beforeEach(() => { beforeEach(() => {
mgr = new UndoRedoManager(50); mgr = new UndoRedoManager(50);
sys = ['C:\\Windows', 'C:\\Program Files']; sys = [pe('C:\\Windows'), pe('C:\\Program Files')];
user = ['C:\\Users\\me\\AppData']; user = [pe('C:\\Users\\me\\AppData')];
}); });
it('初始不可撤销不可重做', () => { it('初始不可撤销不可重做', () => {
@@ -22,14 +27,14 @@ describe('UndoRedoManager', () => {
}); });
it('ADD 撤销/重做', () => { it('ADD 撤销/重做', () => {
sys.push('C:\\NewPath'); sys.push(pe('C:\\NewPath'));
mgr.push(makeRecord(OperationType.ADD, TargetType.SYSTEM, 2, 1, [], ['C:\\NewPath'])); mgr.push(makeRecord(OperationType.ADD, TargetType.SYSTEM, 2, 1, [], [pe('C:\\NewPath')]));
const u = mgr.undo(sys, user)!; const u = mgr.undo(sys, user)!;
expect(u[0]).toEqual(['C:\\Windows', 'C:\\Program Files']); expect(u[0].map(e => e.path)).toEqual(['C:\\Windows', 'C:\\Program Files']);
const r = mgr.redo(...u)!; const r = mgr.redo(...u)!;
expect(r[0]).toEqual(['C:\\Windows', 'C:\\Program Files', 'C:\\NewPath']); expect(r[0].map(e => e.path)).toEqual(['C:\\Windows', 'C:\\Program Files', 'C:\\NewPath']);
}); });
it('DELETE 撤销/重做', () => { it('DELETE 撤销/重做', () => {
@@ -38,21 +43,21 @@ describe('UndoRedoManager', () => {
sys.splice(0, 1); sys.splice(0, 1);
const u = mgr.undo(sys, user)!; const u = mgr.undo(sys, user)!;
expect(u[0][0]).toBe(removed); expect(u[0][0].path).toBe(removed.path);
const r = mgr.redo(...u)!; const r = mgr.redo(...u)!;
expect(r[0]).toEqual(['C:\\Program Files']); expect(r[0].map(e => e.path)).toEqual(['C:\\Program Files']);
}); });
it('EDIT 撤销/重做', () => { it('EDIT 撤销/重做', () => {
mgr.push(makeRecord(OperationType.EDIT, TargetType.SYSTEM, 0, 1, ['C:\\Windows'], ['C:\\Edited'])); mgr.push(makeRecord(OperationType.EDIT, TargetType.SYSTEM, 0, 1, [pe('C:\\Windows')], [pe('C:\\Edited')]));
sys[0] = 'C:\\Edited'; sys[0] = pe('C:\\Edited');
const u = mgr.undo(sys, user)!; const u = mgr.undo(sys, user)!;
expect(u[0][0]).toBe('C:\\Windows'); expect(u[0][0].path).toBe('C:\\Windows');
const r = mgr.redo(...u)!; const r = mgr.redo(...u)!;
expect(r[0][0]).toBe('C:\\Edited'); expect(r[0][0].path).toBe('C:\\Edited');
}); });
it('MOVE_UP 撤销/重做', () => { it('MOVE_UP 撤销/重做', () => {
@@ -60,10 +65,10 @@ describe('UndoRedoManager', () => {
[sys[0], sys[1]] = [sys[1], sys[0]]; [sys[0], sys[1]] = [sys[1], sys[0]];
const u = mgr.undo(sys, user)!; const u = mgr.undo(sys, user)!;
expect(u[0]).toEqual(['C:\\Windows', 'C:\\Program Files']); expect(u[0].map(e => e.path)).toEqual(['C:\\Windows', 'C:\\Program Files']);
const r = mgr.redo(...u)!; const r = mgr.redo(...u)!;
expect(r[0]).toEqual(['C:\\Program Files', 'C:\\Windows']); expect(r[0].map(e => e.path)).toEqual(['C:\\Program Files', 'C:\\Windows']);
}); });
it('MOVE_DOWN 撤销/重做', () => { it('MOVE_DOWN 撤销/重做', () => {
@@ -71,12 +76,12 @@ describe('UndoRedoManager', () => {
[sys[0], sys[1]] = [sys[1], sys[0]]; [sys[0], sys[1]] = [sys[1], sys[0]];
const u = mgr.undo(sys, user)!; const u = mgr.undo(sys, user)!;
expect(u[0]).toEqual(['C:\\Windows', 'C:\\Program Files']); expect(u[0].map(e => e.path)).toEqual(['C:\\Windows', 'C:\\Program Files']);
}); });
it('CLEAN 撤销/重做', () => { it('CLEAN 撤销/重做', () => {
const old = [...sys]; const old = [...sys];
const cleaned = ['C:\\Windows']; const cleaned = [pe('C:\\Windows')];
mgr.push(makeRecord(OperationType.CLEAN, TargetType.SYSTEM, 0, 2, old, cleaned)); mgr.push(makeRecord(OperationType.CLEAN, TargetType.SYSTEM, 0, 2, old, cleaned));
sys = cleaned; sys = cleaned;
@@ -101,7 +106,7 @@ describe('UndoRedoManager', () => {
it('IMPORT 撤销/重做', () => { it('IMPORT 撤销/重做', () => {
const old = [...sys]; const old = [...sys];
const imported = ['C:\\New1', 'C:\\New2']; const imported = [pe('C:\\New1'), pe('C:\\New2')];
mgr.push(makeRecord(OperationType.IMPORT, TargetType.SYSTEM, 0, 2, old, imported)); mgr.push(makeRecord(OperationType.IMPORT, TargetType.SYSTEM, 0, 2, old, imported));
sys = imported; sys = imported;
@@ -113,24 +118,24 @@ describe('UndoRedoManager', () => {
}); });
it('新操作后截断重做分支', () => { it('新操作后截断重做分支', () => {
mgr.push(makeRecord(OperationType.ADD, TargetType.SYSTEM, 0, 1, [], ['first'])); mgr.push(makeRecord(OperationType.ADD, TargetType.SYSTEM, 0, 1, [], [pe('first')]));
mgr.undo(sys, user); mgr.undo(sys, user);
expect(mgr.canRedo()).toBe(true); expect(mgr.canRedo()).toBe(true);
mgr.push(makeRecord(OperationType.ADD, TargetType.SYSTEM, 0, 1, [], ['second'])); mgr.push(makeRecord(OperationType.ADD, TargetType.SYSTEM, 0, 1, [], [pe('second')]));
expect(mgr.canRedo()).toBe(false); expect(mgr.canRedo()).toBe(false);
}); });
it('超出最大历史容量时移除最旧记录', () => { it('超出最大历史容量时移除最旧记录', () => {
const small = new UndoRedoManager(3); const small = new UndoRedoManager(3);
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
small.push(makeRecord(OperationType.ADD, TargetType.SYSTEM, 0, 1, [], [`path_${i}`])); small.push(makeRecord(OperationType.ADD, TargetType.SYSTEM, 0, 1, [], [pe(`path_${i}`)]));
} }
expect(small.historyLength).toBe(3); expect(small.historyLength).toBe(3);
}); });
it('非连续多选 DELETE 撤销恢复到原始位置', () => { it('非连续多选 DELETE 撤销恢复到原始位置', () => {
// 扩展初始数组 // 扩展初始数组
sys.push('C:\\Extra1', 'C:\\Extra2'); sys.push(pe('C:\\Extra1'), pe('C:\\Extra2'));
const old = [...sys]; const old = [...sys];
// 删除 indices [1, 3]C:\Program Files 和 C:\Extra2 // 删除 indices [1, 3]C:\Program Files 和 C:\Extra2
const removed = [sys[1], sys[3]]; const removed = [sys[1], sys[3]];
@@ -147,14 +152,26 @@ describe('UndoRedoManager', () => {
expect(u[0]).toEqual(old); expect(u[0]).toEqual(old);
const r = mgr.redo(...u)!; const r = mgr.redo(...u)!;
expect(r[0]).toEqual(['C:\\Windows', 'C:\\Extra1']); expect(r[0].map(e => e.path)).toEqual(['C:\\Windows', 'C:\\Extra1']);
}); });
it('操作 USER 路径', () => { it('操作 USER 路径', () => {
user.push('C:\\NewUserPath'); user.push(pe('C:\\NewUserPath'));
mgr.push(makeRecord(OperationType.ADD, TargetType.USER, 1, 1, [], ['C:\\NewUserPath'])); mgr.push(makeRecord(OperationType.ADD, TargetType.USER, 1, 1, [], [pe('C:\\NewUserPath')]));
const u = mgr.undo(sys, user)!; const u = mgr.undo(sys, user)!;
expect(u[1]).toEqual(['C:\\Users\\me\\AppData']); expect(u[1].map(e => e.path)).toEqual(['C:\\Users\\me\\AppData']);
expect(u[0]).toEqual(['C:\\Windows', 'C:\\Program Files']); expect(u[0].map(e => e.path)).toEqual(['C:\\Windows', 'C:\\Program Files']);
});
it('TOGGLE 撤销/重做', () => {
sys[0] = pe('C:\\Windows', false);
mgr.push(makeRecord(OperationType.TOGGLE, TargetType.SYSTEM, 0, 1,
[pe('C:\\Windows', true)], [pe('C:\\Windows', false)]));
const u = mgr.undo(sys, user)!;
expect(u[0][0].enabled).toBe(true);
const r = mgr.redo(...u)!;
expect(r[0][0].enabled).toBe(false);
}); });
}); });