Skip to content

Commit c7a2e97

Browse files
authored
fix(types): fix cleanup return type to match async implementation
* fix(types): fix `cleanup` return type to match `async` implementation * test: refactor tests to reduce duplication for different renderers * test: refactor `hydratedServerRenderer` and added comment for clarity about it
1 parent eff2ca6 commit c7a2e97

File tree

95 files changed

+1525
-3674
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+1525
-3674
lines changed

Diff for: jest.config.js

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const { jest: jestConfig } = require('kcd-scripts/config')
2+
module.exports = Object.assign(jestConfig, {
3+
setupFiles: ['<rootDir>/src/__tests__/utils/runForRenderers.ts']
4+
})

Diff for: src/__tests__/asyncHook.test.ts

+258
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
import { useState, useRef, useEffect } from 'react'
2+
3+
describe('async hook tests', () => {
4+
const useSequence = (values: string[], intervalMs = 50) => {
5+
const [first, ...otherValues] = values
6+
const [value, setValue] = useState(() => first)
7+
const index = useRef(0)
8+
9+
useEffect(() => {
10+
const interval = setInterval(() => {
11+
setValue(otherValues[index.current++])
12+
if (index.current >= otherValues.length) {
13+
clearInterval(interval)
14+
}
15+
}, intervalMs)
16+
return () => {
17+
clearInterval(interval)
18+
}
19+
// eslint-disable-next-line react-hooks/exhaustive-deps
20+
}, otherValues)
21+
22+
return value
23+
}
24+
25+
runForRenderers(['default', 'dom', 'native', 'server/hydrated'], ({ renderHook }) => {
26+
test('should wait for next update', async () => {
27+
const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second']))
28+
29+
expect(result.current).toBe('first')
30+
31+
await waitForNextUpdate()
32+
33+
expect(result.current).toBe('second')
34+
})
35+
36+
test('should wait for multiple updates', async () => {
37+
const { result, waitForNextUpdate } = renderHook(() =>
38+
useSequence(['first', 'second', 'third'])
39+
)
40+
41+
expect(result.current).toBe('first')
42+
43+
await waitForNextUpdate()
44+
45+
expect(result.current).toBe('second')
46+
47+
await waitForNextUpdate()
48+
49+
expect(result.current).toBe('third')
50+
})
51+
52+
test('should reject if timeout exceeded when waiting for next update', async () => {
53+
const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second']))
54+
55+
expect(result.current).toBe('first')
56+
57+
await expect(waitForNextUpdate({ timeout: 10 })).rejects.toThrow(
58+
Error('Timed out in waitForNextUpdate after 10ms.')
59+
)
60+
})
61+
62+
test('should not reject when waiting for next update if timeout has been disabled', async () => {
63+
const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'], 1100))
64+
65+
expect(result.current).toBe('first')
66+
67+
await waitForNextUpdate({ timeout: false })
68+
69+
expect(result.current).toBe('second')
70+
})
71+
72+
test('should wait for expectation to pass', async () => {
73+
const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third']))
74+
75+
expect(result.current).toBe('first')
76+
77+
let complete = false
78+
await waitFor(() => {
79+
expect(result.current).toBe('third')
80+
complete = true
81+
})
82+
expect(complete).toBe(true)
83+
})
84+
85+
test('should wait for arbitrary expectation to pass', async () => {
86+
const { waitFor } = renderHook(() => null)
87+
88+
let actual = 0
89+
const expected = 1
90+
91+
setTimeout(() => {
92+
actual = expected
93+
}, 200)
94+
95+
let complete = false
96+
await waitFor(() => {
97+
expect(actual).toBe(expected)
98+
complete = true
99+
})
100+
101+
expect(complete).toBe(true)
102+
})
103+
104+
test('should not hang if expectation is already passing', async () => {
105+
const { result, waitFor } = renderHook(() => useSequence(['first', 'second']))
106+
107+
expect(result.current).toBe('first')
108+
109+
let complete = false
110+
await waitFor(() => {
111+
expect(result.current).toBe('first')
112+
complete = true
113+
})
114+
expect(complete).toBe(true)
115+
})
116+
117+
test('should wait for truthy value', async () => {
118+
const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third']))
119+
120+
expect(result.current).toBe('first')
121+
122+
await waitFor(() => result.current === 'third')
123+
124+
expect(result.current).toBe('third')
125+
})
126+
127+
test('should wait for arbitrary truthy value', async () => {
128+
const { waitFor } = renderHook(() => null)
129+
130+
let actual = 0
131+
const expected = 1
132+
133+
setTimeout(() => {
134+
actual = expected
135+
}, 200)
136+
137+
await waitFor(() => actual === 1)
138+
139+
expect(actual).toBe(expected)
140+
})
141+
142+
test('should reject if timeout exceeded when waiting for expectation to pass', async () => {
143+
const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third']))
144+
145+
expect(result.current).toBe('first')
146+
147+
await expect(
148+
waitFor(
149+
() => {
150+
expect(result.current).toBe('third')
151+
},
152+
{ timeout: 75 }
153+
)
154+
).rejects.toThrow(Error('Timed out in waitFor after 75ms.'))
155+
})
156+
157+
test('should not reject when waiting for expectation to pass if timeout has been disabled', async () => {
158+
const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'], 550))
159+
160+
expect(result.current).toBe('first')
161+
162+
await waitFor(
163+
() => {
164+
expect(result.current).toBe('third')
165+
},
166+
{ timeout: false }
167+
)
168+
169+
expect(result.current).toBe('third')
170+
})
171+
172+
test('should check on interval when waiting for expectation to pass', async () => {
173+
const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third']))
174+
175+
let checks = 0
176+
177+
await waitFor(
178+
() => {
179+
checks++
180+
return result.current === 'third'
181+
},
182+
{ interval: 100 }
183+
)
184+
185+
expect(checks).toBe(3)
186+
})
187+
188+
test('should wait for value to change', async () => {
189+
const { result, waitForValueToChange } = renderHook(() =>
190+
useSequence(['first', 'second', 'third'])
191+
)
192+
193+
expect(result.current).toBe('first')
194+
195+
await waitForValueToChange(() => result.current === 'third')
196+
197+
expect(result.current).toBe('third')
198+
})
199+
200+
test('should wait for arbitrary value to change', async () => {
201+
const { waitForValueToChange } = renderHook(() => null)
202+
203+
let actual = 0
204+
const expected = 1
205+
206+
setTimeout(() => {
207+
actual = expected
208+
}, 200)
209+
210+
await waitForValueToChange(() => actual)
211+
212+
expect(actual).toBe(expected)
213+
})
214+
215+
test('should reject if timeout exceeded when waiting for value to change', async () => {
216+
const { result, waitForValueToChange } = renderHook(() =>
217+
useSequence(['first', 'second', 'third'])
218+
)
219+
220+
expect(result.current).toBe('first')
221+
222+
await expect(
223+
waitForValueToChange(() => result.current === 'third', {
224+
timeout: 75
225+
})
226+
).rejects.toThrow(Error('Timed out in waitForValueToChange after 75ms.'))
227+
})
228+
229+
test('should not reject when waiting for value to change if timeout is disabled', async () => {
230+
const { result, waitForValueToChange } = renderHook(() =>
231+
useSequence(['first', 'second', 'third'], 550)
232+
)
233+
234+
expect(result.current).toBe('first')
235+
236+
await waitForValueToChange(() => result.current === 'third', {
237+
timeout: false
238+
})
239+
240+
expect(result.current).toBe('third')
241+
})
242+
243+
test('should reject if selector throws error', async () => {
244+
const { result, waitForValueToChange } = renderHook(() => useSequence(['first', 'second']))
245+
246+
expect(result.current).toBe('first')
247+
248+
await expect(
249+
waitForValueToChange(() => {
250+
if (result.current === 'second') {
251+
throw new Error('Something Unexpected')
252+
}
253+
return result.current
254+
})
255+
).rejects.toThrow(Error('Something Unexpected'))
256+
})
257+
})
258+
})

Diff for: src/__tests__/autoCleanup.disabled.test.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useEffect } from 'react'
2+
3+
// This verifies that if RHTL_SKIP_AUTO_CLEANUP is set
4+
// then we DON'T auto-wire up the afterEach for folks
5+
describe('skip auto cleanup (disabled) tests', () => {
6+
process.env.RHTL_SKIP_AUTO_CLEANUP = 'true'
7+
8+
runForRenderers(['default', 'dom', 'native', 'server/hydrated'], ({ renderHook }) => {
9+
let cleanupCalled = false
10+
11+
test('first', () => {
12+
const useHookWithCleanup = () => {
13+
useEffect(() => {
14+
return () => {
15+
cleanupCalled = true
16+
}
17+
})
18+
}
19+
renderHook(() => useHookWithCleanup())
20+
})
21+
22+
test('second', () => {
23+
expect(cleanupCalled).toBe(false)
24+
})
25+
})
26+
})

Diff for: src/__tests__/autoCleanup.noAfterEach.test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useEffect } from 'react'
2+
3+
// This verifies that if afterEach is unavailable
4+
// then we DON'T auto-wire up the afterEach for folks
5+
describe('skip auto cleanup (no afterEach) tests', () => {
6+
// @ts-expect-error Turning off AfterEach -- ignore Jest LifeCycle Type
7+
// eslint-disable-next-line no-global-assign
8+
afterEach = false
9+
10+
runForRenderers(['default', 'dom', 'native', 'server/hydrated'], ({ renderHook }) => {
11+
let cleanupCalled = false
12+
13+
test('first', () => {
14+
const useHookWithCleanup = () => {
15+
useEffect(() => {
16+
return () => {
17+
cleanupCalled = true
18+
}
19+
})
20+
}
21+
renderHook(() => useHookWithCleanup())
22+
})
23+
24+
test('second', () => {
25+
expect(cleanupCalled).toBe(false)
26+
})
27+
})
28+
})

Diff for: src/__tests__/autoCleanup.noProcessEnv.test.ts

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { useEffect } from 'react'
2+
3+
// This verifies that if process.env is unavailable
4+
// then we still auto-wire up the afterEach for folks
5+
describe('auto cleanup (no process.env) tests', () => {
6+
process.env = {
7+
...process.env,
8+
get RHTL_SKIP_AUTO_CLEANUP(): string | undefined {
9+
throw new Error('expected')
10+
}
11+
}
12+
13+
runForRenderers(['default', 'dom', 'native', 'server/hydrated'], ({ renderHook }) => {
14+
let cleanupCalled = false
15+
16+
test('first', () => {
17+
const useHookWithCleanup = () => {
18+
useEffect(() => {
19+
return () => {
20+
cleanupCalled = true
21+
}
22+
})
23+
}
24+
renderHook(() => useHookWithCleanup())
25+
})
26+
27+
test('second', () => {
28+
expect(cleanupCalled).toBe(true)
29+
})
30+
})
31+
})

Diff for: src/__tests__/autoCleanup.pure.test.ts

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { useEffect } from 'react'
2+
3+
// This verifies that if pure imports are used
4+
// then we DON'T auto-wire up the afterEach for folks
5+
describe('skip auto cleanup (pure) tests', () => {
6+
runForRenderers(
7+
['default/pure', 'dom/pure', 'native/pure', 'server/hydrated/pure'],
8+
({ renderHook }) => {
9+
let cleanupCalled = false
10+
11+
test('first', () => {
12+
const useHookWithCleanup = () => {
13+
useEffect(() => {
14+
return () => {
15+
cleanupCalled = true
16+
}
17+
})
18+
}
19+
renderHook(() => useHookWithCleanup())
20+
})
21+
22+
test('second', () => {
23+
expect(cleanupCalled).toBe(false)
24+
})
25+
}
26+
)
27+
})

0 commit comments

Comments
 (0)