/** * tests/setup.ts — vitest 全局 setup * * 在所有测试之前执行,负责: * 1. 导入 CSS(避免 import 报错) * 2. polyfill jsdom 缺失的浏览器 API * 3. 每个测试后全局清理 */ import { beforeAll, afterEach, vi } from 'vitest' // 导入全局 CSS import '../src/styles/base.css' import '../src/styles/window.css' import '../src/styles/desktop.css' import '../src/styles/apps.css' import '../src/styles/apps2.css' // ============================================================ // jsdom polyfills // ============================================================ beforeAll(() => { // matchMedia if (!window.matchMedia) { Object.defineProperty(window, 'matchMedia', { writable: true, value: vi.fn().mockImplementation((query: string) => ({ matches: false, media: query, onchange: null, addListener: vi.fn(), removeListener: vi.fn(), addEventListener: vi.fn(), removeEventListener: vi.fn(), dispatchEvent: vi.fn(() => false), })), }) } // scrollIntoView if (!Element.prototype.scrollIntoView) { Element.prototype.scrollIntoView = vi.fn() } // requestAnimationFrame / cancelAnimationFrame if (!window.requestAnimationFrame) { let rafId = 0 const callbacks = new Map() window.requestAnimationFrame = vi.fn((cb: FrameRequestCallback) => { const handle = ++rafId callbacks.set(handle, cb) setTimeout(() => { const fn = callbacks.get(handle) if (fn) { callbacks.delete(handle); fn(Date.now()) } }, 16) return handle }) window.cancelAnimationFrame = vi.fn((handle: number) => { callbacks.delete(handle) }) } // structuredClone 兜底 if (!window.structuredClone) { ;(window as any).structuredClone = (obj: any) => JSON.parse(JSON.stringify(obj)) } }) // ============================================================ // 每个测试后全局清理 // ============================================================ afterEach(() => { // 恢复真实 timers(防止 fake timers 泄漏) vi.useRealTimers() // 清理 localStorage localStorage.clear() })