- 移除 tdesign-react + tdesign-icons-react,新增 antd@6.4.3 + @ant-design/icons@6.2.3 - Layout/Header/Sider/Content 替换 TDesign Layout,Sider 内置折叠管理 - Segmented 替换 RadioGroup 主题切换,ConfigProvider 主题算法切换 - Menu items prop 模式,Sidebar 简化为无 props 纯组件 - Table/Modal/Form/Input.TextArea/Tabs/Tag/Popconfirm 全量迁移 - App.useApp().message 替换 MessagePlugin(hooks 模式) - --td-* CSS 变量替换为 --ant-* antd CSS 变量 - 测试适配:ConfigProvider+App wrapper,.ant-menu-item-selected,antd CSS-in-JS jsdom 兼容 - 文档更新:frontend.md, development/README.md, config.yaml, deploy.md - vendor-antd chunk 755KB gzipped 240KB
115 lines
3.6 KiB
TypeScript
115 lines
3.6 KiB
TypeScript
/**
|
|
* 全局测试配置
|
|
* 主要为后端测试提供基础环境
|
|
* 组件测试使用各自的 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("<!DOCTYPE html><html><body></body></html>", {
|
|
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.Element = dom.window.Element;
|
|
globalThis.getComputedStyle = dom.window.getComputedStyle;
|
|
|
|
// 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 });
|
|
|
|
// 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 = "";
|
|
});
|