Files
hermes-agent/apps/desktop/vitest.setup.ts
ethernet 22df1840d6 fix(desktop): restore localStorage in jsdom tests under Node 26
Node 26 defines its own `localStorage` accessor on the global object,
which returns `undefined` unless the process was started with
`--localstorage-file` (hence the "localStorage is not available because
--localstorage-file was not provided" warning now printed by every
worker). In the jsdom environment `globalThis` IS the window, so that
accessor shadows jsdom's Storage and every `localStorage.getItem(...)` in
a test throws "Cannot read properties of undefined".

CI caught this on the Node 26 bump: `check:test:ui` failed with 22
errors across session.test.ts, terminals.test.ts, model-settings and
onboarding stores — all storage-backed. Reproduced locally against
nodejs_26 (12 failures in src/store/session.test.ts alone) before fixing.

vitest.setup.ts now installs a real in-memory Storage on both globalThis
and window when the global resolves to undefined, before any test module
reads it. Guarded on `typeof === 'undefined'` so Node < 26 and any future
runtime that provides a working Storage keep jsdom's own implementation.

Verified under nodejs_26: the full `--project ui` lane is 378 files /
3268 tests green (was 22 failures).
2026-08-01 21:43:47 -04:00

41 lines
1.9 KiB
TypeScript

import { configure } from '@testing-library/react'
// Node 26 defines its own `localStorage` accessor on the global object, which
// returns `undefined` unless the process was started with --localstorage-file
// (it warns: "localStorage is not available because --localstorage-file was
// not provided"). In the jsdom environment `globalThis` IS the window, so that
// accessor shadows jsdom's Storage and every `localStorage.getItem(...)` in a
// test throws "Cannot read properties of undefined". Install a real in-memory
// Storage when the global resolves to nothing, before any test module reads it.
if (typeof (globalThis as any).localStorage === 'undefined') {
const store = new Map<string, string>()
const storage: Storage = {
get length() {
return store.size
},
key: (i: number) => [...store.keys()][i] ?? null,
getItem: (k: string) => store.get(String(k)) ?? null,
setItem: (k: string, v: string) => void store.set(String(k), String(v)),
removeItem: (k: string) => void store.delete(String(k)),
clear: () => store.clear(),
}
for (const target of [globalThis, (globalThis as any).window].filter(Boolean)) {
Object.defineProperty(target, 'localStorage', {
value: storage,
configurable: true,
writable: true,
})
}
}
// React 19 + Testing Library 16: opt into the act environment so render(),
// fireEvent(), and findBy* queries automatically flush state updates without
// spurious "not wrapped in act(...)" warnings.
;(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true
// findBy*/waitFor default to a 1000ms deadline — too tight for async-heavy
// panels (radix menus, refetch chains) when the full suite runs under xdist
// CPU contention in CI. Success still resolves the instant the node appears;
// the wider deadline only absorbs a starved runner, killing timing flakes.
configure({ asyncUtilTimeout: 5000 })