Skip to content

test(cli): Cover rewind selection and confirm flow#5044

Merged
wenshao merged 2 commits into
QwenLM:mainfrom
doudouOUC:codex/rewind-test-coverage-4187
Jun 13, 2026
Merged

test(cli): Cover rewind selection and confirm flow#5044
wenshao merged 2 commits into
QwenLM:mainfrom
doudouOUC:codex/rewind-test-coverage-4187

Conversation

@doudouOUC

Copy link
Copy Markdown
Collaborator

What this PR does

This PR adds focused regression coverage for the rewind selector flow and the rewind confirmation orchestration. The tests now cover selector navigation and cancel paths, restore option fallback states, the restoring keypress guard, and the main rewind confirmation branches for code, conversation, combined restore, no-client fallback, compressed turns, and file restore failures.

Why it's needed

Issue #4187 tracks missing automated coverage for rewind behavior that was previously verified manually. These paths coordinate UI state, file checkpoint restore, API history truncation, buffer prefill, and chat recording updates, so focused tests reduce the risk of future regressions leaving files and conversation history out of sync.

Reviewer Test Plan

How to verify

Run cd packages/cli && npx vitest run src/ui/components/RewindSelector.test.tsx src/ui/AppContainer.test.tsx and expect both test files to pass, including the new rewind selector and handleRewindConfirm cases. Run npm run typecheck from the repository root and expect TypeScript checks to complete successfully across workspaces.

Evidence (Before & After)

N/A. This is a non-user-visible test coverage change.

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

Node.js v22.22.3, npm 10.9.8.

Risk & Scope

  • Main risk or tradeoff: The new AppContainer tests use the existing context-capture harness and mocks to exercise the rewind action directly, so they intentionally validate orchestration behavior without driving the full dialog UI.
  • Not validated / out of scope: Full end-to-end terminal rewind sessions, cross-platform runtime behavior, and production code changes are out of scope.
  • Breaking changes / migration notes: None.

Linked Issues

Closes #4187

中文说明

What this PR does

这个 PR 为 rewind selector 流程和 rewind confirm 编排逻辑增加了聚焦的回归测试。测试现在覆盖 selector 导航和取消路径、restore options 的 fallback 状态、restoring 状态下的按键屏蔽,以及 code、conversation、both restore、no-client fallback、compressed turn、file restore failure 等主要 rewind confirm 分支。

Why it's needed

Issue #4187 追踪的是 rewind 行为缺少自动化覆盖的问题,此前这些路径主要依赖手工验证。这些路径会同时协调 UI 状态、文件 checkpoint 恢复、API history 截断、buffer 预填和 chat recording 更新,因此聚焦测试可以降低未来回归导致文件和对话历史状态不一致的风险。

Reviewer Test Plan

How to verify

运行 cd packages/cli && npx vitest run src/ui/components/RewindSelector.test.tsx src/ui/AppContainer.test.tsx,预期两个测试文件都通过,并包含新增的 rewind selector 和 handleRewindConfirm 用例。然后在仓库根目录运行 npm run typecheck,预期所有 workspace 的 TypeScript 检查成功完成。

Evidence (Before & After)

N/A。这是非用户可见的测试覆盖改动。

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

Node.js v22.22.3,npm 10.9.8。

Risk & Scope

  • Main risk or tradeoff: 新增的 AppContainer 测试使用现有 context-capture harness 和 mocks 直接触发 rewind action,因此它们刻意验证编排行为,而不是驱动完整 dialog UI。
  • Not validated / out of scope: 完整的端到端终端 rewind 会话、跨平台运行时行为和生产代码改动不在本 PR 范围内。
  • Breaking changes / migration notes: 无。

Linked Issues

Closes #4187

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC doudouOUC marked this pull request as ready for review June 12, 2026 15:07
Copilot AI review requested due to automatic review settings June 12, 2026 15:07

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wenshao wenshao left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The handleRewindConfirm branch where (userItem as HistoryItemUser).promptId is falsy (AppContainer.tsx:2562-2567 — "Cannot restore files: this turn was created before file checkpointing was enabled.") has no test coverage. The rewindUserItem helper always provides a promptId, so this error path is unreachable from the current tests. Consider adding a case that passes a user item without promptId and asserts the error message.

— qwen3.7-max via Qwen Code /review

Comment thread packages/cli/src/ui/AppContainer.test.tsx
Comment thread packages/cli/src/ui/AppContainer.test.tsx
@doudouOUC doudouOUC requested review from DragonnZhang and chiga0 June 12, 2026 17:28
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@wenshao

wenshao commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓ — all required sections present, bilingual, linked to #4187.

On direction: clearly aligned. Issue #4187 explicitly calls for automated coverage of the rewind selector and handleRewindConfirm paths introduced in #4064. Test-only PR closing a known coverage gap — no direction concerns.

On approach: the scope matches the issue's acceptance criteria closely. The renderRewindHarness helper in AppContainer.test.tsx is a reasonable ~90-line reusable setup for exercising the orchestration branches, and the RewindSelector.test.tsx additions fill the remaining gaps (arrow-key navigation, isRestoring guard, legacy confirm cancel). Nothing feels over-scoped or speculative.

Moving on to code review and testing. 🔍

中文说明

感谢贡献!

模板完整 ✓ — 所有必需部分齐全,双语,已关联 #4187

方向:明确对齐。Issue #4187 明确要求为 #4064 引入的 rewind selector 和 handleRewindConfirm 路径补充自动化覆盖。纯测试 PR 填补已知覆盖缺口,无方向问题。

方案:范围与 issue 的验收标准紧密对应。AppContainer.test.tsx 中约 90 行的 renderRewindHarness 辅助函数合理复用了编排分支的测试设置,RewindSelector.test.tsx 的新增用例补齐了剩余缺口(方向键导航、isRestoring 守卫、legacy confirm 取消)。没有过度设计或投机性代码。

进入代码审查和测试 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

2a. Code Review

Reviewed the diff against the source implementations of RewindSelector and handleRewindConfirm. The tests are well-aligned with the production code — every branch assertion I spot-checked maps correctly to the implementation logic.

RewindSelector.test.tsx — the new tests fill real gaps:

  • Arrow-key navigation (up/down) in the pick list was previously untested (only Ctrl+P/N had coverage). New test covers this cleanly.
  • isRestoring keypress guard — uses an unresolved Promise to hold the restoring state, then verifies Escape is blocked. Correct pattern.
  • Legacy confirm cancel via n and Escape — parameterized test, both paths return to pick list. Matches the source handler's handleConfirmSelect(false) logic.
  • "Never mind" option assertion added to the existing restore-options test.

AppContainer.test.tsx — the renderRewindHarness helper (~90 LOC) is a good reusable fixture. All 10 handleRewindConfirm test cases verified against the source:

  • rewind(promptId, truncateHistory) flag logic correctly differs between modes
  • The apiTruncateIndex < 0 compressed-turn check is tested with a deliberately-short apiHistory
  • No-client fallback: conversation-only shows error, both-mode falls back to code-only with warning
  • Outer try/catch: mocked getGeminiClient throwing is properly surfaced through addItem

No critical issues found. No AGENTS.md violations — follows existing test conventions (collocated .test.tsx, vi.mock/vi.spyOn, ink-testing-library).

2b. Test Execution

This is a test-only PR with no user-visible behavior changes, so tmux UI capture is N/A. Verification is the test suite itself:

$ cd packages/cli && npx vitest run src/ui/components/RewindSelector.test.tsx

 ✓ src/ui/components/RewindSelector.test.tsx (10 tests) 344ms

 Test Files  1 passed (1)
      Tests  10 passed (10)
$ cd packages/cli && npx vitest run src/ui/AppContainer.test.tsx

 ✓ src/ui/AppContainer.test.tsx (89 tests) 2510ms

 Test Files  1 passed (1)
      Tests  89 passed (89)
$ npm run typecheck

> @qwen-code/acp-bridge@0.17.1 typecheck
> tsc --noEmit

> @qwen-code/qwen-code@0.17.1 typecheck
> tsc --noEmit

> @qwen-code/qwen-code-core@0.17.1 typecheck
> tsc --noEmit

> @qwen-code/sdk@0.1.8 typecheck
> tsc --noEmit

> @qwen-code/webui@0.17.1 typecheck
> tsc --noEmit

All green. ✅

中文说明

2a. 代码审查

对照 RewindSelectorhandleRewindConfirm 的源码审查了 diff。测试与生产代码对齐良好——我抽查的每个分支断言都正确映射到实现逻辑。

RewindSelector.test.tsx — 新增测试填补了真实缺口:

  • pick list 的方向键导航(上/下)此前未测试(仅有 Ctrl+P/N 覆盖)。新测试干净地覆盖了此路径。
  • isRestoring 按键屏蔽——使用未解决的 Promise 保持恢复状态,然后验证 Escape 被屏蔽。模式正确。
  • Legacy confirm 通过 nEscape 取消——参数化测试,两条路径都返回 pick list。与源码中 handleConfirmSelect(false) 逻辑一致。
  • 在已有的 restore-options 测试中增加了 "Never mind" 选项断言。

AppContainer.test.tsxrenderRewindHarness 辅助函数(约 90 行)是一个良好的可复用 fixture。所有 10 个 handleRewindConfirm 测试用例均对照源码验证:

  • rewind(promptId, truncateHistory) 标志逻辑在不同模式间正确区分
  • apiTruncateIndex < 0 的压缩轮次检查使用了刻意缩短的 apiHistory 进行测试
  • 无客户端 fallback:conversation-only 显示错误,both-mode 回退到 code-only 并附带警告
  • 外层 try/catch:mock 的 getGeminiClient 抛出异常被正确通过 addItem 上报

未发现关键问题。无 AGENTS.md 违规——遵循现有测试约定(collocated .test.tsxvi.mock/vi.spyOnink-testing-library)。

2b. 测试执行

这是纯测试 PR,无用户可见行为变更,tmux UI 捕获为 N/A。验证即为测试套件本身:所有测试通过,typecheck 通过。✅

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

This is a clean, well-scoped test coverage PR. It closes #4187 by adding regression tests for the two rewind surfaces from #4064RewindSelector (the 3-phase Ink component) and handleRewindConfirm (the ~140 LOC orchestration function).

What I liked:

  • The renderRewindHarness helper is the right level of abstraction — it sets up all the mocks needed for the orchestration tests without over-engineering, and each test only overrides what it needs.
  • Test coverage maps directly to the issue's acceptance criteria. Nothing is speculative; nothing is missing.
  • The isRestoring keypress-guard test using an unresolved Promise to hold the restoring state is a clean pattern.

No concerns. The diff is test-only, follows existing conventions, and all 99 tests + typecheck pass.

Approving. ✅

中文说明

这是一个干净、范围合理的测试覆盖 PR。它关闭 #4187,为 #4064 引入的两个 rewind 表面——RewindSelector(三阶段 Ink 组件)和 handleRewindConfirm(约 140 行的编排函数)——添加了回归测试。

优点:

  • renderRewindHarness 辅助函数的抽象层次恰当——为编排测试设置了所有必要的 mock,且没有过度设计,每个测试仅覆盖所需的差异。
  • 测试覆盖直接对应 issue 的验收标准。没有投机性内容,也没有遗漏。
  • isRestoring 按键屏蔽测试使用未解决的 Promise 保持恢复状态,是一个干净的模式。

无顾虑。纯测试变更,遵循现有约定,99 个测试 + typecheck 全部通过。

批准。✅

Qwen Code · qwen3.7-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@wenshao

wenshao commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Runtime verification (real CLI in tmux + mutation testing)

Verdict: solid test-only PR — the new tests pass, are not flaky, have real teeth (they catch regressions in the production rewind code), and assert behavior that matches the real CLI. Recommend merge. One tiny optional follow-up below.

Since this is a test-only change (+453 lines, no production code), "verification" means three things beyond "does CI go green": do the tests actually exercise the code paths they claim (teeth), do they assert correct behavior, and are they stable. I checked all three.

Harness

  • Branch 992537dc6, npm install (build + bundle), Node 22, Linux.
  • vitest runs against source, so mutation testing needs no rebuild. The real-flow cross-check drives the actual qwen TUI under tmux (OpenAI-compatible auth → local mock server, isolated $HOME, a git workspace).

1. Tests pass and are stable

npx vitest run src/ui/components/RewindSelector.test.tsx src/ui/AppContainer.test.tsx99 passed (2 files). The new cases run (not skipped): 10 handleRewindConfirm cases + 4 RewindSelector cases. Ran the rewind suites 3× back-to-back — 32 passed each time, no flakiness (including the async Restoring... pending-guard test). tsc --noEmit on packages/cli passes; the cross-workspace tsc --build during install also passes.

2. The tests have teeth (mutation testing)

I injected a targeted bug into the production rewind code for each major branch and confirmed the matching test fails (genuine assertion failures, not compile errors), then reverted. A test that stays green under a real bug is vacuous coverage — none of these are.

# mutation in production code test that caught it result
M1 handleRewindConfirm: drop the !(option==='both' && hasRestoreFailure) guard (truncate even when file restore failed) skips conversation truncation when both-mode file restore fails ✅ caught (truncateHistory called 1×, expected 0)
M2 handleRewindConfirm: force the rewind() truncate flag to false truncates conversation when both-mode file restore succeeds ✅ caught (rewind('prompt-2', false) vs expected true)
M3 handleRewindConfirm: make the compressed-turn guard (apiTruncateIndex < 0) never fire bails before file restore when the target turn is compressed ✅ caught
M4 handleRewindConfirm: skip the no-client conversation error branch shows an error and returns for conversation-only rewind with no client ✅ caught
M5 RewindSelector: remove the if (isRestoring) return; keypress guard blocks restore-option keypresses while rewind is pending ✅ caught
M6 RewindSelector: make pick-list Escape not call onCancel navigates the pick list with arrow keys and cancels with Escape ✅ caught

6/6 mutations across both files were caught. The tests genuinely lock down the rewind orchestration and the selector's key handling.

3. The tests assert real behavior (tmux cross-check)

To make sure the assertions aren't internally-consistent-but-wrong, I drove the actual rewind flow in the real TUI (two prompts → /rewind) and compared the observable UI to the test assertions:

asserted in tests real CLI
pick list header Rewind Conversation (2 turns), default selection › #2 second prompt
› #1 first prompt, › #2 second prompt
Esc cancels the selector ✅ (selector closed)
restore-options phase shows Restore conversation only + Never mind ✅ (plus the "file restore unavailable" note when no edits were captured)
selecting conversation-only → Conversation rewound. Edit your prompt and press Enter to continue., conversation truncated, prompt pre-filled into the input ✅ (input pre-filled with second prompt here, older turns dropped)

Every behavior the tests assert reproduces in the real CLI.

Minor (optional, non-blocking)

Of the 10 user-facing messages in handleRewindConfirm, 9 are asserted by the new tests. The only uncovered branch is No files needed to be restored. (file restore that changed nothing and failed nothing). Worth a one-line case if you want full message coverage, but not a merge blocker.

Conclusion

The PR does what it claims for #4187: it adds focused, non-flaky regression coverage for the rewind selector and the rewind-confirm orchestration, the coverage is real (mutation-proven), and it matches actual CLI behavior. Looks good to merge.

中文版(点击展开)

本地真实运行验证(tmux 真实 CLI + 变异测试)

结论:这是一个扎实的纯测试 PR——新增测试通过、不 flaky、有真正的“咬合力”(能抓住生产 rewind 代码的回归),且断言的行为与真实 CLI 一致。建议合并。 下面有一个很小的可选 follow-up。

由于这是纯测试改动(+453 行,无生产代码),“验证”除了“CI 是否变绿”之外还意味着三件事:测试是否真的覆盖了它声称的代码路径(咬合力)、是否断言了正确的行为、是否稳定。我把这三点都查了。

测试环境

  • 分支 992537dc6npm install(build + bundle),Node 22,Linux。
  • vitest 直接跑源码,所以变异测试无需重新构建。真实流程交叉验证则在 tmux 下驱动真实的 qwen TUI(OpenAI 兼容鉴权 → 本地 mock server,隔离 $HOME,git 工作区)。

1. 测试通过且稳定

npx vitest run src/ui/components/RewindSelector.test.tsx src/ui/AppContainer.test.tsx99 passed(2 个文件)。新增用例确实执行(未被 skip):10 个 handleRewindConfirm 用例 + 4 个 RewindSelector 用例。把 rewind 相关套件连续跑 3 次——每次 32 passed,无 flaky(包括异步的 Restoring... 挂起守卫用例)。packages/clitsc --noEmit 通过;安装阶段跨 workspace 的 tsc --build 也通过。

2. 测试有“咬合力”(变异测试)

我针对每个主要分支往生产 rewind 代码里注入一个有针对性的 bug,确认对应测试失败(是真正的断言失败,不是编译错误),然后还原。一个在真实 bug 下仍然变绿的测试就是空覆盖——这里没有一个是空的。

# 对生产代码的变异 抓住它的测试 结果
M1 handleRewindConfirm:去掉 !(option==='both' && hasRestoreFailure) 守卫(文件恢复失败时仍截断会话) skips conversation truncation when both-mode file restore fails ✅ 抓住(truncateHistory 被调用 1 次,预期 0)
M2 handleRewindConfirm:把 rewind() 的截断标志强制为 false truncates conversation when both-mode file restore succeeds ✅ 抓住(rewind('prompt-2', false),预期 true
M3 handleRewindConfirm:让“压缩 turn”守卫(apiTruncateIndex < 0)永不触发 bails before file restore when the target turn is compressed ✅ 抓住
M4 handleRewindConfirm:跳过“无 client 的会话回退”错误分支 shows an error and returns for conversation-only rewind with no client ✅ 抓住
M5 RewindSelector:移除 if (isRestoring) return; 按键守卫 blocks restore-option keypresses while rewind is pending ✅ 抓住
M6 RewindSelector:让 pick-list 的 Escape 不调用 onCancel navigates the pick list with arrow keys and cancels with Escape ✅ 抓住

两个文件共 6/6 变异全部被抓住。测试确实锁住了 rewind 编排逻辑和 selector 的按键处理。

3. 测试断言的是真实行为(tmux 交叉验证)

为确认断言不是“自洽但错误”,我在真实 TUI 里走了一遍 rewind 流程(两个 prompt → /rewind),把可观察到的 UI 与测试断言对比:

测试中的断言 真实 CLI
pick list 标题 Rewind Conversation (2 turns),默认选中 › #2 second prompt
› #1 first prompt› #2 second prompt
Esc 取消 selector ✅(selector 关闭)
restore-options 阶段显示 Restore conversation only + Never mind ✅(无捕获改动时还有 “file restore unavailable” 提示)
选择 conversation-only → Conversation rewound. Edit your prompt and press Enter to continue.,会话被截断,prompt 预填进输入框 ✅(输入框预填 second prompt here,更旧的 turn 被移除)

测试断言的每个行为都能在真实 CLI 中复现。

次要(可选,不阻塞合并)

handleRewindConfirm 里 10 条用户可见消息中有 9 条被新增测试断言。唯一未覆盖的分支是 No files needed to be restored.(文件恢复既没改动也没失败)。想要完整消息覆盖的话可以补一个用例,但不是合并阻塞项。

结论

这个 PR 实现了 #4187 的目标:为 rewind selector 和 rewind-confirm 编排增加了聚焦、不 flaky 的回归覆盖,覆盖是真实的(变异测试已证明),且与真实 CLI 行为一致。可以合并。

@wenshao wenshao merged commit 87dc1a3 into QwenLM:main Jun 13, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add tests for RewindSelector + handleRewindConfirm orchestration

4 participants