test: fix flaky UpdateTab reconcile flow (Restarting-button race) (#2065)#2067
Merged
Conversation
…estarting-button race (#2065) Claude-Session: https://claude.ai/code/session_01TSuyZ5XG9JiijEibeJjL6T
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.
Closes #2065
Root cause
The
UpdateTab reconcile flowtests intermittently failed CI with:The component mirrors the
updatingstate intoupdatingRefvia a separate passive effect (useEffect(() => { updatingRef.current = updating }, [updating])), andhandleDisconnect's guard reads that ref synchronously:Because
runUpdate()callssetUpdating(true)outside anyact(after two awaits), the "Reconciling..." button — driven directly by theupdatingstate in JSX — can commit to the DOM and satisfy the test'swaitFor(...'Reconciling...')a scheduler tick before the ref-sync passive effect runs. In that windowupdatingRef.currentis stillfalse, so firingdisconnecthits the early-return, the 1.5s confirmation timer is never armed, and the button never becomes "Restarting...". This is a pure test-timing race — in production the ref-sync effect flushes long before any socket disconnect, so product behavior is correct.Fix (test-only)
Flush pending passive effects (
await act(async () => {})) before firingdisconnectin the sharedfireDisconnectAndConfirmhelper (used by both failing cases, including the StrictMode one) and in the unmount case's inline block. This syncsupdatingRefto the state production always reaches over real network time. No product code changed; all original assertions (restart-polling fallback arming,{ silent: true }health check, StrictMode mountedRef survival) are preserved.Test plan
Ran the file 25x green after the fix (had reproduced the flake ~1 in 8 on clean pre-fix code):
https://claude.ai/code/session_01TSuyZ5XG9JiijEibeJjL6T