fix(test): let the jest scripts exit instead of stalling on open handles - #676
Open
RonenMars wants to merge 1 commit into
Open
fix(test): let the jest scripts exit instead of stalling on open handles#676RonenMars wants to merge 1 commit into
RonenMars wants to merge 1 commit into
Conversation
npm run test:unit passes all 105 suites and then never exits, because a test that builds its own QueryClient leaves React Query's setTimeout(gcTime) pending and node will not exit while that timer is scheduled. Measured: the unit suite finishes in about 20s and was still alive when killed at 120s. useConversations.test.tsx -t "partial failure" finishes its tests in 1.4s and exits on its own at exactly 305s, the 5-minute default gcTime, while the same file's retention tests were still alive at 400s against a 7-day gcTime. test:ci already passed --forceExit for this reason, but test:unit, test:integration, test:e2e and test:i18n did not, so the four scripts a developer reaches for locally were the four that hung. Give them the same flag; the same 105 suites now exit in 20s. Record the identified handle in docs/troubleshooting.md, including the evidence that the correlation is with suites constructing their own client rather than with test-utils/createWrapper. conversation-search-anchor calls createWrapper twelve times and exits in 19s, while useSessionActions.resume builds one client and never exits. That rules out clearing the wrapper's client as a fix and points at the 16 suites that build their own, for whoever wants the leak gone rather than worked around.
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.
npm run test:unitpasses all 105 suites and then never exits.It looks like a hang, and it is easy to kill and record as "the suite hangs" when the run has in fact already succeeded.
Cause
A test that constructs its own
QueryClientnever clears it, so React Query schedulessetTimeout(gcTime)for each cached query once it goes unused, and node will not exit while that timer is pending.Measured on 2026-08-12:
test:unit(105 suites)useConversations.test.tsx -t "partial failure"gcTime, to the seconduseConversations.test.tsx -t "retention gcTime"useConversationsets a 7-daygcTime(hooks/useConversations.ts:423)-t "zzz-no-such-test"The last row rules out module import as the cause: it is a test body that leaves the timer behind.
--detectOpenHandlesreports nothing, which is what makes this hard to place from the symptom alone.Fix
test:cialready passed--forceExitfor exactly this reason.test:unit,test:integration,test:e2eandtest:i18ndid not — so the four scripts a developer actually reaches for locally were the four that hung, while the one CI runs was fine.Giving them the same flag is the whole change.
The same 105 suites, 996 tests, now exit in 20s.
Why not clear the client instead
That was the first plan, and measurement killed it.
The stall does not correlate with
test-utils/createWrapper, it correlates with suites that build their own client:createWrappercallsQueryClientconversation-search-anchoruseSessionActions.resumeuseConversationsgcTime)Clearing the wrapper's client in a global
afterEachwould therefore have fixed nothing observable.Fixing the leak properly means changing the 16 suites that construct their own client, which is a much wider diff than the problem justifies today.
docs/troubleshooting.mdnow records that finding so the next person does not re-run the same experiment, and names the 16 suites as the target if anyone wants the leak gone rather than worked around.Trade-off
--forceExitmasks a genuinely stuck handle rather than surfacing it.That trade was already accepted for
test:ci; this makes the four sibling scripts consistent with it rather than introducing it.Everything printed above the stall is a real result, so no test signal is lost.
Verification
npx jest --ci --runInBand --testPathPattern='__tests__/unit'— 105 suites pass,Jest did not exit one second after the test run has completed., killed at 120s.npm run test:unit— 105 suites, 996 tests, all pass,exit=0at 20s.