Skip to content

Commit 247cadf

Browse files
Added TestErrorBoundary component
1 parent 3052bb6 commit 247cadf

File tree

7 files changed

+151
-5
lines changed

7 files changed

+151
-5
lines changed

README.md

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,24 @@ npm i --save-dev react-assert
1919

2020
### Usage
2121

22+
Imports:
23+
2224
```javascript
2325
import React from "react";
2426
import TestRenderer from "react-test-renderer";
27+
import assert from "node:assert/strict";
28+
import mockFunction from "mock-fn";
29+
30+
import {
31+
assertComponents,
32+
mockComponent,
33+
TestErrorBoundary,
34+
} from "react-assert";
35+
```
2536

26-
// 1. import
27-
import { assertComponents, mockComponent } from "react-assert";
37+
Components:
2838

39+
```javascript
2940
function SubComponent() {
3041
return <p className="sub">Sub</p>;
3142
}
@@ -43,7 +54,11 @@ function MyComponent(props) {
4354
}
4455
MyComponent.displayName = "MyComponent";
4556
MyComponent.SubComp = SubComponent;
57+
```
4658

59+
Tests:
60+
61+
```javascript
4762
describe("MyComponent", () => {
4863
it("should render nested components", () => {
4964
//given
@@ -53,7 +68,7 @@ describe("MyComponent", () => {
5368
const result = TestRenderer.create(<MyComponent text={text} />).root;
5469

5570
//then
56-
// 2. call assertComponents to check expected components tree
71+
// call assertComponents to check expected components tree
5772
assertComponents(
5873
result.children,
5974
<div>
@@ -65,7 +80,7 @@ describe("MyComponent", () => {
6580

6681
it("should render mock components", () => {
6782
//given
68-
// 3. use mockComponent to mock nested components
83+
// use mockComponent to mock nested components
6984
MyComponent.SubComp = mockComponent(SubComponent);
7085
const { SubComp } = MyComponent;
7186
const text = "Hello";
@@ -82,5 +97,33 @@ describe("MyComponent", () => {
8297
</div>
8398
);
8499
});
100+
101+
it("should render error details if error during render", () => {
102+
//given
103+
// suppress intended error
104+
// see: https://github.com/facebook/react/issues/11098#issuecomment-412682721
105+
const savedConsoleError = console.error;
106+
const consoleErrorMock = mockFunction(() => {
107+
console.error = savedConsoleError;
108+
});
109+
console.error = consoleErrorMock;
110+
111+
const ErrorComp = () => {
112+
throw Error("test error");
113+
return <>{"Not rendered"}</>;
114+
};
115+
116+
//when
117+
const result = TestRenderer.create(
118+
<TestErrorBoundary>
119+
<ErrorComp />
120+
<TestErrorBoundary/>
121+
).root;
122+
123+
//then
124+
assert.deepEqual(consoleErrorMock.times, 1);
125+
assert.deepEqual(console.error, savedConsoleError);
126+
assertComponents(result.children, <div>{"Error: test error"}</div>);
127+
});
85128
});
86129
```

index.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react";
1+
import React, { Component } from "react";
22
import TestRenderer from "react-test-renderer";
33

44
export function assertComponent(
@@ -14,3 +14,9 @@ export function assertComponents(
1414
export function mockComponent<
1515
T = React.FunctionComponent<any> | React.ComponentClass<any>
1616
>(comp: T, name?: string): T;
17+
18+
interface State {
19+
error?: object;
20+
}
21+
22+
export class TestErrorBoundary extends Component<any, State> {}

index.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export { default as assertComponent } from "./src/assertComponent.mjs";
22
export { default as assertComponents } from "./src/assertComponents.mjs";
33
export { default as mockComponent } from "./src/mockComponent.mjs";
4+
export { default as TestErrorBoundary } from "./src/TestErrorBoundary.mjs";

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"@types/react": "^17.0.1",
4343
"@types/react-test-renderer": "^17.0.1",
4444
"c8": "^7.13.0",
45+
"mock-fn": "^1.0.0",
4546
"prettier": "^2.8.8",
4647
"react": "^17.0.1",
4748
"react-test-renderer": "^17.0.1",

src/TestErrorBoundary.mjs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React, { Component } from "react";
2+
3+
const h = React.createElement;
4+
5+
/**
6+
* @typedef State
7+
* @prop {object} [error]
8+
*/
9+
10+
/**
11+
* @extends Component<any, State>
12+
*/
13+
class TestErrorBoundary extends Component {
14+
/** @type {State} */
15+
state = {
16+
error: undefined,
17+
};
18+
19+
/**
20+
* @param {any} props
21+
*/
22+
constructor(props) {
23+
super(props);
24+
}
25+
26+
/**
27+
* @param {object} [error]
28+
*/
29+
componentDidCatch(error) {
30+
this.setState({
31+
error,
32+
});
33+
}
34+
35+
render() {
36+
const error = this.state.error;
37+
return error ? h("div", null, `${error}`) : this.props.children;
38+
}
39+
}
40+
TestErrorBoundary.displayName = "TestErrorBoundary";
41+
42+
export default TestErrorBoundary;

test/TestErrorBoundary.test.mjs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from "react";
2+
import TestRenderer from "react-test-renderer";
3+
import assert from "node:assert/strict";
4+
import mockFunction from "mock-fn";
5+
import { assertComponents, TestErrorBoundary } from "../index.mjs";
6+
7+
const h = React.createElement;
8+
9+
const { describe, it } = await (async () => {
10+
// @ts-ignore
11+
return process.isBun // @ts-ignore
12+
? Promise.resolve({ describe: (_, fn) => fn(), it: test })
13+
: import("node:test");
14+
})();
15+
16+
describe("TestErrorBoundary.test.mjs", () => {
17+
it("should render children if no errors", () => {
18+
//when
19+
const result = TestRenderer.create(
20+
h(TestErrorBoundary, null, "some child")
21+
).root;
22+
23+
//then
24+
assertComponents(result.children, "some child");
25+
});
26+
27+
it("should render error details if error during render", () => {
28+
//given
29+
// suppress intended error
30+
// see: https://github.com/facebook/react/issues/11098#issuecomment-412682721
31+
const savedConsoleError = console.error;
32+
const consoleErrorMock = mockFunction(() => {
33+
console.error = savedConsoleError;
34+
});
35+
console.error = consoleErrorMock;
36+
37+
const ErrorComp = () => {
38+
throw Error("test error");
39+
return h(React.Fragment);
40+
};
41+
42+
//when
43+
const result = TestRenderer.create(
44+
h(TestErrorBoundary, null, h(ErrorComp))
45+
).root;
46+
47+
//then
48+
assert.deepEqual(consoleErrorMock.times, 1);
49+
assert.deepEqual(console.error, savedConsoleError);
50+
assertComponents(result.children, h("div", null, "Error: test error"));
51+
});
52+
});

test/all.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ await import("./testRenderer.test.mjs");
22
await import("./assertComponent.test.mjs");
33
await import("./assertComponents.test.mjs");
44
await import("./mockComponent.test.mjs");
5+
await import("./TestErrorBoundary.test.mjs");

0 commit comments

Comments
 (0)