Skip to content

Commit 67ac9bd

Browse files
committed
Merge remote-tracking branch 'upstream/master' into use-react-test-renderer
# Conflicts: # src/index.js
2 parents bfa2bed + 7e26040 commit 67ac9bd

File tree

4 files changed

+103
-20
lines changed

4 files changed

+103
-20
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-hooks-testing-library",
3-
"version": "0.4.0",
3+
"version": "0.4.1",
44
"description": "Simple component wrapper for testing React hooks",
55
"main": "lib/index.js",
66
"typings": "typings/index.d.ts",
@@ -38,7 +38,7 @@
3838
"@babel/preset-env": "7.4.3",
3939
"@babel/preset-react": "7.0.0",
4040
"@types/react": "16.8.13",
41-
"all-contributors-cli": "6.2.0",
41+
"all-contributors-cli": "6.3.0",
4242
"babel-eslint": "10.0.1",
4343
"babel-plugin-module-resolver": "3.2.0",
4444
"codecov": "3.3.0",

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 { create, act } from 'react-test-renderer'
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,31 +52,38 @@ 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, wrapper } = {}) {
44-
const { result, updateResult, addResolver } = resultContainer()
72+
const { result, setValue, setError, addResolver } = resultContainer()
4573
const hookProps = { current: initialProps }
4674

4775
const wrapUiIfNeeded = (innerElement) =>
4876
wrapper ? React.createElement(wrapper, null, innerElement) : innerElement
4977

5078
const toRender = () =>
5179
wrapUiIfNeeded(
52-
<TestHook callback={callback} hookProps={hookProps.current}>
53-
{updateResult}
54-
</TestHook>
80+
<ErrorBoundary onError={setError}>
81+
<Suspense fallback={<Fallback />}>
82+
<TestHook callback={callback} hookProps={hookProps.current}>
83+
{setValue}
84+
</TestHook>
85+
</Suspense>
86+
</ErrorBoundary>
5587
)
5688

5789
let testRenderer

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)