From c59fb8dcbc8a63b5db2de5e0bfe49551f35ded7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rk=20Tolm=C3=A1cs?= Date: Wed, 6 May 2026 17:42:36 +0200 Subject: [PATCH] fix: LocalStorage is empty object on node@25 which breaks tests (#11240) Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com> --- .../excalidraw/tests/helpers/polyfills.ts | 39 +++++++++++++++++++ setupTests.ts | 10 +++-- 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/packages/excalidraw/tests/helpers/polyfills.ts b/packages/excalidraw/tests/helpers/polyfills.ts index d02c7290be..59c490cf9a 100644 --- a/packages/excalidraw/tests/helpers/polyfills.ts +++ b/packages/excalidraw/tests/helpers/polyfills.ts @@ -94,3 +94,42 @@ export const testPolyfills = { // https://github.com/vitest-dev/vitest/pull/4164#issuecomment-2172729965 URL, }; + +export const PolyfillLocalStorage = () => { + // Node.js 25+ provides a native localStorage global that shadows jsdom's, + // and jsdom's own localStorage also uses the native one -- both are broken + // (empty objects without Storage methods). On older Node versions, jsdom + // provides a working localStorage. This polyfill replaces localStorage on + // all supported versions with a standard Storage implementation backed by + // a Map, ensuring consistent behavior regardless of the Node.js version. + const storage = new Map(); + const storagePolyfill: Storage = { + get length() { + return storage.size; + }, + clear() { + storage.clear(); + }, + key(index) { + return Array.from(storage.keys())[index] ?? null; + }, + getItem(key) { + return storage.get(key) ?? null; + }, + setItem(key, value) { + storage.set(key, value); + }, + removeItem(key) { + storage.delete(key); + }, + *[Symbol.iterator]() { + yield* storage.entries(); + }, + }; + + Object.defineProperty(window, "localStorage", { + value: storagePolyfill, + writable: true, + configurable: true, + }); +}; diff --git a/setupTests.ts b/setupTests.ts index 41bc8e165c..4a690f43cb 100644 --- a/setupTests.ts +++ b/setupTests.ts @@ -8,7 +8,13 @@ import { vi } from "vitest"; import polyfill from "./packages/excalidraw/polyfill"; import { mockThrottleRAF } from "./packages/excalidraw/tests/helpers/mocks"; import { yellow } from "./packages/excalidraw/tests/helpers/colorize"; -import { testPolyfills } from "./packages/excalidraw/tests/helpers/polyfills"; +import { + PolyfillLocalStorage, + testPolyfills, +} from "./packages/excalidraw/tests/helpers/polyfills"; + +Object.assign(globalThis, testPolyfills); +PolyfillLocalStorage(); vi.mock("@excalidraw/common", async (importOriginal) => { const module = await importOriginal(); @@ -22,8 +28,6 @@ vi.mock("@excalidraw/common", async (importOriginal) => { // mock for pep.js not working with setPointerCapture() HTMLElement.prototype.setPointerCapture = vi.fn(); -Object.assign(globalThis, testPolyfills); - require("fake-indexeddb/auto"); polyfill();