Skip to content

Commit ef09df7

Browse files
committed
also test fake timers
2nd . wait for + expect returned error thows on not a funciton switching timer types . not ready yet never true fake timers throws generic timeout error when promise rejects with falsy value until timeout
1 parent e1d5730 commit ef09df7

File tree

2 files changed

+156
-1
lines changed

2 files changed

+156
-1
lines changed

src/__tests__/wait-for.test.tsx

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,33 @@ import * as React from 'react';
22
import { Text, View } from 'react-native';
33

44
import { render, screen, waitFor } from '..';
5+
import { useTimerType } from '../test-utils/timers';
56

6-
test('waits for query', async () => {
7+
beforeEach(() => {
8+
jest.useRealTimers();
9+
});
10+
11+
test('waits for expect() assertion to pass', async () => {
12+
const mockFunction = jest.fn();
13+
14+
function AsyncComponent() {
15+
React.useEffect(() => {
16+
setTimeout(() => mockFunction(), 100);
17+
}, []);
18+
19+
return <View />;
20+
}
21+
22+
await render(<AsyncComponent />);
23+
await waitFor(() => expect(mockFunction).toHaveBeenCalled());
24+
expect(mockFunction).toHaveBeenCalledTimes(1);
25+
});
26+
27+
test.each([
28+
{ timerType: 'real' as const },
29+
{ timerType: 'fake' as const },
30+
{ timerType: 'fake-legacy' as const },
31+
])('waits for query with $timerType timers', async ({ timerType }) => {
732
function AsyncComponent() {
833
const [text, setText] = React.useState('Loading...');
934

@@ -14,7 +39,126 @@ test('waits for query', async () => {
1439
return <Text>{text}</Text>;
1540
}
1641

42+
useTimerType(timerType);
1743
await render(<AsyncComponent />);
1844
await waitFor(() => screen.getByText('Loaded'));
1945
expect(screen.getByText('Loaded')).toBeOnTheScreen();
2046
});
47+
48+
test('throws timeout error when condition never becomes true', async () => {
49+
function Component() {
50+
return <Text>Hello</Text>;
51+
}
52+
53+
await render(<Component />);
54+
await expect(waitFor(() => screen.getByText('Never appears'), { timeout: 100 })).rejects.toThrow(
55+
'Unable to find an element with text: Never appears',
56+
);
57+
});
58+
59+
test('uses custom error from onTimeout callback when timeout occurs', async () => {
60+
const customErrorMessage = 'Custom timeout error: Element never appeared';
61+
62+
await render(<View />);
63+
await expect(
64+
waitFor(() => screen.getByText('Never appears'), {
65+
timeout: 100,
66+
onTimeout: () => new Error(customErrorMessage),
67+
}),
68+
).rejects.toThrow(customErrorMessage);
69+
});
70+
71+
test('throws TypeError when expectation is not a function', async () => {
72+
await expect(waitFor(null as any)).rejects.toThrow(
73+
'Received `expectation` arg must be a function',
74+
);
75+
});
76+
77+
test('throws error when switching from real timers to fake timers during waitFor', async () => {
78+
await render(<View />);
79+
80+
const waitForPromise = waitFor(() => {
81+
// This will never pass, but we'll switch timers before timeout
82+
return screen.getByText('Never appears');
83+
});
84+
85+
// Switch to fake timers while waitFor is running
86+
jest.useFakeTimers();
87+
88+
await expect(waitForPromise).rejects.toThrow(
89+
'Changed from using real timers to fake timers while using waitFor',
90+
);
91+
});
92+
93+
test('throws error when switching from fake timers to real timers during waitFor', async () => {
94+
jest.useFakeTimers();
95+
await render(<View />);
96+
97+
const waitForPromise = waitFor(() => {
98+
// This will never pass, but we'll switch timers before timeout
99+
return screen.getByText('Never appears');
100+
});
101+
102+
// Switch to real timers while waitFor is running
103+
jest.useRealTimers();
104+
105+
await expect(waitForPromise).rejects.toThrow(
106+
'Changed from using fake timers to real timers while using waitFor',
107+
);
108+
});
109+
110+
test('converts non-Error thrown value to Error when timeout occurs', async () => {
111+
const errorMessage = 'Custom string error';
112+
113+
let caughtError: unknown;
114+
try {
115+
await waitFor(
116+
() => {
117+
throw errorMessage;
118+
},
119+
{ timeout: 50 },
120+
);
121+
} catch (error) {
122+
caughtError = error;
123+
}
124+
125+
expect(caughtError).toBeInstanceOf(Error);
126+
expect((caughtError as Error).message).toBe(errorMessage);
127+
});
128+
129+
test('continues waiting when expectation returns a promise that rejects', async () => {
130+
let attemptCount = 0;
131+
const maxAttempts = 3;
132+
133+
await waitFor(
134+
() => {
135+
attemptCount++;
136+
if (attemptCount < maxAttempts) {
137+
return Promise.reject(new Error('Not ready yet'));
138+
}
139+
return Promise.resolve('Success');
140+
},
141+
{ timeout: 1000 },
142+
);
143+
144+
expect(attemptCount).toBe(maxAttempts);
145+
});
146+
147+
test('throws timeout error with fake timers when condition never becomes true', async () => {
148+
jest.useFakeTimers();
149+
await render(<View />);
150+
151+
const waitForPromise = waitFor(() => screen.getByText('Never appears'), { timeout: 100 });
152+
153+
await jest.advanceTimersByTimeAsync(100);
154+
155+
await expect(waitForPromise).rejects.toThrow('Unable to find an element with text: Never appears');
156+
});
157+
158+
test('throws generic timeout error when promise rejects with falsy value until timeout', async () => {
159+
await expect(
160+
waitFor(() => Promise.reject(null), {
161+
timeout: 100,
162+
}),
163+
).rejects.toThrow('Timed out in waitFor.');
164+
});

src/test-utils/timers.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export type TimerType = 'real' | 'fake' | 'fake-legacy';
2+
3+
export function useTimerType(type: TimerType): void {
4+
if (type === 'fake-legacy') {
5+
jest.useFakeTimers({ legacyFakeTimers: true });
6+
} else if (type === 'fake') {
7+
jest.useFakeTimers({ legacyFakeTimers: false });
8+
} else {
9+
jest.useRealTimers();
10+
}
11+
}

0 commit comments

Comments
 (0)