Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit cfb0345

Browse files
authored
Merge pull request #35 from mpeyper/suspense-support
Add Suspense and ErrorBoundary to better support suspending from hooks Fixes #27
2 parents 77c3cbf + f5914f4 commit cfb0345

File tree

2 files changed

+97
-14
lines changed

2 files changed

+97
-14
lines changed

‎src/index.js

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,37 @@
1-
import React from 'react'
1+
import React,{Suspense} from 'react'
22
import { render, cleanup, act } from 'react-testing-library'
33

44
function TestHook({ callback, hookProps, children }) {
5-
try {
6-
children(callback(hookProps))
7-
} catch (e) {
8-
children(undefined, e)
5+
children(callback(hookProps))
6+
return null
7+
}
8+
9+
class ErrorBoundary extends React.Component {
10+
constructor(props) {
11+
super(props)
12+
this.state = { hasError: false }
13+
}
14+
15+
static getDerivedStateFromError() {
16+
return { hasError: true }
917
}
18+
19+
componentDidCatch(error) {
20+
this.props.onError(error)
21+
}
22+
23+
componentDidUpdate(prevProps) {
24+
if (this.props != prevProps && this.state.hasError) {
25+
this.setState({ hasError: false })
26+
}
27+
}
28+
29+
render() {
30+
return !this.state.hasError && this.props.children
31+
}
32+
}
33+
34+
function Fallback() {
1035
return null
1136
}
1237

@@ -27,27 +52,34 @@ function resultContainer() {
2752
}
2853
}
2954

55+
const updateResult = (val, err) => {
56+
value = val
57+
error = err
58+
resolvers.splice(0, resolvers.length).forEach((resolve) => resolve())
59+
}
60+
3061
return {
3162
result,
3263
addResolver: (resolver) => {
3364
resolvers.push(resolver)
3465
},
35-
updateResult: (val, err) => {
36-
value = val
37-
error = err
38-
resolvers.splice(0, resolvers.length).forEach((resolve) => resolve())
39-
}
66+
setValue: (val) => updateResult(val),
67+
setError: (err) => updateResult(undefined, err)
4068
}
4169
}
4270

4371
function renderHook(callback, { initialProps, ...options } = {}) {
44-
const { result, updateResult, addResolver } = resultContainer()
72+
const { result, setValue, setError, addResolver } = resultContainer()
4573
const hookProps = { current: initialProps }
4674

4775
const toRender = () => (
48-
<TestHook callback={callback} hookProps={hookProps.current}>
49-
{updateResult}
50-
</TestHook>
76+
<ErrorBoundary onError={setError}>
77+
<Suspense fallback={<Fallback />}>
78+
<TestHook callback={callback} hookProps={hookProps.current}>
79+
{setValue}
80+
</TestHook>
81+
</Suspense>
82+
</ErrorBoundary>
5183
)
5284

5385
const { unmount, rerender: rerenderComponent } = render(toRender(), options)

‎test/suspenseHook.test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { renderHook, cleanup } from 'src'
2+
3+
describe('suspense hook tests', () => {
4+
const cache = {}
5+
const fetchName = (isSuccessful) => {
6+
if (!cache.value) {
7+
cache.value = new Promise((resolve, reject) => {
8+
setTimeout(() => {
9+
if (isSuccessful) {
10+
resolve('Bob')
11+
} else {
12+
reject(new Error('Failed to fetch name'))
13+
}
14+
}, 50)
15+
})
16+
.then((value) => (cache.value = value))
17+
.catch((e) => (cache.value = e))
18+
}
19+
return cache.value
20+
}
21+
22+
const useFetchName = (isSuccessful = true) => {
23+
const name = fetchName(isSuccessful)
24+
if (typeof name.then === 'function' || name instanceof Error) {
25+
throw name
26+
}
27+
return name
28+
}
29+
30+
beforeEach(() => {
31+
delete cache.value
32+
})
33+
34+
afterEach(cleanup)
35+
36+
test('should allow rendering to be suspended', async () => {
37+
const { result, waitForNextUpdate } = renderHook(() => useFetchName(true))
38+
39+
await waitForNextUpdate()
40+
41+
expect(result.current).toBe('Bob')
42+
})
43+
44+
test('should set error if suspense promise rejects', async () => {
45+
const { result, waitForNextUpdate } = renderHook(() => useFetchName(false))
46+
47+
await waitForNextUpdate()
48+
49+
expect(result.error).toEqual(new Error('Failed to fetch name'))
50+
})
51+
})

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /