From 49e33ab94a41c467df2ee9432cbc9519fffa98b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Jastrze=CC=A8bski?= Date: Tue, 26 May 2026 13:16:48 +0200 Subject: [PATCH 1/8] chore: agent docs --- .release-it.json | 2 +- docs/README.md | 31 ++ docs/api/accessibility.md | 26 ++ docs/api/async-utilities.md | 137 +++++++ docs/api/configuration.md | 61 +++ docs/api/fire-event.md | 163 ++++++++ docs/api/jest-matchers.md | 196 ++++++++++ docs/api/other-helpers.md | 94 +++++ docs/api/overview.md | 18 + docs/api/queries.md | 499 ++++++++++++++++++++++++ docs/api/render-hook.md | 176 +++++++++ docs/api/render.md | 49 +++ docs/api/screen.md | 188 +++++++++ docs/api/user-event.md | 295 +++++++++++++++ docs/cookbook/async-events.md | 147 ++++++++ docs/cookbook/custom-render.md | 83 ++++ docs/cookbook/network-requests.md | 375 ++++++++++++++++++ docs/guides/common-mistakes.md | 587 +++++++++++++++++++++++++++++ docs/guides/how-to-query.md | 125 ++++++ docs/guides/llm-guidelines.md | 228 +++++++++++ docs/guides/migration-v14.md | 554 +++++++++++++++++++++++++++ docs/guides/quick-start.md | 65 ++++ docs/guides/testing-environment.md | 123 ++++++ docs/guides/troubleshooting.md | 61 +++ docs/guides/understanding-act.md | 207 ++++++++++ package.json | 7 +- scripts/generate-package-docs.mjs | 294 +++++++++++++++ 27 files changed, 4788 insertions(+), 3 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/api/accessibility.md create mode 100644 docs/api/async-utilities.md create mode 100644 docs/api/configuration.md create mode 100644 docs/api/fire-event.md create mode 100644 docs/api/jest-matchers.md create mode 100644 docs/api/other-helpers.md create mode 100644 docs/api/overview.md create mode 100644 docs/api/queries.md create mode 100644 docs/api/render-hook.md create mode 100644 docs/api/render.md create mode 100644 docs/api/screen.md create mode 100644 docs/api/user-event.md create mode 100644 docs/cookbook/async-events.md create mode 100644 docs/cookbook/custom-render.md create mode 100644 docs/cookbook/network-requests.md create mode 100644 docs/guides/common-mistakes.md create mode 100644 docs/guides/how-to-query.md create mode 100644 docs/guides/llm-guidelines.md create mode 100644 docs/guides/migration-v14.md create mode 100644 docs/guides/quick-start.md create mode 100644 docs/guides/testing-environment.md create mode 100644 docs/guides/troubleshooting.md create mode 100644 docs/guides/understanding-act.md create mode 100644 scripts/generate-package-docs.mjs diff --git a/.release-it.json b/.release-it.json index f76bc11f6..be10c8276 100644 --- a/.release-it.json +++ b/.release-it.json @@ -1,6 +1,6 @@ { "hooks": { - "before:init": ["yarn typecheck", "yarn test", "yarn lint"], + "before:init": ["yarn docs:check", "yarn typecheck", "yarn test", "yarn lint"], "after:bump": "yarn build", "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}." }, diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..c808e4958 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,31 @@ +# React Native Testing Library package docs + +These markdown files are bundled with the npm package for coding agents. They describe the installed package version. + +Start with [LLM Guidelines](./guides/llm-guidelines.md) for quick rules, or use the included page list below to load specific references. + +## Included pages + +- [LLM Guidelines](./guides/llm-guidelines.md) - Quick rules for agents writing RNTL tests. +- [Quick Start](./guides/quick-start.md) - Installation and setup basics. +- [How to Query](./guides/how-to-query.md) - Query priority and selection guidance. +- [Common Mistakes](./guides/common-mistakes.md) - Common anti-patterns and preferred alternatives. +- [Troubleshooting](./guides/troubleshooting.md) - Common integration and runtime issues. +- [Testing Environment](./guides/testing-environment.md) - How RNTL simulates React Native under tests. +- [Understanding act](./guides/understanding-act.md) - How act warnings happen and how to resolve them. +- [Migration to 14.x](./guides/migration-v14.md) - Breaking changes and upgrade steps for RNTL v14. +- [API Overview](./api/overview.md) - Top-level API map. +- [render](./api/render.md) - Rendering components in tests. +- [screen](./api/screen.md) - Recommended global query surface. +- [Queries](./api/queries.md) - Query variants and predicates. +- [Jest Matchers](./api/jest-matchers.md) - Built-in RNTL assertions. +- [User Event](./api/user-event.md) - Realistic user interactions. +- [Fire Event](./api/fire-event.md) - Low-level event triggering. +- [Async Utilities](./api/async-utilities.md) - Async queries and wait helpers. +- [renderHook](./api/render-hook.md) - Testing custom hooks. +- [Configuration](./api/configuration.md) - Runtime configuration options. +- [Accessibility](./api/accessibility.md) - Accessibility helpers and hidden elements. +- [Other Helpers](./api/other-helpers.md) - within, act, cleanup, and related helpers. +- [Custom Render](./cookbook/custom-render.md) - Reusable render wrappers. +- [Async Events](./cookbook/async-events.md) - Testing async interactions. +- [Network Requests](./cookbook/network-requests.md) - Testing components that make network requests. diff --git a/docs/api/accessibility.md b/docs/api/accessibility.md new file mode 100644 index 000000000..66e027e3f --- /dev/null +++ b/docs/api/accessibility.md @@ -0,0 +1,26 @@ +# Accessibility + +## `isHiddenFromAccessibility` + +```ts +function isHiddenFromAccessibility(instance: TestInstance | null): boolean {} +``` + +Also available as `isInaccessible()` alias for React Testing Library compatibility. + +Checks if given element is hidden from assistive technology, e.g. screen readers. + +> [!NOTE] +> Like [`isInaccessible`](https://testing-library.com/docs/dom-testing-library/api-accessibility/#isinaccessible) function from DOM Testing Library this function considers both accessibility elements and presentational elements (regular `View`s) to be accessible, unless they are hidden in terms of host platform. +> +> This covers only part of [ARIA notion of Accessibility Tree](https://www.w3.org/TR/wai-aria-1.2/#tree_exclusion), as ARIA excludes both hidden and presentational elements from the Accessibility Tree. + +For the scope of this function, element is inaccessible when it, or any of its ancestors, meets any of the following conditions: + +- it has `display: none` style +- it has [`aria-hidden`](https://reactnative.dev/docs/accessibility#aria-hidden) prop set to `true` +- it has [`accessibilityElementsHidden`](https://reactnative.dev/docs/accessibility#accessibilityelementshidden-ios) prop set to `true` +- it has [`importantForAccessibility`](https://reactnative.dev/docs/accessibility#importantforaccessibility-android) prop set to `no-hide-descendants` +- it has sibling host element with either [`aria-modal`](https://reactnative.dev/docs/accessibility#aria-modal-ios) or [`accessibilityViewIsModal`](https://reactnative.dev/docs/accessibility#accessibilityviewismodal-ios) prop set to `true` + +Specifying `accessible={false}`, `role="none"`, `accessibilityRole="none"`, or `importantForAccessibility="no"` props does not cause the element to become inaccessible. diff --git a/docs/api/async-utilities.md b/docs/api/async-utilities.md new file mode 100644 index 000000000..6ea6033db --- /dev/null +++ b/docs/api/async-utilities.md @@ -0,0 +1,137 @@ +# Async utilities + +## `findBy*` queries + +The `findBy*` queries are used to find elements that are not instantly available but will be added as a result of some asynchronous action. Learn more details [here](./queries.md#find-by). + +## `waitFor` + +```tsx +function waitFor( + expectation: () => T, + options?: { + timeout?: number; + interval?: number; + onTimeout?: (error: Error) => Error; + }, +): Promise; +``` + +Waits for the `expectation` callback to pass. `waitFor` runs the callback multiple times until timeout is reached, as specified by the `timeout` and `interval` options. The callback must throw an error when the expectation is not met. Returning any value, including a falsy one, is treated as meeting the expectation, and the callback result is returned to the caller. + +```tsx +await waitFor(() => expect(mockFunction).toHaveBeenCalledWith()); +``` + +`waitFor` executes the `expectation` callback every `interval` (default: 50 ms) until `timeout` (default: 1000 ms) is reached. Execution stops as soon as the callback doesn't throw an error, and the callback's return value is returned to the caller. If timeout is reached, `waitFor` re-throws the final error thrown by `expectation`. + +```tsx +// ❌ `waitFor` will return immediately because callback does not throw +await waitFor(() => false); +``` + +`waitFor` is an async function so you need to `await` the result to pause test execution. + +```jsx +// ❌ missing `await`: `waitFor` will just return Promise that will be rejected when the timeout is reached +waitFor(() => expect(1).toBe(2)); +``` + +> [!NOTE] +> You can enforce awaiting `waitFor` by using the [await-async-utils](https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/await-async-utils.md) rule from [eslint-plugin-testing-library](https://github.com/testing-library/eslint-plugin-testing-library). + +Since `waitFor` runs the `expectation` callback multiple times, [avoid performing side effects](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#performing-side-effects-in-waitfor) in `waitFor`. + +```jsx +await waitFor(async () => { + // ❌ button will be pressed on each waitFor iteration + await fireEvent.press(screen.getByText('press me')); + expect(mockOnPress).toHaveBeenCalled(); +}); +``` + +> [!NOTE] +> Avoiding side effects in `expectation` callback can be partially enforced with the [`no-wait-for-side-effects` rule](https://github.com/testing-library/eslint-plugin-testing-library/blob/main/docs/rules/no-wait-for-side-effects.md). + +Use a [single assertion per `waitFor`](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#having-multiple-assertions-in-a-single-waitfor-callback) for consistency and faster failing tests. For multiple assertions, use separate `waitFor` calls. Often you won't need to wrap the second assertion in `waitFor` since the first one waits for the asynchronous change. + +`waitFor` checks whether Jest fake timers are enabled and adapts its behavior in such case. The following snippet is a simplified version of how it behaves when fake timers are enabled: + +```tsx +let fakeTimeRemaining = timeout; +let lastError; + +while (fakeTimeRemaining > 0) { + fakeTimeRemaining = fakeTimeRemaining - interval; + jest.advanceTimersByTime(interval); + try { + // resolve + return expectation(); + } catch (error) { + lastError = error; + } +} + +// reject +throw lastError; +``` + +In the following example we test that a function is called after 10 seconds using fake timers. With fake timers, the test doesn't depend on real time passing, making it faster and more reliable. We don't need to advance fake timers through Jest's API because `waitFor` handles this. + +```tsx +// in component +setTimeout(() => { + someFunction(); +}, 10000); + +// in test +jest.useFakeTimers(); + +await waitFor( + () => { + expect(someFunction).toHaveBeenCalledWith(); + }, + { timeout: 10000 }, +); +``` + +> [!NOTE] +> If you receive warnings related to `act()` function consult our [Understanding Act](../guides/understanding-act.md) function document. + +### Options + +- `timeout`: How long to wait for, in ms. Defaults to 1000 ms (configured by `asyncUtilTimeout` option). +- `interval`: How often to check, in ms. Defaults to 50 ms. +- `onTimeout`: Callback to transform the error before it's thrown. Useful for debugging, e.g., `onTimeout: () => { screen.debug(); }`. + +## `waitForElementToBeRemoved` + +```ts +function waitForElementToBeRemoved( + expectation: () => T, + options?: { + timeout?: number; + interval?: number; + onTimeout?: (error: Error) => Error; + }, +): Promise; +``` + +Waits for non-deterministic periods of time until queried element is removed or times out. `waitForElementToBeRemoved` periodically calls `expectation` every `interval` milliseconds to determine whether the element has been removed or not. + +```jsx +import { render, screen, waitForElementToBeRemoved } from '@testing-library/react-native'; + +test('waiting for an Banana to be removed', async () => { + await render(); + + await waitForElementToBeRemoved(() => screen.getByText('Banana ready')); +}); +``` + +This method expects that the element is initially present in the render tree and then is removed from it. If the element is not present when you call this method it throws an error. + +You can use any of `getBy`, `getAllBy`, `queryBy` and `queryAllBy` queries for `expectation` parameter. + +> [!NOTE] +> If you receive warnings related to `act()` function consult our [Understanding Act](../guides/understanding-act.md) function document. diff --git a/docs/api/configuration.md b/docs/api/configuration.md new file mode 100644 index 000000000..8164dfee1 --- /dev/null +++ b/docs/api/configuration.md @@ -0,0 +1,61 @@ +# Configuration + +## `configure` + +```ts +type Config = { + /** Default timeout, in ms, for `waitFor` and `findBy*` queries. */ + asyncUtilTimeout: number; + + /** Default value for `includeHiddenElements` query option. */ + defaultIncludeHiddenElements: boolean; + + /** Default options for `debug` helper. */ + defaultDebugOptions?: Partial; +}; + +type ConfigAliasOptions = { + /** RTL-compatibility alias for `defaultIncludeHiddenElements`. */ + defaultHidden: boolean; +}; + +function configure(options: Partial) {} +``` + +### `asyncUtilTimeout` option + +Default timeout, in ms, for async helper functions (`waitFor`, `waitForElementToBeRemoved`) and `findBy*` queries. Defaults to 1000 ms. + +### `defaultIncludeHiddenElements` option + +Default value for [includeHiddenElements](./queries.md#includehiddenelements-option) query option for all queries. The default value is set to `false`, so all queries will not match [elements hidden from accessibility](#ishiddenfromaccessibility). This is because the users of the app would not be able to see such elements. + +This option is also available as `defaultHidden` alias for compatibility with [React Testing Library](https://testing-library.com/docs/dom-testing-library/api-configuration/#defaulthidden). + +### `defaultDebugOptions` option + +Default [debug options](#debug) to be used when calling `debug()`. These default options will be overridden by the ones you specify directly when calling `debug()`. + +## `resetToDefaults()` + +```ts +function resetToDefaults() {} +``` + +## Environment variables + +### `RNTL_SKIP_AUTO_CLEANUP` + +Set to `true` to disable automatic `cleanup()` after each test. It works the same as importing `react-native-testing-library/dont-cleanup-after-each` or using `react-native-testing-library/pure`. + +```shell +$ RNTL_SKIP_AUTO_CLEANUP=true jest +``` + +### `RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS` + +Set to `true` to disable auto-detection of fake timers. This might be useful in rare cases when you want to use non-Jest fake timers. See [issue #886](https://github.com/callstack/react-native-testing-library/issues/886) for more details. + +```shell +$ RNTL_SKIP_AUTO_DETECT_FAKE_TIMERS=true jest +``` diff --git a/docs/api/fire-event.md b/docs/api/fire-event.md new file mode 100644 index 000000000..4f970ee05 --- /dev/null +++ b/docs/api/fire-event.md @@ -0,0 +1,163 @@ +# Fire Event API + +## `fireEvent` + +> [!NOTE] +> For common events like `press` or `type`, use the [User Event API](./user-event.md). It simulates events more realistically by emitting a sequence of events with proper event objects that mimic React Native runtime behavior. +> +> Use Fire Event for cases not supported by User Event and for triggering event handlers on composite components. + +```ts +function fireEvent(instance: TestInstance, eventName: string, ...data: unknown[]): Promise; +``` + +The `fireEvent` API triggers event handlers on both host and composite components. It traverses the component tree bottom-up from the passed element to find an enabled event handler named `onXxx` where `xxx` is the event name. + +Unlike User Event, this API does not automatically pass event object to event handler, this is responsibility of the user to construct such object. + +This function uses async `act` internally to execute all pending React updates during event handling. + +```jsx +import { render, screen, fireEvent } from '@testing-library/react-native'; + +test('fire changeText event', async () => { + const onEventMock = jest.fn(); + await render( + // MyComponent renders TextInput which has a placeholder 'Enter details' + // and with `onChangeText` bound to handleChangeText + , + ); + + await fireEvent(screen.getByPlaceholderText('change'), 'onChangeText', 'ab'); + expect(onEventMock).toHaveBeenCalledWith('ab'); +}); +``` + +> [!NOTE] +> `fireEvent` performs checks that should prevent events firing on disabled elements. + +An example using `fireEvent` with native events that aren't already aliased by the `fireEvent` api. + +```jsx +import { TextInput, View } from 'react-native'; +import { fireEvent, render, screen } from '@testing-library/react-native'; + +const onBlurMock = jest.fn(); + +await render( + + + , +); + +// you can omit the `on` prefix +await fireEvent(screen.getByPlaceholderText('my placeholder'), 'blur'); +``` + +FireEvent exposes convenience methods for common events like: `press`, `changeText`, `scroll`. + +### `fireEvent.press` + +> [!NOTE] +> Use the User Event [`press()`](./user-event.md#press) helper instead. It simulates press interactions more realistically, including pressable support. + +```tsx +fireEvent.press: ( + instance: TestInstance, + ...data: Array, +) => Promise +``` + +Invokes `press` event handler on the element or parent element in the tree. + +```jsx +import { View, Text, TouchableOpacity } from 'react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; + +const onPressMock = jest.fn(); +const eventData = { + nativeEvent: { + pageX: 20, + pageY: 30, + }, +}; + +await render( + + + Press me + + , +); + +await fireEvent.press(screen.getByText('Press me'), eventData); +expect(onPressMock).toHaveBeenCalledWith(eventData); +``` + +### `fireEvent.changeText` + +> [!NOTE] +> Use the User Event [`type()`](./user-event.md#type) helper instead. It simulates text change interactions more realistically, including key-by-key typing, element focus, and other editing events. + +```tsx +fireEvent.changeText: ( + instance: TestInstance, + ...data: Array, +) => Promise +``` + +Invokes `changeText` event handler on the element or parent element in the tree. + +```jsx +import { View, TextInput } from 'react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; + +const onChangeTextMock = jest.fn(); +const CHANGE_TEXT = 'content'; + +await render( + + + , +); + +await fireEvent.changeText(screen.getByPlaceholderText('Enter data'), CHANGE_TEXT); +``` + +### `fireEvent.scroll` + +> [!NOTE] +> Prefer [`user.scrollTo`](./user-event.md#scrollto) over `fireEvent.scroll` for `ScrollView`, `FlatList`, and `SectionList` components. User Event simulates events more realistically based on React Native runtime behavior. + +```tsx +fireEvent.scroll: ( + instance: TestInstance, + ...data: Array, +) => Promise +``` + +Invokes `scroll` event handler on the element or parent element in the tree. + +#### On a `ScrollView` + +```jsx +import { ScrollView, Text } from 'react-native'; +import { render, screen, fireEvent } from '@testing-library/react-native'; + +const onScrollMock = jest.fn(); +const eventData = { + nativeEvent: { + contentOffset: { + y: 200, + }, + }, +}; + +await render( + + Content + , +); + +await fireEvent.scroll(screen.getByTestId('scroll-view'), eventData); +``` diff --git a/docs/api/jest-matchers.md b/docs/api/jest-matchers.md new file mode 100644 index 000000000..93f87ccd3 --- /dev/null +++ b/docs/api/jest-matchers.md @@ -0,0 +1,196 @@ +# Jest matchers + +This guide covers the built-in Jest matchers. These matchers make your tests easier to read and work better with accessibility features. + +## Setup + +No setup needed. Matchers are available when you import from `@testing-library/react-native`. + +## Checking element existence + +### `toBeOnTheScreen()` + +```ts +expect(element).toBeOnTheScreen(); +``` + +Checks if an element is attached to the element tree. If you have a reference to an element and it gets unmounted during the test, this assertion will fail. + +## Element Content + +### `toHaveTextContent()` + +```ts +expect(element).toHaveTextContent( + text: string | RegExp, + options?: { + exact?: boolean; + normalizer?: (text: string) => string; + }, +) +``` + +Checks if an element has the specified text content. Accepts `string` or `RegExp`, with optional [text match options](./queries.md#text-match-options) like `exact` and `normalizer`. + +### `toContainElement()` + +```ts +expect(container).toContainElement( + instance: TestInstance | null, +) +``` + +Checks if a container element contains another element. + +### `toBeEmptyElement()` + +```ts +expect(element).toBeEmptyElement(); +``` + +Checks if an element has no child elements or text content. + +## Checking element state + +### `toHaveDisplayValue()` + +```ts +expect(element).toHaveDisplayValue( + value: string | RegExp, + options?: { + exact?: boolean; + normalizer?: (text: string) => string; + }, +) +``` + +Checks if a `TextInput` has the specified display value. Accepts `string` or `RegExp`, with optional [text match options](./queries.md#text-match-options) like `exact` and `normalizer`. + +### `toHaveAccessibilityValue()` + +```ts +expect(element).toHaveAccessibilityValue( + value: { + min?: number; + max?: number; + now?: number; + text?: string | RegExp; + }, +) +``` + +Checks if an element has the specified accessible value. + +The matcher reads accessibility values from `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, `aria-valuetext`, and `accessibilityValue` props. It only checks the values you specify, so the element can have other accessibility value entries and still match. + +For the `text` entry, you can use a string or `RegExp`. + +### `toBeEnabled()` / `toBeDisabled` + +```ts +expect(element).toBeEnabled(); +expect(element).toBeDisabled(); +``` + +Checks if an element is enabled or disabled from `aria-disabled` or `accessibilityState.disabled` props. An element is disabled if it or any ancestor is disabled. + +> [!NOTE] +> These matchers are opposites. Both are available so you can avoid double negations like `expect(element).not.toBeDisabled()`. + +### `toBeSelected()` + +```ts +expect(element).toBeSelected(); +``` + +Checks if an element is selected from `aria-selected` or `accessibilityState.selected` props. + +### `toBeChecked()` / `toBePartiallyChecked()` + +```ts +expect(element).toBeChecked(); +expect(element).toBePartiallyChecked(); +``` + +Checks if an element is checked or partially checked from `aria-checked` or `accessibilityState.checked` props. + +> [!NOTE] +> +> - `toBeChecked()` only works on `Switch` host elements and elements with `checkbox`, `radio`, or `switch` role. +> - `toBePartiallyChecked()` only works on elements with `checkbox` role. + +### `toBeExpanded()` / `toBeCollapsed()` + +```ts +expect(element).toBeExpanded(); +expect(element).toBeCollapsed(); +``` + +Checks if an element is expanded or collapsed from `aria-expanded` or `accessibilityState.expanded` props. + +> [!NOTE] +> These matchers are opposites for expandable elements (those with explicit `aria-expanded` or `accessibilityState.expanded` props). For non-expandable elements, neither matcher will pass. + +### `toBeBusy()` + +```ts +expect(element).toBeBusy(); +``` + +Checks if an element is busy from `aria-busy` or `accessibilityState.busy` props. + +## Checking element style + +### `toBeVisible()` + +```ts +expect(element).toBeVisible(); +``` + +Checks if an element is visible. + +An element is invisible if it or any ancestor has `display: none` or `opacity: 0` styles, or if it's hidden from accessibility. + +### `toHaveStyle()` + +```ts +expect(element).toHaveStyle( + style: StyleProp