fix(test): flush effects before writing input in MainConfig walkthrough tests - #137
Merged
Conversation
…gh tests The four MainConfig walkthrough tests failed on roughly two of every three CI runs, blocking every PR in the repo. The failing case and assertion moved between runs, which read as timing flake but has a specific cause. Ink paints a frame during render, while `useInput` subscribes to stdin in a passive effect. Between those two points the step being left is still the one listening. `waitForFrame` polled `lastFrame()`, so it returned as soon as the new step painted -- before that step's `useInput` was subscribed -- and the next `stdin.write` was delivered to the outgoing step's handlers and dropped. Traced by instrumenting ink's `useInput`: after advancing to ApiKeyStep, the handlers invoked for the API key write were still ProviderStep's `onExit` and its Select's `focusNextOption`, with ApiKeyStep's and BlinkingTextInput's handlers subscribing only afterwards. This is why 1c67a48 (fixed sleeps -> polling) did not settle it: the sleeps had been incidentally covering the effect flush that the polling no longer waits for. `waitForFrame` now awaits `act()` after the frame assertion, so effects commit before the next keystroke. Arrow-key navigation went through the same gap -- `down` and `enter` were written back to back, so a `down` that had not landed would select the wrong provider -- and is now confirmed one highlight at a time in `selectProvider`. The ExitUI test gets the same treatment, since `useListener` also subscribes in an effect. Verified in a clean worktree on Node 24.17: the previous test fails 4/4 runs, this version passes 10/10, and the full suite is 53 files / 345 tests green. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Addresses the review note that the hardcoded provider order duplicated ProviderStep's sort. Rather than importing `providers` and re-implementing that comparator in the test (which trades one duplication for another), `selectProvider` now walks the highlight down until the requested label is selected. `providers` is used only to bound the loop, so the test no longer encodes an ordering at all and keeps working if ProviderStep's sort changes. The explicit unknown-provider guard is gone with it: the bound plus the following `waitForFrame` assertion already turn a label that never highlights into a clear failure instead of a hang. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dawsontoth
marked this pull request as ready for review
July 27, 2026 15:28
|
🎉 This PR is included in version 0.16.29 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the
MainConfigwalkthrough tests inink/main.test.tsx, which were failing on roughly two of every three CI runs and blocking every PR in this repo.The cause
It looked like timing flake — the failing test case and assertion moved between runs — but there's a specific mechanism.
Ink paints a frame during render, while
useInputsubscribes to stdin in a passive effect. Between those two points, the step being left is still the one listening.waitForFramepolledlastFrame(), so it returned as soon as the new step painted — before that step'suseInputhad subscribed — and the followingstdin.writewent to the outgoing step's handlers and was dropped.I confirmed this by instrumenting ink's
useInput. After advancing toApiKeyStep, the handlers actually invoked for the API-key write were still ProviderStep'sonExitand itsSelect'sfocusNextOption;ApiKeyStep's andBlinkingTextInput's handlers subscribed only afterwards:That also explains why 1c67a48 (fixed sleeps → polling) didn't settle it: the sleeps had been incidentally covering the effect flush that polling no longer waits for.
The change
waitForFrameawaitsact()after its frame assertion, so effects commit before the next keystroke.downandenterwere written back to back, so adownthat hadn't landed would silently select the wrong provider.selectProvidernow confirms one highlight at a time.settle(), sinceuseListeneralso subscribes in an effect. It passes today but was latently racy.waitForMaskedInputcollapsed intomaskFor()+waitForFrame; its old signature took atextargument and used only its.length, which was misleading.Verification
Clean worktree, Node 24.17:
dprint check,oxlint,npm run buildWhere to look
selectProvidermakes no ordering assumption. It walks the highlight down until the requested provider is selected, so it survives changes toProviderStep's sort;providerssupplies only the loop bound. (First revision hardcoded the order — @gemini-code-assist flagged the duplication, addressed in 43d86eb.)act()fromreacton an ink tree. It flushes ink's reconciler because ink builds onreact-reconciler, and no act-environment warnings appear. Worth a sanity check if you've seen ink andactinteract badly before.ConfigurationWizardinstead.Both commits were verified the same way (10/10 runs, full suite green, format/lint/build clean).
Note the step-10 cross-model review was not run: the
cross-model-reviewskill isn't installed in this environment, and spawning a Claude reviewer would defeat its purpose. This is a test-only change, but theact()choice and the ordering coupling above are the spots that would most benefit from a second opinion.🤖 Generated with Claude Code (Claude Opus 5)