@testing-library/react-native 14.0.0-rc.0 → 14.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,125 @@
1
+ # How should I query?
2
+
3
+ React Native Testing Library provides various query types for finding views in tests. The number of queries can be confusing. This guide helps you pick the right queries for your test scenarios.
4
+
5
+ ## Query parts
6
+
7
+ Each query is composed of two parts: variant and predicate, which are separated by the `by` word in the middle of the query.
8
+
9
+ Consider the following query:
10
+
11
+ ```ts
12
+ getByRole();
13
+ ```
14
+
15
+ For this query, `getBy*` is the query variant, and `*ByRole` is the predicate.
16
+
17
+ ## Query variant
18
+
19
+ The query variants describe the expected number (and timing) of matching elements, so they differ in their return type.
20
+
21
+ | Variant | Assertion | Return type | Is Async? |
22
+ | ----------------------------------------------- | ----------------------------- | ------------------------------------- | --------- |
23
+ | [`getBy*`](../api/queries.md#get-by) | Exactly one matching element | `TestInstance` | No |
24
+ | [`getAllBy*`](../api/queries.md#get-all-by) | At least one matching element | `Array<TestInstance>` | No |
25
+ | [`queryBy*`](../api/queries.md#query-by) | Zero or one matching element | <code>TestInstance &#124; null</code> | No |
26
+ | [`queryAllBy*`](../api/queries.md#query-all-by) | No assertion | `Array<TestInstance>` | No |
27
+ | [`findBy*`](../api/queries.md#find-by) | Exactly one matching element | `Promise<TestInstance>` | Yes |
28
+ | [`findAllBy*`](../api/queries.md#find-all-by) | At least one matching element | `Promise<Array<TestInstance>>` | Yes |
29
+
30
+ Queries work as implicit assertions on the number of matching elements and will throw an error when the assertion fails.
31
+
32
+ ### Idiomatic query variants
33
+
34
+ Idiomatic query variants clarify test intent and the expected number of matching elements. They will also throw helpful errors if assertions fail to help diagnose the issues.
35
+
36
+ Here are general guidelines for picking idiomatic query variants:
37
+
38
+ 1. Use `getBy*` in the most common case when you expect a **single matching element**. Use other queries only in more specific cases.
39
+ 2. Use `findBy*` when an element is not yet in the element tree, but you expect it to be there as a **result of some asynchronous action**.
40
+ 3. Use `getAllBy*` (and `findAllBy*` for async) if you expect **more than one matching element**, e.g. in a list.
41
+ 4. Use `queryBy*` only when element **should not exist** to use it together with e.g. [`not.toBeOnTheScreen()`](../api/jest-matchers.md#tobeonthescreen) matcher.
42
+
43
+ Avoid using `queryAllBy*` in regular tests, as it provides no assertions on the number of matching elements. You may still find it useful when building reusable custom testing tools.
44
+
45
+ ## Query predicate
46
+
47
+ The query predicate describes how you decide whether to match the given element.
48
+
49
+ | Predicate | Supported elements | Inspected props |
50
+ | ------------------------------------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------- |
51
+ | [`*ByRole`](../api/queries.md#by-role) | all host elements | `role`, `accessibilityRole`,<br /> optional: accessible name, accessibility state and value |
52
+ | [`*ByLabelText`](../api/queries.md#by-label-text) | all host elements | `aria-label`, `aria-labelledby`,<br /> `accessibilityLabel`, `accessibilityLabelledBy`,<br /> `alt` (for `Image`) |
53
+ | [`*ByDisplayValue`](../api/queries.md#by-display-value) | `TextInput` | `value`, `defaultValue` |
54
+ | [`*ByPlaceholderText`](../api/queries.md#by-placeholder-text) | `TextInput` | `placeholder` |
55
+ | [`*ByText`](../api/queries.md#by-text) | `Text` | `children` (text content) |
56
+ | [`*ByHintText`](../api/queries.md#by-hint-text) | all host elements | `accessibilityHint` |
57
+ | [`*ByTestId`](../api/queries.md#by-test-id) | all host elements | `testID` |
58
+
59
+ ### Idiomatic query predicates
60
+
61
+ Choosing the right query predicate helps express test intent and makes tests resemble how users interact with your code (components, screens, etc.), following our [Guiding Principles](https://testing-library.com/docs/guiding-principles). Most predicates also promote proper accessibility props, which add a semantic layer on top of an element tree composed primarily of [`View`](https://reactnative.dev/docs/view) elements.
62
+
63
+ Use query predicates in the following order of priority:
64
+
65
+ ### 1. By Role query
66
+
67
+ The [`*ByRole`](../api/queries.md#by-role) predicate starts with the semantic role of the element and can be narrowed down with additional options. React Native has two role systems: the web/ARIA-compatible one based on [`role`](https://reactnative.dev/docs/accessibility#role) prop and the traditional one based on [`accessibilityRole`](https://reactnative.dev/docs/accessibility#accessibilityrole) prop. You can use either.
68
+
69
+ In most cases, you need to set accessibility roles explicitly (or your component library can set some of them for you). These roles allow assistive technologies (like screen readers) and testing code to understand your view hierarchy better.
70
+
71
+ Some frequently used roles include:
72
+
73
+ - `alert` - important text to be presented to the user, e.g., error message
74
+ - `button`
75
+ - `checkbox` & `switch` - on/off controls
76
+ - `heading` (`header`) - header for content section, e.g., the title of navigation bar
77
+ - `img` (`image`)
78
+ - `link`
79
+ - `menu` & `menuitem`
80
+ - `progressbar`
81
+ - `radiogroup` & `radio`
82
+ - `searchbox` (`search`)
83
+ - `slider` (`adjustable`)
84
+ - `summary`
85
+ - `tablist` & `tab`
86
+ - `text` - static text that cannot change
87
+ - `toolbar` - container for action buttons
88
+
89
+ #### Name option
90
+
91
+ Frequently, you will want to add the [`name`](../api/queries.md#by-role-options) option, which will match both the element's role and its accessible name (= element's accessibility label or text content).
92
+
93
+ Here are a couple of examples:
94
+
95
+ - start button: `getByRole("button", { name: "Start" })`
96
+ - silent mode switch: `getByRole("switch", { name: "Silent Mode" })`
97
+ - screen header: `getByRole("header", { name: "Settings" })`
98
+ - undo menu item: `getByRole("menuitem", { name: "Undo" })`
99
+ - error messages: `getByRole("alert", { name: /Not logged in/ })`
100
+
101
+ ### 2. Text input queries
102
+
103
+ Querying [`TextInput`](https://reactnative.dev/docs/textinput) elements presents a unique challenge as there is no separate role for `TextInput` elements. There is a `searchbox`/`search` role, which can be assigned to `TextInput`, but it should be only used in the context of search inputs, leaving other text inputs without a role to query with.
104
+
105
+ Therefore, you can use the following queries to find relevant text inputs:
106
+
107
+ 1. [`*ByLabelText`](../api/queries.md#by-label-text) - will match the accessibility label of the element. This query will match any host elements, including `TextInput` elements.
108
+ 2. [`*ByPlaceholderText`](../api/queries.md#by-placeholder-text) - will match the placeholder of `TextInput` element. This query will match only `TextInput` elements.
109
+ 3. [`*ByDisplayValue`](../api/queries.md#by-display-value) - will the current (or default) value of `TextInput` element. This query will match only `TextInput` elements.
110
+
111
+ ### 3. Other accessible queries
112
+
113
+ These queries reflect the apps' user experience, both visual and through assistive technologies (e.g. screen reader).
114
+
115
+ These queries include:
116
+
117
+ - [`*ByText`](../api/queries.md#by-text) - will match the text content of the element. This query will match only `Text` elements.
118
+ - [`*ByLabelText`](../api/queries.md#by-label-text) - will match the accessibility label of the element.
119
+ - [`*ByHintText`](../api/queries.md#by-hint-text) - will match the accessibility hint of the element.
120
+
121
+ ### 4. Test ID query
122
+
123
+ As a final predicate, you can use the `testID` prop to find relevant views. Using the [`*ByTestId`](../api/queries.md#by-test-id) predicate offers the most flexibility, but at the same time, it does not represent the user experience, as users are not aware of test IDs.
124
+
125
+ Note that using test IDs is common in end-to-end testing due to various issues with querying views through other means **in that specific context**. For integration and component tests, use the recommended RNTL queries to make tests more reliable and resilient.
@@ -0,0 +1,228 @@
1
+ # LLM Guidelines for React Native Testing Library
2
+
3
+ Actionable guidelines for writing tests with React Native Testing Library (RNTL) v14.
4
+
5
+ ## Core APIs
6
+
7
+ ### render
8
+
9
+ ```tsx
10
+ const result = await render(<Component />, options?);
11
+ ```
12
+
13
+ | Option | Description |
14
+ | --------- | ---------------------------------------------------------------- |
15
+ | `wrapper` | React component to wrap the rendered component (e.g., providers) |
16
+
17
+ | Return | Description |
18
+ | --------------------- | ------------------------------------------------ |
19
+ | `rerender(component)` | Re-render with a new component (async) |
20
+ | `unmount()` | Unmount the rendered component (async) |
21
+ | `toJSON()` | Get JSON representation for snapshots |
22
+ | `debug(options?)` | Print the component tree to console |
23
+ | `container` | Root host element of the rendered tree |
24
+ | `root` | First child host element (your component's root) |
25
+
26
+ ### screen
27
+
28
+ **Prefer `screen`** over destructuring from `render()`. Provides all query methods after `render()` is called.
29
+
30
+ ```tsx
31
+ await render(<Component />);
32
+ screen.getByRole('button'); // Access queries via screen
33
+ ```
34
+
35
+ ### renderHook
36
+
37
+ ```tsx
38
+ const { result, rerender, unmount } = await renderHook(() => useMyHook(), options?);
39
+ ```
40
+
41
+ | Option | Description |
42
+ | -------------- | -------------------------------------------------- |
43
+ | `initialProps` | Initial props passed to the hook |
44
+ | `wrapper` | React component to wrap the hook (e.g., providers) |
45
+
46
+ | Return | Description |
47
+ | ------------------ | ------------------------------------- |
48
+ | `result.current` | Current return value of the hook |
49
+ | `rerender(props?)` | Re-render hook with new props (async) |
50
+ | `unmount()` | Unmount the hook (async) |
51
+
52
+ ## Query Selection
53
+
54
+ - **Prefer `getByRole`** as first choice for querying elements
55
+ - **Query priority**: `getByRole` → `getByLabelText` → `getByPlaceholderText` → `getByText` → `getByDisplayValue` → `getByTestId` (last resort)
56
+ - **Use `findBy*`** for elements that appear asynchronously (after API calls, timeouts, state updates)
57
+ - **Use `queryBy*` ONLY** for checking non-existence (with `.not.toBeOnTheScreen()`)
58
+ - **Never use `getBy*`** for non-existence checks
59
+ - **Avoid `container.queryAll()`** - use `screen` queries instead
60
+ - **Query by visible text**, not `testID` when text is available
61
+
62
+ ## Assertions
63
+
64
+ - **Use RNTL matchers** - prefer semantic matchers over prop assertions
65
+ - **Combine queries with matchers**: `expect(screen.getByText('Hello')).toBeOnTheScreen()`
66
+ - **No redundant null checks** - `getBy*` already throws if not found
67
+
68
+ ## Jest Matchers Reference
69
+
70
+ | Matcher | Description |
71
+ | --------------------------------- | ------------------------------------------------------------------------------------------- |
72
+ | `toBeOnTheScreen()` | Element is present in the element tree |
73
+ | `toBeVisible()` | Element is visible (checks style, `aria-hidden`, `accessibilityElementsHidden`, ancestors) |
74
+ | `toBeEmptyElement()` | Element has no children or text content |
75
+ | `toContainElement(element)` | Element contains another element |
76
+ | `toBeEnabled()` | Element is not disabled (checks `aria-disabled`, `accessibilityState`, ancestors) |
77
+ | `toBeDisabled()` | Element has `aria-disabled` or `accessibilityState={{ disabled: true }}` (checks ancestors) |
78
+ | `toBeBusy()` | Element has `aria-busy` or `accessibilityState={{ busy: true }}` |
79
+ | `toBeChecked()` | Element has `aria-checked` or `accessibilityState={{ checked: true }}` |
80
+ | `toBePartiallyChecked()` | Element has `aria-checked="mixed"` or `accessibilityState={{ checked: 'mixed' }}` |
81
+ | `toBeSelected()` | Element has `aria-selected` or `accessibilityState={{ selected: true }}` |
82
+ | `toBeExpanded()` | Element has `aria-expanded` or `accessibilityState={{ expanded: true }}` |
83
+ | `toBeCollapsed()` | Element has `aria-expanded={false}` or `accessibilityState={{ expanded: false }}` |
84
+ | `toHaveTextContent(text)` | Element has matching text content |
85
+ | `toHaveDisplayValue(value)` | TextInput has matching display value |
86
+ | `toHaveAccessibleName(name?)` | Element has matching `aria-label`, `accessibilityLabel`, or text content |
87
+ | `toHaveAccessibilityValue(value)` | Element has matching `aria-value*` or `accessibilityValue` |
88
+ | `toHaveStyle(style)` | Element has matching style |
89
+ | `toHaveProp(name, value?)` | Element has prop (use semantic matchers when possible) |
90
+
91
+ ## User Interactions
92
+
93
+ **Prefer `userEvent`** over `fireEvent` for realistic user interaction simulation. `userEvent` triggers the complete event sequence that real users would produce.
94
+
95
+ ### userEvent (Preferred)
96
+
97
+ ```tsx
98
+ const user = userEvent.setup();
99
+ ```
100
+
101
+ | Method | Description |
102
+ | ------------------------------------ | ----------------------------------------------------------------------------------- |
103
+ | `user.press(element)` | Press an element (triggers `pressIn`, `pressOut`, `press`) |
104
+ | `user.longPress(element, options?)` | Long press with optional `{ duration }` |
105
+ | `user.type(element, text, options?)` | Type into TextInput (triggers `focus`, `keyPress`, `change`, `changeText` per char) |
106
+ | `user.clear(element)` | Clear TextInput (select all + backspace) |
107
+ | `user.paste(element, text)` | Paste text into TextInput |
108
+ | `user.scrollTo(element, options)` | Scroll a ScrollView with `{ y }` or `{ x }` offset |
109
+
110
+ ### fireEvent (Low-level)
111
+
112
+ Use only when `userEvent` doesn't support the event or when you need direct control.
113
+
114
+ | Method | Description |
115
+ | ---------------------------------------- | --------------------------------------------- |
116
+ | `fireEvent(element, eventName, ...data)` | Fire any event by name |
117
+ | `fireEvent.press(element)` | Fire `onPress` only (no `pressIn`/`pressOut`) |
118
+ | `fireEvent.changeText(element, text)` | Fire `onChangeText` directly |
119
+ | `fireEvent.scroll(element, eventData)` | Fire `onScroll` with event data |
120
+
121
+ ## Async/Await (v14)
122
+
123
+ - **Always `await`**: `render()`, `fireEvent.*`, `renderHook()`, `userEvent.*`
124
+ - **Make test functions `async`**: `test('name', async () => { ... })`
125
+ - **Don't wrap in `act()`** - `render` and `fireEvent` handle it internally
126
+
127
+ ## waitFor Usage
128
+
129
+ - **Use `findBy*`** instead of `waitFor` + `getBy*` when waiting for elements
130
+ - **Never perform side-effects** (like `fireEvent.press()`) inside `waitFor` callbacks
131
+ - **One assertion per `waitFor`** callback
132
+ - **Never pass empty callbacks** - always include a meaningful assertion
133
+ - **Place side-effects before `waitFor`** - perform actions, then wait for result
134
+
135
+ ## Code Organization
136
+
137
+ - **Use `screen`** instead of destructuring from `render()`: `screen.getByText()` not `const { getByText } = render()`
138
+ - **Prefer `userEvent`** over `fireEvent` for realistic interactions
139
+ - **Don't use `cleanup()`** - handled automatically
140
+ - **Name wrappers descriptively**: `ThemeProvider` not `Wrapper`
141
+ - **Install ESLint plugin**: `eslint-plugin-testing-library`
142
+
143
+ ## Quick Checklist
144
+
145
+ - ✅ Using `getByRole` as first choice?
146
+ - ✅ Using `await` for all async operations?
147
+ - ✅ Using `findBy*` for async elements (not `waitFor` + `getBy*`)?
148
+ - ✅ Using `queryBy*` only for non-existence?
149
+ - ✅ Using RNTL matchers (`toBeOnTheScreen()`, `toBeDisabled()`, etc.)?
150
+ - ✅ Using `screen` not destructuring from `render()`?
151
+ - ✅ Avoiding side-effects in `waitFor`?
152
+ - ✅ Using `userEvent` when appropriate?
153
+
154
+ ## Example: Good Pattern
155
+
156
+ ```tsx
157
+ import { render, screen } from '@testing-library/react-native';
158
+ import userEvent from '@testing-library/react-native';
159
+ import { Pressable, Text, TextInput, View } from 'react-native';
160
+
161
+ test('user can submit form', async () => {
162
+ const user = userEvent.setup();
163
+
164
+ const Component = () => {
165
+ const [name, setName] = React.useState('');
166
+ const [submitted, setSubmitted] = React.useState(false);
167
+
168
+ return (
169
+ <View>
170
+ <TextInput role="textbox" aria-label="Name" value={name} onChangeText={setName} />
171
+ <Pressable role="button" aria-label="Submit" onPress={() => setSubmitted(true)}>
172
+ <Text>Submit</Text>
173
+ </Pressable>
174
+ {submitted && <Text role="alert">Form submitted!</Text>}
175
+ </View>
176
+ );
177
+ };
178
+
179
+ await render(<Component />);
180
+
181
+ // ✅ getByRole as first choice
182
+ const input = screen.getByRole('textbox', { name: 'Name' });
183
+ const button = screen.getByRole('button', { name: 'Submit' });
184
+
185
+ // ✅ userEvent for realistic interactions
186
+ await user.type(input, 'John Doe');
187
+ await user.press(button);
188
+
189
+ // ✅ findBy* for async elements
190
+ const successMessage = await screen.findByRole('alert');
191
+
192
+ // ✅ RNTL matchers
193
+ expect(successMessage).toBeOnTheScreen();
194
+ expect(successMessage).toHaveTextContent('Form submitted!');
195
+ });
196
+ ```
197
+
198
+ ## Example: Anti-Patterns
199
+
200
+ ```tsx
201
+ // ❌ Missing await
202
+ test('bad', () => {
203
+ render(<Component />);
204
+ fireEvent.press(screen.getByText('Submit'));
205
+ });
206
+
207
+ // ❌ getBy* for non-existence
208
+ expect(screen.getByText('Error')).not.toBeOnTheScreen();
209
+
210
+ // ❌ waitFor + getBy* instead of findBy*
211
+ await waitFor(() => {
212
+ expect(screen.getByText('Loaded')).toBeOnTheScreen();
213
+ });
214
+
215
+ // ❌ Side-effect in waitFor
216
+ await waitFor(async () => {
217
+ await fireEvent.press(button);
218
+ expect(screen.getByText('Result')).toBeOnTheScreen();
219
+ });
220
+
221
+ // ❌ accessibility* props instead of ARIA
222
+ <Pressable accessibilityRole="button" accessibilityLabel="Submit" />;
223
+
224
+ // ❌ Destructuring from render
225
+ const { getByText } = await render(<Component />);
226
+ ```
227
+
228
+ By following these guidelines, your tests will be more maintainable, accessible, and reliable.