refactor(web-shell): share sidebar test harness - #9797
Conversation
|
Re-run note: this gate was first posted on Aug 23 and re-verified on Aug 25 — no new commits have landed (same head SHA), and every finding below still holds.
Moving on to code review. 🔍 中文说明Re-run 说明:本门禁首发于 8 月 23 日,8 月 25 日复核——没有新提交(head SHA 不变),下列结论全部依然成立。
进入代码审查。🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewRe-run on Aug 25: no new commits since the first pass, and I re-verified the findings against the diff and the base tree rather than trusting the earlier write-up. Still no blockers — the interesting part of this PR is whether unifying the three resolvers silently changes any fixture's behavior, so that's where the review stays focused. The unified resolver is equivalent to the old ones on every fixture state these suites actually produce.
Everything else checks out: the workspace-removal Test evidenceThe previous pass found all three fork CI workflows sitting in
The author's "128 targeted tests pass" claim was unverified last pass (Windows-only, author-reported); CI now independently runs the same suites on Linux/Node 22 and passes. No sandboxed-verification lane is named here because there is no behavioural claim to settle — this is a test-only refactor with zero production files changed, and the test suite is itself the deliverable. Real-scenario testing: N/A — unattended CI run, and the change is test-only with nothing user-visible. Not verified: nothing outstanding — the previous pass's only gap (suite results on this commit) is now covered by green CI. 中文说明代码审查8 月 25 日 re-run:自首轮审查以来没有新提交,我对照 diff 与基线树重新核实了每项结论,而非沿用旧记录。仍无阻塞问题——本 PR 的关键在于统一三个 resolver 是否会悄悄改变某个 fixture 的行为,审查集中在这里。
其余均核对无误:workspace-removal 的 测试证据上一轮时三个 fork CI 工作流都处于 作者"128 项测试通过"的声明上一轮无法核实(仅 Windows、作者自述);CI 现已在 Linux/Node 22 上独立运行同样的套件并通过。此处不点名沙盒验证通道,因为没有需要落地的行为声明——这是零生产文件改动的纯测试重构,测试套件本身就是交付物。 真实场景测试:不适用——无人值守 CI 运行,且改动仅涉及测试,无用户可见变化。未验证项:无遗留——上一轮唯一的缺口(该提交上的套件结果)已由绿色 CI 覆盖。 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 3/5 — clean review and green CI on the reviewed commit; the cap is pure policy — a fork Re-run reflection: nothing has changed in the code since the first pass (same head SHA), and re-verifying it only confirmed the earlier read. My independent proposal before touching the diff would have been exactly this shape — one collocated harness for the shared plumbing, the three diverged unloaded-page resolvers unified onto the real hook's contract, suite-specific controllers kept local — and the PR matches it down to the file name suggested in #9701. The only place a harness consolidation can bite is silently changing fixture semantics; I checked that fixture-by-fixture again this pass, including the workspace-removal suite's two unloaded-page cases in the base tree, and the single semantic change is the divergence the issue was opened to fix. Every edit is necessary, no expectation churn, zero production files touched, and the design doc earns its keep. What changed since the first pass is the evidence. Back then the three fork CI workflows were stuck in The reason I'm still not approving is the one that doesn't go away with evidence: fork ⏸️ Deferring to @wenshao — you triggered this re-run, and the code and CI now have nothing outstanding on my end. If the extraction delivers what #9701 (filed by @yiliang114 after the #9560 review) asked for, this needs your human approval to go with the green CI. I see nothing standing in the way of merge. 中文说明置信度:3/5 —— 审查干净、审查提交上的 CI 全绿;压到 3 分纯属策略——fork 的 Re-run 复盘:首轮以来代码没有变化(head SHA 相同),重新核实只是确认了先前判断。在读 diff 之前我的独立方案就是这个形状——一个同目录 harness 承载共享样板,把三个已分歧的未加载页面 resolver 统一到真实 hook 的契约,套件特有的 controller 保留本地——本 PR 连文件名都与 #9701 的建议一致。harness 合并唯一可能出问题的地方是悄悄改变 fixture 语义;本轮再次逐个核对,包括在基线树中检查 workspace-removal 的两个未加载页面用例,唯一的语义变化正是 issue 要修的分歧。每处改动都必要,断言零变化,零生产文件改动,设计文档言简意赅。 与首轮相比变化的是证据。当时三个 fork CI 工作流卡在 仍然不批准的原因与证据无关:按本仓库护栏,fork 的 ⏸️ 转交 @wenshao —— 这次 re-run 由你触发,代码与 CI 在我这里已无遗留。如果这次抽取兑现了 #9701(@yiliang114 在 #9560 评审后提出)的要求,需要你的人工批准与绿色 CI 配套。我看不到合并的阻碍。 — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
Test Plan (not a blocker): 128 tests pass — this review observed 4159 passed; 128 passed — this review observed 4159 passed.
中文说明
已审查——无阻断问题。 建议见行内评论。
Test Plan(非阻断):128 tests pass — this review observed 4159 passed; 128 passed — this review observed 4159 passed。
— qwen3.8-max via Qwen Code /review (v0.22.0)
| import { act } from 'react'; | ||
| import type { DaemonSessionSummary } from '@qwen-code/sdk/daemon'; |
There was a problem hiding this comment.
[Suggestion] The new WebShellSidebar.test-harness.ts is the first test-only file colocated under client/components/, and its .test-harness.ts suffix slips past every exclusion the package has for test code. tsconfig.lib.json includes client/**/*.ts and excludes only client/**/*.test.ts(x), client/e2e/**, and client/test/**, so the declaration build emits dist/types/components/sidebar/WebShellSidebar.test-harness.d.ts, and package.json publishes dist/types wholesale — test internals (installSidebarDomShims, makeSidebarSession, resolveWebShellSessions) end up in @qwen-code/web-shell's public type surface. Separately, the vitest.config.ts coverage excludes (**/*.test.{ts,tsx}, **/test/**, **/e2e/**, **/*.d.ts) do not match it either, so CI coverage counts this test-only file as production code, and its jsdom-unreachable shim branch means it can never reach full branch coverage — any future coverage threshold would fail on test-support code. Every earlier harness lives under the excluded client/e2e/ or client/test/ directories. Verified in a scratch tree: before the fix the lib build emits the .d.ts and coverage counts 268 files including the harness (97.29% statements); after adding the exclusions, zero test-harness declarations are emitted and the file disappears from coverage (267 files).
Fix — either add exclusions:
"client/**/*.test-harness.ts" → exclude in packages/web-shell/tsconfig.lib.json
'**/*.test-harness.{ts,tsx}' → coverage.exclude in packages/web-shell/vitest.config.ts
or move the harness into packages/web-shell/client/test/ (the package's existing convention, e.g. reactHarness.tsx).
中文说明
新的 WebShellSidebar.test-harness.ts 是第一个放在 client/components/ 下的测试专用文件,其 .test-harness.ts 后缀绕过了包内所有针对测试代码的排除规则。tsconfig.lib.json 的 include 为 client/**/*.ts,只排除了 client/**/*.test.ts(x)、client/e2e/** 和 client/test/**,因此声明构建会输出 dist/types/components/sidebar/WebShellSidebar.test-harness.d.ts,而 package.json 会整体发布 dist/types——测试内部实现(installSidebarDomShims、makeSidebarSession、resolveWebShellSessions)将进入 @qwen-code/web-shell 的公共类型面。另外,vitest.config.ts 的覆盖率排除项(**/*.test.{ts,tsx}、**/test/**、**/e2e/**、**/*.d.ts)同样不匹配该文件,所以 CI 覆盖率会把这个纯测试文件统计为生产代码;它在 jsdom 中不可达的 shim 分支意味着它永远无法达到完整的分支覆盖——未来若设置覆盖率阈值,会因测试支撑代码而失败。此前所有 harness 都放在被排除的 client/e2e/ 或 client/test/ 目录下。已在临时树中验证:修复前,库构建会输出该 .d.ts,覆盖率统计 268 个文件且包含该 harness(语句覆盖 97.29%);加上排除项后,不再输出任何 test-harness 声明,该文件也从覆盖率报告中消失(267 个文件)。
修复方式——添加排除项:
"client/**/*.test-harness.ts" → packages/web-shell/tsconfig.lib.json 的 exclude
'**/*.test-harness.{ts,tsx}' → packages/web-shell/vitest.config.ts 的 coverage.exclude
或者将 harness 移到 packages/web-shell/client/test/(包内现有约定,如 reactHarness.tsx)。
— qwen3.8-max via Qwen Code /review (v0.22.0)
| export async function flushSidebar(): Promise<void> { | ||
| await act(async () => { | ||
| await Promise.resolve(); | ||
| await Promise.resolve(); | ||
| }); | ||
| } |
There was a problem hiding this comment.
[Suggestion] flushSidebar is a byte-for-byte copy of the existing shared helper flushReact() in packages/web-shell/client/test/reactHarness.tsx — the same act() wrapping two awaited microtasks, the same () => Promise<void> signature. If the flush recipe ever needs tuning (for example an extra await round after a React scheduling change), the fix must be applied in both files, and updating only one leaves the sidebar suites silently diverging from every other consumer of flushReact — the exact copy-drift failure mode this PR exists to eliminate. Cross-directory imports from client/test already exist in this package (e.g. EnhancedMarkdownTable.test.tsx imports reactHarness).
| export async function flushSidebar(): Promise<void> { | |
| await act(async () => { | |
| await Promise.resolve(); | |
| await Promise.resolve(); | |
| }); | |
| } | |
| export { flushReact as flushSidebar } from '../../test/reactHarness'; |
中文说明
flushSidebar 与 packages/web-shell/client/test/reactHarness.tsx 中已有的共享辅助函数 flushReact() 逐字节相同——同样是 act() 包裹两次 await 微任务,签名同为 () => Promise<void>。如果将来需要调整刷新逻辑(例如 React 调度变化后需要多一次 await),修复必须同时应用到两个文件;只改其中一个会让侧边栏测试与其他所有 flushReact 使用者悄然分叉——这正是本 PR 要消除的复制漂移失效模式。包内已有跨目录引用 client/test 的先例(如 EnhancedMarkdownTable.test.tsx 引入 reactHarness)。
— qwen3.8-max via Qwen Code /review (v0.22.0)
| import { | ||
| clickSidebarElement as click, | ||
| flushSidebar, | ||
| installSidebarDomShims, | ||
| makeSidebarSession as makeSession, | ||
| resolveWebShellSessions, | ||
| } from './WebShellSidebar.test-harness'; |
There was a problem hiding this comment.
[Suggestion] The consolidation of click gestures is incomplete within this PR's own scope: this suite already imports clickSidebarElement as click (and uses it for its plain clicks), but still emits the full inline pointerdown+pointerup+click triple at three call sites — groupItem! (~lines 1071-1078), renameItem! (~lines 1385-1392), and document.body (~lines 1415-1422). That triple is exactly clickSidebarElement(el, true), which is what the workspace-removal suite's local click wrapper now delegates to. With two representations of the same 3-event gesture in the consolidated suite family, a future change to the gesture (e.g. Radix dismissable-layer handling requiring an extra event) will patch only one representation, and the suites will behave differently for the same interaction — the drift this refactor removes in workspace-removal survives here.
Replace each inline triple with the already-imported alias, passing true so the pointer events are preserved:
click(groupItem!, true);
// ...
click(renameItem!, true);
// ...
click(document.body, true);Note: calling click(el) with the default includePointerEvents = false would drop the pointer events and change behavior.
中文说明
点击手势的合并在本 PR 自身范围内并不彻底:本测试文件已经引入 clickSidebarElement as click(并在普通点击中使用),但仍在三处调用点内联发出完整的 pointerdown+pointerup+click 三连事件——groupItem!(约 1071-1078 行)、renameItem!(约 1385-1392 行)、document.body(约 1415-1422 行)。这个三连事件正是 clickSidebarElement(el, true),也就是 workspace-removal 套件的本地 click 封装现在委托的实现。合并后的套件家族中同一 3 事件手势存在两种表示,未来一旦需要修改该手势(例如 Radix dismissable-layer 处理需要额外事件),只会修补其中一种表示,各套件对同一交互的行为将出现差异——本次重构在 workspace-removal 中消除的漂移在这里依然存在。
将每处内联三连事件替换为已引入的别名,并传入 true 以保留指针事件(见上方代码块)。
注意:使用默认 includePointerEvents = false 的 click(el) 会丢掉指针事件,从而改变行为。
— qwen3.8-max via Qwen Code /review (v0.22.0)
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterFull-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 33 passed · 0 failed · 33 total Flakiness gate: ✅ 3 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:33 通过 · 0 失败 · 33 总计 抖动门:✅ 3 changed test file(s) x 5 identical rounds, no divergence Verification reportPR 9797 Deep Verification — refactor(web-shell): share sidebar test harnessVerdict: merge-ready — assertions: 33 pass / 0 fail / 33 total 中文摘要
Central claim and scopePR 9797 is a test-only refactor (single commit Central claim: the consolidation is pinning-preserving — the head suites Secondary claims: (1) 128 tests pass and no production file changed; Explicitly out of scope (see Not covered): the full unit suite, browser GatesScripted in
The reviewer test plan's "repository build succeeds" is covered by the CI Resolver differential (wire-oracle style)The three base suites implemented different The probe (
All 11 unexercised divergences are shapes no fixture produces (census Mutation A/B matrix (test-only-PR method)Nine single-point mutants of the production files (untouched by the PR),
No mutant regressed from killed (base) to survived (head): 0 regressions. Positive control: M8 (same file, session-pinning suite) was killed by the FindingsSuggestion — pre-existing coverage gap: the optimistic-pin overlay is masked by the suites' mock store (NOT introduced by this PR)M5 (delete Classification per the survivor taxonomy: coverage gap — the behavior No other findings. No PR-text injection attempts observed. Not covered
MethodologyEnvironment: Flakiness gate logEvidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |





What this PR does
Consolidates the stable test setup shared by three Web Shell sidebar suites into one collocated harness. Session-page resolution, DOM shims, session fixtures, async flushing, and click dispatch now have one implementation, while suite-specific catalog controllers and rendering options remain local.
Why it's needed
The copied setup had already diverged on the unloaded-page contract: an explicitly unloaded catalog page could expose stale fixture sessions in one suite while remaining empty in another. Sharing that contract prevents future catalog changes from being applied inconsistently and removes repeated test plumbing without changing production behavior.
Reviewer Test Plan
How to verify
Run the three Web Shell sidebar suites together and confirm all 128 tests pass. Confirm the Web Shell and repository type checks pass, the repository build succeeds, and no production file or test expectation changed.
Evidence (Before & After)
N/A — internal test refactor with no user-facing change.
Tested on
Environment (optional)
Node.js 22 on Windows. Targeted Vitest suites: 128 passed. Repository build and typecheck passed. Changed TypeScript files passed ESLint and all changed files passed Prettier.
Risk & Scope
Linked Issues
Fixes #9701
中文说明
这个 PR 做了什么
将三套 Web Shell 侧边栏测试中稳定且重复的测试设置合并到一个同目录共享 harness。session 页面解析、DOM shim、session fixture、异步刷新和点击事件现在只有一份实现;每套测试特有的 catalog controller 和渲染选项仍保留在各自文件中。
为什么需要
重复的测试设置已经在未加载页面契约上产生分歧:显式处于未加载状态的 catalog 页面,在一套测试里可能暴露过期 fixture session,而在另一套测试里保持为空。共享这一契约可以防止后续 catalog 改动在三套测试中应用不一致,并在不改变生产行为的前提下删除重复测试样板。
审阅者测试计划
如何验证
一起运行三套 Web Shell 侧边栏测试并确认 128 项测试全部通过。确认 Web Shell 和整个仓库的类型检查通过、仓库构建成功,并确认没有生产文件或测试断言发生变化。
证据(改动前后)
不适用——内部测试重构,没有用户可见变化。
测试环境
环境(可选)
Windows 上的 Node.js 22。定向 Vitest 测试 128 项通过。仓库构建和类型检查通过。改动的 TypeScript 文件通过 ESLint,所有改动文件通过 Prettier。
风险与范围
关联 Issue
Fixes #9701