Skip to content

Fix frontend vitest suite on Node >= 22 (web storage globals) - #1500

Open
lbellows wants to merge 1 commit into
roflcoopter:devfrom
lbellows:fix/vitest-node-webstorage
Open

Fix frontend vitest suite on Node >= 22 (web storage globals)#1500
lbellows wants to merge 1 commit into
roflcoopter:devfrom
lbellows:fix/vitest-node-webstorage

Conversation

@lbellows

@lbellows lbellows commented Sep 2, 2026

Copy link
Copy Markdown

Problem

On Node 26 the frontend test suite fails almost entirely — 27 of 47 tests, across every test file that mounts a component:

TypeError: Cannot read properties of undefined (reading 'getItem')
 ❯ src/context/ColorModeContext.tsx:98:33
     const stored = localStorage.getItem("chosenMode");

Node also prints:

ExperimentalWarning: localStorage is not available because --localstorage-file was not provided.

Cause

Two behaviours combine:

  1. Node >= 22 defines its own Web Storage globals. globalThis.localStorage exists as an accessor, but it evaluates to undefined unless the process was started with --localstorage-file. (sessionStorage is defined and usable, but it is Node's, not jsdom's.)

  2. Vitest's jsdom environment will not overwrite a global that already exists, unless the name is on its own hardcoded allowlist. From populateGlobalgetWindowKeys in vitest/dist:

    if (k in global) return keysArray.includes(k);
    return true;

    localStorage and sessionStorage are not on that list, so on Node < 22 they are copied from jsdom via the return true branch, 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 dev with no source changes, and CI does not catch it because ci-frontend.yaml pins NODE_VERSION: 24, where localStorage still 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:

execArgv: ["--no-experimental-webstorage"],

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.execArgv and not test.poolOptions.*.execArgv, since poolOptions was removed in Vitest 4.

tests/environment.test.ts guards the behaviour, so if this regresses it surfaces as one clear failure instead of every component test breaking at once.

Testing

before after
Node 26.7.0 27 failed, 20 passed 49 passed
Node 24 (CI version) 47 passed 49 passed

Also checked: tsc --noEmit, Prettier, and that the flag does not break --pool=threads (worker_threads rejects some Node options in execArgv; this one is fine).

Unrelated issue found while testing

tests/lib/tokens.test.ts has 3 failures that are timezone-dependent, not Node-dependent — they reproduce on Node 24 under TZ=America/New_York and pass under TZ=UTC. toStrictEqual compares dayjs internals, and getDayjs().add(3600, "second") and getDayjsFromDateTimeString(...) build structurally different instances ($u set 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

@netlify

netlify Bot commented Sep 2, 2026

Copy link
Copy Markdown

Deploy Preview for viseron canceled.

Name Link
🔨 Latest commit 318f105
🔍 Latest deploy log https://app.netlify.com/projects/viseron/deploys/6a976b5528225800083f0ab5

@jbowen1085-cyber

This comment was marked as abuse.

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>
@roflcoopter

Copy link
Copy Markdown
Owner

Fuck everyone in this thread. Remove me from this bullshit you bitch ass hacking mother fuckers

On Tue, Sep 1, 2026 at 7:08 PM netlify[bot] @.> wrote: netlify[bot] left a comment (roflcoopter/viseron#1500) <#1500 (comment)> ✅ Deploy Preview for viseron canceled. Name Link 🔨 Latest commit 0b3b7b8 <0b3b7b8> 🔍 Latest deploy log https://app.netlify.com/projects/viseron/deploys/6a9768d45a15470008a5c986 — Reply to this email directly, view it on GitHub <#1500?email_source=notifications&email_token=CDFQIJ66PIIHLRQQ375YLIL5M5QHFA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKNJQGIZTEMZQGMZ2M4TFMFZW63VKON2WE43DOJUWEZLEUVSXMZLOOSWGM33PORSXEX3DNRUWG2Y#issuecomment-5502323033>, or unsubscribe https://github.com/notifications/unsubscribe-auth/CDFQIJ2E4BRY7JQGX3DLLRD5M5QHFAVCNFSNUABFKJSXA33TNF2G64TZHMZDSMJUHE4DINJVHNEXG43VMU5TKMZRHAYTKMZUGQ4KC5QC . You are receiving this because you are subscribed to this thread.Message ID: @.>

Whats with the language? If you get notifications from this repo it's because you are watching it not because of hackers lol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants