> ## Documentation Index
> Fetch the complete documentation index at: https://bun.ll1025.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# DOM 测试

> 了解如何结合 Bun 使用 happy-dom 和 React Testing Library 测试 DOM 元素和组件

Bun 的测试运行器与现有的组件和 DOM 测试库配合良好，包括 React Testing Library 和 happy-dom。

## happy-dom

对于前端代码和组件的无头测试，我们推荐使用 happy-dom。它实现了纯 JavaScript 中完整的 HTML 和 DOM API 集合，因此可以高保真地模拟浏览器环境。

安装 `@happy-dom/global-registrator` 包作为开发依赖。

```bash terminal icon="terminal" theme={null}
bun add -d @happy-dom/global-registrator
```

使用 Bun 的预加载功能在测试运行前注册 happy-dom 全局变量，这会使浏览器 API（如 `document`）在全局作用域中可用。在项目根目录创建一个名为 `happydom.ts` 的文件，包含以下代码：

```ts title="happydom.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { GlobalRegistrator } from "@happy-dom/global-registrator";

GlobalRegistrator.register();
```

要在 `bun test` 之前预加载此文件，请打开或创建一个 `bunfig.toml` 文件并添加以下行。

```toml title="bunfig.toml" icon="settings" theme={null}
[test]
preload = ["./happydom.ts"]
```

现在 `bun test` 会在你的测试之前执行 `happydom.ts`，因此测试可以使用诸如 `document` 和 `window` 之类的浏览器 API。

```ts title="dom.test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { test, expect } from "bun:test";

test("dom 测试", () => {
  document.body.innerHTML = `<button>My button</button>`;
  const button = document.querySelector("button");
  expect(button?.innerText).toEqual("My button");
});
```

### TypeScript 支持

根据你的 `tsconfig.json` 设置，你可能会在上面代码中看到"Cannot find name 'document'"的类型错误。要加载 `document` 和其他浏览器 API 的类型，请在任何测试文件的顶部添加以下三斜杠指令。

```ts title="dom.test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
/// <reference lib="dom" />

import { test, expect } from "bun:test";

test("dom 测试", () => {
  document.body.innerHTML = `<button>My button</button>`;
  const button = document.querySelector("button");
  expect(button?.innerText).toEqual("My button");
});
```

使用 `bun test` 运行测试：

```bash terminal icon="terminal" theme={null}
bun test
```

```
bun test v1.3.3

dom.test.ts:
✓ dom test [0.82ms]

 1 pass
 0 fail
 1 expect() calls
Ran 1 test across 1 file. [125.00ms]
```

## React Testing Library

Bun 可以与 React Testing Library 配合测试 React 组件。按照前述方法设置 happy-dom 后，正常安装和使用 React Testing Library。

```bash terminal icon="terminal" theme={null}
bun add -d @testing-library/react @testing-library/jest-dom
```

```ts title="component.test.tsx" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
/// <reference lib="dom" />

import { test, expect } from 'bun:test';
import { render, screen } from '@testing-library/react';
import '@testing-library/jest-dom';

function Button({ children }: { children: React.ReactNode }) {
  return <button>{children}</button>;
}

test('渲染按钮', () => {
  render(<Button>Click me</Button>);
  expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
```

## 高级 DOM 测试

### 自定义元素

使用相同的设置测试自定义元素和 Web 组件：

```ts title="custom-element.test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
/// <reference lib="dom" />

import { test, expect } from "bun:test";

test("自定义元素", () => {
  // 定义一个自定义元素
  class MyElement extends HTMLElement {
    constructor() {
      super();
      this.innerHTML = "<p>自定义元素内容</p>";
    }
  }

  customElements.define("my-element", MyElement);

  // 在测试中使用它
  document.body.innerHTML = "<my-element></my-element>";
  const element = document.querySelector("my-element");
  expect(element?.innerHTML).toBe("<p>自定义元素内容</p>");
});
```

### 事件测试

测试 DOM 事件和用户交互：

```ts title="events.test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
/// <reference lib="dom" />

import { test, expect } from "bun:test";

test("按钮点击事件", () => {
  let clicked = false;

  document.body.innerHTML = '<button id="test-btn">Click me</button>';
  const button = document.getElementById("test-btn");

  button?.addEventListener("click", () => {
    clicked = true;
  });

  button?.click();
  expect(clicked).toBe(true);
});
```

## 配置技巧

### 全局设置

对于更复杂的设置，创建一个也注册全局模拟的预加载文件：

```ts title="test-setup.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import "@testing-library/jest-dom";

// 注册 happy-dom 全局变量
GlobalRegistrator.register();

// 在此处添加任何全局测试配置
global.ResizeObserver = class ResizeObserver {
  observe() {}
  unobserve() {}
  disconnect() {}
};

// 根据需要模拟其他 API
Object.defineProperty(window, "matchMedia", {
  writable: true,
  value: jest.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(),
    removeListener: jest.fn(),
    addEventListener: jest.fn(),
    removeEventListener: jest.fn(),
    dispatchEvent: jest.fn(),
  })),
});
```

然后更新你的 `bunfig.toml`：

```toml title="bunfig.toml" icon="settings" theme={null}
[test]
preload = ["./test-setup.ts"]
```

## 故障排除

### 常见问题

**DOM API 的 TypeScript 错误**：在测试文件顶部加上 `/// <reference lib="dom" />` 指令。

**缺少全局变量**：检查你的预加载文件是否导入并注册了 `@happy-dom/global-registrator`。

**React 组件渲染问题**：检查是否安装了 `@testing-library/react` 并且 happy-dom 已设置。

### 性能考量

happy-dom 速度很快，但对于非常大的测试套件，你可能需要：

* 使用 `beforeEach` 在测试之间重置 DOM 状态
* 避免在单个测试中创建太多 DOM 元素
* 使用测试库的 `cleanup` 函数

```ts title="test-setup.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { afterEach } from "bun:test";
import { cleanup } from "@testing-library/react";

afterEach(() => {
  cleanup();
  document.body.innerHTML = "";
});
```
