Skip to content

Commit 1f14077

Browse files
author
Joschua Schneider
committed
Add new tests for typescript implementation
1 parent d06bf63 commit 1f14077

File tree

2 files changed

+232
-0
lines changed

2 files changed

+232
-0
lines changed

src/__tests__/ErrorBoundary.spec.tsx

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import React from "react"
2+
import { render, screen } from "@testing-library/react"
3+
4+
import { ErrorBoundary } from "../ErrorBoundary"
5+
6+
/**
7+
* Mock console and suppress errors.
8+
*/
9+
let consoleSpy: jest.SpyInstance | null = null
10+
11+
beforeEach(() => {
12+
consoleSpy = jest.spyOn(console, "error").mockImplementation()
13+
})
14+
15+
afterEach(() => {
16+
consoleSpy?.mockRestore()
17+
})
18+
19+
/**
20+
* Helper Components
21+
*/
22+
23+
const ExplosionErrorMessage = "💥"
24+
const Explosion = () => {
25+
throw new Error(ExplosionErrorMessage)
26+
}
27+
28+
/**
29+
* Test ErrorBoundary component
30+
*/
31+
32+
test("[No Error] Renders children from props.children", async () => {
33+
const onDidCatch = jest.fn()
34+
35+
render(
36+
<ErrorBoundary onDidCatch={onDidCatch}>
37+
<p>Children</p>
38+
</ErrorBoundary>
39+
)
40+
41+
expect(screen.getByText("Children")).toBeInTheDocument()
42+
// No errors should be catched here!
43+
expect(onDidCatch).toHaveBeenCalledTimes(0)
44+
expect(console.error).toHaveBeenCalledTimes(0)
45+
})
46+
47+
test("[No Error] Renders children from props.render()", async () => {
48+
const onDidCatch = jest.fn()
49+
50+
render(
51+
<ErrorBoundary onDidCatch={onDidCatch} render={() => <p>Children</p>} />
52+
)
53+
54+
expect(screen.getByText("Children")).toBeInTheDocument()
55+
// No errors should be catched here!
56+
expect(onDidCatch).toHaveBeenCalledTimes(0)
57+
expect(console.error).toHaveBeenCalledTimes(0)
58+
})
59+
60+
test("[Error] Renders nothing on error (props.children) and calls onDidCatch", async () => {
61+
const onDidCatch = jest.fn()
62+
63+
const { container } = render(
64+
<ErrorBoundary onDidCatch={onDidCatch}>
65+
<p>Children</p>
66+
<Explosion />
67+
</ErrorBoundary>
68+
)
69+
70+
expect(container).toBeEmptyDOMElement()
71+
// React and testing-library calls console.error when a boundary catches, onDidCatch should be called
72+
expect(onDidCatch).toHaveBeenCalledTimes(1)
73+
expect(console.error).toHaveBeenCalledTimes(2)
74+
})
75+
76+
test("[Error] Renders nothing on error (props.render())", async () => {
77+
const onDidCatch = jest.fn()
78+
79+
const { container } = render(
80+
<ErrorBoundary
81+
onDidCatch={onDidCatch}
82+
render={() => (
83+
<>
84+
<p>Children</p>
85+
<Explosion />
86+
</>
87+
)}
88+
/>
89+
)
90+
91+
expect(container).toBeEmptyDOMElement()
92+
// React and testing-library calls console.error when a boundary catches
93+
expect(console.error).toHaveBeenCalledTimes(2)
94+
})
95+
96+
test("[Error] Renders props.renderError()", async () => {
97+
const onDidCatch = jest.fn()
98+
99+
render(
100+
<ErrorBoundary
101+
onDidCatch={onDidCatch}
102+
render={() => (
103+
<>
104+
<p>Children</p>
105+
<Explosion />
106+
</>
107+
)}
108+
renderError={({ error }) => <p>{error.message}</p>}
109+
/>
110+
)
111+
112+
expect(screen.getByText(ExplosionErrorMessage)).toBeInTheDocument()
113+
// React and testing-library calls console.error when a boundary catches
114+
expect(console.error).toHaveBeenCalledTimes(2)
115+
})
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import React, { useState } from "react"
2+
import { act, render, screen, fireEvent } from "@testing-library/react"
3+
import useErrorBoundary from "../index"
4+
import { UseErrorBoundaryOptions } from "../../lib"
5+
import { renderHook } from "@testing-library/react-hooks"
6+
7+
/**
8+
* Mock console and suppress errors.
9+
*/
10+
let consoleSpy: jest.SpyInstance | null = null
11+
12+
beforeEach(() => {
13+
consoleSpy = jest.spyOn(console, "error").mockImplementation()
14+
})
15+
16+
afterEach(() => {
17+
consoleSpy?.mockRestore()
18+
})
19+
20+
/**
21+
* Helper Components
22+
*/
23+
24+
const ExplosionErrorMessage = "💥"
25+
const Explosion = () => {
26+
throw new Error(ExplosionErrorMessage)
27+
}
28+
29+
// Use to test hook behaviour
30+
function ClickToExplode(options?: UseErrorBoundaryOptions) {
31+
const [explode, setExplode] = useState(false)
32+
const { ErrorBoundary, didCatch, error } = useErrorBoundary(options)
33+
34+
return (
35+
<>
36+
<div data-testid="boundary-inside">
37+
<ErrorBoundary>
38+
{explode ? (
39+
<Explosion />
40+
) : (
41+
<button onClick={() => setExplode(true)}>Explode!</button>
42+
)}
43+
</ErrorBoundary>
44+
</div>
45+
<p data-testid="didcatch">{didCatch ? "true" : "false"}</p>
46+
<p data-testid="error-message">{error?.message}</p>
47+
</>
48+
)
49+
}
50+
51+
// Use to test render props of wrapped ErrorBoundary
52+
function InstantExplosion(options?: UseErrorBoundaryOptions) {
53+
const { ErrorBoundary } = useErrorBoundary(options)
54+
55+
return (
56+
<>
57+
<div data-testid="boundary-inside">
58+
<ErrorBoundary
59+
render={() => <Explosion />}
60+
renderError={({ error }) => (
61+
<p data-testid="error-message">{error.message}</p>
62+
)}
63+
/>
64+
</div>
65+
</>
66+
)
67+
}
68+
69+
/**
70+
* Test useErrorBoundary hook
71+
*/
72+
73+
test("Hook initializes state", async () => {
74+
const { result } = renderHook(() => useErrorBoundary())
75+
76+
expect(result.current.didCatch).toBe(false)
77+
expect(result.current.ErrorBoundary).toBeDefined()
78+
expect(result.current.error).toBe(null)
79+
})
80+
81+
test("Wrapped ErrorBoundary catches error", async () => {
82+
const onDidCatch = jest.fn()
83+
84+
render(<ClickToExplode onDidCatch={onDidCatch} />)
85+
86+
act(() => {
87+
fireEvent.click(screen.getByText("Explode!"))
88+
})
89+
90+
// Boundary should render nothing
91+
expect(screen.getByTestId("boundary-inside")).toBeEmptyDOMElement()
92+
// Hook should provide didCatch and error
93+
expect(screen.getByTestId("didcatch")).toHaveTextContent("true")
94+
expect(screen.getByTestId("error-message")).toHaveTextContent(
95+
ExplosionErrorMessage
96+
)
97+
// Provided callback should be called once
98+
expect(onDidCatch).toBeCalledTimes(1)
99+
// React and testing-library calls console.error when a boundary catches
100+
expect(console.error).toHaveBeenCalledTimes(2)
101+
})
102+
103+
test("Wrapped ErrorBoundary catches error with render props", async () => {
104+
const onDidCatch = jest.fn()
105+
106+
render(<InstantExplosion onDidCatch={onDidCatch} />)
107+
108+
// Hook should provide didCatch and error
109+
expect(screen.getByTestId("error-message")).toBeInTheDocument()
110+
expect(screen.getByTestId("error-message")).toHaveTextContent(
111+
ExplosionErrorMessage
112+
)
113+
// Provided callback should be called once
114+
expect(onDidCatch).toBeCalledTimes(1)
115+
// React and testing-library calls console.error when a boundary catches
116+
expect(console.error).toHaveBeenCalledTimes(2)
117+
})

0 commit comments

Comments
 (0)