Skip to content

Commit b09dd00

Browse files
authored
Merge pull request #432 from huchenme/pr/after-unmount
Feature: add addCleanup util function
2 parents 90613ee + 8d0fc82 commit b09dd00

File tree

6 files changed

+148
-6
lines changed

6 files changed

+148
-6
lines changed

.all-contributorsrc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,17 @@
183183
"contributions": [
184184
"test"
185185
]
186+
},
187+
{
188+
"login": "huchenme",
189+
"name": "Hu Chen",
190+
"avatar_url": "https://avatars3.githubusercontent.com/u/2078389?v=4",
191+
"profile": "https://huchen.dev/",
192+
"contributions": [
193+
"code",
194+
"doc",
195+
"example"
196+
]
186197
}
187198
],
188199
"commitConvention": "none"

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,13 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
164164
<td align="center"><a href="https://github.com/102"><img src="https://avatars1.githubusercontent.com/u/5839225?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Roman Gusev</b></sub></a><br /><a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=102" title="Documentation">📖</a></td>
165165
<td align="center"><a href="https://github.com/hemlok"><img src="https://avatars2.githubusercontent.com/u/9043345?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Adam Seckel</b></sub></a><br /><a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=hemlok" title="Code">💻</a></td>
166166
<td align="center"><a href="https://keiya01.github.io/portfolio"><img src="https://avatars1.githubusercontent.com/u/34934510?v=4?s=100" width="100px;" alt=""/><br /><sub><b>keiya sasaki</b></sub></a><br /><a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=keiya01" title="Tests">⚠️</a></td>
167+
<td align="center"><a href="https://huchen.dev/"><img src="https://avatars3.githubusercontent.com/u/2078389?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hu Chen</b></sub></a><br /><a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=huchenme" title="Code">💻</a> <a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=huchenme" title="Documentation">📖</a> <a href="#example-huchenme" title="Examples">💡</a></td>
167168
</tr>
168169
</table>
169170

170-
<!-- markdownlint-enable -->
171+
<!-- markdownlint-restore -->
171172
<!-- prettier-ignore-end -->
173+
172174
<!-- ALL-CONTRIBUTORS-LIST:END -->
173175

174176
This project follows the [all-contributors](https://allcontributors.org/) specification.

docs/api-reference.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ route: '/reference/api'
1010
- [`renderHook`](/reference/api#renderhook)
1111
- [`act`](/reference/api#act)
1212
- [`cleanup`](/reference/api#cleanup)
13+
- [`addCleanup`](/reference/api#addcleanup)
1314

1415
---
1516

@@ -108,7 +109,9 @@ This is the same [`act` function](https://reactjs.org/docs/test-utils.html#act)
108109
function cleanup: Promise<void>
109110
```
110111

111-
Unmounts any rendered hooks rendered with `renderHook`, ensuring all effects have been flushed.
112+
Unmounts any rendered hooks rendered with `renderHook`, ensuring all effects have been flushed. Any
113+
callbacks added with [`addCleanup`](<(/reference/api#addCleanup).>) will also be called when
114+
`cleanup` is run.
112115

113116
> Please note that this is done automatically if the testing framework you're using supports the
114117
> `afterEach` global (like Jest, mocha and Jasmine). If not, you will need to do manual cleanups
@@ -147,6 +150,35 @@ variable to `true` before importing `@testing-library/react-hooks` will also dis
147150
148151
---
149152
153+
## `addCleanup`
154+
155+
```js
156+
function addCleanup(callback: function(): void|Promise<void>): function(): void
157+
```
158+
159+
Add a callback to be called during [`cleanup`](/reference/api#cleanup), returning a function to
160+
remove the cleanup if is no longer required. Cleanups are called in reverse order to being added.
161+
This is usually only relevant when wanting a cleanup to run after the component has been unmounted.
162+
163+
If the provided callback is an `async` function or returns a promise, `cleanup` will wait for it to
164+
be resolved before moving onto the next cleanup callback.
165+
166+
> Please note that any cleanups added using `addCleanup` are removed after `cleanup` is called. For
167+
> cleanups that need to run with every test, it is advised to add them in a `beforeEach` block (or
168+
> equivalent for your test runner).
169+
170+
## `removeCleanup`
171+
172+
```js
173+
function removeCleanup(callback: function(): void|Promise<void>): void
174+
```
175+
176+
Removes a cleanup callback previously added with [`addCleanup`](/reference/api#addCleanup). Once
177+
removed, the provided callback will no longer execute as part of running
178+
[`cleanup`](/reference/api#cleanup).
179+
180+
---
181+
150182
## Async Utilities
151183

152184
### `waitForNextUpdate`

src/cleanup.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ let cleanupCallbacks = []
44

55
async function cleanup() {
66
await flushMicroTasks()
7-
cleanupCallbacks.forEach((cb) => cb())
7+
for (const callback of cleanupCallbacks) {
8+
await callback()
9+
}
810
cleanupCallbacks = []
911
}
1012

1113
function addCleanup(callback) {
12-
cleanupCallbacks.push(callback)
14+
cleanupCallbacks.unshift(callback)
15+
return () => removeCleanup(callback)
1316
}
1417

1518
function removeCleanup(callback) {

src/pure.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,4 @@ function renderHook(callback, { initialProps, wrapper } = {}) {
9999
}
100100
}
101101

102-
export { renderHook, cleanup, act }
102+
export { renderHook, cleanup, addCleanup, removeCleanup, act }

test/cleanup.test.js

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useEffect } from 'react'
2-
import { renderHook, cleanup } from 'src'
2+
import { renderHook, cleanup, addCleanup, removeCleanup } from 'src/pure'
33

44
describe('cleanup tests', () => {
55
test('should flush effects on cleanup', async () => {
@@ -38,4 +38,98 @@ describe('cleanup tests', () => {
3838
expect(cleanupCalled[1]).toBe(true)
3939
expect(cleanupCalled[2]).toBe(true)
4040
})
41+
42+
test('should call cleanups in reverse order', async () => {
43+
let callSequence = []
44+
addCleanup(() => {
45+
callSequence.push('cleanup')
46+
})
47+
addCleanup(() => {
48+
callSequence.push('another cleanup')
49+
})
50+
const hookWithCleanup = () => {
51+
useEffect(() => {
52+
return () => {
53+
callSequence.push('unmount')
54+
}
55+
})
56+
}
57+
renderHook(() => hookWithCleanup())
58+
59+
await cleanup()
60+
61+
expect(callSequence).toEqual(['unmount', 'another cleanup', 'cleanup'])
62+
})
63+
64+
test('should wait for async cleanup', async () => {
65+
let callSequence = []
66+
addCleanup(() => {
67+
callSequence.push('cleanup')
68+
})
69+
addCleanup(async () => {
70+
await new Promise((resolve) => setTimeout(resolve, 10))
71+
callSequence.push('another cleanup')
72+
})
73+
const hookWithCleanup = () => {
74+
useEffect(() => {
75+
return () => {
76+
callSequence.push('unmount')
77+
}
78+
})
79+
}
80+
renderHook(() => hookWithCleanup())
81+
82+
await cleanup()
83+
84+
expect(callSequence).toEqual(['unmount', 'another cleanup', 'cleanup'])
85+
})
86+
87+
test('should remove cleanup using removeCleanup', async () => {
88+
let callSequence = []
89+
addCleanup(() => {
90+
callSequence.push('cleanup')
91+
})
92+
const anotherCleanup = () => {
93+
callSequence.push('another cleanup')
94+
}
95+
addCleanup(anotherCleanup)
96+
const hookWithCleanup = () => {
97+
useEffect(() => {
98+
return () => {
99+
callSequence.push('unmount')
100+
}
101+
})
102+
}
103+
renderHook(() => hookWithCleanup())
104+
105+
removeCleanup(anotherCleanup)
106+
107+
await cleanup()
108+
109+
expect(callSequence).toEqual(['unmount', 'cleanup'])
110+
})
111+
112+
test('should remove cleanup using returned handler', async () => {
113+
let callSequence = []
114+
addCleanup(() => {
115+
callSequence.push('cleanup')
116+
})
117+
const remove = addCleanup(() => {
118+
callSequence.push('another cleanup')
119+
})
120+
const hookWithCleanup = () => {
121+
useEffect(() => {
122+
return () => {
123+
callSequence.push('unmount')
124+
}
125+
})
126+
}
127+
renderHook(() => hookWithCleanup())
128+
129+
remove()
130+
131+
await cleanup()
132+
133+
expect(callSequence).toEqual(['unmount', 'cleanup'])
134+
})
41135
})

0 commit comments

Comments
 (0)