diff --git a/src/index.js b/src/index.js
index f1c396de..aa6417cc 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,12 +1,37 @@
-import React from 'react'
+import React, { Suspense } from 'react'
import { render, cleanup, act } from 'react-testing-library'
function TestHook({ callback, hookProps, children }) {
- try {
- children(callback(hookProps))
- } catch (e) {
- children(undefined, e)
+ children(callback(hookProps))
+ return null
+}
+
+class ErrorBoundary extends React.Component {
+ constructor(props) {
+ super(props)
+ this.state = { hasError: false }
+ }
+
+ static getDerivedStateFromError() {
+ return { hasError: true }
}
+
+ componentDidCatch(error) {
+ this.props.onError(error)
+ }
+
+ componentDidUpdate(prevProps) {
+ if (this.props != prevProps && this.state.hasError) {
+ this.setState({ hasError: false })
+ }
+ }
+
+ render() {
+ return !this.state.hasError && this.props.children
+ }
+}
+
+function Fallback() {
return null
}
@@ -27,27 +52,34 @@ function resultContainer() {
}
}
+ const updateResult = (val, err) => {
+ value = val
+ error = err
+ resolvers.splice(0, resolvers.length).forEach((resolve) => resolve())
+ }
+
return {
result,
addResolver: (resolver) => {
resolvers.push(resolver)
},
- updateResult: (val, err) => {
- value = val
- error = err
- resolvers.splice(0, resolvers.length).forEach((resolve) => resolve())
- }
+ setValue: (val) => updateResult(val),
+ setError: (err) => updateResult(undefined, err)
}
}
function renderHook(callback, { initialProps, ...options } = {}) {
- const { result, updateResult, addResolver } = resultContainer()
+ const { result, setValue, setError, addResolver } = resultContainer()
const hookProps = { current: initialProps }
const toRender = () => (
-
- {updateResult}
-
+
+ }>
+
+ {setValue}
+
+
+
)
const { unmount, rerender: rerenderComponent } = render(toRender(), options)
diff --git a/test/suspenseHook.test.js b/test/suspenseHook.test.js
new file mode 100644
index 00000000..f613664f
--- /dev/null
+++ b/test/suspenseHook.test.js
@@ -0,0 +1,51 @@
+import { renderHook, cleanup } from 'src'
+
+describe('suspense hook tests', () => {
+ const cache = {}
+ const fetchName = (isSuccessful) => {
+ if (!cache.value) {
+ cache.value = new Promise((resolve, reject) => {
+ setTimeout(() => {
+ if (isSuccessful) {
+ resolve('Bob')
+ } else {
+ reject(new Error('Failed to fetch name'))
+ }
+ }, 50)
+ })
+ .then((value) => (cache.value = value))
+ .catch((e) => (cache.value = e))
+ }
+ return cache.value
+ }
+
+ const useFetchName = (isSuccessful = true) => {
+ const name = fetchName(isSuccessful)
+ if (typeof name.then === 'function' || name instanceof Error) {
+ throw name
+ }
+ return name
+ }
+
+ beforeEach(() => {
+ delete cache.value
+ })
+
+ afterEach(cleanup)
+
+ test('should allow rendering to be suspended', async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useFetchName(true))
+
+ await waitForNextUpdate()
+
+ expect(result.current).toBe('Bob')
+ })
+
+ test('should set error if suspense promise rejects', async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useFetchName(false))
+
+ await waitForNextUpdate()
+
+ expect(result.error).toEqual(new Error('Failed to fetch name'))
+ })
+})