| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- /**
- * Test setup file for Vitest.
- * Configures testing environment, mocks, and MSW server.
- */
- import '@testing-library/jest-dom';
- import { afterAll, afterEach, beforeAll, vi } from 'vitest';
- import { cleanup } from '@testing-library/react';
- import { server } from './mocks/server';
- // Initialize i18n for tests (suppresses react-i18next warnings)
- import '../i18n';
- // Setup MSW server
- beforeAll(() =>
- server.listen({
- // Bypass unhandled requests silently (don't warn, just let them through)
- // Handlers use wildcard (*) prefix to match any origin
- onUnhandledRequest: 'bypass',
- })
- );
- afterEach(() => {
- cleanup();
- server.resetHandlers();
- });
- afterAll(() => server.close());
- // Mock window.matchMedia for responsive components
- // Uses a plain function (not vi.fn) so vi.restoreAllMocks() in tests can't wipe it
- Object.defineProperty(window, 'matchMedia', {
- writable: true,
- value: (query: string) => ({
- matches: false,
- media: query,
- onchange: null,
- addListener: () => {},
- removeListener: () => {},
- addEventListener: () => {},
- removeEventListener: () => {},
- dispatchEvent: () => true,
- }),
- });
- // Mock ResizeObserver
- class ResizeObserverMock {
- observe = vi.fn();
- unobserve = vi.fn();
- disconnect = vi.fn();
- }
- vi.stubGlobal('ResizeObserver', ResizeObserverMock);
- // Mock IntersectionObserver
- class IntersectionObserverMock {
- observe = vi.fn();
- unobserve = vi.fn();
- disconnect = vi.fn();
- root = null;
- rootMargin = '';
- thresholds = [];
- }
- vi.stubGlobal('IntersectionObserver', IntersectionObserverMock);
- // Mock WebSocket
- class MockWebSocket {
- static readonly CONNECTING = 0;
- static readonly OPEN = 1;
- static readonly CLOSING = 2;
- static readonly CLOSED = 3;
- readyState = MockWebSocket.OPEN;
- onopen: ((event: Event) => void) | null = null;
- onclose: ((event: CloseEvent) => void) | null = null;
- onmessage: ((event: MessageEvent) => void) | null = null;
- onerror: ((event: Event) => void) | null = null;
- url: string;
- constructor(url: string) {
- this.url = url;
- setTimeout(() => this.onopen?.(new Event('open')), 0);
- }
- send = vi.fn();
- close = vi.fn();
- }
- vi.stubGlobal('WebSocket', MockWebSocket);
- // Mock scrollTo
- window.scrollTo = vi.fn();
- // Silence jsdom's "Not implemented: navigation (except hash changes)"
- // warning when production code does ``window.location.href = '/setup'``
- // (AuthContext setup-redirect) or other full-page nav assignments.
- //
- // jsdom defines ``href`` as a non-configurable accessor on
- // ``Location.prototype``, so it cannot be redefined on the instance via
- // ``Object.defineProperty``. We wrap the real jsdom Location in a Proxy
- // that turns ``href = "..."`` writes into silent no-ops; everything
- // else (reads of ``pathname`` / ``search`` / ``hash``, writes to
- // ``hash``, ``assign()`` / ``replace()`` calls, ``history.replaceState``
- // updating ``search``) passes through unchanged. The ``get`` trap is
- // deliberately permissive: returning a substitute value for a non-
- // configurable target property violates Proxy invariants and the spread
- // operator (``{ ...window.location }``) walks every key — tests that
- // use the spread to copy the location object must keep working.
- {
- const realLocation = window.location;
- const locationProxy = new Proxy(realLocation, {
- set(target, prop, value) {
- if (prop === 'href') {
- // Silently swallow "navigation not implemented". Tests asserting
- // on the redirect should replace ``window.location`` themselves
- // (several existing tests do exactly this).
- return true;
- }
- Reflect.set(target, prop, value);
- return true;
- },
- get(target, prop, receiver) {
- // Return the exact value present on the target. Returning a
- // bound/wrapped version of a non-configurable function (``assign``
- // is one) violates Proxy invariants (the spread operator at one
- // call site triggers this). Production code that does
- // ``window.location.assign(url)`` calls the function with the
- // proxy as ``this``, which jsdom still accepts because its
- // Location methods unwrap their receiver internally.
- return Reflect.get(target, prop, receiver);
- },
- });
- Object.defineProperty(window, 'location', {
- configurable: true,
- writable: true,
- value: locationProxy,
- });
- }
- // Mock localStorage
- const localStorageMock = {
- getItem: vi.fn(),
- setItem: vi.fn(),
- removeItem: vi.fn(),
- clear: vi.fn(),
- };
- Object.defineProperty(window, 'localStorage', { value: localStorageMock });
- // Suppress console output during tests (reduces noise)
- // Remove these lines if you need to debug test output
- vi.spyOn(console, 'log').mockImplementation(() => {});
- vi.spyOn(console, 'warn').mockImplementation(() => {});
- vi.spyOn(console, 'error').mockImplementation(() => {});
|