diff --git a/findings.md b/findings.md new file mode 100644 index 0000000..293d65f --- /dev/null +++ b/findings.md @@ -0,0 +1,9 @@ +# Findings + +- 2026-03-20:Task 5 要求在 `vibe/preview-panel-option-a` 分支(HEAD `b7e4d944d3738ed33a4c5d1ffeb5e698b07bf3db`)上完成 `npx vitest run`、`npm run lint`、`npm run typecheck` 并更新 planning files。 +- 2026-03-20:工作区 `/Users/lhly/chromeex/cloud-drive-renamer/.worktrees/preview-panel-option-a` 已切换到 Task 5 验证;前期 Task 4 结论仍保持可参考性但不再是当前目标。 +- 2026-03-20:`tests/e2e/batch-rename.spec.ts` 直接访问 `https://pan.quark.cn`、等待扩展注入并操作 shadow DOM 中的 `rename-dialog`,依赖在线网站、未登录环境与真实 DOM,因此在常规 CI/本地环境中易失败、耗时并且不可控。 +- 2026-03-20:主控复核 `npx vitest run` 的实际结果为 31 个测试文件、249 个测试,全部通过,运行耗时约 15.16s;早先子代理记录的 62/497 计数不准确,已修正。 +- 2026-03-20:`npm run lint` 成功退出但报告 270 条 warning;其中以 `@typescript-eslint/no-explicit-any` 为主,也包含部分 `no-console` 与 `@typescript-eslint/no-unused-vars`,当前计划仅记录警告,不做改动。 +- 2026-03-20:`npm run typecheck`(`tsc --noEmit`)已通过,未发现新的类型错误。 +- 2026-03-20:Playwright 评估结论:由于测试需要访问 `https://pan.quark.cn`、等待扩展注入并登录,当前执行环境无法复现这些条件,因此决定跳过 `npx playwright test tests/e2e/batch-rename.spec.ts`。 diff --git a/progress.md b/progress.md new file mode 100644 index 0000000..d884443 --- /dev/null +++ b/progress.md @@ -0,0 +1,11 @@ +# Progress + +- 2026-03-20:接手 Task 5 验证,明确需运行 `npx vitest run`、`npm run lint`、`npm run typecheck` 并复核 Playwright 是否必要。 +- 2026-03-20:根据规划指令阅读 `planning-with-files` 技能、执行 session catchup、确认 `/Users/lhly/chromeex/cloud-drive-renamer/.worktrees/preview-panel-option-a` 以及 `vibe/preview-panel-option-a`。 +- 2026-03-20:查看 `tests/e2e/batch-rename.spec.ts`,确认它依赖 `https://pan.quark.cn` 页面、shadow DOM 交互,意味着 Playwright 测试在一般环境中需要真实扩展注入与登录凭据。 +- 2026-03-20:更新 `task_plan.md`/`findings.md`/`progress.md` 以匹配 Task 5 的目标和执行流程,准备后续验证步骤。 +- 2026-03-20:执行 `npx vitest run`,31 个测试文件、249 个测试通过,完整运行耗时约 15.16 秒(未出现失败)。 +- 2026-03-20:运行 `npm run lint`,命令成功返回但报告 270 条 warning(以 `@typescript-eslint/no-explicit-any` 为主,也包含 `no-console` 与 `@typescript-eslint/no-unused-vars`);未列出错误。 +- 2026-03-20:运行 `npm run typecheck`(`tsc --noEmit`)成功,未发现类型错误。 +- 2026-03-20:评估 `npx playwright test tests/e2e/batch-rename.spec.ts`;由于测试需要访问 `https://pan.quark.cn`、等待扩展注入并登入,本地/CI 无法提供,因此决定跳过执行并在输出中说明原因。 +- 2026-03-20:主控再次复核 `npx vitest run` / `npm run lint` / `npm run typecheck`,确认验证结果成立,并修正 planning files 中早先写大的测试计数。 diff --git a/src/content/components/virtual-preview-list.ts b/src/content/components/virtual-preview-list.ts index 5ef7fcb..c09e5ee 100644 --- a/src/content/components/virtual-preview-list.ts +++ b/src/content/components/virtual-preview-list.ts @@ -121,8 +121,8 @@ export class VirtualPreviewList extends LitElement { const newNameClasses = ['new-name', 'new-name-primary', statusClass].filter(Boolean).join(' '); return html` -
-
+
+
${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(); + }); +});