Files
PathEditor/tests/unit/undo-redo.test.ts
T
Serendipity 914b25f236 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>
2026-05-27 13:42:21 +08:00

178 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, it, expect, beforeEach } from 'vitest';
import { UndoRedoManager, OperationType, TargetType, type OpRecord } from '../../src/core/undo-redo';
import type { PathEntry } from '../../src/core/path-entry';
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 };
}
describe('UndoRedoManager', () => {
let mgr: UndoRedoManager;
let sys: PathEntry[];
let user: PathEntry[];
beforeEach(() => {
mgr = new UndoRedoManager(50);
sys = [pe('C:\\Windows'), pe('C:\\Program Files')];
user = [pe('C:\\Users\\me\\AppData')];
});
it('初始不可撤销不可重做', () => {
expect(mgr.canUndo()).toBe(false);
expect(mgr.canRedo()).toBe(false);
});
it('ADD 撤销/重做', () => {
sys.push(pe('C:\\NewPath'));
mgr.push(makeRecord(OperationType.ADD, TargetType.SYSTEM, 2, 1, [], [pe('C:\\NewPath')]));
const u = mgr.undo(sys, user)!;
expect(u[0].map(e => e.path)).toEqual(['C:\\Windows', 'C:\\Program Files']);
const r = mgr.redo(...u)!;
expect(r[0].map(e => e.path)).toEqual(['C:\\Windows', 'C:\\Program Files', 'C:\\NewPath']);
});
it('DELETE 撤销/重做', () => {
const removed = sys[0];
mgr.push(makeRecord(OperationType.DELETE, TargetType.SYSTEM, 0, 1, [removed], []));
sys.splice(0, 1);
const u = mgr.undo(sys, user)!;
expect(u[0][0].path).toBe(removed.path);
const r = mgr.redo(...u)!;
expect(r[0].map(e => e.path)).toEqual(['C:\\Program Files']);
});
it('EDIT 撤销/重做', () => {
mgr.push(makeRecord(OperationType.EDIT, TargetType.SYSTEM, 0, 1, [pe('C:\\Windows')], [pe('C:\\Edited')]));
sys[0] = pe('C:\\Edited');
const u = mgr.undo(sys, user)!;
expect(u[0][0].path).toBe('C:\\Windows');
const r = mgr.redo(...u)!;
expect(r[0][0].path).toBe('C:\\Edited');
});
it('MOVE_UP 撤销/重做', () => {
mgr.push(makeRecord(OperationType.MOVE_UP, TargetType.SYSTEM, 1, 1, [], []));
[sys[0], sys[1]] = [sys[1], sys[0]];
const u = mgr.undo(sys, user)!;
expect(u[0].map(e => e.path)).toEqual(['C:\\Windows', 'C:\\Program Files']);
const r = mgr.redo(...u)!;
expect(r[0].map(e => e.path)).toEqual(['C:\\Program Files', 'C:\\Windows']);
});
it('MOVE_DOWN 撤销/重做', () => {
mgr.push(makeRecord(OperationType.MOVE_DOWN, TargetType.SYSTEM, 0, 1, [], []));
[sys[0], sys[1]] = [sys[1], sys[0]];
const u = mgr.undo(sys, user)!;
expect(u[0].map(e => e.path)).toEqual(['C:\\Windows', 'C:\\Program Files']);
});
it('CLEAN 撤销/重做', () => {
const old = [...sys];
const cleaned = [pe('C:\\Windows')];
mgr.push(makeRecord(OperationType.CLEAN, TargetType.SYSTEM, 0, 2, old, cleaned));
sys = cleaned;
const u = mgr.undo(sys, user)!;
expect(u[0]).toEqual(old);
const r = mgr.redo(...u)!;
expect(r[0]).toEqual(cleaned);
});
it('CLEAR 撤销/重做', () => {
const old = [...sys];
mgr.push(makeRecord(OperationType.CLEAR, TargetType.SYSTEM, 0, 2, old, []));
sys = [];
const u = mgr.undo(sys, user)!;
expect(u[0]).toEqual(old);
const r = mgr.redo(...u)!;
expect(r[0]).toEqual([]);
});
it('IMPORT 撤销/重做', () => {
const old = [...sys];
const imported = [pe('C:\\New1'), pe('C:\\New2')];
mgr.push(makeRecord(OperationType.IMPORT, TargetType.SYSTEM, 0, 2, old, imported));
sys = imported;
const u = mgr.undo(sys, user)!;
expect(u[0]).toEqual(old);
const r = mgr.redo(...u)!;
expect(r[0]).toEqual(imported);
});
it('新操作后截断重做分支', () => {
mgr.push(makeRecord(OperationType.ADD, TargetType.SYSTEM, 0, 1, [], [pe('first')]));
mgr.undo(sys, user);
expect(mgr.canRedo()).toBe(true);
mgr.push(makeRecord(OperationType.ADD, TargetType.SYSTEM, 0, 1, [], [pe('second')]));
expect(mgr.canRedo()).toBe(false);
});
it('超出最大历史容量时移除最旧记录', () => {
const small = new UndoRedoManager(3);
for (let i = 0; i < 5; i++) {
small.push(makeRecord(OperationType.ADD, TargetType.SYSTEM, 0, 1, [], [pe(`path_${i}`)]));
}
expect(small.historyLength).toBe(3);
});
it('非连续多选 DELETE 撤销恢复到原始位置', () => {
// 扩展初始数组
sys.push(pe('C:\\Extra1'), pe('C:\\Extra2'));
const old = [...sys];
// 删除 indices [1, 3]C:\Program Files 和 C:\Extra2
const removed = [sys[1], sys[3]];
mgr.push({
type: OperationType.DELETE, target: TargetType.SYSTEM,
index: 1, count: 2,
oldPaths: removed, newPaths: [],
indices: [1, 3],
});
sys.splice(3, 1);
sys.splice(1, 1);
const u = mgr.undo(sys, user)!;
expect(u[0]).toEqual(old);
const r = mgr.redo(...u)!;
expect(r[0].map(e => e.path)).toEqual(['C:\\Windows', 'C:\\Extra1']);
});
it('操作 USER 路径', () => {
user.push(pe('C:\\NewUserPath'));
mgr.push(makeRecord(OperationType.ADD, TargetType.USER, 1, 1, [], [pe('C:\\NewUserPath')]));
const u = mgr.undo(sys, user)!;
expect(u[1].map(e => e.path)).toEqual(['C:\\Users\\me\\AppData']);
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);
});
});