import * as ReactTestingLibrary from "@testing-library/react"; import type { ReactNode } from "react"; import { StrictMode } from "react"; import { flushMicrotasks, nextFrame, wrapAsync } from "./__utils.ts"; export * from "./index.ts"; /** * Options for the `render` function. Accepts every option from Testing Library's * `render` (except `queries`), plus `strictMode` to wrap the rendered UI in * React's `StrictMode`. * @example * ```tsx * const options: RenderOptions = { strictMode: true }; * await render(, options); * ``` */ export interface RenderOptions extends Omit< ReactTestingLibrary.RenderOptions, "queries" > { strictMode?: boolean; } function wrapRender any>( renderFn: T, ): Promise> { return wrapAsync(async () => { const output: ReturnType = renderFn(); await flushMicrotasks(); await nextFrame(); await flushMicrotasks(); return output; }); } /** * Renders a React element into the document for testing, waiting for effects and * the next frame to flush before resolving. * * Built on Testing Library's `render`, it returns `unmount` to remove the tree and * an async `rerender` to update it with new UI. Pass `strictMode: true` to wrap * the element in React's `StrictMode`, or any other Testing Library render option. * @example * ```tsx * const { rerender, unmount } = await render(); * await click(q.button("Submit")); * await rerender(); * unmount(); * ``` */ export async function render(ui: ReactNode, options?: RenderOptions) { const { strictMode, wrapper: Wrapper, ...renderOptions } = options ?? {}; const wrapper = (props: { children: ReactNode }) => { const element = Wrapper ? : props.children; if (!strictMode) return element; return {element}; }; return wrapRender(() => { const { unmount, rerender } = ReactTestingLibrary.render(ui, { ...renderOptions, wrapper, }); return { unmount, rerender: (newUi: ReactNode) => wrapRender(() => rerender(newUi)), }; }); }