cloud-drive-renamer/tests/unit/file-selector-panel-undo.test.ts
Zain 99e4444e1d fix: 更新123云盘域名并修复重命名同步与撤销问题
- 将域名从 www.123pan.com 改为 yun.123pan.cn
- 为iframe内文件列表添加DOM补丁,支持重命名后同步
- 修复撤销最后操作时错误写入崩溃恢复进度的问题
2026-08-10 11:35:55 +08:00

245 行
7.2 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { FileSelectorPanel } from '../../src/content/components/file-selector-panel';
import { FileItem, PlatformAdapter, RenameResult } from '../../src/types/platform';
import { BatchResults } from '../../src/types/core';
import { crashRecovery } from '../../src/core/crash-recovery';
import { LastRenameOperation } from '../../src/types/undo';
type FileSelectorPanelTestHarness = FileSelectorPanel & {
lastRenameOperation: LastRenameOperation | null;
allFiles: FileItem[];
executionResults: BatchResults | null;
clearLastRenameOperationIfOutOfScope(): void;
updateLastRenameOperationFromExecute(results: BatchResults): void;
mergeLastRenameOperationFromRetry(results: BatchResults): void;
handleUndoLastRename(): Promise<void>;
};
class PanelMockAdapter implements PlatformAdapter {
readonly platform = 'quark' as const;
directoryKey = 'dir-a';
getCurrentDirectoryKey(): string {
return this.directoryKey;
}
async getSelectedFiles(): Promise<FileItem[]> {
return [];
}
async getAllFiles(): Promise<FileItem[]> {
return [];
}
async renameFile(_fileId: string, newName: string): Promise<RenameResult> {
return { success: true, newName };
}
async checkNameConflict(): Promise<boolean> {
return false;
}
async getFileInfo(fileId: string): Promise<FileItem> {
return {
id: fileId,
name: `${fileId}.txt`,
ext: '.txt',
parentId: this.directoryKey,
size: 0,
mtime: Date.now(),
};
}
getConfig() {
return {
platform: 'quark' as const,
requestInterval: 10,
maxRetries: 3,
};
}
}
describe('FileSelectorPanel undo helpers', () => {
beforeEach(() => {
vi.stubGlobal('chrome', {
storage: {
local: {
get: vi.fn(async () => ({})),
set: vi.fn(async () => undefined),
remove: vi.fn(async () => undefined),
},
},
});
});
it('clears last rename operation when current scope no longer matches', () => {
const panel = new FileSelectorPanel() as FileSelectorPanelTestHarness;
const adapter = new PanelMockAdapter();
panel.adapter = adapter;
panel.lastRenameOperation = {
platform: 'quark',
directoryKey: 'dir-b',
createdAt: 1,
updatedAt: 1,
items: [{ fileId: '1', original: 'a.txt', renamed: 'b.txt', index: 0 }],
};
panel.clearLastRenameOperationIfOutOfScope();
expect(panel.lastRenameOperation).toBeNull();
});
it('stores a new last rename operation after execute success results', () => {
const panel = new FileSelectorPanel() as FileSelectorPanelTestHarness;
const adapter = new PanelMockAdapter();
panel.adapter = adapter;
const results: BatchResults = {
success: [{ fileId: '1', original: 'old-a.txt', renamed: 'new-a.txt', index: 0 }],
failed: [],
};
panel.updateLastRenameOperationFromExecute(results);
expect(panel.lastRenameOperation?.directoryKey).toBe('dir-a');
expect(panel.lastRenameOperation?.items).toEqual([
{ fileId: '1', original: 'old-a.txt', renamed: 'new-a.txt', index: 0 },
]);
});
it('merges retry success items into the current last rename operation', () => {
const panel = new FileSelectorPanel() as FileSelectorPanelTestHarness;
const adapter = new PanelMockAdapter();
panel.adapter = adapter;
panel.lastRenameOperation = {
platform: 'quark',
directoryKey: 'dir-a',
createdAt: 1,
updatedAt: 1,
items: [{ fileId: '1', original: 'old-a.txt', renamed: 'new-a.txt', index: 0 }],
};
const retryResults: BatchResults = {
success: [{ fileId: '2', original: 'old-b.txt', renamed: 'new-b.txt', index: 1 }],
failed: [],
};
panel.mergeLastRenameOperationFromRetry(retryResults);
expect(panel.lastRenameOperation?.items.map((item) => item.fileId)).toEqual(['1', '2']);
});
it('undoes the last rename operation and clears the record after full success', async () => {
const panel = new FileSelectorPanel() as FileSelectorPanelTestHarness;
const adapter = new PanelMockAdapter();
panel.adapter = adapter;
panel.allFiles = [
{
id: '1',
name: 'new-a.txt',
ext: '.txt',
parentId: 'dir-a',
size: 0,
mtime: Date.now(),
},
];
panel.lastRenameOperation = {
platform: 'quark',
directoryKey: 'dir-a',
createdAt: 1,
updatedAt: 1,
items: [{ fileId: '1', original: 'old-a.txt', renamed: 'new-a.txt', index: 0 }],
};
await panel.handleUndoLastRename();
expect(panel.lastRenameOperation).toBeNull();
expect(panel.allFiles[0].name).toBe('old-a.txt');
expect(panel.executionResults?.success).toHaveLength(1);
});
it('does not write crash recovery progress while undoing the last rename operation', async () => {
const markAsCompleted = vi.spyOn(crashRecovery, 'markAsCompleted').mockResolvedValue();
const markAsFailed = vi.spyOn(crashRecovery, 'markAsFailed').mockResolvedValue();
const panel = new FileSelectorPanel() as FileSelectorPanelTestHarness;
const adapter = new PanelMockAdapter();
panel.adapter = adapter;
panel.allFiles = [
{
id: '1',
name: 'new-a.txt',
ext: '.txt',
parentId: 'dir-a',
size: 0,
mtime: Date.now(),
},
{
id: '2',
name: 'new-b.txt',
ext: '.txt',
parentId: 'dir-a',
size: 0,
mtime: Date.now(),
},
];
panel.lastRenameOperation = {
platform: 'quark',
directoryKey: 'dir-a',
createdAt: 1,
updatedAt: 1,
items: [
{ fileId: '1', original: 'old-a.txt', renamed: 'new-a.txt', index: 0 },
{ fileId: '2', original: 'old-b.txt', renamed: 'new-b.txt', index: 1 },
],
};
await panel.handleUndoLastRename();
expect(panel.executionResults?.success).toHaveLength(2);
expect(markAsCompleted).not.toHaveBeenCalled();
expect(markAsFailed).not.toHaveBeenCalled();
});
it('undoes both initial success items and retry-merged success items together', async () => {
const panel = new FileSelectorPanel() as FileSelectorPanelTestHarness;
const adapter = new PanelMockAdapter();
panel.adapter = adapter;
panel.allFiles = [
{
id: '1',
name: 'new-a.txt',
ext: '.txt',
parentId: 'dir-a',
size: 0,
mtime: Date.now(),
},
{
id: '2',
name: 'new-b.txt',
ext: '.txt',
parentId: 'dir-a',
size: 0,
mtime: Date.now(),
},
];
panel.lastRenameOperation = {
platform: 'quark',
directoryKey: 'dir-a',
createdAt: 1,
updatedAt: 1,
items: [{ fileId: '1', original: 'old-a.txt', renamed: 'new-a.txt', index: 0 }],
};
panel.mergeLastRenameOperationFromRetry({
success: [{ fileId: '2', original: 'old-b.txt', renamed: 'new-b.txt', index: 1 }],
failed: [],
});
await panel.handleUndoLastRename();
expect(panel.lastRenameOperation).toBeNull();
expect(panel.allFiles.map((file) => file.name)).toEqual(['old-a.txt', 'old-b.txt']);
expect(panel.executionResults?.success).toHaveLength(2);
});
});