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

# DOM Testing

> Test browser APIs, DOM elements, and React components in Bun using happy-dom and React Testing Library.

Bun's test runner is compatible with popular DOM and component testing libraries. The recommended setup uses [happy-dom](https://github.com/capricorn86/happy-dom) to simulate a browser environment.

## Setting up happy-dom

happy-dom implements a complete set of HTML and DOM APIs in JavaScript, making it possible to run browser-targeting code in Bun without a real browser.

<Steps>
  <Step title="Install the package">
    ```bash theme={null}
    bun add -d @happy-dom/global-registrator
    ```
  </Step>

  <Step title="Create a preload file">
    Create `happydom.ts` at the root of your project to register the browser globals:

    ```typescript happydom.ts theme={null}
    import { GlobalRegistrator } from "@happy-dom/global-registrator";

    GlobalRegistrator.register();
    ```
  </Step>

  <Step title="Register the preload in bunfig.toml">
    ```toml bunfig.toml theme={null}
    [test]
    preload = ["./happydom.ts"]
    ```

    This executes `happydom.ts` before any test file runs, making `document`, `window`, and other browser globals available.
  </Step>
</Steps>

## Writing DOM tests

Once happy-dom is registered, you can use browser APIs directly in your tests:

```typescript dom.test.ts theme={null}
/// <reference lib="dom" />

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

test("renders a button", () => {
  document.body.innerHTML = `<button id="submit">Submit</button>`;
  const button = document.querySelector("button");
  expect(button?.textContent).toBe("Submit");
});

test("handles click events", () => {
  let clicked = false;
  document.body.innerHTML = `<button id="btn">Click</button>`;
  const btn = document.getElementById("btn");

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

  expect(clicked).toBe(true);
});
```

<Note>
  Add `/// <reference lib="dom" />` at the top of test files to get TypeScript type checking for browser APIs like `document` and `window`.
</Note>

## React Testing Library

After setting up happy-dom, install React Testing Library:

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

```typescript Button.test.tsx theme={null}
/// <reference lib="dom" />

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

function Counter() {
  const [count, setCount] = React.useState(0);
  return (
    <div>
      <span data-testid="count">{count}</span>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}

test("increments counter on click", () => {
  render(<Counter />);

  expect(screen.getByTestId("count")).toHaveTextContent("0");
  fireEvent.click(screen.getByRole("button", { name: "Increment" }));
  expect(screen.getByTestId("count")).toHaveTextContent("1");
});
```

## Testing custom elements

happy-dom supports the Custom Elements API:

```typescript custom-element.test.ts theme={null}
/// <reference lib="dom" />

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

test("custom element renders content", () => {
  class AlertBox extends HTMLElement {
    constructor() {
      super();
      this.innerHTML = `<div class="alert">${this.getAttribute("message")}</div>`;
    }
  }

  customElements.define("alert-box", AlertBox);

  document.body.innerHTML = `<alert-box message="Something went wrong"></alert-box>`;
  const box = document.querySelector("alert-box");
  expect(box?.querySelector(".alert")?.textContent).toBe("Something went wrong");
});
```

## Advanced setup

For projects that need additional globals polyfilled (e.g., `ResizeObserver`, `matchMedia`), create a more comprehensive preload file:

```typescript test-setup.ts theme={null}
import { GlobalRegistrator } from "@happy-dom/global-registrator";
import "@testing-library/jest-dom";
import { afterEach } from "bun:test";
import { cleanup } from "@testing-library/react";

// Register all browser globals
GlobalRegistrator.register();

// Polyfill APIs not in happy-dom
global.ResizeObserver = class ResizeObserver {
  observe() {}
  unobserve() {}
  disconnect() {}
};

// Clean up after each test
afterEach(() => {
  cleanup();
  document.body.innerHTML = "";
});
```

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

## JSDOM as an alternative

If you prefer jsdom, install it and register it similarly:

```bash theme={null}
bun add -d jsdom
```

```typescript jsdom-setup.ts theme={null}
import { JSDOM } from "jsdom";

const dom = new JSDOM("<!DOCTYPE html><html><body></body></html>");

global.window = dom.window as unknown as Window & typeof globalThis;
global.document = dom.window.document;
global.navigator = dom.window.navigator;
```

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

## Troubleshooting

<AccordionGroup>
  <Accordion title="TypeScript errors for document or window">
    Add the triple-slash DOM reference to the top of the file:

    ```typescript theme={null}
    /// <reference lib="dom" />
    ```

    Alternatively, add `"lib": ["dom"]` to your `tsconfig.json`.
  </Accordion>

  <Accordion title="Globals not available in tests">
    Ensure your preload file is registered in `bunfig.toml` and that `GlobalRegistrator.register()` is called before any tests run.
  </Accordion>

  <Accordion title="React component rendering issues">
    Confirm that both `@testing-library/react` and `@happy-dom/global-registrator` are installed as dev dependencies, and that the preload file is loaded before test files.
  </Accordion>

  <Accordion title="State leaking between tests">
    Reset the DOM in `afterEach` to prevent state from one test affecting the next:

    ```typescript theme={null}
    import { afterEach } from "bun:test";
    import { cleanup } from "@testing-library/react";

    afterEach(() => {
      cleanup();
      document.body.innerHTML = "";
    });
    ```
  </Accordion>
</AccordionGroup>
