On Node 26 the entire frontend suite fails: 27 of 47 tests error with
`TypeError: Cannot read properties of undefined (reading 'getItem')`, most
of them from `ColorModeContext` reading `localStorage` on mount.
Node >= 22 defines its own Web Storage globals. `localStorage` is only
usable when the process was started with `--localstorage-file`; without it
the global exists but evaluates to `undefined`. That matters because
vitest's jsdom environment skips any global that is already defined unless
the name appears in its own allowlist (`populateGlobal` -> `getWindowKeys`
in vitest/dist), and `localStorage` and `sessionStorage` are not on that
list. So jsdom's storage is never installed and Node's unusable global is
what the tests see.
Start the workers with `--no-experimental-webstorage` so the Node globals
are not defined at all and jsdom owns them again. The flag is accepted on
Node 22, 24 and 26, and is a no-op on versions that have no web storage
to disable.
Add tests/environment.test.ts to guard it, so a regression shows up as one
clear failure rather than as every component test breaking at once.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Problem
On Node 26 the frontend test suite fails almost entirely — 27 of 47 tests, across every test file that mounts a component:
Node also prints:
Cause
Two behaviours combine:
Node >= 22 defines its own Web Storage globals.
globalThis.localStorageexists as an accessor, but it evaluates toundefinedunless the process was started with--localstorage-file. (sessionStorageis defined and usable, but it is Node's, not jsdom's.)Vitest's jsdom environment will not overwrite a global that already exists, unless the name is on its own hardcoded allowlist. From
populateGlobal→getWindowKeysinvitest/dist:localStorageandsessionStorageare not on that list, so on Node < 22 they are copied from jsdom via thereturn truebranch, and on Node >= 22 they are silently dropped.The result is that jsdom's storage is never installed and every test sees Node's unusable global. This is environmental — it reproduces on
devwith no source changes, and CI does not catch it becauseci-frontend.yamlpinsNODE_VERSION: 24, wherelocalStoragestill happens to be usable through jsdom.Fix
Start the vitest workers with
--no-experimental-webstorage, so Node does not define the globals at all and jsdom owns them again:I checked the flag is accepted on Node 22, 24 and 26, so it is safe for the pinned CI version as well as for contributors on newer Node. Note this is
test.execArgvand nottest.poolOptions.*.execArgv, sincepoolOptionswas removed in Vitest 4.tests/environment.test.tsguards the behaviour, so if this regresses it surfaces as one clear failure instead of every component test breaking at once.Testing
Also checked:
tsc --noEmit, Prettier, and that the flag does not break--pool=threads(worker_threads rejects some Node options inexecArgv; this one is fine).Unrelated issue found while testing
tests/lib/tokens.test.tshas 3 failures that are timezone-dependent, not Node-dependent — they reproduce on Node 24 underTZ=America/New_Yorkand pass underTZ=UTC.toStrictEqualcompares dayjs internals, andgetDayjs().add(3600, "second")andgetDayjsFromDateTimeString(...)build structurally different instances ($uset vs absent) outside UTC. CI never sees it because GitHub runners are UTC. Left out of this PR to keep it focused — happy to send a follow-up if you'd like.🤖 Generated with Claude Code