Skip to content

Commit 70ca5ee

Browse files
committed
migrate old tests
1 parent ef09df7 commit 70ca5ee

File tree

1 file changed

+255
-2
lines changed

1 file changed

+255
-2
lines changed

src/__tests__/wait-for.test.tsx

Lines changed: 255 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react';
2-
import { Text, View } from 'react-native';
2+
import { Pressable, Text, TouchableOpacity, View } from 'react-native';
33

4-
import { render, screen, waitFor } from '..';
4+
import { configure, fireEvent, render, screen, waitFor } from '..';
55
import { useTimerType } from '../test-utils/timers';
66

77
beforeEach(() => {
@@ -162,3 +162,256 @@ test('throws generic timeout error when promise rejects with falsy value until t
162162
}),
163163
).rejects.toThrow('Timed out in waitFor.');
164164
});
165+
166+
test('waits for element with custom interval', async () => {
167+
const mockFn = jest.fn(() => {
168+
throw Error('test');
169+
});
170+
171+
try {
172+
await waitFor(() => mockFn(), { timeout: 400, interval: 200 });
173+
} catch {
174+
// suppress
175+
}
176+
177+
expect(mockFn).toHaveBeenCalledTimes(2);
178+
});
179+
180+
test('waitFor defaults to asyncUtilTimeout config option', async () => {
181+
class BananaContainer extends React.Component<object, any> {
182+
state = { fresh: false };
183+
184+
onChangeFresh = async () => {
185+
await new Promise((resolve) => setTimeout(resolve, 300));
186+
this.setState({ fresh: true });
187+
};
188+
189+
render() {
190+
return (
191+
<View>
192+
{this.state.fresh && <Text>Fresh</Text>}
193+
<TouchableOpacity onPress={this.onChangeFresh}>
194+
<Text>Change freshness!</Text>
195+
</TouchableOpacity>
196+
</View>
197+
);
198+
}
199+
}
200+
201+
configure({ asyncUtilTimeout: 100 });
202+
await render(<BananaContainer />);
203+
204+
fireEvent.press(screen.getByText('Change freshness!'));
205+
206+
expect(screen.queryByText('Fresh')).toBeNull();
207+
208+
await expect(waitFor(() => screen.getByText('Fresh'))).rejects.toThrow();
209+
210+
// Async action ends after 300ms and we only waited 100ms, so we need to wait
211+
// for the remaining async actions to finish
212+
await waitFor(() => screen.getByText('Fresh'), { timeout: 1000 });
213+
});
214+
215+
test('waitFor timeout option takes precedence over asyncUtilTimeout config option', async () => {
216+
class BananaContainer extends React.Component<object, any> {
217+
state = { fresh: false };
218+
219+
onChangeFresh = async () => {
220+
await new Promise((resolve) => setTimeout(resolve, 300));
221+
this.setState({ fresh: true });
222+
};
223+
224+
render() {
225+
return (
226+
<View>
227+
{this.state.fresh && <Text>Fresh</Text>}
228+
<TouchableOpacity onPress={this.onChangeFresh}>
229+
<Text>Change freshness!</Text>
230+
</TouchableOpacity>
231+
</View>
232+
);
233+
}
234+
}
235+
236+
configure({ asyncUtilTimeout: 2000 });
237+
await render(<BananaContainer />);
238+
239+
fireEvent.press(screen.getByText('Change freshness!'));
240+
241+
expect(screen.queryByText('Fresh')).toBeNull();
242+
243+
await expect(waitFor(() => screen.getByText('Fresh'), { timeout: 100 })).rejects.toThrow();
244+
245+
// Async action ends after 300ms and we only waited 100ms, so we need to wait
246+
// for the remaining async actions to finish
247+
await waitFor(() => screen.getByText('Fresh'));
248+
});
249+
250+
test('waits for async event with fireEvent', async () => {
251+
const Comp = ({ onPress }: { onPress: () => void }) => {
252+
const [state, setState] = React.useState(false);
253+
254+
React.useEffect(() => {
255+
if (state) {
256+
onPress();
257+
}
258+
}, [state, onPress]);
259+
260+
return (
261+
<Pressable
262+
onPress={async () => {
263+
await Promise.resolve();
264+
setState(true);
265+
}}
266+
>
267+
<Text>Trigger</Text>
268+
</Pressable>
269+
);
270+
};
271+
272+
const spy = jest.fn();
273+
await render(<Comp onPress={spy} />);
274+
275+
await fireEvent.press(screen.getByText('Trigger'));
276+
277+
await waitFor(() => {
278+
expect(spy).toHaveBeenCalled();
279+
});
280+
});
281+
282+
test.each([
283+
[false, false],
284+
[true, false],
285+
[true, true],
286+
])(
287+
'flushes scheduled updates before returning (fakeTimers = %s, legacyFakeTimers = %s)',
288+
async (fakeTimers, legacyFakeTimers) => {
289+
if (fakeTimers) {
290+
jest.useFakeTimers({ legacyFakeTimers });
291+
}
292+
293+
function Apple({ onPress }: { onPress: (color: string) => void }) {
294+
const [color, setColor] = React.useState('green');
295+
const [syncedColor, setSyncedColor] = React.useState(color);
296+
297+
// On mount, set the color to "red" in a promise microtask
298+
React.useEffect(() => {
299+
// eslint-disable-next-line @typescript-eslint/no-floating-promises, promise/catch-or-return, promise/prefer-await-to-then
300+
Promise.resolve('red').then((c) => setColor(c));
301+
}, []);
302+
303+
// Sync the `color` state to `syncedColor` state, but with a delay caused by the effect
304+
React.useEffect(() => {
305+
setSyncedColor(color);
306+
}, [color]);
307+
308+
return (
309+
<View testID="root">
310+
<Text>{color}</Text>
311+
<Pressable onPress={() => onPress(syncedColor)}>
312+
<Text>Trigger</Text>
313+
</Pressable>
314+
</View>
315+
);
316+
}
317+
318+
const onPress = jest.fn();
319+
await render(<Apple onPress={onPress} />);
320+
321+
// Required: this `waitFor` will succeed on first check, because the "root" view is there
322+
// since the initial mount.
323+
await waitFor(() => screen.getByTestId('root'));
324+
325+
// This `waitFor` will also succeed on first check, because the promise that sets the
326+
// `color` state to "red" resolves right after the previous `await waitFor` statement.
327+
await waitFor(() => screen.getByText('red'));
328+
329+
// Check that the `onPress` callback is called with the already-updated value of `syncedColor`.
330+
await fireEvent.press(screen.getByText('Trigger'));
331+
expect(onPress).toHaveBeenCalledWith('red');
332+
},
333+
);
334+
335+
test.each([true, false])(
336+
'it should not depend on real time when using fake timers (legacyFakeTimers = %s)',
337+
async (legacyFakeTimers) => {
338+
jest.useFakeTimers({ legacyFakeTimers });
339+
const WAIT_FOR_INTERVAL = 20;
340+
const WAIT_FOR_TIMEOUT = WAIT_FOR_INTERVAL * 5;
341+
342+
const blockThread = (timeToBlockThread: number, legacyFakeTimers: boolean) => {
343+
jest.useRealTimers();
344+
const end = Date.now() + timeToBlockThread;
345+
346+
while (Date.now() < end) {
347+
// do nothing
348+
}
349+
350+
jest.useFakeTimers({ legacyFakeTimers });
351+
};
352+
353+
const mockErrorFn = jest.fn(() => {
354+
// Wait 2 times interval so that check time is longer than interval
355+
blockThread(WAIT_FOR_INTERVAL * 2, legacyFakeTimers);
356+
throw new Error('test');
357+
});
358+
359+
await expect(
360+
async () =>
361+
await waitFor(mockErrorFn, {
362+
timeout: WAIT_FOR_TIMEOUT,
363+
interval: WAIT_FOR_INTERVAL,
364+
}),
365+
).rejects.toThrow();
366+
367+
// Verify that the `waitFor` callback has been called the expected number of times
368+
// (timeout / interval + 1), so it confirms that the real duration of callback did not
369+
// cause the real clock timeout when running using fake timers.
370+
expect(mockErrorFn).toHaveBeenCalledTimes(WAIT_FOR_TIMEOUT / WAIT_FOR_INTERVAL + 1);
371+
},
372+
);
373+
374+
test.each([false, true])(
375+
'waits for assertion until timeout is met with fake timers and interval (legacyFakeTimers = %s)',
376+
async (legacyFakeTimers) => {
377+
jest.useFakeTimers({ legacyFakeTimers });
378+
379+
const mockFn = jest.fn(() => {
380+
throw Error('test');
381+
});
382+
383+
try {
384+
await waitFor(() => mockFn(), { timeout: 400, interval: 200 });
385+
} catch {
386+
// suppress
387+
}
388+
389+
expect(mockFn).toHaveBeenCalledTimes(3);
390+
},
391+
);
392+
393+
test.each([false, true])(
394+
'waits for assertion until timeout is met with fake timers, interval, and onTimeout (legacyFakeTimers = %s)',
395+
async (legacyFakeTimers) => {
396+
jest.useFakeTimers({ legacyFakeTimers });
397+
398+
const mockErrorFn = jest.fn(() => {
399+
throw Error('test');
400+
});
401+
402+
const mockHandleFn = jest.fn((e) => e);
403+
404+
try {
405+
await waitFor(() => mockErrorFn(), {
406+
timeout: 400,
407+
interval: 200,
408+
onTimeout: mockHandleFn,
409+
});
410+
} catch {
411+
// suppress
412+
}
413+
414+
expect(mockErrorFn).toHaveBeenCalledTimes(3);
415+
expect(mockHandleFn).toHaveBeenCalledTimes(1);
416+
},
417+
);

0 commit comments

Comments
 (0)