test(desktop): restore web storage under Node 22+

Node 22+ defines a global `localStorage` accessor that stays inert
unless the process was started with `--localstorage-file`. vitest's
jsdom environment only copies a window key onto `globalThis` when
nothing is there yet, so Node's inert stub wins over jsdom's real
Storage and `window.localStorage` reads back undefined.

Every suite whose `beforeEach` calls `localStorage.clear()` therefore
died at setup, taking the whole file with it — 12 files and 396 tests
on a clean tree, none of them a real failure. Install a Storage
implementation from a setup file so browser semantics hold regardless
of Node version.

Local desktop suite: 396 failures across 12 files -> 2 across 1.
This commit is contained in:
程序员阿江(Relakkes)
2026-08-08 23:57:32 +08:00
parent f5b3faac12
commit 7dbd7811d5
2 changed files with 59 additions and 1 deletions
+57
View File
@@ -0,0 +1,57 @@
/**
* Node 22+ defines a global `localStorage` / `sessionStorage` accessor that
* stays inert unless the process was started with `--localstorage-file`.
* vitest's jsdom environment only copies a window key onto `globalThis` when
* nothing is there yet, so Node's inert stub wins over jsdom's real Storage and
* every `localStorage.clear()` in a `beforeEach` throws. Installing a real
* Storage here restores browser semantics regardless of Node version.
*/
class MemoryStorage implements Storage {
#entries = new Map<string, string>()
get length(): number {
return this.#entries.size
}
key(index: number): string | null {
return [...this.#entries.keys()][index] ?? null
}
getItem(key: string): string | null {
return this.#entries.get(String(key)) ?? null
}
setItem(key: string, value: string): void {
this.#entries.set(String(key), String(value))
}
removeItem(key: string): void {
this.#entries.delete(String(key))
}
clear(): void {
this.#entries.clear()
}
[name: string]: unknown
}
function installStorage(name: 'localStorage' | 'sessionStorage'): void {
let usable = false
try {
const existing = (globalThis as Record<string, unknown>)[name] as Storage | undefined
usable = typeof existing?.setItem === 'function' && typeof existing?.clear === 'function'
} catch {
// Node's accessor throws when web storage is unavailable — treat as missing.
}
if (usable) return
Object.defineProperty(globalThis, name, {
value: new MemoryStorage(),
configurable: true,
writable: true,
})
}
installStorage('localStorage')
installStorage('sessionStorage')
+2 -1
View File
@@ -13,7 +13,7 @@ export default defineConfig({
environment: 'jsdom',
globals: true,
css: true,
setupFiles: [],
setupFiles: ['./src/test/webStorage.ts'],
coverage: {
include: ['src/**/*.{ts,tsx}'],
exclude: [
@@ -21,6 +21,7 @@ export default defineConfig({
'src/**/*.d.ts',
'src/types/**',
'src/mocks/**',
'src/test/**',
'src/vite-env.d.ts',
],
},