UNPKG

@testing-library/react-render-stream

Version:
314 lines (304 loc) 16.6 kB
import * as ReactDOMClient from 'react-dom/client'; import { RenderOptions, RenderHookOptions } from '@testing-library/react/pure.js'; import { queries, Queries, prettyFormat, BoundFunction as BoundFunction$1, Screen } from '@testing-library/dom'; import React$1 from 'react'; declare const assertableSymbol: unique symbol; /** * A function or object that can be used in assertions, like e.g. ```ts expect(assertable).toRerender() expect(assertable).not.toRerender() expect(assertable).toRenderExactlyTimes(3) ``` */ type Assertable = { [assertableSymbol]: RenderStream<any, any>; }; type OriginalQueries = typeof queries; type SyncQueries = { [K in keyof OriginalQueries as K extends `${'find'}${string}` ? never : K]: OriginalQueries[K]; }; declare const syncQueries: SyncQueries; type BoundFunction<T> = T extends (container: HTMLElement, ...args: infer P) => infer R ? (...args: P) => R : never; type BoundSyncFunctions<Q> = Q extends typeof syncQueries ? { getByLabelText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.GetByText<T>>>): ReturnType<queries.GetByText<T>>; getAllByLabelText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByText<T>>>): ReturnType<queries.AllByText<T>>; queryByLabelText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.QueryByText<T>>>): ReturnType<queries.QueryByText<T>>; queryAllByLabelText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByText<T>>>): ReturnType<queries.AllByText<T>>; getByPlaceholderText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.GetByBoundAttribute<T>>>): ReturnType<queries.GetByBoundAttribute<T>>; getAllByPlaceholderText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByBoundAttribute<T>>>): ReturnType<queries.AllByBoundAttribute<T>>; queryByPlaceholderText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.QueryByBoundAttribute<T>>>): ReturnType<queries.QueryByBoundAttribute<T>>; queryAllByPlaceholderText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByBoundAttribute<T>>>): ReturnType<queries.AllByBoundAttribute<T>>; getByText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.GetByText<T>>>): ReturnType<queries.GetByText<T>>; getAllByText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByText<T>>>): ReturnType<queries.AllByText<T>>; queryByText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.QueryByText<T>>>): ReturnType<queries.QueryByText<T>>; queryAllByText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByText<T>>>): ReturnType<queries.AllByText<T>>; getByAltText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.GetByBoundAttribute<T>>>): ReturnType<queries.GetByBoundAttribute<T>>; getAllByAltText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByBoundAttribute<T>>>): ReturnType<queries.AllByBoundAttribute<T>>; queryByAltText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.QueryByBoundAttribute<T>>>): ReturnType<queries.QueryByBoundAttribute<T>>; queryAllByAltText<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByBoundAttribute<T>>>): ReturnType<queries.AllByBoundAttribute<T>>; getByTitle<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.GetByBoundAttribute<T>>>): ReturnType<queries.GetByBoundAttribute<T>>; getAllByTitle<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByBoundAttribute<T>>>): ReturnType<queries.AllByBoundAttribute<T>>; queryByTitle<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.QueryByBoundAttribute<T>>>): ReturnType<queries.QueryByBoundAttribute<T>>; queryAllByTitle<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByBoundAttribute<T>>>): ReturnType<queries.AllByBoundAttribute<T>>; getByDisplayValue<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.GetByBoundAttribute<T>>>): ReturnType<queries.GetByBoundAttribute<T>>; getAllByDisplayValue<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByBoundAttribute<T>>>): ReturnType<queries.AllByBoundAttribute<T>>; queryByDisplayValue<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.QueryByBoundAttribute<T>>>): ReturnType<queries.QueryByBoundAttribute<T>>; queryAllByDisplayValue<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByBoundAttribute<T>>>): ReturnType<queries.AllByBoundAttribute<T>>; getByRole<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.GetByRole<T>>>): ReturnType<queries.GetByRole<T>>; getAllByRole<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByRole<T>>>): ReturnType<queries.AllByRole<T>>; queryByRole<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.QueryByRole<T>>>): ReturnType<queries.QueryByRole<T>>; queryAllByRole<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByRole<T>>>): ReturnType<queries.AllByRole<T>>; getByTestId<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.GetByBoundAttribute<T>>>): ReturnType<queries.GetByBoundAttribute<T>>; getAllByTestId<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByBoundAttribute<T>>>): ReturnType<queries.AllByBoundAttribute<T>>; queryByTestId<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.QueryByBoundAttribute<T>>>): ReturnType<queries.QueryByBoundAttribute<T>>; queryAllByTestId<T extends HTMLElement = HTMLElement>(...args: Parameters<BoundFunction<queries.AllByBoundAttribute<T>>>): ReturnType<queries.AllByBoundAttribute<T>>; } & { [P in keyof Q]: BoundFunction<Q[P]>; } : { [P in keyof Q]: BoundFunction<Q[P]>; }; type AsyncRenderResult<Q extends Queries = SyncQueries, Container extends ReactDOMClient.Container = HTMLElement, BaseElement extends ReactDOMClient.Container = Container> = { container: Container; baseElement: BaseElement; debug: (baseElement?: ReactDOMClient.Container | Array<ReactDOMClient.Container> | undefined, maxLength?: number | undefined, options?: prettyFormat.OptionsReceived | undefined) => void; rerender: (rerenderUi: React$1.ReactNode) => Promise<void>; unmount: () => void; asFragment: () => DocumentFragment; } & { [P in keyof Q]: BoundFunction$1<Q[P]>; }; type RenderWithoutActAsync = { <Q extends Queries = SyncQueries, Container extends ReactDOMClient.Container = HTMLElement, BaseElement extends ReactDOMClient.Container = Container>(this: any, ui: React$1.ReactNode, options: Pick<RenderOptions<Q, Container, BaseElement>, 'container' | 'baseElement' | 'queries' | 'wrapper'>): Promise<AsyncRenderResult<Q, Container, BaseElement>>; (this: any, ui: React$1.ReactNode, options?: Pick<RenderOptions, 'container' | 'baseElement' | 'wrapper'> | undefined): Promise<AsyncRenderResult<SyncQueries, ReactDOMClient.Container, ReactDOMClient.Container>>; }; declare function cleanup(): void; interface BaseRender { id: string; phase: 'mount' | 'update' | 'nested-update'; actualDuration: number; baseDuration: number; startTime: number; commitTime: number; /** * The number of renders that have happened so far (including this render). */ count: number; } type SyncScreen<Q extends Queries = SyncQueries> = BoundSyncFunctions<Q> & Pick<Screen, 'debug' | 'logTestingPlaygroundURL'>; interface Render<Snapshot, Q extends Queries = SyncQueries> extends BaseRender { /** * The snapshot, as returned by the `takeSnapshot` option of `createRenderStream`. */ snapshot: Snapshot; /** * A DOM snapshot of the rendered component, if the `snapshotDOM` * option of `createRenderStream` was enabled. */ readonly domSnapshot: HTMLElement; /** * Returns a callback to receive a `screen` instance that is scoped to the * DOM snapshot of this `Render` instance. * Note: this is used as a callback to prevent linter errors. * @example * ```diff * const { withinDOM } = RenderedComponent.takeRender(); * -expect(screen.getByText("foo")).toBeInTheDocument(); * +expect(withinDOM().getByText("foo")).toBeInTheDocument(); * ``` */ withinDOM: () => SyncScreen<Q>; renderedComponents: Array<string | React.ComponentType>; } type ValidSnapshot = void | (object & { call?: never; }); interface NextRenderOptions { timeout?: number; } interface ReplaceSnapshot<Snapshot> { (newSnapshot: Snapshot): void; (updateSnapshot: (lastSnapshot: Readonly<Snapshot>) => Snapshot): void; } interface MergeSnapshot<Snapshot> { (partialSnapshot: Partial<Snapshot>): void; (updatePartialSnapshot: (lastSnapshot: Readonly<Snapshot>) => Partial<Snapshot>): void; } interface RenderStream<Snapshot extends ValidSnapshot, Q extends Queries = SyncQueries> { mergeSnapshot: MergeSnapshot<Snapshot>; replaceSnapshot: ReplaceSnapshot<Snapshot>; /** * An array of all renders that have happened so far. * Errors thrown during component render will be captured here, too. */ renders: Array<Render<Snapshot, Q> | { phase: 'snapshotError'; count: number; error: unknown; }>; /** * Peeks the next render from the current iterator position, without advancing the iterator. * If no render has happened yet, it will wait for the next render to happen. * @throws {WaitForRenderTimeoutError} if no render happens within the timeout */ peekRender: (options?: NextRenderOptions) => Promise<Render<Snapshot, Q>>; /** * Iterates to the next render and returns it. * If no render has happened yet, it will wait for the next render to happen. * @throws {WaitForRenderTimeoutError} if no render happens within the timeout */ takeRender: Assertable & ((options?: NextRenderOptions) => Promise<Render<Snapshot, Q>>); /** * Returns the total number of renders. */ totalRenderCount: () => number; /** * Returns the current render. * @throws {Error} if no render has happened yet */ getCurrentRender: () => Render<Snapshot, Q>; /** * Waits for the next render to happen. * Does not advance the render iterator. */ waitForNextRender: (options?: NextRenderOptions) => Promise<Render<Snapshot, Q>>; } interface RenderStreamWithRenderFn<Snapshot extends ValidSnapshot, Q extends Queries = SyncQueries> extends RenderStream<Snapshot, Q> { render: RenderWithoutActAsync; } type RenderStreamOptions<Snapshot extends ValidSnapshot, Q extends Queries = SyncQueries> = { onRender?: (info: BaseRender & { snapshot: Snapshot; replaceSnapshot: ReplaceSnapshot<Snapshot>; mergeSnapshot: MergeSnapshot<Snapshot>; }) => void; snapshotDOM?: boolean; initialSnapshot?: Snapshot; /** * This will skip renders during which no renders tracked by * `useTrackRenders` occured. */ skipNonTrackingRenders?: boolean; queries?: Q; }; declare class WaitForRenderTimeoutError extends Error { constructor(); } declare function createRenderStream<Snapshot extends ValidSnapshot = void, Q extends Queries = SyncQueries>({ onRender, snapshotDOM, initialSnapshot, skipNonTrackingRenders, queries, }?: RenderStreamOptions<Snapshot, Q>): RenderStreamWithRenderFn<Snapshot, Q>; declare function useTrackRenders({ name }?: { name?: string; }): void; interface SnapshotStream<Snapshot, Props> extends Assertable { /** * An array of all renders that have happened so far. * Errors thrown during component render will be captured here, too. */ renders: Array<Render<{ value: Snapshot; }, never> | { phase: 'snapshotError'; count: number; error: unknown; }>; /** * Peeks the next render from the current iterator position, without advancing the iterator. * If no render has happened yet, it will wait for the next render to happen. * @throws {WaitForRenderTimeoutError} if no render happens within the timeout */ peekSnapshot(options?: NextRenderOptions): Promise<Snapshot>; /** * Iterates to the next render and returns it. * If no render has happened yet, it will wait for the next render to happen. * @throws {WaitForRenderTimeoutError} if no render happens within the timeout */ takeSnapshot: Assertable & ((options?: NextRenderOptions) => Promise<Snapshot>); /** * Returns the total number of renders. */ totalSnapshotCount(): number; /** * Returns the current render. * @throws {Error} if no render has happened yet */ getCurrentSnapshot(): Snapshot; /** * Waits for the next render to happen. * Does not advance the render iterator. */ waitForNextSnapshot(options?: NextRenderOptions): Promise<Snapshot>; rerender: (rerenderCallbackProps: VoidOptionalArg<Props>) => Promise<void>; unmount: () => void; } /** * if `Arg` can be `undefined`, replace it with `void` to make type represent an optional argument in a function argument position */ type VoidOptionalArg<Arg> = Arg extends any ? undefined extends Arg ? void : Arg : Arg; declare function renderHookToSnapshotStream<ReturnValue, Props = void>(renderCallback: (props: Props) => ReturnValue, { initialProps, ...renderOptions }?: RenderHookOptions<Props>): Promise<SnapshotStream<ReturnValue, Props>>; interface DisableActEnvironmentOptions { /** * If `true`, all modifications of values set by `disableActEnvironment` * will be prevented until `cleanup` is called. * * @default true */ preventModification?: boolean; /** * If `true`, will change the configuration of the testing library to * prevent auto-wrapping e.g. `userEvent` calls in `act`. * * @default true */ adjustTestingLibConfig?: boolean; } /** * Helper to temporarily disable a React 18+ act environment. * * By default, this also adjusts the configuration of @testing-library/dom * to prevent auto-wrapping of user events in `act`, as well as preventing * all modifications of values set by this method until `cleanup` is called * or the returned `Disposable` is disposed of. * * Both of these behaviors can be disabled with the option, of the defaults * can be changed for all calls to this method by modifying * `disableActEnvironment.defaultOptions`. * * This returns a disposable and can be used in combination with `using` to * automatically restore the state from before this method call after your test. * * @example * ```ts * test("my test", () => { * using _disabledAct = disableActEnvironment(); * * // your test code here * * // as soon as this scope is left, the environment will be cleaned up * }) * ``` * * If you can not use the explicit resouce management keyword `using`, * you can also manually call `cleanup`: * * @example * ```ts * test("my test", () => { * const { cleanup } = disableActEnvironment(); * * try { * // your test code here * } finally { * cleanup(); * } * }) * ``` * * For more context on what `act` is and why you shouldn't use it in renderStream tests, * https://github.com/reactwg/react-18/discussions/102 is probably the best resource we have. */ declare function disableActEnvironment({ preventModification, adjustTestingLibConfig, }?: DisableActEnvironmentOptions): { cleanup: () => void; } & Disposable; declare namespace disableActEnvironment { var defaultOptions: Required<DisableActEnvironmentOptions>; } export { type Assertable, type RenderWithoutActAsync as AsyncRenderFn, type DisableActEnvironmentOptions, type NextRenderOptions, type RenderStream, type RenderStreamOptions, type RenderStreamWithRenderFn, type SnapshotStream, type SyncScreen, WaitForRenderTimeoutError, cleanup, createRenderStream, disableActEnvironment, renderHookToSnapshotStream, useTrackRenders };