From 247cadffb124c9ab3378b39e0b8ecded7f61fd21 Mon Sep 17 00:00:00 2001 From: Viktor Podzigun Date: Mon, 26 Jun 2023 21:00:23 +0200 Subject: [PATCH] Added TestErrorBoundary component --- README.md | 51 +++++++++++++++++++++++++++++--- index.d.ts | 8 ++++- index.mjs | 1 + package.json | 1 + src/TestErrorBoundary.mjs | 42 ++++++++++++++++++++++++++ test/TestErrorBoundary.test.mjs | 52 +++++++++++++++++++++++++++++++++ test/all.mjs | 1 + 7 files changed, 151 insertions(+), 5 deletions(-) create mode 100644 src/TestErrorBoundary.mjs create mode 100644 test/TestErrorBoundary.test.mjs diff --git a/README.md b/README.md index 126f659..3569c77 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,24 @@ npm i --save-dev react-assert ### Usage +Imports: + ```javascript import React from "react"; import TestRenderer from "react-test-renderer"; +import assert from "node:assert/strict"; +import mockFunction from "mock-fn"; + +import { + assertComponents, + mockComponent, + TestErrorBoundary, +} from "react-assert"; +``` -// 1. import -import { assertComponents, mockComponent } from "react-assert"; +Components: +```javascript function SubComponent() { return

Sub

; } @@ -43,7 +54,11 @@ function MyComponent(props) { } MyComponent.displayName = "MyComponent"; MyComponent.SubComp = SubComponent; +``` +Tests: + +```javascript describe("MyComponent", () => { it("should render nested components", () => { //given @@ -53,7 +68,7 @@ describe("MyComponent", () => { const result = TestRenderer.create().root; //then - // 2. call assertComponents to check expected components tree + // call assertComponents to check expected components tree assertComponents( result.children,
@@ -65,7 +80,7 @@ describe("MyComponent", () => { it("should render mock components", () => { //given - // 3. use mockComponent to mock nested components + // use mockComponent to mock nested components MyComponent.SubComp = mockComponent(SubComponent); const { SubComp } = MyComponent; const text = "Hello"; @@ -82,5 +97,33 @@ describe("MyComponent", () => {
); }); + + it("should render error details if error during render", () => { + //given + // suppress intended error + // see: https://github.com/facebook/react/issues/11098#issuecomment-412682721 + const savedConsoleError = console.error; + const consoleErrorMock = mockFunction(() => { + console.error = savedConsoleError; + }); + console.error = consoleErrorMock; + + const ErrorComp = () => { + throw Error("test error"); + return <>{"Not rendered"}; + }; + + //when + const result = TestRenderer.create( + + + + ).root; + + //then + assert.deepEqual(consoleErrorMock.times, 1); + assert.deepEqual(console.error, savedConsoleError); + assertComponents(result.children,
{"Error: test error"}
); + }); }); ``` diff --git a/index.d.ts b/index.d.ts index 9d224bf..a424cd1 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1,4 +1,4 @@ -import React from "react"; +import React, { Component } from "react"; import TestRenderer from "react-test-renderer"; export function assertComponent( @@ -14,3 +14,9 @@ export function assertComponents( export function mockComponent< T = React.FunctionComponent | React.ComponentClass >(comp: T, name?: string): T; + +interface State { + error?: object; +} + +export class TestErrorBoundary extends Component {} diff --git a/index.mjs b/index.mjs index 8cc64f3..6d37a06 100644 --- a/index.mjs +++ b/index.mjs @@ -1,3 +1,4 @@ export { default as assertComponent } from "./src/assertComponent.mjs"; export { default as assertComponents } from "./src/assertComponents.mjs"; export { default as mockComponent } from "./src/mockComponent.mjs"; +export { default as TestErrorBoundary } from "./src/TestErrorBoundary.mjs"; diff --git a/package.json b/package.json index 3cf780c..18630b5 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@types/react": "^17.0.1", "@types/react-test-renderer": "^17.0.1", "c8": "^7.13.0", + "mock-fn": "^1.0.0", "prettier": "^2.8.8", "react": "^17.0.1", "react-test-renderer": "^17.0.1", diff --git a/src/TestErrorBoundary.mjs b/src/TestErrorBoundary.mjs new file mode 100644 index 0000000..8e41923 --- /dev/null +++ b/src/TestErrorBoundary.mjs @@ -0,0 +1,42 @@ +import React, { Component } from "react"; + +const h = React.createElement; + +/** + * @typedef State + * @prop {object} [error] + */ + +/** + * @extends Component + */ +class TestErrorBoundary extends Component { + /** @type {State} */ + state = { + error: undefined, + }; + + /** + * @param {any} props + */ + constructor(props) { + super(props); + } + + /** + * @param {object} [error] + */ + componentDidCatch(error) { + this.setState({ + error, + }); + } + + render() { + const error = this.state.error; + return error ? h("div", null, `${error}`) : this.props.children; + } +} +TestErrorBoundary.displayName = "TestErrorBoundary"; + +export default TestErrorBoundary; diff --git a/test/TestErrorBoundary.test.mjs b/test/TestErrorBoundary.test.mjs new file mode 100644 index 0000000..d786a1d --- /dev/null +++ b/test/TestErrorBoundary.test.mjs @@ -0,0 +1,52 @@ +import React from "react"; +import TestRenderer from "react-test-renderer"; +import assert from "node:assert/strict"; +import mockFunction from "mock-fn"; +import { assertComponents, TestErrorBoundary } from "../index.mjs"; + +const h = React.createElement; + +const { describe, it } = await (async () => { + // @ts-ignore + return process.isBun // @ts-ignore + ? Promise.resolve({ describe: (_, fn) => fn(), it: test }) + : import("node:test"); +})(); + +describe("TestErrorBoundary.test.mjs", () => { + it("should render children if no errors", () => { + //when + const result = TestRenderer.create( + h(TestErrorBoundary, null, "some child") + ).root; + + //then + assertComponents(result.children, "some child"); + }); + + it("should render error details if error during render", () => { + //given + // suppress intended error + // see: https://github.com/facebook/react/issues/11098#issuecomment-412682721 + const savedConsoleError = console.error; + const consoleErrorMock = mockFunction(() => { + console.error = savedConsoleError; + }); + console.error = consoleErrorMock; + + const ErrorComp = () => { + throw Error("test error"); + return h(React.Fragment); + }; + + //when + const result = TestRenderer.create( + h(TestErrorBoundary, null, h(ErrorComp)) + ).root; + + //then + assert.deepEqual(consoleErrorMock.times, 1); + assert.deepEqual(console.error, savedConsoleError); + assertComponents(result.children, h("div", null, "Error: test error")); + }); +}); diff --git a/test/all.mjs b/test/all.mjs index 28b4a6a..53a9a03 100644 --- a/test/all.mjs +++ b/test/all.mjs @@ -2,3 +2,4 @@ await import("./testRenderer.test.mjs"); await import("./assertComponent.test.mjs"); await import("./assertComponents.test.mjs"); await import("./mockComponent.test.mjs"); +await import("./TestErrorBoundary.test.mjs");