/** * 全局测试配置 * 主要为后端测试提供基础环境 * 组件测试使用各自的 test-utils.tsx */ /* eslint-disable @typescript-eslint/no-empty-function */ // Set up jsdom for ALL tests (both backend and frontend) import { JSDOM } from "jsdom"; const dom = new JSDOM("", { pretendToBeVisual: true, url: "http://localhost", }); globalThis.document = dom.window.document; globalThis.window = dom.window as unknown as typeof globalThis & Window; globalThis.navigator = dom.window.navigator; globalThis.HTMLElement = dom.window.HTMLElement; globalThis.HTMLBodyElement = dom.window.HTMLBodyElement; globalThis.HTMLHtmlElement = dom.window.HTMLHtmlElement; globalThis.Element = dom.window.Element; globalThis.getComputedStyle = (element: Element, pseudoElt?: null | string) => { // jsdom 不支持伪元素计算样式;antd/rc-trigger 会传入伪元素参数,测试中退回普通样式即可。 return dom.window.getComputedStyle(element, pseudoElt ? undefined : pseudoElt); }; // Ensure document.body exists if (!globalThis.document.body) { const body = globalThis.document.createElement("body"); globalThis.document.documentElement.appendChild(body); } // CRITICAL: Set up polyfills BEFORE any other imports // This ensures @testing-library/react sees these when it loads // IE-style event handling polyfill (React fallback) const nodeProto = dom.window.Node.prototype; const elementProto = dom.window.Element.prototype; const htmlElementProto = dom.window.HTMLElement.prototype; const attachEventFn = () => {}; const detachEventFn = () => {}; Object.defineProperty(nodeProto, "attachEvent", { configurable: true, value: attachEventFn, writable: true }); Object.defineProperty(nodeProto, "detachEvent", { configurable: true, value: detachEventFn, writable: true }); Object.defineProperty(elementProto, "attachEvent", { configurable: true, value: attachEventFn, writable: true }); Object.defineProperty(elementProto, "detachEvent", { configurable: true, value: detachEventFn, writable: true }); Object.defineProperty(htmlElementProto, "attachEvent", { configurable: true, value: attachEventFn, writable: true }); Object.defineProperty(htmlElementProto, "detachEvent", { configurable: true, value: detachEventFn, writable: true }); // 抑制 antd/rc-trigger 在 jsdom 中产生的 NaN height warning const originalStderrWrite = process.stderr.write.bind(process.stderr); process.stderr.write = (chunk: string | Uint8Array, encodingOrCb?: unknown, cb?: unknown) => { const str = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString(); if (str.includes("NaN") && str.includes("height") && str.includes("css style property")) return true; return originalStderrWrite( chunk, encodingOrCb as Parameters[1], cb as Parameters[2], ); }; const originalConsoleError = console.error; console.error = (...args: unknown[]) => { const message = args.map(String).join(" "); if (message.includes("NaN") && message.includes("height") && message.includes("css style property")) return; originalConsoleError(...args); }; const originalConsoleWarn = console.warn; console.warn = (...args: unknown[]) => { const message = args.map(String).join(" "); if (message.includes("NaN") && message.includes("height") && message.includes("css style property")) return; originalConsoleWarn(...args); }; // Other polyfills globalThis.ResizeObserver = class { disconnect() {} observe() {} unobserve() {} }; globalThis.MutationObserver = class { disconnect() {} observe() {} takeRecords() { return []; } unobserve() {} }; globalThis.IntersectionObserver = class { disconnect() {} observe() {} takeRecords() { return []; } unobserve() {} } as unknown as typeof IntersectionObserver; globalThis.requestAnimationFrame = (cb: FrameRequestCallback) => setTimeout(cb, 16); globalThis.cancelAnimationFrame = (id: number) => clearTimeout(id); Object.defineProperty(dom.window, "matchMedia", { value: (query: string) => ({ addEventListener: () => {}, addListener: () => {}, dispatchEvent: () => true, matches: false, media: query, onchange: null, removeEventListener: () => {}, removeListener: () => {}, }), writable: true, }); dom.window.Element.prototype.scrollTo = () => {}; dom.window.Element.prototype.scrollIntoView = () => {}; Object.defineProperty(dom.window, "customElements", { value: { define: () => {}, get: () => undefined, }, writable: true, }); globalThis.customElements = dom.window.customElements; globalThis.ShadowRoot = class ShadowRoot extends dom.window.DocumentFragment {} as unknown as typeof ShadowRoot; globalThis.SVGElement = class SVGElement extends dom.window.Element {} as unknown as typeof SVGElement; globalThis.Selection = class Selection { addRange() {} removeAllRanges() {} } as unknown as typeof Selection; globalThis.Range = class Range extends dom.window.DocumentFragment {} as unknown as typeof Range; import { afterEach } from "bun:test"; afterEach(() => { document.body.innerHTML = ""; });