镜像自地址
https://github.com/lhly/cloud-drive-renamer.git
已同步 2026-10-20 13:47:47 +08:00
- 将域名从 www.123pan.com 改为 yun.123pan.cn - 为iframe内文件列表添加DOM补丁,支持重命名后同步 - 修复撤销最后操作时错误写入崩溃恢复进度的问题
245 行
7.2 KiB
TypeScript
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);
|
|
});
|
|
});
|