Skip to content

Commit 9d98ea4

Browse files
committed
Major
1 parent 7b2ea85 commit 9d98ea4

File tree

9 files changed

+179
-30
lines changed

9 files changed

+179
-30
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"changes": { "package.json": "Major" },
3+
"note": "Deploy major",
4+
"date": "2026-01-01T12:16:43.962770100Z"
5+
}

README.md

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,76 @@
11
# bun-test-env-dom
2-
bun-test-env-dom is a preload library that provides a ready-to-use DOM
2+
3+
A preload library for Bun that provides a ready-to-use DOM environment for testing. It automatically sets up [happy-dom](https://github.com/capricorn86/happy-dom) and enables proper snapshot testing for React and HTML elements with beautifully formatted output.
4+
5+
## Installation
6+
7+
```bash
8+
bun add -d bun-test-env-dom
9+
```
10+
11+
## Setup
12+
13+
Add the following to your `bunfig.toml`:
14+
15+
```toml
16+
[test]
17+
preload = ["bun-test-env-dom"]
18+
```
19+
20+
That's it! The DOM environment is automatically configured when tests run.
21+
22+
## Features
23+
24+
### Snapshot Testing for React & HTML Elements
25+
26+
Snapshot testing works seamlessly with both React elements and HTML elements. The HTML output is automatically formatted for readable snapshots.
27+
28+
```tsx
29+
import { expect, test } from 'bun:test'
30+
import { render } from 'bun-test-env-dom'
31+
32+
test('React element snapshot', () => {
33+
expect(<Component />).toMatchSnapshot()
34+
})
35+
36+
test('HTML element snapshot', () => {
37+
const { container } = render(<Component />)
38+
expect(container).toMatchSnapshot()
39+
})
40+
```
41+
42+
### Re-exported @testing-library/react
43+
44+
All functions from `@testing-library/react` are re-exported, so you can import directly from `bun-test-env-dom`:
45+
46+
```tsx
47+
import { render, screen, fireEvent } from 'bun-test-env-dom'
48+
49+
describe('HomePage', () => {
50+
it('should render', () => {
51+
const { container } = render(<HomePage />)
52+
expect(container).toMatchSnapshot()
53+
})
54+
})
55+
```
56+
57+
### Full TypeScript Support for Custom Matchers
58+
59+
All additional matchers from `@testing-library/jest-dom` are fully typed:
60+
61+
```tsx
62+
import { expect, test } from 'bun:test'
63+
import { render } from 'bun-test-env-dom'
64+
65+
test('custom matchers', () => {
66+
const { getByRole } = render(<Button pressed>Click me</Button>)
67+
68+
expect(getByRole('button')).toBePressed()
69+
expect(getByRole('button')).toBeVisible()
70+
expect(getByRole('button')).toHaveTextContent('Click me')
71+
})
72+
```
73+
74+
## License
75+
76+
Apache-2.0

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "bun-test-env-dom",
33
"type": "module",
4-
"version": "0.0.1",
4+
"version": "0.0.4",
55
"description": "bun-test-env-dom is a preload library that provides a ready-to-use DOM for testing",
66
"author": "JeongMin Oh",
77
"license": "Apache-2.0",
@@ -49,7 +49,7 @@
4949
"lint:fix": "biome check --write .",
5050
"lint": "biome check .",
5151
"test": "bun test",
52-
"build": "tsc && bun build --target node src/index.ts --production --outfile dist/index.cjs --format cjs --packages external && bun build --target node src/index.ts --production --outfile dist/index.mjs --format esm --packages external",
52+
"build": "tsc && bun scripts/clean-dts.ts && bun build --target node src/index.ts --production --outfile dist/index.cjs --format cjs --packages external && bun build --target node src/index.ts --production --outfile dist/index.mjs --format esm --packages external",
5353
"prepare": "bun run husky"
5454
}
5555
}

scripts/clean-dts.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { readdir, unlink } from 'node:fs/promises'
2+
import { join } from 'node:path'
3+
4+
const distDir = join(import.meta.dirname, '..', 'dist')
5+
const files = await readdir(distDir)
6+
7+
for (const file of files) {
8+
if (file.endsWith('.d.ts') && file !== 'index.d.ts') {
9+
await unlink(join(distDir, file))
10+
}
11+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots
2+
3+
exports[`SnapshotTest React element snapshot 1`] = `
4+
"<div>
5+
<div>
6+
before
7+
<div>
8+
SnapshotTestInner
9+
</div>
10+
after
11+
</div>
12+
</div>"
13+
`;
14+
15+
exports[`SnapshotTest HTML element snapshot with render 1`] = `
16+
"<div>
17+
<div>
18+
before
19+
<div>
20+
SnapshotTestInner
21+
</div>
22+
after
23+
</div>
24+
</div>"
25+
`;

src/__tests__/snapshot.test.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { describe, expect, test } from 'bun:test'
2+
import { createElement } from 'react'
3+
import { render } from '../index.ts'
4+
5+
function SnapshotTestInner() {
6+
return createElement('div', null, 'SnapshotTestInner')
7+
}
8+
9+
function SnapshotTest() {
10+
return createElement(
11+
'div',
12+
null,
13+
'before',
14+
createElement(SnapshotTestInner),
15+
'after',
16+
)
17+
}
18+
19+
describe('SnapshotTest', () => {
20+
test('React element snapshot', () => {
21+
expect(createElement(SnapshotTest)).toMatchSnapshot()
22+
})
23+
24+
test('HTML element snapshot with render', () => {
25+
const { container } = render(createElement(SnapshotTest))
26+
expect(container).toMatchSnapshot()
27+
})
28+
})

src/formatHTMLElement.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { prettyHTML } from './prettyHTML.ts'
22

33
export function formatHTMLElement(value: HTMLElement) {
4-
const html = value.outerHTML
5-
return prettyHTML(html)
4+
return prettyHTML(value.outerHTML)
65
}

src/index.ts

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,34 +8,38 @@ import type { ReactElement } from 'react'
88
import { formatHTMLElement } from './formatHTMLElement.ts'
99
import { isReactElement } from './isReactElement.ts'
1010

11-
GlobalRegistrator.register()
12-
expect.extend(matchers)
11+
if (!GlobalRegistrator.isRegistered) {
12+
GlobalRegistrator.register()
13+
expect.extend(matchers)
1314

14-
const originalExpect = expect
15-
test.mock.module('bun:test', () => {
16-
return {
17-
...test,
18-
expect: (value: unknown) => {
19-
if (isReactElement(value)) {
20-
const { container } = render(value as ReactElement)
21-
return originalExpect(formatHTMLElement(container))
22-
}
23-
if (value instanceof HTMLElement) {
24-
return originalExpect(formatHTMLElement(value))
25-
}
26-
return originalExpect(value)
27-
},
28-
}
29-
})
15+
const originalExpect = expect
16+
test.mock.module('bun:test', () => {
17+
return {
18+
...test,
19+
expect: (value: unknown) => {
20+
if (isReactElement(value)) {
21+
const { container } = render(value as ReactElement)
22+
return originalExpect(formatHTMLElement(container))
23+
}
24+
if (value instanceof HTMLElement) {
25+
return originalExpect(formatHTMLElement(value))
26+
}
27+
return originalExpect(value)
28+
},
29+
}
30+
})
3031

31-
// Optional: cleans up `render` after each test
32-
afterEach(() => {
33-
cleanup()
34-
})
32+
// Optional: cleans up `render` after each test
33+
afterEach(() => {
34+
cleanup()
35+
})
36+
}
3537

3638
declare module 'bun:test' {
3739
interface Matchers<T>
3840
extends TestingLibraryMatchers<typeof expect.stringContaining, T> {}
3941
interface AsymmetricMatchers
4042
extends TestingLibraryMatchers<unknown, unknown> {}
4143
}
44+
45+
export * from '@testing-library/react'

tsconfig.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"moduleResolution": "bundler",
1313
"allowImportingTsExtensions": true,
1414
"verbatimModuleSyntax": true,
15-
"noEmit": true,
15+
"emitDeclarationOnly": true,
16+
"outDir": "./dist",
1617

1718
// Best practices
1819
"strict": true,
@@ -24,6 +25,8 @@
2425
// Some stricter flags (disabled by default)
2526
"noUnusedLocals": false,
2627
"noUnusedParameters": false,
27-
"noPropertyAccessFromIndexSignature": false
28-
}
28+
"noPropertyAccessFromIndexSignature": false,
29+
"declaration": true
30+
},
31+
"include": ["src/index.ts"]
2932
}

0 commit comments

Comments
 (0)