${item.file.name}
@@ -270,12 +270,11 @@ export class VirtualPreviewList extends LitElement {
.old-name {
font-size: 12px;
color: var(--cdr-text-secondary, #595959);
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
}
.old-name.old-name-secondary {
+ flex: 1 1 0;
+ min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -293,10 +292,12 @@ export class VirtualPreviewList extends LitElement {
gap: 8px;
}
- .new-name-text,
- .new-name.new-name-primary .new-name-text {
- flex: 1;
+ .new-name-text {
+ flex: 1 1 0;
min-width: 0;
+ }
+
+ .new-name-primary .new-name-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
diff --git a/task_plan.md b/task_plan.md
new file mode 100644
index 0000000..e17dd10
--- /dev/null
+++ b/task_plan.md
@@ -0,0 +1,17 @@
+# Task Plan
+
+## Goal
+完成 Task 5 的全量验证:运行 `npx vitest run`、`npm run lint`、`npm run typecheck`,评估是否有必要执行 `npx playwright test tests/e2e/batch-rename.spec.ts`,并以新的规划文件/验证状态提交变更。
+
+## Phases
+| Phase | Status | Notes |
+| --- | --- | --- |
+| 1. 校准 Task 5 上下文与规划 | complete | 已更新 planning files、确认工作区 `/Users/lhly/chromeex/cloud-drive-renamer/.worktrees/preview-panel-option-a` 以及分支 `vibe/preview-panel-option-a`。 |
+| 2. 运行 `npx vitest run` | complete | 全量单测(31 个测试文件、249 个测试)通过,命令耗时 15.16s。 |
+| 3. 运行 `npm run lint` 与 `npm run typecheck` | complete | `npm run lint` 成功但报告 270 条 warning(以 `@typescript-eslint/no-explicit-any` 为主,也包含 `no-console` 与 `@typescript-eslint/no-unused-vars`);`npm run typecheck`(`tsc --noEmit`)通过。 |
+| 4. 评估 `npx playwright test tests/e2e/batch-rename.spec.ts` | complete | 该测试依赖 `https://pan.quark.cn` 站点、扩展注入与登录凭据,当前环境无凭据且不可控,因此评估后决定跳过执行。 |
+| 5. 汇总结果、提交 planning files | complete | planning files 已记录所有命令/评估结论并准备提交。 |
+
+## Errors Encountered
+| Error | Attempt | Resolution |
+| --- | --- | --- |
diff --git a/tests/unit/virtual-preview-list.test.ts b/tests/unit/virtual-preview-list.test.ts
index 5847543..2c55b6b 100644
--- a/tests/unit/virtual-preview-list.test.ts
+++ b/tests/unit/virtual-preview-list.test.ts
@@ -1,4 +1,5 @@
import { describe, expect, it, vi } from 'vitest';
+import { PreviewPanel } from '../../src/content/components/preview-panel';
import { VirtualPreviewList } from '../../src/content/components/virtual-preview-list';
import { I18nService } from '../../src/utils/i18n';
@@ -183,27 +184,126 @@ describe('VirtualPreviewList pending preview mapping', () => {
const newNameText = previewContent?.querySelector('.new-name-text');
expect(newNameText).toBeTruthy();
- const styleContent = Array.from(element.shadowRoot?.querySelectorAll('style') ?? [])
- .map((style) => style.textContent ?? '')
+ const stylesArray = Array.isArray(VirtualPreviewList.styles)
+ ? VirtualPreviewList.styles
+ : [VirtualPreviewList.styles];
+ const staticCss = stylesArray
+ .map((result) => ('cssText' in result ? (result as any).cssText : result.toString()))
.join(' ');
- const extractBlock = (selector: string) => {
- const start = styleContent.indexOf(selector);
- expect(start, `missing ${selector}`).toBeGreaterThan(-1);
- const braceOpen = styleContent.indexOf('{', start);
- const braceClose = styleContent.indexOf('}', braceOpen);
- return styleContent.slice(braceOpen + 1, braceClose);
+ const selectorBlock = (selector: string) => {
+ const escaped = selector.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&');
+ const regex = new RegExp(`${escaped}\\s*\\{([^}]*)\\}`, 's');
+ const match = regex.exec(staticCss);
+ expect(match, `missing ${selector}`).toBeTruthy();
+ return match![1];
};
- const oldNameDeclarations = extractBlock('.old-name.old-name-secondary');
+ const oldNameDeclarations = selectorBlock('.old-name.old-name-secondary');
expect(oldNameDeclarations).toContain('overflow: hidden');
expect(oldNameDeclarations).toContain('text-overflow: ellipsis');
expect(oldNameDeclarations).toContain('white-space: nowrap');
- const newNameDeclarations = extractBlock('.new-name.new-name-primary .new-name-text');
+ const baseOldNameDeclarations = selectorBlock('.old-name');
+ expect(baseOldNameDeclarations).toContain('font-size: 12px');
+ expect(baseOldNameDeclarations).toContain('color: var(--cdr-text-secondary, #595959)');
+ expect(baseOldNameDeclarations).not.toContain('overflow: hidden');
+ expect(baseOldNameDeclarations).not.toContain('text-overflow: ellipsis');
+ expect(baseOldNameDeclarations).not.toContain('white-space: nowrap');
+
+ const newNameDeclarations = selectorBlock('.new-name-primary .new-name-text');
expect(newNameDeclarations).toContain('overflow: hidden');
expect(newNameDeclarations).toContain('text-overflow: ellipsis');
expect(newNameDeclarations).toContain('white-space: nowrap');
+ const baseNewNameDeclarations = selectorBlock('.new-name-text');
+ expect(baseNewNameDeclarations).not.toContain('overflow: hidden');
+ expect(baseNewNameDeclarations).not.toContain('text-overflow: ellipsis');
+ expect(baseNewNameDeclarations).not.toContain('white-space: nowrap');
+
element.remove();
});
});
+
+describe('PreviewPanel integration with VirtualPreviewList', () => {
+ it('passes mixed preview items and status mode through while keeping stats accurate', async () => {
+ const items = [
+ {
+ file: {
+ id: 'file-done',
+ name: 'done-old.txt',
+ ext: 'txt',
+ size: 1,
+ mtime: Date.now(),
+ isDir: false,
+ },
+ newName: 'done-new.txt',
+ conflict: false,
+ done: true,
+ },
+ {
+ file: {
+ id: 'file-error',
+ name: 'error-old.txt',
+ ext: 'txt',
+ size: 1,
+ mtime: Date.now(),
+ isDir: false,
+ },
+ newName: 'error-new.txt',
+ conflict: false,
+ error: 'extract_episode_not_found',
+ },
+ {
+ file: {
+ id: 'file-pending',
+ name: 'pending-old.txt',
+ ext: 'txt',
+ size: 1,
+ mtime: Date.now(),
+ isDir: false,
+ },
+ newName: 'pending-new.txt',
+ conflict: false,
+ done: false,
+ },
+ ];
+
+ const panel = new PreviewPanel();
+ panel.items = items;
+ panel.conflictCount = 1;
+ panel.showStatus = true;
+
+ document.body.appendChild(panel);
+ await panel.updateComplete;
+
+ const stats = new Map(
+ Array.from(panel.shadowRoot?.querySelectorAll('.panel-stats .stat-item') ?? []).map((item) => [
+ item.querySelector('.stat-label')?.textContent?.trim() ?? '',
+ item.querySelector('.stat-value')?.textContent?.trim() ?? '',
+ ])
+ );
+
+ expect(stats.get(I18nService.t('preview_items'))).toBe('3');
+ expect(stats.get(I18nService.t('progress_remaining'))).toBe('1');
+ expect(stats.get(I18nService.t('progress_success'))).toBe('1');
+ expect(stats.get(I18nService.t('progress_failed'))).toBe('1');
+ expect(stats.get(I18nService.t('preview_summary_conflict'))).toBe('1');
+
+ const previewList = panel.shadowRoot?.querySelector('virtual-preview-list') as VirtualPreviewList | null;
+ expect(previewList).toBeTruthy();
+ expect(previewList?.items).toBe(items);
+ expect(previewList?.showStatus).toBe(true);
+
+ await previewList?.updateComplete;
+
+ const previewText = previewList?.shadowRoot?.textContent ?? '';
+ expect(previewText).toContain('done-old.txt');
+ expect(previewText).toContain('done-new.txt');
+ expect(previewText).toContain('error-old.txt');
+ expect(previewText).toContain('error-new.txt');
+ expect(previewText).toContain('pending-old.txt');
+ expect(previewText).toContain('pending-new.txt');
+
+ panel.remove();
+ });
+});