Skip to content

Commit 6d6414a

Browse files
committed
Review waitUntil + coverage
1 parent 25edfab commit 6d6414a

2 files changed

Lines changed: 256 additions & 4 deletions

File tree

src/util/waitUntil.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import { DEFAULT_OPTIONS } from '../constants.js'
22

33
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
44

5+
export type ConditionResult = { success: boolean; results: boolean[] }
6+
57
/**
68
* wait for expectation to succeed
79
* @param condition function
810
* @param isNot https://jestjs.io/docs/expect#thisisnot
911
* @param options wait, interval, etc
1012
*/
1113
export const waitUntil = async (
12-
condition: () => Promise<boolean | { success: boolean; results: boolean[] }>,
14+
condition: () => Promise<boolean | ConditionResult>,
1315
isNot = false,
1416
{ wait = DEFAULT_OPTIONS.wait, interval = DEFAULT_OPTIONS.interval } = {}
1517
): Promise<boolean> => {
@@ -25,8 +27,8 @@ export const waitUntil = async (
2527
}
2628

2729
const start = Date.now()
28-
let error: Error | undefined
29-
let result: boolean | { success: boolean; results: boolean[] } = false
30+
let error: unknown
31+
let result: boolean | ConditionResult = false
3032

3133
while (Date.now() - start <= wait) {
3234
try {
@@ -36,7 +38,7 @@ export const waitUntil = async (
3638
break
3739
}
3840
} catch (err) {
39-
error = err instanceof Error ? err : new Error(String(err))
41+
error = err
4042
}
4143
await sleep(interval)
4244
}

test/util/waitUntil.test.ts

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import { describe, test, expect, vi } from 'vitest'
2+
import type { ConditionResult } from '../../src/util/waitUntil'
3+
import { waitUntil } from '../../src/util/waitUntil'
4+
5+
describe(waitUntil, () => {
6+
describe('given single result', () => {
7+
describe('given isNot is false', () => {
8+
const isNot = false
9+
10+
test('should return true when condition is met immediately', async () => {
11+
const condition = vi.fn().mockResolvedValue(true)
12+
13+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 100 })
14+
15+
expect(result).toBe(true)
16+
})
17+
18+
test('should return false when condition is not met and wait is 0', async () => {
19+
const condition = vi.fn().mockResolvedValue(false)
20+
21+
const result = await waitUntil(condition, isNot, { wait: 0 })
22+
23+
expect(result).toBe(false)
24+
})
25+
26+
test('should return true when condition is met within wait time', async () => {
27+
const condition = vi.fn().mockResolvedValueOnce(false).mockResolvedValueOnce(false).mockResolvedValueOnce(true)
28+
29+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 })
30+
31+
expect(result).toBe(true)
32+
expect(condition).toHaveBeenCalledTimes(3)
33+
})
34+
35+
test('should return false when condition is not met within wait time', async () => {
36+
const condition = vi.fn().mockResolvedValue(false)
37+
38+
const result = await waitUntil(condition, isNot, { wait: 200, interval: 50 })
39+
40+
expect(result).toBe(false)
41+
})
42+
43+
test('should throw error if condition throws and never recovers', async () => {
44+
const condition = vi.fn().mockRejectedValue(new Error('Test error'))
45+
46+
await expect(waitUntil(condition, isNot, { wait: 200, interval: 50 })).rejects.toThrow('Test error')
47+
})
48+
49+
test('should recover from errors if condition eventually succeeds', async () => {
50+
const condition = vi.fn()
51+
.mockRejectedValueOnce(new Error('Not ready yet'))
52+
.mockRejectedValueOnce(new Error('Not ready yet'))
53+
.mockResolvedValueOnce(true)
54+
55+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 })
56+
57+
expect(result).toBe(true)
58+
expect(condition).toHaveBeenCalledTimes(3)
59+
})
60+
61+
test('should use default options when not provided', async () => {
62+
const condition = vi.fn().mockResolvedValue(true)
63+
64+
const result = await waitUntil(condition)
65+
66+
expect(result).toBe(true)
67+
})
68+
})
69+
70+
describe('given isNot is true', () => {
71+
const isNot = true
72+
73+
test('should handle isNot flag correctly when condition is true', async () => {
74+
const condition = vi.fn().mockResolvedValue(true)
75+
76+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 100 })
77+
78+
expect(result).toBe(false)
79+
})
80+
81+
test('should handle isNot flag correctly when condition is true and wait is 0', async () => {
82+
const condition = vi.fn().mockResolvedValue(true)
83+
84+
const result = await waitUntil(condition, isNot, { wait: 0 })
85+
86+
expect(result).toBe(false)
87+
})
88+
89+
test('should handle isNot flag correctly when condition is false', async () => {
90+
const condition = vi.fn().mockResolvedValue(false)
91+
92+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 100 })
93+
94+
expect(result).toBe(true)
95+
})
96+
97+
test('should handle isNot flag correctly when condition is false and wait is 0', async () => {
98+
const condition = vi.fn().mockResolvedValue(false)
99+
100+
const result = await waitUntil(condition, isNot, { wait: 0 })
101+
102+
expect(result).toBe(true)
103+
})
104+
105+
test('should throw error if condition throws and never recovers', async () => {
106+
const condition = vi.fn().mockRejectedValue(new Error('Test error'))
107+
108+
await expect(waitUntil(condition, isNot, { wait: 200, interval: 50 })).rejects.toThrow('Test error')
109+
})
110+
111+
test('should do all the attempts to succeed even with isNot true', async () => {
112+
const condition = vi.fn()
113+
.mockRejectedValueOnce(new Error('Not ready yet'))
114+
.mockRejectedValueOnce(new Error('Not ready yet'))
115+
.mockResolvedValueOnce(true)
116+
117+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 })
118+
119+
expect(result).toBe(false)
120+
expect(condition).toHaveBeenCalledTimes(3)
121+
})
122+
})
123+
})
124+
125+
describe('given multiple results', () => {
126+
let conditionResult: ConditionResult
127+
128+
describe('given isNot is false', () => {
129+
const isNot = false
130+
131+
test('should return false when condition returns empty array', async () => {
132+
conditionResult = { success: false, results: [] }
133+
const condition = vi.fn().mockResolvedValue(conditionResult)
134+
135+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 100 })
136+
137+
expect(result).toBe(false)
138+
})
139+
140+
test('should return true when condition is met immediately', async () => {
141+
conditionResult = { success: true, results: [true] }
142+
const condition = vi.fn().mockResolvedValue(conditionResult)
143+
144+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 100 })
145+
146+
expect(result).toBe(true)
147+
})
148+
149+
test('should return false when condition is not met and wait is 0', async () => {
150+
conditionResult = { success: false, results: [false] }
151+
const condition = vi.fn().mockResolvedValue(conditionResult)
152+
153+
const result = await waitUntil(condition, isNot, { wait: 0 })
154+
155+
expect(result).toBe(false)
156+
})
157+
158+
test('should return true when condition is met within wait time', async () => {
159+
const condition = vi.fn().mockResolvedValueOnce({ success: false, results: [false] }).mockResolvedValueOnce({ success: false, results: [false] }).mockResolvedValueOnce({ success: true, results: [true] })
160+
161+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 })
162+
163+
expect(result).toBe(true)
164+
expect(condition).toHaveBeenCalledTimes(3)
165+
})
166+
167+
test('should return false when condition is not met within wait time', async () => {
168+
conditionResult = { success: false, results: [false] }
169+
const condition = vi.fn().mockResolvedValue(conditionResult)
170+
171+
const result = await waitUntil(condition, isNot, { wait: 200, interval: 50 })
172+
173+
expect(result).toBe(false)
174+
})
175+
176+
test('should recover from errors if condition eventually succeeds', async () => {
177+
const condition = vi.fn()
178+
.mockRejectedValueOnce(new Error('Not ready yet'))
179+
.mockRejectedValueOnce(new Error('Not ready yet'))
180+
.mockResolvedValueOnce({ success: true, results: [true] })
181+
182+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 })
183+
184+
expect(result).toBe(true)
185+
expect(condition).toHaveBeenCalledTimes(3)
186+
})
187+
})
188+
189+
describe('given isNot is true', () => {
190+
const isNot = true
191+
192+
test('should return false when condition returns empty array', async () => {
193+
conditionResult = { success: false, results: [] }
194+
const condition = vi.fn().mockResolvedValue(conditionResult)
195+
196+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 100 })
197+
198+
expect(result).toBe(false)
199+
})
200+
201+
test('should handle isNot flag correctly when condition is true', async () => {
202+
conditionResult = { success: true, results: [true] }
203+
const condition = vi.fn().mockResolvedValue(conditionResult)
204+
205+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 100 })
206+
207+
expect(result).toBe(false)
208+
})
209+
210+
test('should handle isNot flag correctly when condition is true and wait is 0', async () => {
211+
conditionResult = { success: true, results: [true] }
212+
const condition = vi.fn().mockResolvedValue(conditionResult)
213+
214+
const result = await waitUntil(condition, isNot, { wait: 0 })
215+
216+
expect(result).toBe(false)
217+
})
218+
219+
test('should handle isNot flag correctly when condition is false', async () => {
220+
conditionResult = { success: false, results: [false] }
221+
const condition = vi.fn().mockResolvedValue(conditionResult)
222+
223+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 100 })
224+
225+
expect(result).toBe(true)
226+
})
227+
228+
test('should handle isNot flag correctly when condition is false and wait is 0', async () => {
229+
conditionResult = { success: false, results: [false] }
230+
const condition = vi.fn().mockResolvedValue(conditionResult)
231+
232+
const result = await waitUntil(condition, isNot, { wait: 0 })
233+
234+
expect(result).toBe(true)
235+
})
236+
237+
test('should do all the attempts to succeed even with isNot true', async () => {
238+
const condition = vi.fn()
239+
.mockRejectedValueOnce(new Error('Not ready yet'))
240+
.mockRejectedValueOnce(new Error('Not ready yet'))
241+
.mockResolvedValueOnce({ success: true, results: [true] })
242+
243+
const result = await waitUntil(condition, isNot, { wait: 1000, interval: 50 })
244+
245+
expect(result).toBe(false)
246+
expect(condition).toHaveBeenCalledTimes(3)
247+
})
248+
})
249+
})
250+
})

0 commit comments

Comments
 (0)