Skip to content

Commit 49fbb22

Browse files
chore: update README (#37)
1 parent f5cc8bc commit 49fbb22

3 files changed

Lines changed: 91 additions & 116 deletions

File tree

AGENTS.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313

1414
### Architecture
1515

16-
- **`src/renderer.ts`**: The main entry point. Exports `createRoot` which initializes the custom React reconciler.
16+
- **`src/index.ts`**: The public entry point that exports `createRoot` from `renderer.ts`.
17+
- **`src/renderer.ts`**: Contains the main implementation. Exports `createRoot` which initializes the custom React reconciler.
1718
- **`src/reconciler.ts`**: Implements the `react-reconciler` host config, translating React updates into operations on the internal tree.
1819
- **`src/host-element.ts`**: Defines `HostElement`, a wrapper around the internal fiber nodes that provides a user-friendly, DOM-like API (e.g., `children`, `props`, `parent`).
1920
- **`src/render-to-json.ts`**: Handles the serialization of `HostElement` trees into JSON format for snapshots.
@@ -41,7 +42,7 @@ The project uses **Bun** as the preferred runtime/package manager for developmen
4142
## Development Conventions
4243

4344
- **Language:** Strict TypeScript.
44-
- **Styling:** Code formatting is enforced by Prettier (`.prettierrc` implied by scripts).
45+
- **Styling:** Code formatting is enforced by Prettier (configured via npm scripts, no explicit config file, uses defaults).
4546
- **Testing:** Unit tests are located in `src/__tests__/`. Tests use `jest` and `ts-jest`.
4647
- **Linting:** ESLint is used for static analysis.
4748
- **Git:** Commits seem to follow standard conventions (implied by `release-it`).

README.md

Lines changed: 87 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,22 @@
11
# Test Renderer for React
22

3-
A lightweight, JavaScript-only replacement for the deprecated React Test Renderer.
3+
A lightweight, JS-only building block for creating Testing Library-style libraries.
44

5-
## Why Use It?
5+
This library is used by [React Native Testing Library](https://github.com/callstack/react-native-testing-library) but is written generically to support different React variants and custom renderers.
66

7-
- **Pure JavaScript Testing** - Test React components in Jest or Vitest without browser or native dependencies
8-
- **Universal** - Can be used to simulate React Native or any other React renderer
9-
- **React 19 Ready** - Modern alternative as React Test Renderer is now deprecated
10-
- **Lightweight** - Minimal dependencies and small bundle size
11-
- **Type-safe** - Written in TypeScript with full type definitions
12-
- **Flexible Configuration** - Customizable reconciler options for different use cases
7+
This library also serves as a replacement for the deprecated React Test Renderer. It is built using [React Reconciler](https://github.com/facebook/react/tree/main/packages/react-reconciler) to provide a custom renderer that operates on host elements by default, with proper escape hatches when needed. Most React Reconciler options are exposed for maximum flexibility.
138

149
## Installation
1510

1611
```bash
17-
npm install -D test-renderer
12+
yarn add -D test-renderer
1813
```
1914

20-
## Basic Usage
15+
## Getting Started
2116

2217
```tsx
23-
import { act } from "react";
2418
import { createRoot } from "test-renderer";
19+
import { act } from "react";
2520

2621
test("renders a component", async () => {
2722
const renderer = createRoot();
@@ -45,144 +40,126 @@ test("renders a component", async () => {
4540

4641
### `createRoot(options?)`
4742

48-
Creates a new test renderer instance.
43+
Creates a new test renderer root instance.
4944

50-
```tsx
51-
const renderer = createRoot(options);
52-
```
45+
**Parameters:**
46+
47+
- `options` (optional): Configuration options for the renderer. See [`RootOptions`](#rootoptions) below.
5348

54-
Returns a `Root` object with:
49+
**Returns:** A `Root` object with the following properties:
5550

56-
- `render(element)` - Render a React element. Must be called within `act()`.
57-
- `unmount()` - Unmount and clean up. Must be called within `act()`.
58-
- `container` - A wrapper `HostElement` that contains the rendered element(s). Use this to query and inspect the rendered tree.
51+
- `render(element: ReactElement)`: Renders a React element into the root. Must be called within `act()`.
52+
- `unmount()`: Unmounts the root and cleans up. Must be called within `act()`.
53+
- `container`: A `HostElement` wrapper that contains the rendered element(s). Use this to query and inspect the rendered tree.
54+
55+
**Example:**
56+
57+
```tsx
58+
const renderer = createRoot();
59+
await act(async () => {
60+
renderer.render(<div>Hello!</div>);
61+
});
62+
```
5963

6064
### `RootOptions`
6165

62-
| Option | Type | Description |
63-
| -------------------- | ---------------------------- | ----------------------------------------------------------------- |
64-
| `textComponents` | `string[]` | Element types that can contain text (for React Native simulation) |
65-
| `createNodeMock` | `(element) => object` | Create mock objects for refs |
66-
| `identifierPrefix` | `string` | Prefix for `useId()` generated IDs |
67-
| `isStrictMode` | `boolean` | Enable React Strict Mode |
68-
| `onCaughtError` | `(error, errorInfo) => void` | Called when Error Boundary catches an error |
69-
| `onUncaughtError` | `(error, errorInfo) => void` | Called for uncaught errors |
70-
| `onRecoverableError` | `(error, errorInfo) => void` | Called when React recovers from errors |
66+
Configuration options for the test renderer. Many of these options correspond to React Reconciler configuration options. For detailed information about reconciler-specific options, refer to the [React Reconciler source code](https://github.com/facebook/react/tree/main/packages/react-reconciler).
67+
68+
| Option | Type | Description |
69+
| -------------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
70+
| `textComponentTypes` | `string[]` | Types of host components that are allowed to contain text nodes. Trying to render text outside of these components will throw an error. Useful for simulating React Native's text rendering rules. |
71+
| `publicTextComponentTypes` | `string[]` | Host component types to display to users in error messages when they try to render text outside of `textComponentTypes`. Defaults to `textComponentTypes` if not provided. |
72+
| `createNodeMock` | `(element: ReactElement) => object` | Function to create mock objects for refs. Called once per element that has a ref. Defaults to returning an empty object. |
73+
| `identifierPrefix` | `string` | A string prefix React uses for IDs generated by `useId()`. Useful to avoid conflicts when using multiple roots. |
74+
| `isStrictMode` | `boolean` | Enable React Strict Mode. When enabled, components render twice and effects run twice in development. |
75+
| `onCaughtError` | `(error: unknown, errorInfo: { componentStack?: string }) => void` | Callback called when React catches an error in an Error Boundary. Called with the error caught by the Error Boundary and an errorInfo object containing the component stack. |
76+
| `onUncaughtError` | `(error: unknown, errorInfo: { componentStack?: string }) => void` | Callback called when an error is thrown and not caught by an Error Boundary. Called with the error that was thrown and an errorInfo object containing the component stack. |
77+
| `onRecoverableError` | `(error: unknown, errorInfo: { componentStack?: string }) => void` | Callback called when React automatically recovers from errors. Called with an error React throws and an errorInfo object containing the component stack. Some recoverable errors may include the original error cause as `error.cause`. |
7178

7279
### `HostElement`
7380

74-
The rendered element wrapper with a DOM-like API:
81+
A wrapper around rendered host elements that provides a DOM-like API for querying and inspecting the rendered tree.
82+
83+
**Properties:**
7584

76-
| Property/Method | Description |
77-
| ------------------------------- | ---------------------------------------- |
78-
| `type` | Element type (e.g., `"div"`, `"span"`) |
79-
| `props` | Element props object |
80-
| `children` | Array of child elements and text strings |
81-
| `parent` | Parent element or `null` |
82-
| `toJSON()` | Convert to JSON for snapshots |
83-
| `queryAll(predicate, options?)` | Find all matching descendant elements |
85+
- `type: string`: The element type (e.g., `"View"`, `"div"`). Returns an empty string for the container element.
86+
- `props: HostElementProps`: The element's props object.
87+
- `children: HostNode[]`: Array of child nodes (elements and text strings). Hidden children are excluded.
88+
- `parent: HostElement | null`: The parent element, or `null` if this is the root container.
89+
- `unstable_fiber: Fiber | null`: Access to the underlying React Fiber node. **Warning:** This is an unstable API that exposes internal React Reconciler structures which may change without warning in future React versions. Use with caution and only when absolutely necessary.
8490

85-
## Querying Elements
91+
**Methods:**
8692

87-
Use `queryAll()` to find elements in the rendered tree:
93+
- `toJSON(): JsonElement | null`: Converts this element to a JSON representation suitable for snapshots. Returns `null` if the element is hidden.
94+
- `queryAll(predicate: (element: HostElement) => boolean, options?: QueryOptions): HostElement[]`: Finds all descendant elements matching the predicate. See [Query Options](#query-options) below.
95+
96+
**Example:**
8897

8998
```tsx
9099
const renderer = createRoot();
91100
await act(async () => {
92-
renderer.render(
93-
<div>
94-
<button data-testid="btn-1">First</button>
95-
<button data-testid="btn-2">Second</button>
96-
</div>,
97-
);
101+
renderer.render(<div className="container">Hello</div>);
98102
});
99103

100-
// Find all buttons
101-
const buttons = renderer.container.queryAll((el) => el.type === "button");
102-
expect(buttons).toHaveLength(2);
103-
104-
// Find by props
105-
const btn1 = renderer.container.queryAll((el) => el.props["data-testid"] === "btn-1");
106-
expect(btn1[0].children).toContain("First");
104+
const root = renderer.container.children[0] as HostElement;
105+
expect(root.type).toBe("div");
106+
expect(root.props.className).toBe("container");
107+
expect(root.children).toContain("Hello");
107108
```
108109

109-
### Query Options
110+
### `QueryOptions`
110111

111-
```tsx
112-
queryAll(predicate, {
113-
includeSelf: false, // Include the element itself in results
114-
matchDeepestOnly: false, // Only return deepest matches (exclude ancestors)
115-
});
116-
```
112+
Options for configuring element queries.
117113

118-
## React Native Simulation
114+
| Option | Type | Default | Description |
115+
| ------------------ | --------- | ------- | -------------------------------------------------------------------------------------------------------------------- |
116+
| `includeSelf` | `boolean` | `false` | Include the element itself in the results if it matches the predicate. |
117+
| `matchDeepestOnly` | `boolean` | `false` | Exclude any ancestors of deepest matched elements even if they match the predicate. Only return the deepest matches. |
119118

120-
Use `textComponents` to simulate React Native's text rendering rules:
119+
**Example:**
121120

122121
```tsx
123-
import { createElement } from "react";
124-
125-
const renderer = createRoot({
126-
textComponents: ["Text", "RCTText"],
127-
});
122+
// Find all divs, including nested ones
123+
const allDivs = container.queryAll((el) => el.type === "div");
128124

129-
// This works - text inside Text component
130-
await act(async () => {
131-
renderer.render(createElement("Text", null, "Hello!"));
132-
});
125+
// Find only the deepest divs (exclude parent divs if they contain matching children)
126+
const deepestDivs = container.queryAll((el) => el.type === "div", { matchDeepestOnly: true });
133127

134-
// This throws - text outside Text component
135-
await act(async () => {
136-
renderer.render(<View>Hello!</View>); // Error!
137-
});
128+
// Include the container itself if it matches
129+
const includingSelf = container.queryAll((el) => el.type === "div", { includeSelf: true });
138130
```
139131

140-
## Mocking Refs
132+
## Migration from React Test Renderer
133+
134+
This library serves as a replacement for the deprecated React Test Renderer. The main differences are:
135+
136+
- **Host element focus**: This library operates on host components by default, while React Test Renderer worked with a mix of host and composite components. You can access the underlying fiber via `unstable_fiber` if needed.
137+
- **Built on React Reconciler**: This library is built using React Reconciler, providing a custom renderer implementation.
138+
- **Exposed reconciler options**: Most React Reconciler configuration options are exposed through `RootOptions` for maximum flexibility.
141139

142-
Use `createNodeMock` to provide mock objects for refs:
140+
For most use cases, the migration is straightforward:
143141

144142
```tsx
145-
const renderer = createRoot({
146-
createNodeMock: (element) => {
147-
if (element.type === "input") {
148-
return {
149-
focus: jest.fn(),
150-
value: "",
151-
};
152-
}
153-
return {};
154-
},
155-
});
143+
// Before (React Test Renderer)
144+
import TestRenderer from "react-test-renderer";
145+
const tree = TestRenderer.create(<MyComponent />);
156146

147+
// After (test-renderer)
148+
import { createRoot } from "test-renderer";
149+
const root = createRoot();
157150
await act(async () => {
158-
renderer.render(<input ref={inputRef} />);
151+
root.render(<MyComponent />);
159152
});
160-
161-
// inputRef.current is now the mock object
162-
inputRef.current.focus();
153+
const tree = root.container;
163154
```
164155

165-
## Error Handling
166-
167-
Handle React errors with custom callbacks:
168-
169-
```tsx
170-
const renderer = createRoot({
171-
onCaughtError: (error, errorInfo) => {
172-
// Called when an Error Boundary catches an error
173-
console.log("Caught:", error.message);
174-
console.log("Component stack:", errorInfo.componentStack);
175-
},
176-
onUncaughtError: (error, errorInfo) => {
177-
// Called for uncaught render errors
178-
},
179-
});
180-
```
156+
## Supported React Features
181157

182-
## Key Differences from React Test Renderer
158+
This library supports all modern React features including:
183159

184-
- Works at host component level only (no composite components)
185-
- Expost all reconciler configuration options
160+
- Concurrent rendering
161+
- Error boundaries
162+
- Suspense boundaries
186163

187164
## License
188165

src/host-element.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,7 @@ export class HostElement {
8484
* @param options - Optional query configuration.
8585
* @returns Array of matching elements.
8686
*/
87-
queryAll(
88-
predicate: (element: HostElement, options?: QueryOptions) => boolean,
89-
options?: QueryOptions,
90-
): HostElement[] {
87+
queryAll(predicate: (element: HostElement) => boolean, options?: QueryOptions): HostElement[] {
9188
return queryAll(this, predicate, options);
9289
}
9390

0 commit comments

Comments
 (0)