@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,49 @@
|
|
|
1
|
+
# `render` API
|
|
2
|
+
|
|
3
|
+
## `render` function
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
async function render<T>(
|
|
7
|
+
element: React.ReactElement<T>,
|
|
8
|
+
options?: RenderOptions,
|
|
9
|
+
): Promise<RenderResult>;
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The `render` function is the entry point for writing React Native Testing Library tests. It deeply renders the given React element and returns helpers to query the output. The function is async and uses async `act` internally, so all pending React updates run before it resolves. This works with async React features like `Suspense` boundaries and the `use()` hook.
|
|
13
|
+
|
|
14
|
+
```jsx
|
|
15
|
+
import { render, screen } from '@testing-library/react-native';
|
|
16
|
+
|
|
17
|
+
test('basic test', async () => {
|
|
18
|
+
await render(<MyApp />);
|
|
19
|
+
expect(screen.getAllByRole('button', { name: 'start' })).toBeOnTheScreen();
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
> When using React context providers like Redux Provider, you'll likely want to wrap the rendered component with them. In such cases, create your own custom `render` method. [Follow this guide on how to set it up](https://testing-library.com/docs/react-testing-library/setup#custom-render).
|
|
24
|
+
|
|
25
|
+
### Options
|
|
26
|
+
|
|
27
|
+
You can customize the `render` method by passing options as the second argument:
|
|
28
|
+
|
|
29
|
+
#### `wrapper`
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
wrapper?: React.ComponentType<any>,
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Wraps the tested component in an additional wrapper component. Use this to create custom render functions for common React Context providers.
|
|
36
|
+
|
|
37
|
+
> [!NOTE] Text string validation
|
|
38
|
+
> Test Renderer enforces React Native's requirement that text strings must be rendered within a `<Text>` component. If you render a `string` value under components other than `<Text>` (e.g., under `<View>`), it throws an `Invariant Violation: Text strings must be rendered within a <Text> component` error. This matches React Native's runtime behavior.
|
|
39
|
+
>
|
|
40
|
+
> This validation is always enabled and cannot be disabled. Your tests will catch the same text rendering errors that would occur in production.
|
|
41
|
+
|
|
42
|
+
### Result
|
|
43
|
+
|
|
44
|
+
The `render` function returns a promise that resolves to the same queries and utilities as the [`screen`](./screen.md) object. Use `screen` for queries and the lifecycle methods from the render result when needed.
|
|
45
|
+
|
|
46
|
+
See [this article](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-screen) from Kent C. Dodds for more details.
|
|
47
|
+
|
|
48
|
+
> [!NOTE] Type information
|
|
49
|
+
> Query results and element references use the `TestInstance` type from [Test Renderer](https://github.com/mdjastrzebski/test-renderer). If you need to type element variables, import this type directly from `test-renderer`.
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# `screen` object
|
|
2
|
+
|
|
3
|
+
```ts
|
|
4
|
+
let screen: {
|
|
5
|
+
...queries;
|
|
6
|
+
rerender(element: React.Element<unknown>): Promise<void>;
|
|
7
|
+
unmount(): Promise<void>;
|
|
8
|
+
debug(options?: DebugOptions): void
|
|
9
|
+
toJSON(): JsonElement | null;
|
|
10
|
+
container: TestInstance;
|
|
11
|
+
root: TestInstance | null;
|
|
12
|
+
};
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The `screen` object provides access to queries and utilities for the currently rendered UI.
|
|
16
|
+
|
|
17
|
+
This object is assigned after the `render` call and cleared after each test by calling [`cleanup`](./other-helpers.md#cleanup). If no `render` call has been made in a given test, then it holds a special object and throws a helpful error on each property and method access.
|
|
18
|
+
|
|
19
|
+
### `...queries`
|
|
20
|
+
|
|
21
|
+
The main feature of `screen` is its queries for finding elements in the view hierarchy.
|
|
22
|
+
|
|
23
|
+
See [Queries](./queries.md) for a complete list.
|
|
24
|
+
|
|
25
|
+
#### Example
|
|
26
|
+
|
|
27
|
+
```jsx
|
|
28
|
+
import { render, screen } from '@testing-library/react-native';
|
|
29
|
+
|
|
30
|
+
test('example', async () => {
|
|
31
|
+
await render(<MyComponent />);
|
|
32
|
+
const buttonStart = screen.getByRole('button', { name: 'start' });
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### `rerender`
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
function rerender(element: React.Element<unknown>): Promise<void>;
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Re-render the in-memory tree with a new root element. This simulates a React update render at the root. If the new element has the same type (and `key`) as the previous element, the tree will be updated; otherwise, it will re-mount a new tree, in both cases triggering the appropriate lifecycle events.
|
|
43
|
+
|
|
44
|
+
This method is async and uses async `act` internally to execute all pending React updates during updating. This works with async React features like `Suspense` boundaries and the `use()` hook.
|
|
45
|
+
|
|
46
|
+
```jsx
|
|
47
|
+
import { render, screen } from '@testing-library/react-native';
|
|
48
|
+
|
|
49
|
+
test('async rerender test', async () => {
|
|
50
|
+
await render(<MyComponent initialData="first" />);
|
|
51
|
+
|
|
52
|
+
await screen.rerender(<MyComponent initialData="updated" />);
|
|
53
|
+
expect(screen.getByText('updated')).toBeOnTheScreen();
|
|
54
|
+
});
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### `unmount`
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
function unmount(): Promise<void>;
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Unmount the in-memory tree, triggering the appropriate lifecycle events.
|
|
64
|
+
|
|
65
|
+
This method is async and uses async `act` internally to execute all pending React updates during unmounting. This works with async React features like `Suspense` boundaries and the `use()` hook.
|
|
66
|
+
|
|
67
|
+
> [!NOTE]
|
|
68
|
+
> Usually you should not need to call `unmount` as it is done automatically if your test runner supports `afterEach` hook (like Jest, mocha, Jasmine).
|
|
69
|
+
|
|
70
|
+
### `debug`
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
function debug(options?: { message?: string; mapProps?: MapPropsFunction }): void;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Pretty prints deeply rendered component passed to `render`.
|
|
77
|
+
|
|
78
|
+
#### `message` option
|
|
79
|
+
|
|
80
|
+
You can provide a message that will be printed on top.
|
|
81
|
+
|
|
82
|
+
```jsx
|
|
83
|
+
await render(<Component />);
|
|
84
|
+
screen.debug({ message: 'optional message' });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
logs optional message and colored JSX:
|
|
88
|
+
|
|
89
|
+
```jsx
|
|
90
|
+
optional message
|
|
91
|
+
|
|
92
|
+
<View
|
|
93
|
+
onPress={[Function bound fn]}
|
|
94
|
+
>
|
|
95
|
+
<Text>Press me</Text>
|
|
96
|
+
</View>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### `mapProps` option
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
function debug({ mapProps: (props) => ({}) });
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
You can use the `mapProps` option to transform the props that will be printed :
|
|
106
|
+
|
|
107
|
+
```jsx
|
|
108
|
+
await render(<View style={{ backgroundColor: 'red' }} />);
|
|
109
|
+
screen.debug({ mapProps: ({ style, ...props }) => ({ props }) });
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This will log the rendered JSX without the `style` props.
|
|
113
|
+
|
|
114
|
+
The `children` prop cannot be filtered out so the following will print all rendered components with all props but `children` filtered out.
|
|
115
|
+
|
|
116
|
+
This option can be used to target specific props when debugging a query (for instance, keeping only the `children` prop when debugging a `getByText` query).
|
|
117
|
+
|
|
118
|
+
You can also transform prop values so that they are more readable (e.g., flatten styles).
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import { StyleSheet } from 'react-native';
|
|
122
|
+
|
|
123
|
+
screen.debug({
|
|
124
|
+
mapProps: ({ style, ...props }) => ({ style: StyleSheet.flatten(style), ...props }),
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Or remove props that have little value when debugging tests, e.g. path prop for svgs
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
screen.debug({ mapProps: ({ path, ...props }) => ({ ...props }) });
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### `toJSON`
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
function toJSON(): JsonElement | null;
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Get the rendered component JSON representation, e.g. for snapshot testing.
|
|
141
|
+
|
|
142
|
+
### `container`
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
const container: TestInstance;
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Returns a pseudo-element container whose children are the elements you asked to render. This is the root container element from [Test Renderer](https://github.com/mdjastrzebski/test-renderer).
|
|
149
|
+
|
|
150
|
+
The `container` provides access to the entire rendered tree. Use it to query or manipulate the rendered output, similar to how `container` works in [React Testing Library](https://testing-library.com/docs/react-testing-library/other#container-1).
|
|
151
|
+
|
|
152
|
+
```jsx
|
|
153
|
+
import { render, screen } from '@testing-library/react-native';
|
|
154
|
+
|
|
155
|
+
test('example', async () => {
|
|
156
|
+
await render(<MyComponent />);
|
|
157
|
+
// container contains the entire rendered tree
|
|
158
|
+
const container = screen.container;
|
|
159
|
+
expect(container).toBeTruthy();
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### `root`
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
const root: TestInstance | null;
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Returns the rendered root [host element](../guides/testing-environment.md#host-and-composite-components), or `null` if nothing was rendered. This is the first child of the `container`, which represents the actual root element you rendered.
|
|
170
|
+
|
|
171
|
+
This API is useful for component tests where you need to access the root host view without using `*ByTestId` queries or similar methods.
|
|
172
|
+
|
|
173
|
+
> [!NOTE]
|
|
174
|
+
> In rare cases where your root element is a `React.Fragment` with multiple children, the `container` will have more than one child, and `root` will return only the first one. In such cases, use `container.children` to access all rendered elements.
|
|
175
|
+
|
|
176
|
+
```jsx
|
|
177
|
+
import { render, screen } from '@testing-library/react-native';
|
|
178
|
+
|
|
179
|
+
test('example', async () => {
|
|
180
|
+
await render(
|
|
181
|
+
<View testID="root-view">
|
|
182
|
+
<Text>Hello</Text>
|
|
183
|
+
</View>,
|
|
184
|
+
);
|
|
185
|
+
// root is the View element you rendered
|
|
186
|
+
expect(screen.root.props.testID).toBe('root-view');
|
|
187
|
+
});
|
|
188
|
+
```
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# User Event interactions
|
|
2
|
+
|
|
3
|
+
## Comparison with Fire Event API
|
|
4
|
+
|
|
5
|
+
Fire Event is our original event simulation API. It can invoke **any event handler** declared on **either host or composite elements**. Suppose the element does not have `onEventName` event handler for the passed `eventName` event, or the element is disabled. In that case, Fire Event will traverse up the component tree, looking for an event handler on both host and composite elements along the way. By default, it will **not pass any event data**, but the user might provide it in the last argument.
|
|
6
|
+
|
|
7
|
+
In contrast, User Event provides realistic event simulation for user interactions like `press` or `type`. Each interaction will trigger a **sequence of events** corresponding to React Native runtime behavior. These events will be invoked **only on host elements**, and **will automatically receive event data** corresponding to each event.
|
|
8
|
+
|
|
9
|
+
If User Event supports a given interaction, prefer it over the Fire Event counterpart. It makes tests more realistic and reliable. When User Event doesn't support the event or you need to invoke event handlers on composite elements, use Fire Event.
|
|
10
|
+
|
|
11
|
+
## `setup()`
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
userEvent.setup(options?: {
|
|
15
|
+
delay?: number;
|
|
16
|
+
advanceTimers?: (delay: number) => Promise<void> | void;
|
|
17
|
+
})
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Example
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
const user = userEvent.setup();
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Creates a User Event object instance, which can be used to trigger events.
|
|
27
|
+
|
|
28
|
+
### Options
|
|
29
|
+
|
|
30
|
+
- `delay` controls the default delay between subsequent events, e.g., keystrokes.
|
|
31
|
+
- `advanceTimers` is a time advancement utility function that should be used for fake timers. The default setup handles both real timers and Jest fake timers.
|
|
32
|
+
|
|
33
|
+
## `press()`
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
press(
|
|
37
|
+
instance: TestInstance,
|
|
38
|
+
): Promise<void>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Example
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const user = userEvent.setup();
|
|
45
|
+
await user.press(element);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Simulates a press on any pressable element, e.g. `Pressable`, `TouchableOpacity`, `Text`, `TextInput`, etc. Unlike `fireEvent.press()`, which only calls the `onPress` prop, this function simulates the entire press interaction by reproducing the event sequence emitted by React Native runtime. It triggers additional events like `pressIn` and `pressOut`.
|
|
49
|
+
|
|
50
|
+
This event will take a minimum of 130 ms to run due to the internal React Native logic. Consider using fake timers to speed up test execution for tests involving `press` and `longPress` interactions.
|
|
51
|
+
|
|
52
|
+
## `longPress()`
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
longPress(
|
|
56
|
+
instance: TestInstance,
|
|
57
|
+
options?: { duration?: number }
|
|
58
|
+
): Promise<void>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Example
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const user = userEvent.setup();
|
|
65
|
+
await user.longPress(element);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Simulates a long press user interaction. In React Native, the `longPress` event is emitted when the press duration exceeds the long press threshold (by default, 500 ms). In other aspects, this action behaves similarly to regular `press` action, e.g., by emitting `pressIn` and `pressOut` events. The press duration is customizable through the options, which is useful when using the `delayLongPress` prop.
|
|
69
|
+
|
|
70
|
+
This event will, by default, take 500 ms to run. Due to internal React Native logic, it will take at least 130 ms regardless of the duration option passed. Consider using fake timers to speed up test execution for tests involving `press` and `longPress` interactions.
|
|
71
|
+
|
|
72
|
+
### Options
|
|
73
|
+
|
|
74
|
+
- `duration` - duration of the press in milliseconds. The default value is 500 ms.
|
|
75
|
+
|
|
76
|
+
## `type()`
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
type(
|
|
80
|
+
instance: TestInstance,
|
|
81
|
+
text: string,
|
|
82
|
+
options?: {
|
|
83
|
+
skipPress?: boolean;
|
|
84
|
+
skipBlur?: boolean;
|
|
85
|
+
submitEditing?: boolean;
|
|
86
|
+
}
|
|
87
|
+
): Promise<void>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Example
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
const user = userEvent.setup();
|
|
94
|
+
await user.type(textInput, 'Hello world!');
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Simulates focusing on a `TextInput` element, typing `text` one character at a time, and leaving the element.
|
|
98
|
+
|
|
99
|
+
This function supports only host `TextInput` elements. Passing other element types will result in throwing an error.
|
|
100
|
+
|
|
101
|
+
> [!NOTE]
|
|
102
|
+
> This function will add text to the text already present in the text input (as specified by `value` or `defaultValue` props). To replace existing text, use [`clear()`](#clear) helper first.
|
|
103
|
+
|
|
104
|
+
### Options
|
|
105
|
+
|
|
106
|
+
- `skipPress` - if true, `pressIn` and `pressOut` events will not be triggered.
|
|
107
|
+
- `skipBlur` - if true, `endEditing` and `blur` events will not be triggered when typing is complete.
|
|
108
|
+
- `submitEditing` - if true, `submitEditing` event will be triggered after typing the text.
|
|
109
|
+
|
|
110
|
+
### Sequence of events
|
|
111
|
+
|
|
112
|
+
The sequence of events depends on the `multiline` prop and the passed options.
|
|
113
|
+
|
|
114
|
+
Events will not be emitted if the `editable` prop is set to `false`.
|
|
115
|
+
|
|
116
|
+
**Entering the element**:
|
|
117
|
+
|
|
118
|
+
- `pressIn` (optional)
|
|
119
|
+
- `focus`
|
|
120
|
+
- `pressOut` (optional)
|
|
121
|
+
|
|
122
|
+
The `pressIn` and `pressOut` events are sent by default but can be skipped by passing the `skipPress: true` option.
|
|
123
|
+
|
|
124
|
+
**Typing (for each character)**:
|
|
125
|
+
|
|
126
|
+
- `keyPress`
|
|
127
|
+
- `change`
|
|
128
|
+
- `changeText`
|
|
129
|
+
- `selectionChange`
|
|
130
|
+
- `contentSizeChange` (only multiline)
|
|
131
|
+
|
|
132
|
+
**Leaving the element**:
|
|
133
|
+
|
|
134
|
+
- `submitEditing` (optional)
|
|
135
|
+
- `endEditing`
|
|
136
|
+
- `blur`
|
|
137
|
+
|
|
138
|
+
The `submitEditing` event is skipped by default. It can sent by setting the `submitEditing: true` option.
|
|
139
|
+
The `endEditing` and `blur` events can be skipped by passing the `skipBlur: true` option.
|
|
140
|
+
|
|
141
|
+
## `clear()`
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
clear(
|
|
145
|
+
instance: TestInstance,
|
|
146
|
+
): Promise<void>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Example
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
const user = userEvent.setup();
|
|
153
|
+
await user.clear(textInput);
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Simulates clearing the content of a `TextInput` element.
|
|
157
|
+
|
|
158
|
+
This function supports only host `TextInput` elements. Passing other element types will result in throwing an error.
|
|
159
|
+
|
|
160
|
+
### Sequence of events
|
|
161
|
+
|
|
162
|
+
Events will not be emitted if the `editable` prop is set to `false`.
|
|
163
|
+
|
|
164
|
+
**Entering the element**:
|
|
165
|
+
|
|
166
|
+
- `focus`
|
|
167
|
+
|
|
168
|
+
**Selecting all content**:
|
|
169
|
+
|
|
170
|
+
- `selectionChange`
|
|
171
|
+
|
|
172
|
+
**Pressing backspace**:
|
|
173
|
+
|
|
174
|
+
- `keyPress`
|
|
175
|
+
- `change`
|
|
176
|
+
- `changeText`
|
|
177
|
+
- `selectionChange`
|
|
178
|
+
|
|
179
|
+
**Leaving the element**:
|
|
180
|
+
|
|
181
|
+
- `endEditing`
|
|
182
|
+
- `blur`
|
|
183
|
+
|
|
184
|
+
## `paste()`
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
paste(
|
|
188
|
+
instance: TestInstance,
|
|
189
|
+
text: string,
|
|
190
|
+
): Promise<void>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Example
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
const user = userEvent.setup();
|
|
197
|
+
await user.paste(textInput, 'Text to paste');
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Simulates pasting text into a `TextInput` element.
|
|
201
|
+
|
|
202
|
+
This function supports only host `TextInput` elements. Passing other element types will result in throwing an error.
|
|
203
|
+
|
|
204
|
+
### Sequence of events
|
|
205
|
+
|
|
206
|
+
Events will not be emitted if the `editable` prop is set to `false`.
|
|
207
|
+
|
|
208
|
+
**Entering the element**:
|
|
209
|
+
|
|
210
|
+
- `focus`
|
|
211
|
+
|
|
212
|
+
**Selecting all content**:
|
|
213
|
+
|
|
214
|
+
- `selectionChange`
|
|
215
|
+
|
|
216
|
+
**Pasting the text**:
|
|
217
|
+
|
|
218
|
+
- `change`
|
|
219
|
+
- `changeText`
|
|
220
|
+
- `selectionChange`
|
|
221
|
+
- `contentSizeChange` (only multiline)
|
|
222
|
+
|
|
223
|
+
**Leaving the element**:
|
|
224
|
+
|
|
225
|
+
- `endEditing`
|
|
226
|
+
- `blur`
|
|
227
|
+
|
|
228
|
+
## `scrollTo()` \
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
scrollTo(
|
|
232
|
+
instance: TestInstance,
|
|
233
|
+
options: {
|
|
234
|
+
y: number;
|
|
235
|
+
momentumY?: number;
|
|
236
|
+
contentSize?: { width: number; height: number };
|
|
237
|
+
layoutMeasurement?: { width: number; height: number };
|
|
238
|
+
} | {
|
|
239
|
+
x: number;
|
|
240
|
+
momentumX?: number;
|
|
241
|
+
contentSize?: { width: number; height: number };
|
|
242
|
+
layoutMeasurement?: { width: number; height: number };
|
|
243
|
+
}
|
|
244
|
+
): Promise<void>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Example
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
const user = userEvent.setup();
|
|
251
|
+
await user.scrollTo(scrollView, { y: 100, momentumY: 200 });
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Simulates scrolling a host `ScrollView` element.
|
|
255
|
+
|
|
256
|
+
This function supports only host `ScrollView` elements, passing other element types will result in an error. Note that `FlatList` is accepted as it renders to a host `ScrollView` element.
|
|
257
|
+
|
|
258
|
+
Scroll interaction should match the `ScrollView` element direction:
|
|
259
|
+
|
|
260
|
+
- for a vertical scroll view (default or `horizontal={false}`), you should pass only the `y` option (and optionally also `momentumY`).
|
|
261
|
+
- for a horizontal scroll view (`horizontal={true}`), you should pass only the `x` option (and optionally `momentumX`).
|
|
262
|
+
|
|
263
|
+
Each scroll interaction consists of a mandatory drag scroll part, which simulates the user dragging the scroll view with his finger (the `y` or `x` option). This may optionally be followed by a momentum scroll movement, which simulates the inertial movement of scroll view content after the user lifts his finger (`momentumY` or `momentumX` options).
|
|
264
|
+
|
|
265
|
+
### Options
|
|
266
|
+
|
|
267
|
+
- `y` - target vertical drag scroll offset
|
|
268
|
+
- `x` - target horizontal drag scroll offset
|
|
269
|
+
- `momentumY` - target vertical momentum scroll offset
|
|
270
|
+
- `momentumX` - target horizontal momentum scroll offset
|
|
271
|
+
- `contentSize` - passed to `ScrollView` events and enabling `FlatList` updates
|
|
272
|
+
- `layoutMeasurement` - passed to `ScrollView` events and enabling `FlatList` updates
|
|
273
|
+
|
|
274
|
+
User Event will generate several intermediate scroll steps to simulate user scroll interaction. You should not rely on exact number or values of these scrolls steps as they might be change in the future version.
|
|
275
|
+
|
|
276
|
+
This function will remember where the last scroll ended, so subsequent scroll interaction will starts from that position. The initial scroll position will be assumed to be `{ y: 0, x: 0 }`.
|
|
277
|
+
|
|
278
|
+
To simulate a `FlatList` (and other controls based on `VirtualizedList`) scrolling, you should pass the `contentSize` and `layoutMeasurement` options, which enable the underlying logic to update the currently visible window.
|
|
279
|
+
|
|
280
|
+
### Sequence of events
|
|
281
|
+
|
|
282
|
+
The sequence of events depends on whether the scroll includes an optional momentum scroll component.
|
|
283
|
+
|
|
284
|
+
**Drag scroll**:
|
|
285
|
+
|
|
286
|
+
- `contentSizeChange`
|
|
287
|
+
- `scrollBeginDrag`
|
|
288
|
+
- `scroll` (multiple events)
|
|
289
|
+
- `scrollEndDrag`
|
|
290
|
+
|
|
291
|
+
**Momentum scroll (optional)**:
|
|
292
|
+
|
|
293
|
+
- `momentumScrollBegin`
|
|
294
|
+
- `scroll` (multiple events)
|
|
295
|
+
- `momentumScrollEnd`
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Async Events
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
In RNTL v14, all tests are async since `render()`, `fireEvent()`, and other core APIs return Promises. Beyond the basic async APIs, there are additional async utilities for handling events that complete over time:
|
|
6
|
+
|
|
7
|
+
1. **Waiting for elements to appear**: Use `findBy*` queries when elements appear after some delay (e.g., after data fetching).
|
|
8
|
+
2. **Waiting for conditions**: Use `waitFor()` to wait for arbitrary conditions to be met.
|
|
9
|
+
3. **Waiting for elements to disappear**: Use `waitForElementToBeRemoved()` when elements should be removed after some action.
|
|
10
|
+
|
|
11
|
+
These utilities help you write reliable tests that properly handle timing in your application.
|
|
12
|
+
|
|
13
|
+
### Example
|
|
14
|
+
|
|
15
|
+
Consider a test for a user signing in with correct credentials:
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
test('User can sign in with correct credentials', async () => {
|
|
19
|
+
// Typical test setup
|
|
20
|
+
const user = userEvent.setup();
|
|
21
|
+
await render(<App />);
|
|
22
|
+
|
|
23
|
+
// No need to use async here, components are already rendered
|
|
24
|
+
expect(screen.getByRole('header', { name: 'Sign in to Hello World App!' })).toBeOnTheScreen();
|
|
25
|
+
|
|
26
|
+
// Using await as User Event requires it
|
|
27
|
+
await user.type(screen.getByLabelText('Username'), 'admin');
|
|
28
|
+
await user.type(screen.getByLabelText('Password'), 'admin1');
|
|
29
|
+
await user.press(screen.getByRole('button', { name: 'Sign In' }));
|
|
30
|
+
|
|
31
|
+
// Using await as sign in operation is asynchronous
|
|
32
|
+
expect(await screen.findByRole('header', { name: 'Welcome admin!' })).toBeOnTheScreen();
|
|
33
|
+
|
|
34
|
+
// Follow-up assertions do not need to be async, as we already waited for sign in operation to complete
|
|
35
|
+
expect(
|
|
36
|
+
screen.queryByRole('header', { name: 'Sign in to Hello World App' }),
|
|
37
|
+
).not.toBeOnTheScreen();
|
|
38
|
+
expect(screen.queryByLabelText('Username')).not.toBeOnTheScreen();
|
|
39
|
+
expect(screen.queryByLabelText('Password')).not.toBeOnTheScreen();
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Async utilities
|
|
44
|
+
|
|
45
|
+
There are several asynchronous utilities you might use in your tests.
|
|
46
|
+
|
|
47
|
+
### `findBy*` queries
|
|
48
|
+
|
|
49
|
+
The most common are the [`findBy*` queries](../api/queries.md#find-by). These are useful when waiting for a matching element to appear. They can be understood as a [`getBy*` queries](../api/queries.md#get-by) used in conjunction with a [`waitFor` function](../api/async-utilities.md#waitfor).
|
|
50
|
+
|
|
51
|
+
They accept the same predicates as `getBy*` queries like `findByRole`, `findByTest`, etc. They also have a multiple elements variant called [`findAllBy*`](../api/queries.md#find-all-by).
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
function findByRole: (
|
|
55
|
+
role: TextMatch,
|
|
56
|
+
queryOptions?: {
|
|
57
|
+
// Query specific options
|
|
58
|
+
}
|
|
59
|
+
waitForOptions?: {
|
|
60
|
+
timeout?: number;
|
|
61
|
+
interval?: number;
|
|
62
|
+
// ..
|
|
63
|
+
}
|
|
64
|
+
): Promise<TestInstance>;
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Each query has a default `timeout` value of 1000 ms and a default `interval` of 50 ms. Custom timeout and check intervals can be specified if needed, as shown below:
|
|
68
|
+
|
|
69
|
+
#### Example
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const button = await screen.findByRole(
|
|
73
|
+
'button',
|
|
74
|
+
{ name: 'Start' },
|
|
75
|
+
{ timeout: 1000, interval: 50 },
|
|
76
|
+
);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Alternatively, a default global `timeout` value can be set using the [`configure` function](../api/configuration.md#configure):
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
configure({ asyncUtilTimeout: timeout });
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### `waitFor` function
|
|
86
|
+
|
|
87
|
+
The `waitFor` function is another option, serving as a lower-level utility in more advanced cases.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
function waitFor<T>(
|
|
91
|
+
expectation: () => T,
|
|
92
|
+
options?: {
|
|
93
|
+
timeout: number;
|
|
94
|
+
interval: number;
|
|
95
|
+
},
|
|
96
|
+
): Promise<T>;
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
It accepts an `expectation` to be validated and repeats the check every defined interval until it no longer throws an error. Similarly to `findBy*` queries they accept `timeout` and `interval` options and have the same default values of 1000ms for timeout, and a checking interval of 50 ms.
|
|
100
|
+
|
|
101
|
+
#### Example
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
await waitFor(() => expect(mockAPI).toHaveBeenCalledTimes(1));
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
If you want to use it with `getBy*` queries, use the `findBy*` queries instead, as they essentially do the same, but offer better developer experience.
|
|
108
|
+
|
|
109
|
+
### `waitForElementToBeRemoved` function
|
|
110
|
+
|
|
111
|
+
A specialized function, [`waitForElementToBeRemoved`](../api/async-utilities.md#waitforelementtoberemoved), is used to verify that a matching element was present but has since been removed.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
function waitForElementToBeRemoved<T>(
|
|
115
|
+
expectation: () => T,
|
|
116
|
+
options?: {
|
|
117
|
+
timeout: number;
|
|
118
|
+
interval: number;
|
|
119
|
+
},
|
|
120
|
+
): Promise<T> {}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This function is, in a way, the negation of `waitFor` as it expects the initial expectation to be true (not throw an error), only to turn invalid (start throwing errors) on subsequent runs. It operates using the same `timeout` and `interval` parameters as `findBy*` queries and `waitFor`.
|
|
124
|
+
|
|
125
|
+
#### Example
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
await waitForElementToBeRemoved(() => getByText('Hello World'));
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Fake Timers
|
|
132
|
+
|
|
133
|
+
Asynchronous tests can take long to execute due to the delays introduced by asynchronous operations. To mitigate this, fake timers can be used. These are particularly useful when delays are mere waits, such as the 130 milliseconds wait introduced by the UserEvent `press()` event due to React Native runtime behavior or simulated 1000 wait in a API call mock. Fake timers allow for precise fast-forwarding through these wait periods.
|
|
134
|
+
|
|
135
|
+
Here are the basics of using [Jest fake timers](https://jestjs.io/docs/timer-mocks):
|
|
136
|
+
|
|
137
|
+
- Enable fake timers with: `jest.useFakeTimers()`
|
|
138
|
+
- Disable fake timers with: `jest.useRealTimers()`
|
|
139
|
+
- Advance fake timers forward with: `jest.advanceTimersByTime(interval)`
|
|
140
|
+
- Run **all timers** to completion with: `jest.runAllTimers()`
|
|
141
|
+
- Run **currently pending timers** to completion with: `jest.runOnlyPendingTimers()`
|
|
142
|
+
|
|
143
|
+
Be cautious when running all timers to completion as it might create an infinite loop if these timers schedule follow-up timers. In such cases, it's safer to use `jest.runOnlyPendingTimers()` to avoid ending up in an infinite loop of scheduled tasks.
|
|
144
|
+
|
|
145
|
+
You can use both built-in Jest fake timers, as well as [Sinon.JS fake timers](https://sinonjs.org/releases/latest/fake-timers/).
|
|
146
|
+
|
|
147
|
+
Note: you do not need to advance timers by hand when using User Event API, as it's automatically.
|