Skip to content

Commit 5e309bf

Browse files
DavidBabinecclaude
andauthored
test: stub user-preference fetches in the global setup to stop a cross-test leak (#60)
Admin surfaces load user preferences on mount (e.g. module-inserter favourites via useModuleInserterPreference → getUserPreference), firing a real fetch to /admin/api/cms/me/preferences/<key>. In tests that hits localhost and rejects with ECONNREFUSED. The preference store is a module-level singleton whose load promise can outlive the component that triggered it, so the rejection surfaces during a LATER test's cleanup() as an AggregateError — a render-timing-dependent cross-test flake. It was masked while canvas frames mounted slowly (the tests timed out before the preference fetch settled) and reappeared once frames mount synchronously (#58), failing the release Verify step on canvasFrameMounting's first test. Return the server's "never set" envelope ({ value: null }) for preference GETs in the global test preload, so the load resolves cleanly to each caller's own default with no network call. Tests that need specific preference data still override globalThis.fetch in their own setup; getUserPreference callers that inject a fetchImpl bypass this entirely. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bb36941 commit 5e309bf

1 file changed

Lines changed: 42 additions & 0 deletions

File tree

src/__tests__/setup.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,48 @@ for (const key of GLOBALS_TO_COPY) {
115115
}
116116
}
117117

118+
// ---------------------------------------------------------------------------
119+
// Default user-preference fetches to the "never set" envelope.
120+
//
121+
// Admin surfaces load user preferences on mount (e.g. the module-inserter
122+
// favourites via `useModuleInserterPreference` → `getUserPreference`), which
123+
// fires a real `fetch` to `/admin/api/cms/me/preferences/<key>`. In tests that
124+
// hits `http://localhost` and rejects with ECONNREFUSED. Because the
125+
// preference store is a module-level singleton whose load promise can outlive
126+
// the component that triggered it, that rejection surfaces during a LATER
127+
// test's `cleanup()` as an `AggregateError` — a cross-test flake that depends
128+
// on render timing (it was masked while canvas frames mounted slowly, and
129+
// reappeared once they mount synchronously).
130+
//
131+
// Returning the server's "never set" signal (`{ value: null }`) makes every
132+
// such load resolve cleanly to the caller's own default, with no network call.
133+
// Tests that need specific preference data still override `globalThis.fetch`
134+
// in their own setup; `getUserPreference` callers that inject a `fetchImpl`
135+
// bypass this entirely.
136+
{
137+
const realFetch = globalThis.fetch
138+
const PREFERENCES_PATH = '/admin/api/cms/me/preferences/'
139+
;(globalThis as Record<string, unknown>).fetch = (async (
140+
input: RequestInfo | URL,
141+
init?: RequestInit,
142+
): Promise<Response> => {
143+
const url =
144+
typeof input === 'string'
145+
? input
146+
: input instanceof URL
147+
? input.href
148+
: (input as Request).url
149+
const method = (init?.method ?? (input as Request).method ?? 'GET').toUpperCase()
150+
if (method === 'GET' && url.includes(PREFERENCES_PATH)) {
151+
return new Response(JSON.stringify({ value: null }), {
152+
status: 200,
153+
headers: { 'Content-Type': 'application/json' },
154+
})
155+
}
156+
return realFetch(input, init)
157+
}) as typeof globalThis.fetch
158+
}
159+
118160
// ---------------------------------------------------------------------------
119161
// Tame DOM serialization in failure output.
120162
//

0 commit comments

Comments
 (0)