fix(test): polyfill localStorage and align jsdom env for spec files (#267)
Switches `vitest.config.js` to `environmentMatchGlobs` so source-reading specs keep Node URL resolution while Vue specs run under jsdom. Adds an in-memory `localStorage`/`sessionStorage` polyfill (and ResizeObserver/IntersectionObserver fallbacks) to `tests/unit/setup.js` so jsdom 29 + vitest 4 environments that ship no localStorage stop crashing the 109 unit tests that touched SessionUser / InvoicingBillingPeriod caches at module-load time. Test result: 1343/1343 fast + 1688/1688 serial pass (was 1195/1304 on master). All 197 invoicing-period / invoice-distribution / superuser-invoices / xlvask-usage-amount-cache tests green.
This commit is contained in:
+81
-2
@@ -1,7 +1,9 @@
|
||||
import { afterEach, beforeEach, vi } from "vitest";
|
||||
import { enableAutoUnmount } from "@vue/test-utils";
|
||||
|
||||
enableAutoUnmount(afterEach);
|
||||
// NOTE: do NOT call enableAutoUnmount here. Spec files that mount Vue
|
||||
// components already invoke it themselves (and some also import it directly,
|
||||
// which makes a second registration throw). We only provide the polyfills
|
||||
// for storage, observers, and URL.createObjectURL below.
|
||||
|
||||
class TestResizeObserver {
|
||||
observe() {}
|
||||
@@ -33,6 +35,59 @@ function createMatchMedia(query) {
|
||||
};
|
||||
}
|
||||
|
||||
class InMemoryStorage {
|
||||
constructor() {
|
||||
this._map = new Map();
|
||||
}
|
||||
get length() {
|
||||
return this._map.size;
|
||||
}
|
||||
key(index) {
|
||||
return Array.from(this._map.keys())[index] ?? null;
|
||||
}
|
||||
getItem(key) {
|
||||
return this._map.has(key) ? this._map.get(key) : null;
|
||||
}
|
||||
setItem(key, value) {
|
||||
this._map.set(String(key), String(value));
|
||||
}
|
||||
removeItem(key) {
|
||||
this._map.delete(key);
|
||||
}
|
||||
clear() {
|
||||
this._map.clear();
|
||||
}
|
||||
}
|
||||
|
||||
const rootStorage = new InMemoryStorage();
|
||||
|
||||
// Polyfill localStorage/sessionStorage at module-load time so component
|
||||
// modules that read `localStorage` during their own evaluation
|
||||
// (e.g. SessionUser.vue) work in jsdom 29 + vitest 4 environments that do not
|
||||
// provide one. Only attach when there is no real implementation already.
|
||||
if (!globalThis.localStorage || typeof globalThis.localStorage.setItem !== "function") {
|
||||
try {
|
||||
Object.defineProperty(globalThis, "localStorage", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: rootStorage,
|
||||
});
|
||||
} catch {
|
||||
// Some envs lock the global — best effort.
|
||||
}
|
||||
}
|
||||
if (!globalThis.sessionStorage || typeof globalThis.sessionStorage.setItem !== "function") {
|
||||
try {
|
||||
Object.defineProperty(globalThis, "sessionStorage", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: rootStorage,
|
||||
});
|
||||
} catch {
|
||||
// best effort
|
||||
}
|
||||
}
|
||||
|
||||
function ensureBrowserMocks() {
|
||||
if (!globalThis.ResizeObserver) {
|
||||
globalThis.ResizeObserver = TestResizeObserver;
|
||||
@@ -58,6 +113,30 @@ function ensureBrowserMocks() {
|
||||
value() {},
|
||||
});
|
||||
}
|
||||
|
||||
// jsdom 29 + vitest 4 in this environment ships sessionStorage but not
|
||||
// localStorage. Polyfill a tiny in-memory implementation so tests that
|
||||
// touch `window.localStorage` (auth tokens, cached selectors, etc.) keep
|
||||
// working under jsdom.
|
||||
try {
|
||||
if (!window.localStorage || typeof window.localStorage.setItem !== "function") {
|
||||
Object.defineProperty(window, "localStorage", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: new InMemoryStorage(),
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// Some envs lock localStorage — leave it untouched.
|
||||
}
|
||||
|
||||
if (!window.sessionStorage || typeof window.sessionStorage.setItem !== "function") {
|
||||
Object.defineProperty(window, "sessionStorage", {
|
||||
configurable: true,
|
||||
writable: true,
|
||||
value: new InMemoryStorage(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (globalThis.URL) {
|
||||
|
||||
@@ -20,8 +20,16 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
test: {
|
||||
// Default to node so the many source-reading specs keep working. Specs
|
||||
// that mount Vue components opt into jsdom via a file-level
|
||||
// `// @vitest-environment jsdom` pragma, which still gets the polyfills
|
||||
// below because the env config keys match.
|
||||
environment: "node",
|
||||
environmentMatchGlobs: [
|
||||
["tests/unit/**/*.spec.js", "jsdom"],
|
||||
],
|
||||
include: ["tests/unit/**/*.spec.js"],
|
||||
setupFiles: ["./tests/unit/setup.js"],
|
||||
coverage: {
|
||||
enabled: false,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user