@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.
- package/README.md +1 -6
- package/build/helpers/accessibility.js +24 -7
- package/build/helpers/accessibility.js.map +1 -1
- package/docs/README.md +31 -0
- package/docs/agents/architecture.md +21 -0
- package/docs/agents/build-and-validation.md +27 -0
- package/docs/agents/code-style.md +12 -0
- package/docs/agents/example-apps.md +56 -0
- package/docs/agents/git-workflow.md +13 -0
- package/docs/agents/testing.md +20 -0
- package/docs/api/accessibility.md +26 -0
- package/docs/api/async-utilities.md +137 -0
- package/docs/api/configuration.md +61 -0
- package/docs/api/fire-event.md +165 -0
- package/docs/api/jest-matchers.md +198 -0
- package/docs/api/other-helpers.md +94 -0
- package/docs/api/overview.md +18 -0
- package/docs/api/queries.md +500 -0
- package/docs/api/render-hook.md +176 -0
- package/docs/api/render.md +49 -0
- package/docs/api/screen.md +188 -0
- package/docs/api/user-event.md +295 -0
- package/docs/cookbook/async-events.md +147 -0
- package/docs/cookbook/custom-render.md +83 -0
- package/docs/cookbook/network-requests.md +375 -0
- package/docs/guides/common-mistakes.md +587 -0
- package/docs/guides/how-to-query.md +125 -0
- package/docs/guides/llm-guidelines.md +228 -0
- package/docs/guides/migration-v14.md +553 -0
- package/docs/guides/quick-start.md +77 -0
- package/docs/guides/testing-environment.md +123 -0
- package/docs/guides/troubleshooting.md +61 -0
- package/docs/guides/understanding-act.md +207 -0
- package/package.json +9 -7
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
# Queries
|
|
2
|
+
|
|
3
|
+
Queries are one of the main building blocks for the React Native Testing Library. They enable you to find relevant elements in the element tree, which represents your application's user interface when running under tests.
|
|
4
|
+
|
|
5
|
+
## Accessing queries
|
|
6
|
+
|
|
7
|
+
All queries described below are accessible in two main ways: through the `screen` object or by capturing the `render` function call result.
|
|
8
|
+
|
|
9
|
+
### Using `screen` object
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { render, screen } from '@testing-library/react-native';
|
|
13
|
+
|
|
14
|
+
test('accessing queries using "screen" object', async () => {
|
|
15
|
+
await render(...);
|
|
16
|
+
|
|
17
|
+
screen.getByRole("button", { name: "Start" });
|
|
18
|
+
})
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Use the `screen` object exported by `@testing-library/react-native` to access queries. This object contains all available query methods bound to the most recently rendered UI.
|
|
22
|
+
|
|
23
|
+
### Using `render` result
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { render } from '@testing-library/react-native';
|
|
27
|
+
|
|
28
|
+
test('accessing queries using "render" result', async () => {
|
|
29
|
+
const { getByRole } = await render(...);
|
|
30
|
+
getByRole("button", { name: "Start" });
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
You can also capture query functions from the `render` function return value. This provides the same query functions as the `screen` object.
|
|
35
|
+
|
|
36
|
+
## Query parts
|
|
37
|
+
|
|
38
|
+
Each query is composed of two parts: variant and predicate, which are separated by the `by` word in the middle of the name.
|
|
39
|
+
|
|
40
|
+
Consider the following query:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
getByRole()
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For this query, `getBy*` is the query variant, and `*ByRole` is the predicate.
|
|
47
|
+
|
|
48
|
+
## Query variant
|
|
49
|
+
|
|
50
|
+
The query variants describe the expected number (and timing) of matching elements, so they differ in their return type.
|
|
51
|
+
|
|
52
|
+
| Variant | Assertion | Return type | Is Async? |
|
|
53
|
+
| ------------------------------------------ | ----------------------------- | ------------------------------------- | --------- |
|
|
54
|
+
| [`getBy*`](./queries.md#get-by) | Exactly one matching element | `TestInstance` | No |
|
|
55
|
+
| [`getAllBy*`](./queries.md#get-all-by) | At least one matching element | `Array<TestInstance>` | No |
|
|
56
|
+
| [`queryBy*`](./queries.md#query-by) | Zero or one matching element | <code>TestInstance | null</code> | No |
|
|
57
|
+
| [`queryAllBy*`](./queries.md#query-all-by) | No assertion | `Array<TestInstance>` | No |
|
|
58
|
+
| [`findBy*`](./queries.md#find-by) | Exactly one matching element | `Promise<TestInstance>` | Yes |
|
|
59
|
+
| [`findAllBy*`](./queries.md#find-all-by) | At least one matching element | `Promise<Array<TestInstance>>` | Yes |
|
|
60
|
+
|
|
61
|
+
Queries work as implicit assertions on the number of matching elements and will throw an error when the assertion fails.
|
|
62
|
+
|
|
63
|
+
### `getBy*` queries
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
getByX(...): TestInstance
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`getBy*` queries return the single matching element for a query, and throw an error if no elements match or if more than one match is found. If you need to find more than one element, then use `getAllBy`.
|
|
70
|
+
|
|
71
|
+
### `getAllBy*` queries
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
getAllByX(...): TestInstance[]
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`getAllBy*` queries return an array of all matching elements for a query and throw an error if no elements match.
|
|
78
|
+
|
|
79
|
+
### `queryBy*` queries
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
queryByX(...): TestInstance | null
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`queryBy*` queries return the first matching node for a query, or `null` if no elements match. Use these to assert that an element is not present. They throw if more than one match is found (use `queryAllBy` instead).
|
|
86
|
+
|
|
87
|
+
### `queryAllBy*` queries
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
queryAllByX(...): TestInstance[]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`queryAllBy*` queries return an array of all matching nodes for a query and return an empty array (`[]`) when no elements match.
|
|
94
|
+
|
|
95
|
+
### `findBy*` queries
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
findByX(
|
|
99
|
+
...,
|
|
100
|
+
waitForOptions?: {
|
|
101
|
+
timeout?: number,
|
|
102
|
+
interval?: number,
|
|
103
|
+
},
|
|
104
|
+
): Promise<TestInstance>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`findBy*` queries return a promise which resolves when a matching element is found. The promise is rejected if no elements match or if more than one match is found after a default timeout of 1000 ms. If you need to find more than one element use `findAllBy*` queries.
|
|
108
|
+
|
|
109
|
+
### `findAllBy*` queries
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
findAllByX(
|
|
113
|
+
...,
|
|
114
|
+
waitForOptions?: {
|
|
115
|
+
timeout?: number,
|
|
116
|
+
interval?: number,
|
|
117
|
+
},
|
|
118
|
+
): Promise<TestInstance[]>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`findAllBy*` queries return a promise which resolves to an array of matching elements. The promise is rejected if no elements match after a default timeout of 1000 ms.
|
|
122
|
+
|
|
123
|
+
> [!INFO]
|
|
124
|
+
> `findBy*` and `findAllBy*` queries accept optional `waitForOptions` object arguments, which can contain `timeout`, `interval` and `onTimeout` properties which have the same meaning as respective options for [`waitFor`](./async-utilities.md#waitfor) function.
|
|
125
|
+
|
|
126
|
+
> [!INFO]
|
|
127
|
+
> In cases when your `findBy*` and `findAllBy*` queries throw when unable to find matching elements, it is helpful to pass `onTimeout: () => { screen.debug(); }` callback using the `waitForOptions` parameter.
|
|
128
|
+
|
|
129
|
+
## Query predicates
|
|
130
|
+
|
|
131
|
+
_Note: most methods like this one return a [`TestInstance`](https://github.com/mdjastrzebski/test-renderer#test-instance) with following properties that you may be interested in:_
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
type TestInstance = {
|
|
135
|
+
type: string;
|
|
136
|
+
props: { [propName: string]: any };
|
|
137
|
+
parent: TestInstance | null;
|
|
138
|
+
children: Array<TestInstance | string>;
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `*ByRole`
|
|
143
|
+
|
|
144
|
+
> getByRole, getAllByRole, queryByRole, queryAllByRole, findByRole, findAllByRole
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
getByRole(
|
|
148
|
+
role: TextMatch,
|
|
149
|
+
options?: {
|
|
150
|
+
name?: TextMatch
|
|
151
|
+
disabled?: boolean,
|
|
152
|
+
selected?: boolean,
|
|
153
|
+
checked?: boolean | 'mixed',
|
|
154
|
+
busy?: boolean,
|
|
155
|
+
expanded?: boolean,
|
|
156
|
+
value: {
|
|
157
|
+
min?: number;
|
|
158
|
+
max?: number;
|
|
159
|
+
now?: number;
|
|
160
|
+
text?: TextMatch;
|
|
161
|
+
},
|
|
162
|
+
includeHiddenElements?: boolean;
|
|
163
|
+
}
|
|
164
|
+
): TestInstance;
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Returns a `TestInstance` with matching `role` or `accessibilityRole` prop.
|
|
168
|
+
|
|
169
|
+
> [!INFO]
|
|
170
|
+
> In order for `*ByRole` queries to match an element it needs to be considered an accessibility element:
|
|
171
|
+
>
|
|
172
|
+
> 1. `Text`, `TextInput` and `Switch` elements are these by default.
|
|
173
|
+
> 2. `View` elements need an explicit [`accessible`](https://reactnative.dev/docs/accessibility#accessible) prop set to `true`
|
|
174
|
+
> 3. Some React Native composite components like `Pressable` & `TouchableOpacity` render host `View` element with `accessible` prop already set.
|
|
175
|
+
|
|
176
|
+
```jsx
|
|
177
|
+
import { render, screen } from '@testing-library/react-native';
|
|
178
|
+
|
|
179
|
+
await render(
|
|
180
|
+
<Pressable accessibilityRole="button" disabled>
|
|
181
|
+
<Text>Hello</Text>
|
|
182
|
+
</Pressable>,
|
|
183
|
+
);
|
|
184
|
+
const element = screen.getByRole('button');
|
|
185
|
+
const element2 = screen.getByRole('button', { name: 'Hello' });
|
|
186
|
+
const element3 = screen.getByRole('button', { name: 'Hello', disabled: true });
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### Options
|
|
190
|
+
|
|
191
|
+
- `name`: Finds an element with given `role`/`accessibilityRole` and an accessible name (= accessability label or text content).
|
|
192
|
+
|
|
193
|
+
- `disabled`: You can filter elements by their disabled state (coming either from `aria-disabled` prop or `accessbilityState.disabled` prop). The possible values are `true` or `false`. Querying `disabled: false` will also match elements with `disabled: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details).
|
|
194
|
+
- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `disabled` state.
|
|
195
|
+
- This option can alternatively be expressed using the [`toBeEnabled()` / `toBeDisabled()`](./jest-matchers.md#tobeenabled) Jest matchers.
|
|
196
|
+
|
|
197
|
+
- `selected`: You can filter elements by their selected state (coming either from `aria-selected` prop or `accessbilityState.selected` prop). The possible values are `true` or `false`. Querying `selected: false` will also match elements with `selected: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details).
|
|
198
|
+
- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `selected` state.
|
|
199
|
+
- This option can alternatively be expressed using the [`toBeSelected()`](./jest-matchers.md#tobeselected) Jest matcher.
|
|
200
|
+
|
|
201
|
+
* `checked`: You can filter elements by their checked state (coming either from `aria-checked` prop or `accessbilityState.checked` prop). The possible values are `true`, `false`, or `"mixed"`.
|
|
202
|
+
- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `checked` state.
|
|
203
|
+
- This option can alternatively be expressed using the [`toBeChecked()` / `toBePartiallyChecked()`](./jest-matchers.md#tobechecked) Jest matchers.
|
|
204
|
+
|
|
205
|
+
* `busy`: You can filter elements by their busy state (coming either from `aria-busy` prop or `accessbilityState.busy` prop). The possible values are `true` or `false`. Querying `busy: false` will also match elements with `busy: undefined` (see the [wiki](https://github.com/callstack/react-native-testing-library/wiki/Accessibility:-State) for more details).
|
|
206
|
+
- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `busy` state.
|
|
207
|
+
- This option can alternatively be expressed using the [`toBeBusy()`](./jest-matchers.md#tobebusy) Jest matcher.
|
|
208
|
+
|
|
209
|
+
* `expanded`: You can filter elements by their expanded state (coming either from `aria-expanded` prop or `accessbilityState.expanded` prop). The possible values are `true` or `false`.
|
|
210
|
+
- See [React Native's accessibilityState](https://reactnative.dev/docs/accessibility#accessibilitystate) docs to learn more about the `expanded` state.
|
|
211
|
+
- This option can alternatively be expressed using the [`toBeExpanded()` / `toBeCollapsed()`](./jest-matchers.md#tobeexpanded) Jest matchers.
|
|
212
|
+
|
|
213
|
+
* `value`: Filter elements by their accessibility value, based on either `aria-valuemin`, `aria-valuemax`, `aria-valuenow`, `aria-valuetext` or `accessibilityValue` props. Accessiblity value conceptually consists of numeric `min`, `max` and `now` entries, as well as string `text` entry.
|
|
214
|
+
- See React Native [accessibilityValue](https://reactnative.dev/docs/accessibility#accessibilityvalue) docs to learn more about the accessibility value concept.
|
|
215
|
+
- This option can alternatively be expressed using the [`toHaveAccessibilityValue()`](./jest-matchers.md#tohaveaccessibilityvalue) Jest matcher.
|
|
216
|
+
|
|
217
|
+
### `*ByLabelText`
|
|
218
|
+
|
|
219
|
+
> getByLabelText, getAllByLabelText, queryByLabelText, queryAllByLabelText, findByLabelText, findAllByLabelText
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
getByLabelText(
|
|
223
|
+
text: TextMatch,
|
|
224
|
+
options?: {
|
|
225
|
+
exact?: boolean;
|
|
226
|
+
normalizer?: (text: string) => string;
|
|
227
|
+
includeHiddenElements?: boolean;
|
|
228
|
+
},
|
|
229
|
+
): TestInstance;
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Returns a `TestInstance` with matching label:
|
|
233
|
+
|
|
234
|
+
- either by matching [`aria-label`](https://reactnative.dev/docs/accessibility#aria-label)/[`accessibilityLabel`](https://reactnative.dev/docs/accessibility#accessibilitylabel) prop
|
|
235
|
+
- or by matching text content of view referenced by [`aria-labelledby`](https://reactnative.dev/docs/accessibility#aria-labelledby-android)/[`accessibilityLabelledBy`](https://reactnative.dev/docs/accessibility#accessibilitylabelledby-android) prop
|
|
236
|
+
- or by matching the [`alt`](https://reactnative.dev/docs/image#alt) prop on `Image` elements
|
|
237
|
+
|
|
238
|
+
When `accessibilityLabelledBy` references multiple elements with an array, their text content is joined with spaces in the referenced order and matched as a single label. `aria-labelledby` follows React Native's single `nativeID` value behavior.
|
|
239
|
+
|
|
240
|
+
```jsx
|
|
241
|
+
import { render, screen } from '@testing-library/react-native';
|
|
242
|
+
|
|
243
|
+
await render(<MyComponent />);
|
|
244
|
+
const element = screen.getByLabelText('my-label');
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### `*ByPlaceholderText`
|
|
248
|
+
|
|
249
|
+
> getByPlaceholderText, getAllByPlaceholderText, queryByPlaceholderText, queryAllByPlaceholderText, findByPlaceholderText, findAllByPlaceholderText
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
getByPlaceholderText(
|
|
253
|
+
text: TextMatch,
|
|
254
|
+
options?: {
|
|
255
|
+
exact?: boolean;
|
|
256
|
+
normalizer?: (text: string) => string;
|
|
257
|
+
includeHiddenElements?: boolean;
|
|
258
|
+
}
|
|
259
|
+
): TestInstance;
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Returns a `TestInstance` for a `TextInput` with a matching placeholder – may be a string or regular expression.
|
|
263
|
+
|
|
264
|
+
```jsx
|
|
265
|
+
import { render, screen } from '@testing-library/react-native';
|
|
266
|
+
|
|
267
|
+
await render(<MyComponent />);
|
|
268
|
+
const element = screen.getByPlaceholderText('username');
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### `*ByDisplayValue`
|
|
272
|
+
|
|
273
|
+
> getByDisplayValue, getAllByDisplayValue, queryByDisplayValue, queryAllByDisplayValue, findByDisplayValue, findAllByDisplayValue
|
|
274
|
+
|
|
275
|
+
```ts
|
|
276
|
+
getByDisplayValue(
|
|
277
|
+
value: TextMatch,
|
|
278
|
+
options?: {
|
|
279
|
+
exact?: boolean;
|
|
280
|
+
normalizer?: (text: string) => string;
|
|
281
|
+
includeHiddenElements?: boolean;
|
|
282
|
+
},
|
|
283
|
+
): TestInstance;
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Returns a `TestInstance` for a `TextInput` with a matching display value – may be a string or regular expression.
|
|
287
|
+
|
|
288
|
+
```jsx
|
|
289
|
+
import { render, screen } from '@testing-library/react-native';
|
|
290
|
+
|
|
291
|
+
await render(<MyComponent />);
|
|
292
|
+
const element = screen.getByDisplayValue('username');
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### `*ByText`
|
|
296
|
+
|
|
297
|
+
> getByText, getAllByText, queryByText, queryAllByText, findByText, findAllByText
|
|
298
|
+
|
|
299
|
+
```ts
|
|
300
|
+
getByText(
|
|
301
|
+
text: TextMatch,
|
|
302
|
+
options?: {
|
|
303
|
+
exact?: boolean;
|
|
304
|
+
normalizer?: (text: string) => string;
|
|
305
|
+
includeHiddenElements?: boolean;
|
|
306
|
+
}
|
|
307
|
+
): TestInstance;
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Returns a `TestInstance` with matching text – may be a string or regular expression.
|
|
311
|
+
|
|
312
|
+
This method will join `<Text>` siblings to find matches, similarly to [how React Native handles these components](https://reactnative.dev/docs/text#containers). This will allow for querying for strings that will be visually rendered together, but may be semantically separate React components.
|
|
313
|
+
|
|
314
|
+
```jsx
|
|
315
|
+
import { render, screen } from '@testing-library/react-native';
|
|
316
|
+
|
|
317
|
+
await render(<MyComponent />);
|
|
318
|
+
const element = screen.getByText('banana');
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### `*ByHintText`
|
|
322
|
+
|
|
323
|
+
> getByA11yHint, getAllByA11yHint, queryByA11yHint, queryAllByA11yHint, findByA11yHint, findAllByA11yHint
|
|
324
|
+
> getByAccessibilityHint, getAllByAccessibilityHint, queryByAccessibilityHint, queryAllByAccessibilityHint, findByAccessibilityHint, findAllByAccessibilityHint
|
|
325
|
+
> getByHintText, getAllByHintText, queryByHintText, queryAllByHintText, findByHintText, findAllByHintText
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
getByHintText(
|
|
329
|
+
hint: TextMatch,
|
|
330
|
+
options?: {
|
|
331
|
+
exact?: boolean;
|
|
332
|
+
normalizer?: (text: string) => string;
|
|
333
|
+
includeHiddenElements?: boolean;
|
|
334
|
+
},
|
|
335
|
+
): TestInstance;
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Returns a `TestInstance` with matching `accessibilityHint` prop.
|
|
339
|
+
|
|
340
|
+
```jsx
|
|
341
|
+
import { render, screen } from '@testing-library/react-native';
|
|
342
|
+
|
|
343
|
+
await render(<MyComponent />);
|
|
344
|
+
const element = screen.getByHintText('Plays a song');
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
> [!INFO]
|
|
348
|
+
> Please consult [Apple guidelines on how `accessibilityHint` should be used](https://developer.apple.com/documentation/objectivec/nsobject/1615093-accessibilityhint).
|
|
349
|
+
|
|
350
|
+
### `*ByTestId`
|
|
351
|
+
|
|
352
|
+
> getByTestId, getAllByTestId, queryByTestId, queryAllByTestId, findByTestId, findAllByTestId
|
|
353
|
+
|
|
354
|
+
```ts
|
|
355
|
+
getByTestId(
|
|
356
|
+
testId: TextMatch,
|
|
357
|
+
options?: {
|
|
358
|
+
exact?: boolean;
|
|
359
|
+
normalizer?: (text: string) => string;
|
|
360
|
+
includeHiddenElements?: boolean;
|
|
361
|
+
},
|
|
362
|
+
): TestInstance;
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Returns a `TestInstance` with matching `testID` prop. `testID` – may be a string or a regular expression.
|
|
366
|
+
|
|
367
|
+
```jsx
|
|
368
|
+
import { render, screen } from '@testing-library/react-native';
|
|
369
|
+
|
|
370
|
+
await render(<MyComponent />);
|
|
371
|
+
const element = screen.getByTestId('unique-id');
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
> [!INFO]
|
|
375
|
+
> Following [the guiding principles](https://testing-library.com/docs/guiding-principles), use this only when other queries don't work for your use case. `testID` attributes don't resemble how your software is used and should be avoided when possible. They're useful for end-to-end testing on real devices, e.g. with Detox. Learn more from the blog post ["Making your UI tests resilient to change"](https://kentcdodds.com/blog/making-your-ui-tests-resilient-to-change).
|
|
376
|
+
|
|
377
|
+
### Common options
|
|
378
|
+
|
|
379
|
+
Usually query first argument can be a **string** or a **regex**. All queries take at least the [`hidden`](#hidden-option) option as an optionnal second argument and some queries accept more options which change string matching behaviour. See [TextMatch](#textmatch) for more info.
|
|
380
|
+
|
|
381
|
+
#### `includeHiddenElements` option
|
|
382
|
+
|
|
383
|
+
All queries have the `includeHiddenElements` option which affects whether [elements hidden from accessibility](./accessibility.md#ishiddenfromaccessibility) are matched by the query. By default queries will not match hidden elements, because the users of the app would not be able to see such elements.
|
|
384
|
+
|
|
385
|
+
You can configure the default value with the [`configure` function](./configuration.md#configure).
|
|
386
|
+
|
|
387
|
+
This option is also available as `hidden` alias for compatibility with [React Testing Library](https://testing-library.com/docs/queries/byrole#hidden).
|
|
388
|
+
|
|
389
|
+
**Examples**
|
|
390
|
+
|
|
391
|
+
```tsx
|
|
392
|
+
await render(<Text style={{ display: 'none' }}>Hidden from accessibility</Text>);
|
|
393
|
+
|
|
394
|
+
// Exclude hidden elements
|
|
395
|
+
expect(
|
|
396
|
+
screen.queryByText('Hidden from accessibility', {
|
|
397
|
+
includeHiddenElements: false,
|
|
398
|
+
}),
|
|
399
|
+
).not.toBeOnTheScreen();
|
|
400
|
+
|
|
401
|
+
// Include hidden elements
|
|
402
|
+
expect(
|
|
403
|
+
screen.getByText('Hidden from accessibility', { includeHiddenElements: true }),
|
|
404
|
+
).toBeOnTheScreen();
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## TextMatch type
|
|
408
|
+
|
|
409
|
+
```ts
|
|
410
|
+
type TextMatch = string | RegExp;
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Most of the query APIs take a `TextMatch` as an argument, which means the argument can be either a _string_ or _regex_.
|
|
414
|
+
|
|
415
|
+
### Examples
|
|
416
|
+
|
|
417
|
+
Given the following render:
|
|
418
|
+
|
|
419
|
+
```jsx
|
|
420
|
+
await render(<Text>Hello World</Text>);
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Will **find a match**:
|
|
424
|
+
|
|
425
|
+
```js
|
|
426
|
+
// Matching a string:
|
|
427
|
+
screen.getByText('Hello World'); // full string match
|
|
428
|
+
screen.getByText('llo Worl', { exact: false }); // substring match
|
|
429
|
+
screen.getByText('hello world', { exact: false }); // ignore case-sensitivity
|
|
430
|
+
|
|
431
|
+
// Matching a regex:
|
|
432
|
+
screen.getByText(/World/); // substring match
|
|
433
|
+
screen.getByText(/world/i); // substring match, ignore case
|
|
434
|
+
screen.getByText(/^hello world$/i); // full string match, ignore case-sensitivity
|
|
435
|
+
screen.getByText(/Hello W?oRlD/i); // advanced regex
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Will **NOT find a match**
|
|
439
|
+
|
|
440
|
+
```js
|
|
441
|
+
// substring does not match
|
|
442
|
+
screen.getByText('llo Worl');
|
|
443
|
+
// full string does not match
|
|
444
|
+
screen.getByText('Goodbye World');
|
|
445
|
+
|
|
446
|
+
// case-sensitive regex with different case
|
|
447
|
+
screen.getByText(/hello world/);
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Options
|
|
451
|
+
|
|
452
|
+
#### Precision
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
type TextMatchOptions = {
|
|
456
|
+
exact?: boolean;
|
|
457
|
+
normalizer?: (text: string) => string;
|
|
458
|
+
};
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Queries that take a `TextMatch` also accept an object as the second argument that can contain options that affect the precision of string matching:
|
|
462
|
+
|
|
463
|
+
- `exact`: Defaults to `true`; matches full strings, case-sensitive. When false, matches substrings and is not case-sensitive.
|
|
464
|
+
- `exact` has no effect on regex argument.
|
|
465
|
+
- In most cases using a `regex` instead of a string gives you more control over fuzzy matching and should be preferred over `{ exact: false }`.
|
|
466
|
+
- `normalizer`: An optional function which overrides normalization behavior. See [Normalization](#normalization).
|
|
467
|
+
|
|
468
|
+
`exact` option defaults to `true` but if you want to search for a text slice or make text matching case-insensitive you can override it. That being said we advise you to use regex in more complex scenarios.
|
|
469
|
+
|
|
470
|
+
#### Normalization
|
|
471
|
+
|
|
472
|
+
Before running any matching logic against text, it is automatically normalized. By default, normalization consists of trimming whitespace from the start and end of text, and collapsing multiple adjacent whitespace characters into a single space.
|
|
473
|
+
|
|
474
|
+
If you want to prevent that normalization, or provide alternative normalization (e.g. to remove Unicode control characters), you can provide a `normalizer` function in the options object. This function will be given a string and is expected to return a normalized version of that string.
|
|
475
|
+
|
|
476
|
+
> [!INFO]
|
|
477
|
+
> Specifying a value for `normalizer` replaces the built-in normalization, but you can call `getDefaultNormalizer` to obtain a built-in normalizer, either to adjust that normalization or to call it from your own normalizer.
|
|
478
|
+
|
|
479
|
+
`getDefaultNormalizer` take options object which allows the selection of behaviour:
|
|
480
|
+
|
|
481
|
+
- `trim`: Defaults to `true`. Trims leading and trailing whitespace.
|
|
482
|
+
- `collapseWhitespace`: Defaults to `true`. Collapses inner whitespace (newlines, tabs repeated spaces) into a single space.
|
|
483
|
+
|
|
484
|
+
##### Normalization Examples
|
|
485
|
+
|
|
486
|
+
To perform a match against text without trimming:
|
|
487
|
+
|
|
488
|
+
```typescript
|
|
489
|
+
screen.getByText('text', {
|
|
490
|
+
normalizer: getDefaultNormalizer({ trim: false }),
|
|
491
|
+
});
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
To override normalization to remove some Unicode characters whilst keeping some (but not all) of the built-in normalization behavior:
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
screen.getByText('text', {
|
|
498
|
+
normalizer: (str) => getDefaultNormalizer({ trim: false })(str).replace(/[\u200E-\u200F]*/g, ''),
|
|
499
|
+
});
|
|
500
|
+
```
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# `renderHook` function
|
|
2
|
+
|
|
3
|
+
## `renderHook`
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
async function renderHook<Result, Props>(
|
|
7
|
+
hookFn: (props: Props) => Result,
|
|
8
|
+
options?: RenderHookOptions<Props>,
|
|
9
|
+
): Promise<RenderHookResult<Result, Props>>;
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Renders a test component that calls the provided `callback` (and any hooks it uses) on each render. Returns a Promise that resolves to a [`RenderHookResult`](#renderhookresult) object.
|
|
13
|
+
|
|
14
|
+
**This is the recommended default API** for testing hooks. It uses async `act` internally to ensure all pending React updates are executed during rendering. This makes it compatible with async React features like `Suspense` boundaries and the `use()` hook.
|
|
15
|
+
|
|
16
|
+
- **Returns a Promise**: Should be awaited
|
|
17
|
+
- **Async methods**: Both `rerender` and `unmount` return Promises and should be awaited
|
|
18
|
+
- **Suspense support**: Compatible with `Suspense` boundaries and `use()` hook
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { renderHook, act } from '@testing-library/react-native';
|
|
22
|
+
import { useCount } from '../useCount';
|
|
23
|
+
|
|
24
|
+
it('should increment count', async () => {
|
|
25
|
+
const { result } = await renderHook(() => useCount());
|
|
26
|
+
|
|
27
|
+
expect(result.current.count).toBe(0);
|
|
28
|
+
await act(() => {
|
|
29
|
+
// Note that you should wrap the calls to functions your hook returns with `act` if they trigger an update of your hook's state to ensure pending useEffects are run before your next assertion.
|
|
30
|
+
result.current.increment();
|
|
31
|
+
});
|
|
32
|
+
expect(result.current.count).toBe(1);
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
// useCount.js
|
|
38
|
+
import { useState } from 'react';
|
|
39
|
+
|
|
40
|
+
export const useCount = () => {
|
|
41
|
+
const [count, setCount] = useState(0);
|
|
42
|
+
const increment = () => setCount((previousCount) => previousCount + 1);
|
|
43
|
+
|
|
44
|
+
return { count, increment };
|
|
45
|
+
};
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The `renderHook` function accepts the following arguments:
|
|
49
|
+
|
|
50
|
+
**Callback**: A function called on each render of the test component. This function should call one or more hooks for testing.
|
|
51
|
+
|
|
52
|
+
The callback receives `props` from the `initialProps` option, or from a subsequent `rerender` call if provided.
|
|
53
|
+
|
|
54
|
+
### `options`
|
|
55
|
+
|
|
56
|
+
A `RenderHookOptions<Props>` object with the following properties:
|
|
57
|
+
|
|
58
|
+
#### `initialProps`
|
|
59
|
+
|
|
60
|
+
The initial values to pass as `props` to the `callback` function of `renderHook`. The `Props` type is determined by the type passed to or inferred by the `renderHook` call.
|
|
61
|
+
|
|
62
|
+
#### `wrapper`
|
|
63
|
+
|
|
64
|
+
A React component that wraps the test component. Use this to add context providers so hooks can access them with `useContext`.
|
|
65
|
+
|
|
66
|
+
### Result
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
interface RenderHookResult<Result, Props> {
|
|
70
|
+
result: { current: Result };
|
|
71
|
+
rerender: (props: Props) => Promise<void>;
|
|
72
|
+
unmount: () => Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The `renderHook` function returns a Promise that resolves to an object with the following properties:
|
|
77
|
+
|
|
78
|
+
#### `result`
|
|
79
|
+
|
|
80
|
+
The `current` value contains whatever the `callback` returned from `renderHook`. The `Result` type is determined by the type passed to or inferred by the `renderHook` call.
|
|
81
|
+
|
|
82
|
+
**Note:** When using React Suspense, `result.current` will be `null` while the hook is suspended.
|
|
83
|
+
|
|
84
|
+
#### `rerender`
|
|
85
|
+
|
|
86
|
+
An async function that rerenders the test component and recalculates hooks. If `newProps` are passed, they replace the `callback` function's `initialProps` for subsequent rerenders. The `Props` type is determined by the type passed to or inferred by the `renderHook` call.
|
|
87
|
+
|
|
88
|
+
**Note**: This method returns a Promise and should be awaited.
|
|
89
|
+
|
|
90
|
+
#### `unmount`
|
|
91
|
+
|
|
92
|
+
An async function to unmount the test component. This is commonly used to trigger cleanup effects for `useEffect` hooks.
|
|
93
|
+
|
|
94
|
+
**Note**: This method returns a Promise and should be awaited.
|
|
95
|
+
|
|
96
|
+
### Examples
|
|
97
|
+
|
|
98
|
+
Additional examples of using `renderHook`:
|
|
99
|
+
|
|
100
|
+
#### With `initialProps`
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { useState, useEffect } from 'react';
|
|
104
|
+
import { renderHook, act } from '@testing-library/react-native';
|
|
105
|
+
|
|
106
|
+
const useCount = (initialCount: number) => {
|
|
107
|
+
const [count, setCount] = useState(initialCount);
|
|
108
|
+
const increment = () => setCount((previousCount) => previousCount + 1);
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
setCount(initialCount);
|
|
112
|
+
}, [initialCount]);
|
|
113
|
+
|
|
114
|
+
return { count, increment };
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
it('should increment count', async () => {
|
|
118
|
+
const { result, rerender } = await renderHook((initialCount: number) => useCount(initialCount), {
|
|
119
|
+
initialProps: 1,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
expect(result.current.count).toBe(1);
|
|
123
|
+
|
|
124
|
+
await act(() => {
|
|
125
|
+
result.current.increment();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
expect(result.current.count).toBe(2);
|
|
129
|
+
await rerender(5);
|
|
130
|
+
expect(result.current.count).toBe(5);
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### With `wrapper`
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
it('should use context value', async () => {
|
|
138
|
+
function Wrapper({ children }: { children: ReactNode }) {
|
|
139
|
+
return <Context.Provider value="provided">{children}</Context.Provider>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const { result } = await renderHook(() => useHook(), { wrapper: Wrapper });
|
|
143
|
+
// ...
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### With React Suspense
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
import { renderHook, act } from '@testing-library/react-native';
|
|
151
|
+
import { Text } from 'react-native';
|
|
152
|
+
|
|
153
|
+
function useSuspendingHook(promise: Promise<string>) {
|
|
154
|
+
return React.use(promise);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
it('handles hook with suspense', async () => {
|
|
158
|
+
let resolvePromise: (value: string) => void;
|
|
159
|
+
const promise = new Promise<string>((resolve) => {
|
|
160
|
+
resolvePromise = resolve;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const { result } = await renderHook(useSuspendingHook, {
|
|
164
|
+
initialProps: promise,
|
|
165
|
+
wrapper: ({ children }) => (
|
|
166
|
+
<React.Suspense fallback={<Text>Loading...</Text>}>{children}</React.Suspense>
|
|
167
|
+
),
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Initially suspended, result should not be available
|
|
171
|
+
expect(result.current).toBeNull();
|
|
172
|
+
|
|
173
|
+
await act(() => resolvePromise('resolved'));
|
|
174
|
+
expect(result.current).toBe('resolved');
|
|
175
|
+
});
|
|
176
|
+
```
|