0
# Async Utilities
1
2
Promise-based utilities for handling asynchronous DOM changes and waiting for elements to appear or disappear. Essential for testing applications with dynamic content, async operations, and time-dependent UI changes.
3
4
## Capabilities
5
6
### Wait For
7
8
Wait for asynchronous operations to complete by polling a callback function until it succeeds or times out.
9
10
```typescript { .api }
11
function waitFor<T>(
12
callback: () => Promise<T> | T,
13
options?: waitForOptions,
14
): Promise<T>;
15
16
interface waitForOptions {
17
/** Container to observe for mutations (defaults to document) */
18
container?: HTMLElement;
19
/** Maximum time to wait in milliseconds (defaults to 1000ms) */
20
timeout?: number;
21
/** How often to check the callback in milliseconds (defaults to 50ms) */
22
interval?: number;
23
/** Custom error handler for timeout cases */
24
onTimeout?: (error: Error) => Error;
25
/** MutationObserver configuration for DOM change detection */
26
mutationObserverOptions?: MutationObserverInit;
27
}
28
```
29
30
**Usage Examples:**
31
32
```typescript
33
import { waitFor, getByText } from "@testing-library/dom";
34
35
// Wait for element to appear after async operation
36
await waitFor(() => {
37
expect(getByText(container, "Loading complete")).toBeInTheDocument();
38
});
39
40
// Wait for element with custom timeout
41
await waitFor(
42
() => getByText(container, "Async content"),
43
{ timeout: 5000 }
44
);
45
46
// Wait for element to disappear
47
await waitFor(() => {
48
expect(queryByText(container, "Loading...")).not.toBeInTheDocument();
49
});
50
51
// Wait with custom interval for faster checking
52
await waitFor(
53
() => expect(getByTestId(container, "status")).toHaveTextContent("Ready"),
54
{ interval: 10 }
55
);
56
57
// Wait for specific container changes
58
const specificContainer = document.querySelector('.dynamic-section');
59
await waitFor(
60
() => getByText(specificContainer, "Updated content"),
61
{ container: specificContainer }
62
);
63
64
// Custom timeout error handling
65
await waitFor(
66
() => getByText(container, "Rare element"),
67
{
68
timeout: 3000,
69
onTimeout: (error) => {
70
return new Error(`Custom timeout message: ${error.message}`);
71
}
72
}
73
);
74
75
// Wait for multiple conditions
76
await waitFor(() => {
77
const button = getByRole(container, "button", { name: "Submit" });
78
const input = getByLabelText(container, "Email");
79
expect(button).not.toBeDisabled();
80
expect(input).toHaveValue("user@example.com");
81
});
82
83
// Wait for API response to update UI
84
async function submitForm() {
85
fireEvent.click(getByRole(container, "button", { name: "Submit" }));
86
87
await waitFor(() => {
88
expect(getByText(container, "Form submitted successfully")).toBeInTheDocument();
89
});
90
}
91
92
// Wait for animation to complete
93
await waitFor(() => {
94
const modal = getByRole(container, "dialog");
95
expect(modal).toHaveClass("fade-in-complete");
96
}, { timeout: 2000 });
97
```
98
99
### Wait For Element To Be Removed
100
101
Wait for element(s) to be removed from the DOM. Useful for testing element removal, modal closing, or content cleanup.
102
103
```typescript { .api }
104
function waitForElementToBeRemoved<T>(
105
callback: T | (() => T),
106
options?: waitForOptions,
107
): Promise<void>;
108
```
109
110
**Usage Examples:**
111
112
```typescript
113
import { waitForElementToBeRemoved, getByText, queryByText } from "@testing-library/dom";
114
115
// Wait for loading spinner to disappear
116
const loadingSpinner = getByText(container, "Loading...");
117
await waitForElementToBeRemoved(loadingSpinner);
118
119
// Wait using callback function (safer for elements that might not exist)
120
await waitForElementToBeRemoved(() => queryByText(container, "Loading..."));
121
122
// Wait for modal to be removed
123
const modal = getByRole(container, "dialog");
124
fireEvent.click(getByRole(modal, "button", { name: "Close" }));
125
await waitForElementToBeRemoved(modal);
126
127
// Wait for multiple elements to be removed
128
const notifications = getAllByRole(container, "alert");
129
fireEvent.click(getByRole(container, "button", { name: "Clear all" }));
130
await waitForElementToBeRemoved(notifications);
131
132
// Wait with custom timeout
133
await waitForElementToBeRemoved(
134
() => queryByTestId(container, "temporary-message"),
135
{ timeout: 5000 }
136
);
137
138
// Wait for element in specific container
139
const sidebar = document.querySelector('.sidebar');
140
await waitForElementToBeRemoved(
141
() => queryByText(sidebar, "Temporary item"),
142
{ container: sidebar }
143
);
144
145
// Combine with user interactions
146
async function closeModal() {
147
const modal = getByRole(container, "dialog");
148
const closeButton = getByRole(modal, "button", { name: "Close" });
149
150
fireEvent.click(closeButton);
151
152
// Wait for modal to be completely removed
153
await waitForElementToBeRemoved(modal);
154
}
155
156
// Wait for toast notification to auto-dismiss
157
async function showToast() {
158
fireEvent.click(getByRole(container, "button", { name: "Show notification" }));
159
160
// Verify toast appears
161
const toast = getByRole(container, "alert");
162
expect(toast).toBeInTheDocument();
163
164
// Wait for auto-dismiss after 3 seconds
165
await waitForElementToBeRemoved(toast, { timeout: 4000 });
166
}
167
```
168
169
## Advanced Usage Patterns
170
171
### Waiting for Network Requests
172
173
```typescript
174
// Wait for data to load after API call
175
async function loadUserData(userId: string) {
176
fireEvent.click(getByRole(container, "button", { name: `Load user ${userId}` }));
177
178
// Wait for loading state to appear
179
await waitFor(() => {
180
expect(getByText(container, "Loading user data...")).toBeInTheDocument();
181
});
182
183
// Wait for loading to complete and data to appear
184
await waitFor(() => {
185
expect(getByText(container, `User: ${userId}`)).toBeInTheDocument();
186
}, { timeout: 5000 });
187
188
// Ensure loading indicator is gone
189
await waitForElementToBeRemoved(() => queryByText(container, "Loading user data..."));
190
}
191
```
192
193
### Waiting for Form Validation
194
195
```typescript
196
async function testFormValidation() {
197
const emailInput = getByLabelText(container, "Email");
198
const submitButton = getByRole(container, "button", { name: "Submit" });
199
200
// Enter invalid email
201
fireEvent.change(emailInput, { target: { value: "invalid-email" } });
202
fireEvent.click(submitButton);
203
204
// Wait for validation error to appear
205
await waitFor(() => {
206
expect(getByText(container, "Please enter a valid email")).toBeInTheDocument();
207
});
208
209
// Enter valid email
210
fireEvent.change(emailInput, { target: { value: "user@example.com" } });
211
212
// Wait for error to disappear
213
await waitForElementToBeRemoved(() => queryByText(container, "Please enter a valid email"));
214
}
215
```
216
217
### Waiting with MutationObserver Options
218
219
```typescript
220
// Wait with specific DOM observation settings
221
await waitFor(
222
() => expect(getByTestId(container, "dynamic-list")).toHaveClass("loaded"),
223
{
224
mutationObserverOptions: {
225
attributes: true,
226
attributeFilter: ['class'],
227
subtree: true,
228
}
229
}
230
);
231
```
232
233
## Error Handling
234
235
Both functions will throw descriptive errors when timeouts occur:
236
237
```typescript
238
try {
239
await waitFor(() => {
240
getByText(container, "Element that never appears");
241
}, { timeout: 1000 });
242
} catch (error) {
243
// Error includes information about what was being waited for
244
console.log(error.message); // Includes debugging information
245
}
246
247
try {
248
const nonExistentElement = getByText(container, "Does not exist");
249
await waitForElementToBeRemoved(nonExistentElement);
250
} catch (error) {
251
// Handle case where element doesn't exist initially
252
console.log("Element was not found to begin with");
253
}
254
```
255
256
## Types
257
258
```typescript { .api }
259
interface waitForOptions {
260
container?: HTMLElement;
261
timeout?: number;
262
interval?: number;
263
onTimeout?: (error: Error) => Error;
264
mutationObserverOptions?: MutationObserverInit;
265
}
266
267
// waitFor return type is inferred from callback
268
function waitFor<T>(
269
callback: () => Promise<T> | T,
270
options?: waitForOptions,
271
): Promise<T>;
272
273
// waitForElementToBeRemoved always returns Promise<void>
274
function waitForElementToBeRemoved<T>(
275
callback: T | (() => T),
276
options?: waitForOptions,
277
): Promise<void>;
278
```