Skip to content

Commit d5219d2

Browse files
thymikeeEsemesek
authored andcommitted
feat: add waitForElement (#20)
* feat: add waitForElement * add docs * update docs
1 parent 2541277 commit d5219d2

File tree

5 files changed

+147
-2
lines changed

5 files changed

+147
-2
lines changed

Diff for: README.md

+26
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,32 @@ const { getByTestId } = render(
254254
fireEvent.scroll(getByTestId('scroll-view'), eventData);
255255
```
256256

257+
## `waitForElement`
258+
259+
Defined as:
260+
261+
```jsx
262+
function waitForExpect<T: ReactTestInstance>(
263+
expectation: () => T,
264+
timeout: number = 4500,
265+
interval: number = 50
266+
): Promise<T> {}
267+
```
268+
269+
Wait for non-deterministic periods of time until your element appears or times out. `waitForExpect` periodically calls `expectation` every `interval` milliseconds to determine whether the element appeared or not.
270+
271+
```jsx
272+
import { render, waitForElement } from 'react-testing-library';
273+
274+
test('waiting for an Banana to be ready', async () => {
275+
const { getByText } = render(<Banana />);
276+
277+
await waitForElement(() => getByText('Banana ready'));
278+
});
279+
```
280+
281+
If you're using Jest's [Timer Mocks](https://jestjs.io/docs/en/timer-mocks#docsNav), remember not to use `async/await` syntax as it will stall your tests.
282+
257283
## `debug`
258284

259285
Log prettified shallowly rendered component or test instance (just like snapshot) to stdout.

Diff for: src/__tests__/fireEvent.test.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import {
77
ScrollView,
88
TextInput,
99
} from '../__mocks__/reactNativeMock';
10-
import fireEvent from '../fireEvent';
11-
import { render } from '..';
10+
import { render, fireEvent } from '..';
1211

1312
const OnPressComponent = ({ onPress }) => (
1413
<View>

Diff for: src/__tests__/waitForElement.test.js

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
// @flow
2+
/* eslint-disable react/no-multi-comp */
3+
import React from 'react';
4+
import { View, Text, TouchableOpacity } from '../__mocks__/reactNativeMock';
5+
import { render, fireEvent, waitForElement } from '..';
6+
7+
class Banana extends React.Component<*, *> {
8+
changeFresh = () => {
9+
this.props.onChangeFresh();
10+
};
11+
12+
render() {
13+
return (
14+
<View>
15+
{this.props.fresh && <Text testID="fresh">Fresh</Text>}
16+
<TouchableOpacity onPress={this.changeFresh} type="primary">
17+
Change freshness!
18+
</TouchableOpacity>
19+
</View>
20+
);
21+
}
22+
}
23+
24+
class BananaContainer extends React.Component<*, *> {
25+
state = { fresh: false };
26+
27+
onChangeFresh = async () => {
28+
await new Promise(resolve => setTimeout(resolve, 300));
29+
this.setState({ fresh: true });
30+
};
31+
32+
render() {
33+
return (
34+
<Banana onChangeFresh={this.onChangeFresh} fresh={this.state.fresh} />
35+
);
36+
}
37+
}
38+
39+
test('waits for element until it stops throwing', async () => {
40+
const { getByTestId, getByName, queryByTestId } = render(<BananaContainer />);
41+
42+
fireEvent.press(getByName('TouchableOpacity'));
43+
44+
expect(queryByTestId('fresh')).toBeNull();
45+
46+
const freshBananaText = await waitForElement(() => getByTestId('fresh'));
47+
48+
expect(freshBananaText.props.children).toBe('Fresh');
49+
});
50+
51+
test('waits for element until timeout is met', async () => {
52+
const { getByTestId, getByName } = render(<BananaContainer />);
53+
54+
fireEvent.press(getByName('TouchableOpacity'));
55+
56+
await expect(
57+
waitForElement(() => getByTestId('fresh'), 100)
58+
).rejects.toThrow();
59+
});
60+
61+
test('waits for element with custom interval', async () => {
62+
const mockFn = jest.fn(() => {
63+
throw Error('test');
64+
});
65+
66+
try {
67+
await waitForElement(() => mockFn(), 400, 200);
68+
} catch (e) {
69+
// suppress
70+
}
71+
72+
expect(mockFn).toBeCalledTimes(3);
73+
});
74+
75+
test('works with fake timers', async () => {
76+
jest.useFakeTimers();
77+
78+
const mockFn = jest.fn(() => {
79+
throw Error('test');
80+
});
81+
82+
try {
83+
waitForElement(() => mockFn(), 400, 200);
84+
} catch (e) {
85+
// suppress
86+
}
87+
jest.runTimersToTime(400);
88+
89+
expect(mockFn).toBeCalledTimes(3);
90+
91+
jest.useRealTimers();
92+
});

Diff for: src/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import shallow from './shallow';
44
import flushMicrotasksQueue from './flushMicrotasksQueue';
55
import debug from './debug';
66
import fireEvent from './fireEvent';
7+
import waitForElement from './waitForElement';
78

89
export { render };
910
export { shallow };
1011
export { flushMicrotasksQueue };
1112
export { debug };
1213
export { fireEvent };
14+
export { waitForElement };

Diff for: src/waitForElement.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// @flow
2+
export default function waitForExpect<T: ReactTestInstance>(
3+
expectation: () => T,
4+
timeout: number = 4500,
5+
interval: number = 50
6+
) {
7+
const startTime = Date.now();
8+
return new Promise<T>((resolve, reject) => {
9+
const rejectOrRerun = error => {
10+
if (Date.now() - startTime >= timeout) {
11+
reject(error);
12+
return;
13+
}
14+
setTimeout(runExpectation, interval);
15+
};
16+
function runExpectation() {
17+
try {
18+
const result = expectation();
19+
resolve(result);
20+
} catch (error) {
21+
rejectOrRerun(error);
22+
}
23+
}
24+
setTimeout(runExpectation, 0);
25+
});
26+
}

0 commit comments

Comments
 (0)