import { vi } from 'vitest'; // jsdom lacks ResizeObserver global.ResizeObserver = class { observe = vi.fn(); unobserve = vi.fn(); disconnect = vi.fn(); } as unknown as typeof ResizeObserver; // jsdom lacks Web Animations API Element.prototype.animate = vi.fn().mockReturnValue({ onfinish: null, cancel: vi.fn(), finish: vi.fn(), pause: vi.fn(), play: vi.fn(), }); // jsdom lacks SVG geometry methods (SVGElement.prototype as any).getTotalLength = vi.fn(() => 0); // Robust localStorage mock for jsdom environment const localStorageMock = (() => { let store: Record = {}; return { getItem: vi.fn((key: string) => store[key] || null), setItem: vi.fn((key: string, value: string) => { store[key] = value.toString(); }), removeItem: vi.fn((key: string) => { delete store[key]; }), clear: vi.fn(() => { store = {}; }), key: vi.fn((index: number) => Object.keys(store)[index] || null), get length() { return Object.keys(store).length; }, }; })(); Object.defineProperty(window, 'localStorage', { value: localStorageMock, writable: true, }); // jsdom lacks PointerEvent; back it with MouseEvent so clientX/clientY survive. if (typeof PointerEvent === 'undefined') { class PointerEventPolyfill extends MouseEvent { pointerId: number; constructor(type: string, params: PointerEventInit = {}) { super(type, params); this.pointerId = params.pointerId ?? 1; } } // @ts-expect-error assigning polyfill to the global scope global.PointerEvent = PointerEventPolyfill; } // jsdom lacks pointer capture HTMLElement.prototype.setPointerCapture = vi.fn(); HTMLElement.prototype.releasePointerCapture = vi.fn(); // jsdom lacks the Popover API. Minimal shim: methods toggle an internal flag, // dispatch a `toggle` event ({ oldState, newState }), and make // matches(':popover-open') reflect the flag so components can sync state. if (typeof HTMLElement.prototype.showPopover !== 'function') { const openFlag = new WeakSet(); const fireToggle = (el: HTMLElement, oldState: string, newState: string) => { const event = new Event('toggle') as Event & { oldState: string; newState: string }; event.oldState = oldState; event.newState = newState; el.dispatchEvent(event); }; HTMLElement.prototype.showPopover = function showPopover(this: HTMLElement) { if (openFlag.has(this)) { return; } openFlag.add(this); fireToggle(this, 'closed', 'open'); }; HTMLElement.prototype.hidePopover = function hidePopover(this: HTMLElement) { if (!openFlag.has(this)) { return; } openFlag.delete(this); fireToggle(this, 'open', 'closed'); }; HTMLElement.prototype.togglePopover = function togglePopover(this: HTMLElement) { if (openFlag.has(this)) { this.hidePopover(); return !openFlag.has(this); } this.showPopover(); return openFlag.has(this); }; const originalMatches = Element.prototype.matches; Element.prototype.matches = function matches(this: Element, selector: string): boolean { if (selector === ':popover-open') { return this instanceof HTMLElement && openFlag.has(this); } return originalMatches.call(this, selector); } as typeof Element.prototype.matches; }