Summary
packages/stx/test/signals/fetch-interceptors.test.ts fails intermittently because a request made by one test is recorded against another. It is invisible in file order and reproduces readily under --randomize, which is also why the suite's assertion count drifts run to run while every test still passes.
Reproduction
bun test packages/stx/test/signals/fetch-interceptors.test.ts --randomize3 to 4 runs in 6 fail. Two of the failures seen:
(fail) configureFetch reaches every primitive (#1855) > adds a header to useMutation
expect(seen).toHaveLength(1) Expected length: 1 Received length: 2
(fail) a headers function is resolved (#1855) > useMutation evaluates headers per request
expect(headerOf(seen[0], 'X-Trace')).toBe('m') Expected: "m" Received: undefinedThe second is the clearer symptom: seen[0] is not the request that test made. It belongs to an earlier test and arrived first.
Cause
Most tests in the file fire a request without awaiting it — g.window.stx.useFetch('/api/a', { immediate: false }).refetch() is the shape under test — and the error-path tests leave a primitive that retries. Those primitives stay alive after their test ends, and the globalThis.fetch mock installed by the next test records whatever they do next.
So the next test sees a request it never made, and seen[0] / toHaveLength(1) describe the wrong thing.
What does not fix it
Two attempts, both measured over 6+ randomized runs:
- Capturing a fresh array per test in the mock's closure (so a straggler lands in the array belonging to the test that caused it). Sound for a request that completes between tests, and it changes nothing measurable: 4/6 runs still failed, against 3/6 without it. The dominant path is a live primitive firing during a later test, which reaches that test's mock however the array is held.
- Draining in
afterEach(60ms before restoringfetch). 3/8 still failed. The stragglers are not merely in flight; they are periodic.
What would fix it
The requests have to be attributable to the test that caused them, or the primitives have to stop when their test does:
- Dispose the primitives between tests. The real defect — a
useQueryoruseFetchoutliving its test and retrying into the next one is also what an application would hit on unmount. Worth checking whether the runtime exposes a way to stop one; if it does not, that gap is the more interesting bug. - Or give each test its own URL and assert on the entries matching it rather than on
seen[0]andseen.length. Mechanical, but five assertions read URLs back (:138,:449,:458,:468,:475) and one of those exercises a derived URL, so it needs care rather than a blanket rename.
Why it matters beyond this file
The full suite's expect() count varies run to run — 32,618 / 32,623 / 32,624 / 32,629 across four runs — while the same 13,718 tests pass. Every directory is stable in isolation, so at least one cross-file interaction of this kind is live in CI today, and a suite that varies is a suite that will eventually hide a real regression rather than report it.