镜像自地址
https://github.com/lhly/cloud-drive-renamer.git
已同步 2026-10-20 13:47:47 +08:00
Merge branch 'vibe/preview-panel-option-a'
# Conflicts: # src/content/components/virtual-preview-list.ts # tests/unit/virtual-preview-list.test.ts
这个提交包含在:
当前提交
ac517f16db
9
findings.md
普通文件
9
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`。
|
||||||
11
progress.md
普通文件
11
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 中早先写大的测试计数。
|
||||||
@ -121,8 +121,8 @@ export class VirtualPreviewList extends LitElement {
|
|||||||
const newNameClasses = ['new-name', 'new-name-primary', statusClass].filter(Boolean).join(' ');
|
const newNameClasses = ['new-name', 'new-name-primary', statusClass].filter(Boolean).join(' ');
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="${previewItemClasses}" data-preview-item="true">
|
<div class="${previewItemClasses}">
|
||||||
<div class="preview-content" data-preview-content="true">
|
<div class="preview-content">
|
||||||
<div class="old-name-row">
|
<div class="old-name-row">
|
||||||
<div class="old-name old-name-secondary" title=${item.file.name}>
|
<div class="old-name old-name-secondary" title=${item.file.name}>
|
||||||
${item.file.name}
|
${item.file.name}
|
||||||
@ -270,12 +270,11 @@ export class VirtualPreviewList extends LitElement {
|
|||||||
.old-name {
|
.old-name {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--cdr-text-secondary, #595959);
|
color: var(--cdr-text-secondary, #595959);
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.old-name.old-name-secondary {
|
.old-name.old-name-secondary {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -293,10 +292,12 @@ export class VirtualPreviewList extends LitElement {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.new-name-text,
|
.new-name-text {
|
||||||
.new-name.new-name-primary .new-name-text {
|
flex: 1 1 0;
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.new-name-primary .new-name-text {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
17
task_plan.md
普通文件
17
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 |
|
||||||
|
| --- | --- | --- |
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
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 { VirtualPreviewList } from '../../src/content/components/virtual-preview-list';
|
||||||
import { I18nService } from '../../src/utils/i18n';
|
import { I18nService } from '../../src/utils/i18n';
|
||||||
|
|
||||||
@ -183,27 +184,126 @@ describe('VirtualPreviewList pending preview mapping', () => {
|
|||||||
const newNameText = previewContent?.querySelector('.new-name-text');
|
const newNameText = previewContent?.querySelector('.new-name-text');
|
||||||
expect(newNameText).toBeTruthy();
|
expect(newNameText).toBeTruthy();
|
||||||
|
|
||||||
const styleContent = Array.from(element.shadowRoot?.querySelectorAll('style') ?? [])
|
const stylesArray = Array.isArray(VirtualPreviewList.styles)
|
||||||
.map((style) => style.textContent ?? '')
|
? VirtualPreviewList.styles
|
||||||
|
: [VirtualPreviewList.styles];
|
||||||
|
const staticCss = stylesArray
|
||||||
|
.map((result) => ('cssText' in result ? (result as any).cssText : result.toString()))
|
||||||
.join(' ');
|
.join(' ');
|
||||||
const extractBlock = (selector: string) => {
|
const selectorBlock = (selector: string) => {
|
||||||
const start = styleContent.indexOf(selector);
|
const escaped = selector.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&');
|
||||||
expect(start, `missing ${selector}`).toBeGreaterThan(-1);
|
const regex = new RegExp(`${escaped}\\s*\\{([^}]*)\\}`, 's');
|
||||||
const braceOpen = styleContent.indexOf('{', start);
|
const match = regex.exec(staticCss);
|
||||||
const braceClose = styleContent.indexOf('}', braceOpen);
|
expect(match, `missing ${selector}`).toBeTruthy();
|
||||||
return styleContent.slice(braceOpen + 1, braceClose);
|
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('overflow: hidden');
|
||||||
expect(oldNameDeclarations).toContain('text-overflow: ellipsis');
|
expect(oldNameDeclarations).toContain('text-overflow: ellipsis');
|
||||||
expect(oldNameDeclarations).toContain('white-space: nowrap');
|
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('overflow: hidden');
|
||||||
expect(newNameDeclarations).toContain('text-overflow: ellipsis');
|
expect(newNameDeclarations).toContain('text-overflow: ellipsis');
|
||||||
expect(newNameDeclarations).toContain('white-space: nowrap');
|
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();
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户