@testing-library/react-render-stream
Version:
## What is this library?
314 lines (304 loc) • 16.6 kB
TypeScript
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 };