镜像自地址
https://github.com/lhly/cloud-drive-renamer.git
已同步 2026-10-21 06:01:51 +08:00
307 行
8.7 KiB
TypeScript
307 行
8.7 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { FileSelectorPanel } from '../../src/content/components/file-selector-panel';
|
|
import { BatchExecutor, ExecutorState } from '../../src/core/executor';
|
|
import { crashRecovery } from '../../src/core/crash-recovery';
|
|
import type { FileItem, PlatformAdapter, RenameResult } from '../../src/types/platform';
|
|
import * as rulePresets from '../../src/utils/rule-presets';
|
|
|
|
type FileSelectorPanelPresetHarness = FileSelectorPanel & {
|
|
ruleConfig: any;
|
|
allFiles: FileItem[];
|
|
uncheckList: Set<string>;
|
|
newNameMap: Map<string, string>;
|
|
extractErrorMap: Map<string, string>;
|
|
episodeExtractAssistState: any;
|
|
recentRulePresets: any[];
|
|
templateRulePresets: any[];
|
|
handleApplyRulePreset(event: CustomEvent<{ presetId: string }>): Promise<void>;
|
|
handleExecute(): Promise<void>;
|
|
handleAssistFocusFailure(event: CustomEvent<{ fileId: string }>): void;
|
|
handleRenameRuleTemplate(event: CustomEvent<{ presetId: string; name: string }>): Promise<void>;
|
|
};
|
|
|
|
class PresetTestAdapter implements PlatformAdapter {
|
|
readonly platform = 'aliyun' as const;
|
|
|
|
getCurrentDirectoryKey(): string {
|
|
return 'root';
|
|
}
|
|
|
|
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}.mkv`,
|
|
ext: '.mkv',
|
|
parentId: 'root',
|
|
size: 1,
|
|
mtime: Date.now(),
|
|
};
|
|
}
|
|
|
|
getConfig() {
|
|
return {
|
|
platform: 'aliyun' as const,
|
|
requestInterval: 0,
|
|
maxConcurrent: 1,
|
|
maxRetries: 1,
|
|
};
|
|
}
|
|
}
|
|
|
|
describe('FileSelectorPanel rule preset integration', () => {
|
|
beforeEach(() => {
|
|
vi.restoreAllMocks();
|
|
vi.stubGlobal('chrome', {
|
|
storage: {
|
|
local: {
|
|
get: vi.fn(async () => ({})),
|
|
set: vi.fn(async () => undefined),
|
|
remove: vi.fn(async () => undefined),
|
|
clear: vi.fn(async () => undefined),
|
|
},
|
|
},
|
|
runtime: {
|
|
sendMessage: vi.fn(async () => undefined),
|
|
},
|
|
});
|
|
vi.stubGlobal('localStorage', {
|
|
getItem: vi.fn(),
|
|
setItem: vi.fn(),
|
|
removeItem: vi.fn(),
|
|
clear: vi.fn(),
|
|
key: vi.fn(),
|
|
length: 0,
|
|
});
|
|
vi.stubGlobal('confirm', vi.fn(() => true));
|
|
vi.stubGlobal('alert', vi.fn());
|
|
vi.stubGlobal('prompt', vi.fn(() => null));
|
|
vi.spyOn(crashRecovery, 'saveOperationState').mockResolvedValue();
|
|
vi.spyOn(crashRecovery, 'clearOperationState').mockResolvedValue();
|
|
vi.spyOn(BatchExecutor.prototype, 'getState').mockReturnValue(ExecutorState.COMPLETED);
|
|
});
|
|
|
|
it('applies a preset config and resets assist state', async () => {
|
|
const panel = new FileSelectorPanel() as FileSelectorPanelPresetHarness;
|
|
vi.spyOn(rulePresets, 'getRulePresetById').mockResolvedValue({
|
|
id: 'template-episode',
|
|
name: '剧集模板',
|
|
source: 'template',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
config: {
|
|
type: 'episodeExtract',
|
|
params: {
|
|
template: '{prefix}.S{season}E{episode}{ext}',
|
|
prefix: 'Show',
|
|
season: 2,
|
|
offset: 1,
|
|
leadingZeroCount: 2,
|
|
helperPre: '第',
|
|
helperPost: '集',
|
|
},
|
|
},
|
|
});
|
|
|
|
panel.episodeExtractAssistState = {
|
|
sampleFileId: 'old',
|
|
sampleFileName: 'old-sample',
|
|
fillTarget: 'helperPost',
|
|
fillMode: 'segment',
|
|
suggestedFailureFileId: 'old',
|
|
};
|
|
|
|
await panel.handleApplyRulePreset(new CustomEvent('apply-rule-preset', { detail: { presetId: 'template-episode' } }));
|
|
|
|
expect(panel.ruleConfig.type).toBe('episodeExtract');
|
|
expect(panel.ruleConfig.params.prefix).toBe('Show');
|
|
expect(panel.episodeExtractAssistState.sampleFileId).toBeNull();
|
|
expect(panel.episodeExtractAssistState.fillTarget).toBe('prefix');
|
|
expect(panel.episodeExtractAssistState.fillMode).toBe('full-name');
|
|
});
|
|
|
|
|
|
it('blocks invalid preset application and shows alert', async () => {
|
|
const panel = new FileSelectorPanel() as FileSelectorPanelPresetHarness;
|
|
const alertSpy = vi.spyOn(window, 'alert');
|
|
vi.spyOn(rulePresets, 'getRulePresetById').mockResolvedValue({
|
|
id: 'invalid-template',
|
|
name: '失效模板',
|
|
source: 'template',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
config: {
|
|
type: 'replace',
|
|
params: { search: '', replace: '' },
|
|
},
|
|
});
|
|
|
|
panel.ruleConfig = {
|
|
type: 'prefix',
|
|
params: { prefix: 'Keep', separator: '.' },
|
|
};
|
|
|
|
await panel.handleApplyRulePreset(new CustomEvent('apply-rule-preset', { detail: { presetId: 'invalid-template' } }));
|
|
|
|
expect(alertSpy).toHaveBeenCalledTimes(1);
|
|
expect(panel.ruleConfig.type).toBe('prefix');
|
|
expect(panel.ruleConfig.params.prefix).toBe('Keep');
|
|
});
|
|
|
|
it('records recent presets after a successful execution', async () => {
|
|
const panel = new FileSelectorPanel() as FileSelectorPanelPresetHarness;
|
|
panel.adapter = new PresetTestAdapter();
|
|
panel.ruleConfig = {
|
|
type: 'prefix',
|
|
params: { prefix: 'Renamed-', separator: '' },
|
|
};
|
|
panel.allFiles = [
|
|
{
|
|
id: 'file-1',
|
|
name: 'episode-01.mkv',
|
|
ext: '.mkv',
|
|
parentId: 'root',
|
|
size: 1,
|
|
mtime: 1,
|
|
},
|
|
];
|
|
panel.uncheckList = new Set();
|
|
panel.newNameMap = new Map([['file-1', 'Renamed-episode-01.mkv']]);
|
|
panel.extractErrorMap = new Map();
|
|
|
|
vi.spyOn(BatchExecutor.prototype, 'execute').mockResolvedValue({
|
|
success: [
|
|
{
|
|
fileId: 'file-1',
|
|
original: 'episode-01.mkv',
|
|
renamed: 'Renamed-episode-01.mkv',
|
|
index: 0,
|
|
},
|
|
],
|
|
failed: [],
|
|
});
|
|
const recentSpy = vi.spyOn(rulePresets, 'recordRecentRulePreset').mockResolvedValue([
|
|
{
|
|
id: 'recent-1',
|
|
source: 'recent',
|
|
createdAt: 1,
|
|
updatedAt: 2,
|
|
config: panel.ruleConfig,
|
|
},
|
|
]);
|
|
|
|
await panel.handleExecute();
|
|
|
|
expect(recentSpy).toHaveBeenCalledWith(panel.ruleConfig);
|
|
expect(panel.recentRulePresets).toHaveLength(1);
|
|
expect(panel.recentRulePresets[0].id).toBe('recent-1');
|
|
});
|
|
|
|
it('focuses the target failure item from preview navigation', async () => {
|
|
const panel = new FileSelectorPanel() as FileSelectorPanelPresetHarness;
|
|
panel.allFiles = [
|
|
{
|
|
id: 'file-1',
|
|
name: 'Show.S01E01.mkv',
|
|
ext: '.mkv',
|
|
parentId: 'root',
|
|
size: 1,
|
|
mtime: 1,
|
|
},
|
|
{
|
|
id: 'file-2',
|
|
name: 'Show.S01E02.mkv',
|
|
ext: '.mkv',
|
|
parentId: 'root',
|
|
size: 1,
|
|
mtime: 2,
|
|
},
|
|
];
|
|
panel.uncheckList = new Set();
|
|
panel.episodeExtractAssistState = {
|
|
sampleFileId: null,
|
|
sampleFileName: null,
|
|
fillTarget: 'prefix',
|
|
fillMode: 'segment',
|
|
suggestedFailureFileId: null,
|
|
};
|
|
|
|
panel.handleAssistFocusFailure(
|
|
new CustomEvent('episode-assist-focus-failure', { detail: { fileId: 'file-2' } })
|
|
);
|
|
|
|
expect(panel.episodeExtractAssistState.sampleFileId).toBe('file-2');
|
|
expect(panel.episodeExtractAssistState.sampleFileName).toBe('Show.S01E02.mkv');
|
|
expect(panel.episodeExtractAssistState.suggestedFailureFileId).toBe('file-2');
|
|
});
|
|
|
|
|
|
it('renames template presets and refreshes template list', async () => {
|
|
const panel = new FileSelectorPanel() as FileSelectorPanelPresetHarness;
|
|
panel.templateRulePresets = [
|
|
{
|
|
id: 'template-1',
|
|
name: '旧模板',
|
|
source: 'template',
|
|
createdAt: 1,
|
|
updatedAt: 1,
|
|
config: {
|
|
type: 'prefix',
|
|
params: { prefix: 'Show', separator: '.' },
|
|
},
|
|
},
|
|
];
|
|
|
|
vi.spyOn(rulePresets, 'renameTemplateRulePreset').mockResolvedValue({
|
|
replaced: false,
|
|
record: {
|
|
id: 'template-1',
|
|
name: '新模板',
|
|
source: 'template',
|
|
createdAt: 1,
|
|
updatedAt: 2,
|
|
config: {
|
|
type: 'prefix',
|
|
params: { prefix: 'Show', separator: '.' },
|
|
},
|
|
},
|
|
records: [
|
|
{
|
|
id: 'template-1',
|
|
name: '新模板',
|
|
source: 'template',
|
|
createdAt: 1,
|
|
updatedAt: 2,
|
|
config: {
|
|
type: 'prefix',
|
|
params: { prefix: 'Show', separator: '.' },
|
|
},
|
|
},
|
|
],
|
|
});
|
|
|
|
await panel.handleRenameRuleTemplate(
|
|
new CustomEvent('rename-rule-template', { detail: { presetId: 'template-1', name: '新模板' } })
|
|
);
|
|
|
|
expect(panel.templateRulePresets).toHaveLength(1);
|
|
expect(panel.templateRulePresets[0].name).toBe('新模板');
|
|
});
|
|
|
|
});
|