diff --git a/README.md b/README.md index 7832a32ee..480f8ef03 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ ## What is this library? -This library allows you to make render-per-render assertions on your React -components and hooks. This is usually not necessary, but can be highly -beneficial when testing hot code paths. +This library allows you to make committed-render-to-committed-render assertions +on your React components and hooks. This is usually not necessary, but can be +highly beneficial when testing hot code paths. ## Who is this library for? @@ -36,7 +36,7 @@ test('iterate through renders with DOM snapshots', async () => { const {takeRender, render} = createRenderStream({ snapshotDOM: true, }) - const utils = render() + const utils = await render() const incrementButton = utils.getByText('Increment') await userEvent.click(incrementButton) await userEvent.click(incrementButton) @@ -63,23 +63,19 @@ test('iterate through renders with DOM snapshots', async () => { In every place you would call ```js -const renderStream = createRenderStream(options) -const utils = renderStream.render(, options) +const {takeRender, render} = createRenderStream(options) +const utils = await render(, options) ``` you can also call ```js -const renderStream = renderToRenderStream(, combinedOptions) -// if required -const utils = await renderStream.renderResultPromise +const {takeRender, utils} = await renderToRenderStream( + , + combinedOptions, +) ``` -This might be shorter (especially in cases where you don't need to access -`utils`), but keep in mind that the render is executed **asynchronously** after -calling `renderToRenderStream`, and that you need to `await renderResultPromise` -if you need access to `utils` as returned by `render`. - ### `renderHookToSnapshotStream` Usage is very similar to RTL's `renderHook`, but you get a `snapshotStream` @@ -87,7 +83,7 @@ object back that you can iterate with `takeSnapshot` calls. ```jsx test('`useQuery` with `skip`', async () => { - const {takeSnapshot, rerender} = renderHookToSnapshotStream( + const {takeSnapshot, rerender} = await renderHookToSnapshotStream( ({skip}) => useQuery(query, {skip}), { wrapper: ({children}) => {children}, @@ -105,7 +101,7 @@ test('`useQuery` with `skip`', async () => { expect(result.data).toEqual({hello: 'world 1'}) } - rerender({skip: true}) + await rerender({skip: true}) { const snapshot = await takeSnapshot() expect(snapshot.loading).toBe(false) @@ -146,7 +142,7 @@ test('`useTrackRenders` with suspense', async () => { } const {takeRender, render} = createRenderStream() - render() + await render() { const {renderedComponents} = await takeRender() expect(renderedComponents).toEqual([App, LoadingComponent]) @@ -179,7 +175,7 @@ test('custom snapshots with `replaceSnapshot`', async () => { const {takeRender, replaceSnapshot, render} = createRenderStream<{ value: number }>() - const utils = render() + const utils = await render() const incrementButton = utils.getByText('Increment') await userEvent.click(incrementButton) { @@ -215,16 +211,14 @@ test('assertions in `onRender`', async () => { ) } - const {takeRender, replaceSnapshot, renderResultPromise} = - renderToRenderStream<{ - value: number - }>({ - onRender(info) { - // you can use `expect` here - expect(info.count).toBe(info.snapshot.value + 1) - }, - }) - const utils = await renderResultPromise + const {takeRender, replaceSnapshot, utils} = await renderToRenderStream<{ + value: number + }>({ + onRender(info) { + // you can use `expect` here + expect(info.count).toBe(info.snapshot.value + 1) + }, + }) const incrementButton = utils.getByText('Increment') await userEvent.click(incrementButton) await userEvent.click(incrementButton) @@ -247,7 +241,7 @@ This library adds to matchers to `expect` that can be used like ```tsx test('basic functionality', async () => { - const {takeRender} = renderToRenderStream() + const {takeRender} = await renderToRenderStream() await expect(takeRender).toRerender() await takeRender() @@ -285,17 +279,45 @@ await expect(snapshotStream).toRerender() > [!TIP] > > If you don't want these matchers not to be automatically installed, you can -> import from `@testing-library/react-render-stream` instead. +> import from `@testing-library/react-render-stream/pure` instead. +> Keep in mind that if you use the `/pure` import, you have to call the +> `cleanup` export manually after each test. + +## Usage side-by side with `@testing-library/react` or other tools that set `IS_REACT_ACT_ENVIRONMENT` or use `act` + +This library is written in a way if should not be used with `act`, and it will +throw an error if `IS_REACT_ACT_ENVIRONMENT` is `true`. + +React Testing Library usually sets `IS_REACT_ACT_ENVIRONMENT` to `true` +globally, and wraps some helpers like `userEvent.click` in `act` calls. + +To use this library side-by-side with React Testing Library, we ship the +`disableActEnvironment` helper to undo these changes temporarily. + +It returns a `Disposable` and can be used together with the `using` keyword to +automatically clean up once the scope is left: -## A note on `act`. +```ts +test('my test', () => { + using _disabledAct = disableActEnvironment() -You might want to avoid using this library with `act`, as `act` -[can end up batching multiple renders](https://github.com/facebook/react/issues/30031#issuecomment-2183951296) -into one in a way that would not happen in a production application. + // your test code here -While that is convenient in a normal test suite, it defeats the purpose of this -library. + // as soon as this scope is left, the environment will be cleaned up +}) +``` -Keep in mind that tools like `userEvent.click` use `act` internally. Many of -those calls would only trigger one render anyways, so it can be okay to use -them, but avoid this for longer-running actions inside of `act` calls. +If you cannot use `using`, you can also manually call the returned `cleanup` +function: + +```ts +test('my test', () => { + const {cleanup} = disableActEnvironment() + + try { + // your test code here + } finally { + cleanup() + } +}) +```