import { afterEach, beforeEach, vi } from "vitest"; // 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() {} unobserve() {} disconnect() {} } class TestIntersectionObserver { observe() {} unobserve() {} disconnect() {} takeRecords() { return []; } } function createMatchMedia(query) { return { matches: false, media: query, onchange: null, addListener() {}, removeListener() {}, addEventListener() {}, removeEventListener() {}, dispatchEvent() { return false; }, }; } 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; } if (!globalThis.IntersectionObserver) { globalThis.IntersectionObserver = TestIntersectionObserver; } if (globalThis.window) { if (!window.matchMedia) { Object.defineProperty(window, "matchMedia", { configurable: true, writable: true, value: createMatchMedia, }); } if (!window.scrollTo) { Object.defineProperty(window, "scrollTo", { configurable: true, writable: true, 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) { if (!URL.createObjectURL) { Object.defineProperty(URL, "createObjectURL", { configurable: true, writable: true, value: vi.fn(() => "blob:vitest-object-url"), }); } if (!URL.revokeObjectURL) { Object.defineProperty(URL, "revokeObjectURL", { configurable: true, writable: true, value: vi.fn(), }); } } } function clearStorage(storage) { try { storage?.clear(); } catch { // Some jsdom URL modes can make storage unavailable. } } beforeEach(() => { ensureBrowserMocks(); }); afterEach(async () => { await Promise.resolve(); await Promise.resolve(); vi.clearAllTimers(); vi.useRealTimers(); vi.clearAllMocks(); vi.unstubAllGlobals(); if (globalThis.document?.body) { document.body.innerHTML = ""; } if (globalThis.window) { clearStorage(window.localStorage); clearStorage(window.sessionStorage); } ensureBrowserMocks(); });