Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 4 additions & 18 deletions apps/loopover-miner-ui/vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { cleanup } from "@testing-library/react";
import { afterEach, vi } from "vitest";
import { patchJsdomLocalStorageForNode26 } from "../../test/helpers/vitest-jsdom-node26-localstorage";

// Unmount React trees between tests so jsdom state never leaks across cases.
afterEach(() => {
Expand All @@ -16,24 +17,9 @@ class ResizeObserverStub {
}
globalThis.ResizeObserver ??= ResizeObserverStub as unknown as typeof ResizeObserver;

// Node 26 predefines its own experimental `globalThis.localStorage` accessor (nodejs/node#60303) that
// returns undefined unless the process was started with --localstorage-file. Because that property already
// *exists* on globalThis before jsdom's env is installed, Vitest's populateGlobal skips copying jsdom's
// working Storage over it, so any bare `localStorage.*` call (theme-toggle.test.tsx, mirroring
// theme-toggle.tsx:39) throws "Cannot read properties of undefined". jsdom's real Storage still lives on the
// raw JSDOM window (globalThis.jsdom.window, which is a distinct object from the `window`/globalThis alias);
// point the global at it unconditionally -- a no-op on Node 22/24 where globalThis.localStorage already *is*
// this object, and the actual fix on Node 26+. A `??=` guard would not help (the broken accessor already
// counts as "present"); the property is configurable so redefining it is safe.
const jsdomLocalStorage = (globalThis as { jsdom?: { window?: { localStorage?: Storage } } }).jsdom?.window
?.localStorage;
if (jsdomLocalStorage) {
Object.defineProperty(globalThis, "localStorage", {
value: jsdomLocalStorage,
configurable: true,
writable: true,
});
}
// See vitest-jsdom-node26-localstorage.ts's own header. Exercised directly by theme-toggle.test.tsx,
// mirroring theme-toggle.tsx:39.
patchJsdomLocalStorageForNode26();

// #7075: ChatConversation wires handlePortfolioQueueChatCommand, which imports chat-action-registry →
// governor-chokepoint → governor-ledger → node:sqlite. jsdom/Vite cannot bundle that builtin (same twin
Expand Down
27 changes: 6 additions & 21 deletions apps/loopover-ui/vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { cleanup } from "@testing-library/react";
import { afterEach } from "vitest";
import { patchJsdomLocalStorageForNode26 } from "../../test/helpers/vitest-jsdom-node26-localstorage";

// Unmount React trees between tests so jsdom state never leaks across cases.
afterEach(() => {
Expand All @@ -16,24 +17,8 @@ class ResizeObserverStub {
}
globalThis.ResizeObserver ??= ResizeObserverStub as unknown as typeof ResizeObserver;

// Node 26 predefines its own experimental `globalThis.localStorage` accessor (nodejs/node#60303) that
// returns undefined unless the process was started with --localstorage-file. Because that property already
// *exists* on globalThis before jsdom's env is installed, Vitest's populateGlobal skips copying jsdom's
// working Storage over it, so any bare `localStorage.*` call (use-local-storage.test.ts most directly,
// plus routes/index.test.tsx, api/try-it.test.ts, app-panels/onboarding-preview-card.test.tsx, and
// lib/analytics-window.test.ts) throws "Cannot read properties of undefined". jsdom's real Storage still
// lives on the raw JSDOM window (globalThis.jsdom.window, a distinct object from the `window`/globalThis
// alias); point the global at it
// unconditionally -- a no-op on Node 22/24 where globalThis.localStorage already *is* this object, and the
// actual fix on Node 26+. A `??=` guard would not help (the broken accessor already counts as "present");
// the property is configurable so redefining it is safe. Mirrors apps/loopover-miner-ui/vitest.setup.ts's
// own guard (#7597), which fixed the identical gap there but not here.
const jsdomLocalStorage = (globalThis as { jsdom?: { window?: { localStorage?: Storage } } }).jsdom
?.window?.localStorage;
if (jsdomLocalStorage) {
Object.defineProperty(globalThis, "localStorage", {
value: jsdomLocalStorage,
configurable: true,
writable: true,
});
}
// See vitest-jsdom-node26-localstorage.ts's own header: Node 26's broken globalThis.localStorage
// accessor shadows jsdom's real Storage before Vitest can install it. Exercised directly by
// use-local-storage.test.ts, plus routes/index, api/try-it, app-panels/onboarding-preview-card, and
// lib/analytics-window.
patchJsdomLocalStorageForNode26();
24 changes: 4 additions & 20 deletions packages/loopover-ui-kit/vitest.setup.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,13 @@
import { cleanup } from "@testing-library/react";
import { afterEach } from "vitest";
import { patchJsdomLocalStorageForNode26 } from "../../test/helpers/vitest-jsdom-node26-localstorage";

// Unmount React trees between tests so jsdom state never leaks across cases (mirrors
// apps/loopover-miner-ui/vitest.setup.ts's own cleanup).
afterEach(() => {
cleanup();
});

// Node 26 predefines its own experimental `globalThis.localStorage` accessor (nodejs/node#60303) that
// returns undefined unless the process was started with --localstorage-file. Because that property already
// *exists* on globalThis before jsdom's env is installed, Vitest's populateGlobal skips copying jsdom's
// working Storage over it, so any bare `localStorage.*` call would throw "Cannot read properties of
// undefined" on Node 26+ -- no component here calls it today, but this package is a shared UI kit other
// workspaces build on, so the guard is preventive rather than a fix for a currently-red test. jsdom's real
// Storage still lives on the raw JSDOM window (globalThis.jsdom.window, a distinct object from the
// `window`/globalThis alias); point the global at it unconditionally -- a no-op on Node 22/24 where
// globalThis.localStorage already *is* this object, and the actual fix on Node 26+. A `??=` guard would not
// help (the broken accessor already counts as "present"); the property is configurable so redefining it is
// safe. Mirrors apps/loopover-miner-ui/vitest.setup.ts's own guard (#7597).
const jsdomLocalStorage = (globalThis as { jsdom?: { window?: { localStorage?: Storage } } }).jsdom?.window
?.localStorage;
if (jsdomLocalStorage) {
Object.defineProperty(globalThis, "localStorage", {
value: jsdomLocalStorage,
configurable: true,
writable: true,
});
}
// See vitest-jsdom-node26-localstorage.ts's own header. No component here calls localStorage today --
// this package is a shared UI kit other workspaces build on, so the guard is preventive.
patchJsdomLocalStorageForNode26();
27 changes: 27 additions & 0 deletions test/helpers/vitest-jsdom-node26-localstorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Node 26 predefines its own experimental `globalThis.localStorage` accessor (nodejs/node#60303) that
* returns undefined unless the process was started with --localstorage-file. Because that property
* already *exists* on globalThis before jsdom's environment is installed, Vitest's populateGlobal skips
* copying jsdom's working Storage over it, so any bare `localStorage.*` call throws "Cannot read
* properties of undefined" under Node 26 while passing on Node 22/24.
*
* Call this once from a jsdom-environment vitest.setup.ts. Points globalThis.localStorage at jsdom's
* real Storage from the raw JSDOM window unconditionally: a no-op where the global already is that
* object, the fix on Node 26+. A `??=` guard would not help (the broken accessor already counts as
* "present"); the property is configurable so redefining it is safe.
*
* Shared by apps/loopover-ui, apps/loopover-miner-ui, and packages/loopover-ui-kit's vitest.setup.ts
* (originally three separate copies of this same guard -- #7597, #7612) so a future jsdom-environment
* workspace only has to import this rather than rediscover the bug.
*/
export function patchJsdomLocalStorageForNode26(): void {
const jsdomLocalStorage = (globalThis as { jsdom?: { window?: { localStorage?: Storage } } }).jsdom
?.window?.localStorage;
if (jsdomLocalStorage) {
Object.defineProperty(globalThis, "localStorage", {
value: jsdomLocalStorage,
configurable: true,
writable: true,
});
}
}
Loading