Skip to content
Ilha
Esc
navigateopen⌘Jpreview
On this page

Test components

Render components in tests with renderToString, mount them with happy-dom, and assert updates.

Test components with Bun’s runner plus happy-dom. No browser, no emulator.

Setup

Install happy-dom’s global registrator and preload it:

npm install -d @happy-dom/global-registrator
pnpm add -d @happy-dom/global-registrator
yarn add -d @happy-dom/global-registrator
bun add -d @happy-dom/global-registrator
# bunfig.toml
[test]
preload = "./happydom.ts"
// happydom.ts
import { class GlobalRegistratorGlobalRegistrator } from "@happy-dom/global-registrator";

class GlobalRegistratorGlobalRegistrator.
GlobalRegistrator.register(options?: {
    width?: number;
    height?: number;
    url?: string;
    settings?: IOptionalBrowserSettings;
}): void
Registers Happy DOM globally.
@paramoptions Options.@paramoptions.width Window width. Defaults to "1024".@paramoptions.height Window height. Defaults to "768".@paramoptions.url URL.@paramoptions.settings Settings.
register
();

Render to HTML

renderToString is async — it waits until in-flight work is idle:

import { const atom: AtomFnatom, function renderToString(fn: Component, opts?: RenderToStringOptions): Promise<string>renderToString } from "ilha";
import { const expect: Expect
Asserts that a value matches some criteria.
@linkhttps://jestjs.io/docs/expect#reference@example```ts expect(1 + 1).toBe(2); expect([1,2,3]).toContain(2); expect(null).toBeNull(); ```@paramactual The actual (received) value
expect
, const test: Test<[]>
Runs a test.
@example```ts test("can check if using Bun", () => { expect(Bun).toBeDefined(); }); test("can make a fetch() request", async () => { const response = await fetch("https://example.com/"); expect(response.ok).toBe(true); }); ```@paramlabel the label for the test@paramfn the test function
test
} from "bun:test";
const const Counter: () => ViewCounter = () => { const const count: AtomHandle<number>count = atom<number>(init: number | Atom<number> | Effect<number, any, any> | Stream<number, any, any>): AtomHandle<number>atom(0); return ( <IntrinsicElements[string]: anybutton type: stringtype="button" onclick: () => voidonclick={() => const count: AtomHandle<number>count.AtomHandle<number>.update(f: (current: number) => number): voidupdate((n: numbern: number) => n: numbern + 1)} > {const count: AtomHandle<number>count} </IntrinsicElements[string]: anybutton> ); }; function test(label: string, fn: (done: (err?: unknown) => void) => void | Promise<unknown>, options?: number | TestOptions): void
Runs a test.
@example```ts test("can check if using Bun", () => { expect(Bun).toBeDefined(); }); test("can make a fetch() request", async () => { const response = await fetch("https://example.com/"); expect(response.ok).toBe(true); }); ```@paramlabel the label for the test@paramfn the test function
test
("renders the count", async () => {
const const html: stringhtml = await function renderToString(fn: Component, opts?: RenderToStringOptions): Promise<string>renderToString(const Counter: () => ViewCounter); expect<string>(actual: string, customFailMessage?: string): Matchers<string> (+2 overloads)
@paramactual the actual value@paramcustomFailMessage an optional custom message to display if the test fails.
expect
(const html: stringhtml).MatchersBuiltin<string>.toContain(expected: string): void (+1 overload)
Asserts that a value contains what is expected. The value must be an array or iterable, which includes strings.
@exampleexpect([1, 2, 3]).toContain(1); expect(new Set([true])).toContain(true); expect("hello").toContain("o");@paramexpected the expected value
toContain
(">0<");
});

renderToString needs a DOM. In Node, ilha registers happy-dom automatically when document is missing — the preload above covers browser-only APIs your component touches at import time.

Mount and simulate events

For behavior, mount into a detached element and dispatch events:

import { const atom: AtomFnatom, function mount(el: Element, fn: Component, opts?: MountOptions): () => voidmount } from "ilha";
import { const expect: Expect
Asserts that a value matches some criteria.
@linkhttps://jestjs.io/docs/expect#reference@example```ts expect(1 + 1).toBe(2); expect([1,2,3]).toContain(2); expect(null).toBeNull(); ```@paramactual The actual (received) value
expect
, const test: Test<[]>
Runs a test.
@example```ts test("can check if using Bun", () => { expect(Bun).toBeDefined(); }); test("can make a fetch() request", async () => { const response = await fetch("https://example.com/"); expect(response.ok).toBe(true); }); ```@paramlabel the label for the test@paramfn the test function
test
} from "bun:test";
const const Counter: () => ViewCounter = () => { const const count: AtomHandle<number>count = atom<number>(init: number | Atom<number> | Effect<number, any, any> | Stream<number, any, any>): AtomHandle<number>atom(0); return ( <IntrinsicElements[string]: anybutton type: stringtype="button" onclick: () => voidonclick={() => const count: AtomHandle<number>count.AtomHandle<number>.update(f: (current: number) => number): voidupdate((n: numbern: number) => n: numbern + 1)} > {const count: AtomHandle<number>count} </IntrinsicElements[string]: anybutton> ); }; function test(label: string, fn: (done: (err?: unknown) => void) => void | Promise<unknown>, options?: number | TestOptions): void
Runs a test.
@example```ts test("can check if using Bun", () => { expect(Bun).toBeDefined(); }); test("can make a fetch() request", async () => { const response = await fetch("https://example.com/"); expect(response.ok).toBe(true); }); ```@paramlabel the label for the test@paramfn the test function
test
("clicking increments", async () => {
const const el: HTMLDivElementel = var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.createElement<"div">(tagName: "div", options?: ElementCreationOptions): HTMLDivElement (+2 overloads)
In an HTML document, the **`document.createElement()`** method creates the HTML element specified by localName, or an HTMLUnknownElement if localName isn't recognized. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createElement)
createElement
("div");
var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.body: HTMLElement
The **`Document.body`** property represents the <body> or <frameset> node of the current document, or null if no such element exists. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/body)
body
.ParentNode.append(...nodes: (Node | string)[]): void
Inserts nodes after the last child of node, while replacing strings in nodes with equivalent Text nodes. Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/append)
append
(const el: HTMLDivElementel);
const const unmount: () => voidunmount = function mount(el: Element, fn: Component, opts?: MountOptions): () => voidmount(const el: HTMLDivElementel, const Counter: () => ViewCounter); await new
var Promise: PromiseConstructor
new <unknown>(executor: (resolve: (value: unknown) => void, reject: (reason?: any) => void) => void) => Promise<unknown>
Creates a new Promise.
@paramexecutor A callback used to initialize the promise. This callback is passed two arguments: a resolve callback used to resolve the promise with a value or the result of another promise, and a reject callback used to reject the promise with a provided reason or error.
Promise
((r: (value: unknown) => voidr) => function setTimeout<[value: unknown]>(callback: (value: unknown) => void, delay?: number, value?: unknown): NodeJS.Timeout (+4 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout)
setTimeout
(r: (value: unknown) => voidr, 5));
const const button: HTMLButtonElementbutton = const el: HTMLDivElementel.ParentNode.querySelector<"button">(selectors: "button"): HTMLButtonElement | null (+4 overloads)
Returns the first element that is a descendant of node that matches selectors. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/querySelector)
querySelector
("button")!;
const button: HTMLButtonElementbutton.HTMLElement.click(): void
The **`HTMLElement.click()`** method simulates a mouse click on an element. When called on an element, the element's click event is fired (unless its disabled attribute is set). [MDN Reference](https://developer.mozilla.org/docs/Web/API/HTMLElement/click)
click
();
await new
var Promise: PromiseConstructor
new <unknown>(executor: (resolve: (value: unknown) => void, reject: (reason?: any) => void) => void) => Promise<unknown>
Creates a new Promise.
@paramexecutor A callback used to initialize the promise. This callback is passed two arguments: a resolve callback used to resolve the promise with a value or the result of another promise, and a reject callback used to reject the promise with a provided reason or error.
Promise
((r: (value: unknown) => voidr) => function setTimeout<[value: unknown]>(callback: (value: unknown) => void, delay?: number, value?: unknown): NodeJS.Timeout (+4 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout)
setTimeout
(r: (value: unknown) => voidr, 5));
expect<string>(actual: string, customFailMessage?: string): Matchers<string> (+2 overloads)
@paramactual the actual value@paramcustomFailMessage an optional custom message to display if the test fails.
expect
(const button: HTMLButtonElementbutton.Element.textContent: string
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/textContent)
textContent
).MatchersBuiltin<string>.toContain(expected: string): void (+1 overload)
Asserts that a value contains what is expected. The value must be an array or iterable, which includes strings.
@exampleexpect([1, 2, 3]).toContain(1); expect(new Set([true])).toContain(true); expect("hello").toContain("o");@paramexpected the expected value
toContain
("1");
const unmount: () => voidunmount(); const el: HTMLDivElementel.ChildNode.remove(): void
Removes node. [MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/remove)
remove
();
});

The short sleeps let microtasks paint. unmount() stops streams and listeners — call it before removing the host so a late event cannot paint into a detached tree.

Hydration round-trip

Render, inject, then hydrate to verify a snapshot restores:

import { const atom: AtomFnatom, function mount(el: Element, fn: Component, opts?: MountOptions): () => voidmount, function renderToString(fn: Component, opts?: RenderToStringOptions): Promise<string>renderToString } from "ilha";
import { const expect: Expect
Asserts that a value matches some criteria.
@linkhttps://jestjs.io/docs/expect#reference@example```ts expect(1 + 1).toBe(2); expect([1,2,3]).toContain(2); expect(null).toBeNull(); ```@paramactual The actual (received) value
expect
, const test: Test<[]>
Runs a test.
@example```ts test("can check if using Bun", () => { expect(Bun).toBeDefined(); }); test("can make a fetch() request", async () => { const response = await fetch("https://example.com/"); expect(response.ok).toBe(true); }); ```@paramlabel the label for the test@paramfn the test function
test
} from "bun:test";
const const App: () => ViewApp = () => <IntrinsicElements[string]: anyp>hello</IntrinsicElements[string]: anyp>; function test(label: string, fn: (done: (err?: unknown) => void) => void | Promise<unknown>, options?: number | TestOptions): void
Runs a test.
@example```ts test("can check if using Bun", () => { expect(Bun).toBeDefined(); }); test("can make a fetch() request", async () => { const response = await fetch("https://example.com/"); expect(response.ok).toBe(true); }); ```@paramlabel the label for the test@paramfn the test function
test
("hydrates SSR markup", async () => {
const const html: stringhtml = await function renderToString(fn: Component, opts?: RenderToStringOptions): Promise<string>renderToString(const App: () => ViewApp); const const el: HTMLDivElementel = var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.createElement<"div">(tagName: "div", options?: ElementCreationOptions): HTMLDivElement (+2 overloads)
In an HTML document, the **`document.createElement()`** method creates the HTML element specified by localName, or an HTMLUnknownElement if localName isn't recognized. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/createElement)
createElement
("div");
const el: HTMLDivElementel.Element.innerHTML: string
The **`innerHTML`** property of the Element interface gets or sets the HTML or XML markup contained within the element, omitting any shadow roots in both cases. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/innerHTML)
innerHTML
= const html: stringhtml; // test host; markup is your own render output
var document: Document
**`window.document`** returns a reference to the document contained in the window. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/document)
document
.Document.body: HTMLElement
The **`Document.body`** property represents the <body> or <frameset> node of the current document, or null if no such element exists. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/body)
body
.ParentNode.append(...nodes: (Node | string)[]): void
Inserts nodes after the last child of node, while replacing strings in nodes with equivalent Text nodes. Throws a "HierarchyRequestError" DOMException if the constraints of the node tree are violated. [MDN Reference](https://developer.mozilla.org/docs/Web/API/Document/append)
append
(const el: HTMLDivElementel);
const const unmount: () => voidunmount = function mount(el: Element, fn: Component, opts?: MountOptions): () => voidmount(const el: HTMLDivElementel, const App: () => ViewApp, { hydrate?: boolean | undefinedhydrate: true }); await new
var Promise: PromiseConstructor
new <unknown>(executor: (resolve: (value: unknown) => void, reject: (reason?: any) => void) => void) => Promise<unknown>
Creates a new Promise.
@paramexecutor A callback used to initialize the promise. This callback is passed two arguments: a resolve callback used to resolve the promise with a value or the result of another promise, and a reject callback used to reject the promise with a provided reason or error.
Promise
((r: (value: unknown) => voidr) => function setTimeout<[value: unknown]>(callback: (value: unknown) => void, delay?: number, value?: unknown): NodeJS.Timeout (+4 overloads)
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout)
setTimeout
(r: (value: unknown) => voidr, 5));
expect<string>(actual: string, customFailMessage?: string): Matchers<string> (+2 overloads)
@paramactual the actual value@paramcustomFailMessage an optional custom message to display if the test fails.
expect
(const el: HTMLDivElementel.Element.textContent: string
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Node/textContent)
textContent
).MatchersBuiltin<string>.toContain(expected: string): void (+1 overload)
Asserts that a value contains what is expected. The value must be an array or iterable, which includes strings.
@exampleexpect([1, 2, 3]).toContain(1); expect(new Set([true])).toContain(true); expect("hello").toContain("o");@paramexpected the expected value
toContain
("hello");
const unmount: () => voidunmount(); const el: HTMLDivElementel.ChildNode.remove(): void
Removes node. [MDN Reference](https://developer.mozilla.org/docs/Web/API/CharacterData/remove)
remove
();
});

A snapshot-order bug logs a hydrate mismatch warning and falls back to a full mount — assert on the warning in CI if hydration is load-bearing for you.

Topic Guide
Render and hydrate Render
Streams in tests Streams
Server islands in dev Server islands

Was this page helpful?