From 9ac6c67a7ce11aaf7b10db1af3249e8bf491ffa7 Mon Sep 17 00:00:00 2001
From: Abhijeet Prasad
Date: Wed, 3 Jun 2020 10:25:12 -0400
Subject: [PATCH 01/17] ref(react): Update name for Profiler
The Profiler now has a mandatory name field, which describes what is being
profiled
---
packages/react/README.md | 2 ++
packages/react/src/profiler.tsx | 19 +++++++------------
2 files changed, 9 insertions(+), 12 deletions(-)
diff --git a/packages/react/README.md b/packages/react/README.md
index 8df1272d44af..c4430a6af6e0 100644
--- a/packages/react/README.md
+++ b/packages/react/README.md
@@ -7,6 +7,8 @@
# Official Sentry SDK for ReactJS
+Note this library is in active development and not ready for production usage.
+
## Links
- [Official SDK Docs](https://docs.sentry.io/quickstart/)
diff --git a/packages/react/src/profiler.tsx b/packages/react/src/profiler.tsx
index 16373639c18a..c8435ba9a6d2 100644
--- a/packages/react/src/profiler.tsx
+++ b/packages/react/src/profiler.tsx
@@ -39,28 +39,25 @@ function afterNextFrame(callback: Function): void {
timeout = window.setTimeout(done, 100);
}
-const getInitActivity = (componentDisplayName: string): number | null => {
+const getInitActivity = (name: string): number | null => {
const tracingIntegration = getCurrentHub().getIntegration(TRACING_GETTER);
if (tracingIntegration !== null) {
// tslint:disable-next-line:no-unsafe-any
- const activity = (tracingIntegration as any).constructor.pushActivity(componentDisplayName, {
- description: `<${componentDisplayName}>`,
+ return (tracingIntegration as any).constructor.pushActivity(name, {
+ description: `<${name}>`,
op: 'react',
});
-
- // tslint:disable-next-line: no-unsafe-any
- return activity;
}
logger.warn(
- `Unable to profile component ${componentDisplayName} due to invalid Tracing Integration. Please make sure to setup the Tracing integration.`,
+ `Unable to profile component ${name} due to invalid Tracing Integration. Please make sure to setup the Tracing integration.`,
);
return null;
};
interface ProfilerProps {
- componentDisplayName?: string;
+ name: string;
}
class Profiler extends React.Component {
@@ -68,9 +65,7 @@ class Profiler extends React.Component {
public constructor(props: ProfilerProps) {
super(props);
- const { componentDisplayName = UNKNOWN_COMPONENT } = this.props;
-
- this.activity = getInitActivity(componentDisplayName);
+ this.activity = getInitActivity(this.props.name);
}
public componentDidMount(): void {
@@ -103,7 +98,7 @@ function withProfiler(WrappedComponent: React.ComponentType
const componentDisplayName = WrappedComponent.displayName || WrappedComponent.name || UNKNOWN_COMPONENT;
const Wrapped: React.FC
= (props: P) => (
-
+
);
From 40c23d1b000ddd02cb0a4ef5b697c4e712438557 Mon Sep 17 00:00:00 2001
From: Abhijeet Prasad
Date: Wed, 3 Jun 2020 13:00:43 -0400
Subject: [PATCH 02/17] feat(react): Add initial error boundary component
---
packages/react/src/errorboundary.tsx | 36 ++++++++++++++++++++++
packages/react/test/errorboundary.test.tsx | 18 +++++++++++
2 files changed, 54 insertions(+)
create mode 100644 packages/react/src/errorboundary.tsx
create mode 100644 packages/react/test/errorboundary.test.tsx
diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx
new file mode 100644
index 000000000000..545d97b86bb3
--- /dev/null
+++ b/packages/react/src/errorboundary.tsx
@@ -0,0 +1,36 @@
+import * as React from 'react';
+import * as Sentry from '@sentry/browser';
+
+interface ErrorBoundaryProps {}
+
+interface ErrorBoundaryState {
+ hasError: boolean;
+}
+
+class ErrorBoundary extends React.Component {
+ constructor(props: ErrorBoundaryProps) {
+ super(props);
+ this.state = {
+ hasError: false,
+ };
+ }
+
+ public static getDerivedStateFromError(_: Error): ErrorBoundaryState {
+ return { hasError: true };
+ }
+
+ public componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
+ Sentry.captureException(Error);
+ console.log(error);
+ console.log(errorInfo.componentStack);
+ }
+
+ public render(): React.ReactNode {
+ if (this.state.hasError) {
+ return null;
+ }
+ return this.props.children;
+ }
+}
+
+export { ErrorBoundary };
diff --git a/packages/react/test/errorboundary.test.tsx b/packages/react/test/errorboundary.test.tsx
new file mode 100644
index 000000000000..f63932b23532
--- /dev/null
+++ b/packages/react/test/errorboundary.test.tsx
@@ -0,0 +1,18 @@
+import * as React from 'react';
+import { create } from 'react-test-renderer';
+
+import { ErrorBoundary } from '../src/errorboundary';
+
+describe('ErrorBoundary', () => {
+ it('Does not fail', () => {
+ function Bomb() {
+ return {new Error('💥 CABOOM 💥')}
;
+ }
+
+ create(
+
+
+ ,
+ );
+ });
+});
From 4688e6398b22222d4e8d5794cf084aa33cc3a9f7 Mon Sep 17 00:00:00 2001
From: Abhijeet Prasad
Date: Wed, 3 Jun 2020 15:25:28 -0400
Subject: [PATCH 03/17] chore(react): use @testing-library/react
---
packages/react/package.json | 3 +-
packages/react/test/errorboundary.test.tsx | 1 -
packages/react/test/profiler.test.tsx | 6 +-
yarn.lock | 195 +++++++++++++++++++--
4 files changed, 184 insertions(+), 21 deletions(-)
diff --git a/packages/react/package.json b/packages/react/package.json
index 6154493f260b..a0e8501e6cf5 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -27,16 +27,15 @@
"react-dom": "^16.0.0"
},
"devDependencies": {
+ "@testing-library/react": "^10.0.6",
"@types/hoist-non-react-statics": "^3.3.1",
"@types/react": "^16.9.35",
- "@types/react-test-renderer": "^16.9.2",
"jest": "^24.7.1",
"npm-run-all": "^4.1.2",
"prettier": "^1.17.0",
"prettier-check": "^2.0.0",
"react": "^16.0.0",
"react-dom": "^16.0.0",
- "react-test-renderer": "^16.13.1",
"rimraf": "^2.6.3",
"tslint": "^5.16.0",
"tslint-react": "^5.0.0",
diff --git a/packages/react/test/errorboundary.test.tsx b/packages/react/test/errorboundary.test.tsx
index f63932b23532..8379c3f86246 100644
--- a/packages/react/test/errorboundary.test.tsx
+++ b/packages/react/test/errorboundary.test.tsx
@@ -1,5 +1,4 @@
import * as React from 'react';
-import { create } from 'react-test-renderer';
import { ErrorBoundary } from '../src/errorboundary';
diff --git a/packages/react/test/profiler.test.tsx b/packages/react/test/profiler.test.tsx
index fa208211010c..008803e38ac4 100644
--- a/packages/react/test/profiler.test.tsx
+++ b/packages/react/test/profiler.test.tsx
@@ -1,5 +1,5 @@
+import { render } from '@testing-library/react';
import * as React from 'react';
-import { create } from 'react-test-renderer';
import { UNKNOWN_COMPONENT, withProfiler } from '../src/profiler';
@@ -44,7 +44,7 @@ describe('withProfiler', () => {
expect(mockPopActivity).toHaveBeenCalledTimes(0);
- const profiler = create();
+ const profiler = render();
profiler.unmount();
jest.runAllTimers();
@@ -58,7 +58,7 @@ describe('withProfiler', () => {
const ProfiledComponent = withProfiler(() => Testing
);
expect(mockPushActivity).toHaveBeenCalledTimes(0);
- create();
+ render();
expect(mockPushActivity).toHaveBeenCalledTimes(1);
expect(mockPushActivity).toHaveBeenLastCalledWith(UNKNOWN_COMPONENT, {
description: `<${UNKNOWN_COMPONENT}>`,
diff --git a/yarn.lock b/yarn.lock
index abf8e70b088b..59040bf54b46 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -111,6 +111,21 @@
core-js "^2.5.7"
regenerator-runtime "^0.12.0"
+"@babel/runtime-corejs3@^7.7.4":
+ version "7.10.2"
+ resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.10.2.tgz#3511797ddf9a3d6f3ce46b99cc835184817eaa4e"
+ integrity sha512-+a2M/u7r15o3dV1NEizr9bRi+KUVnrs/qYxF0Z06DAPx/4VCWaz1WA7EcbE+uqGgt39lp5akWGmHsTseIkHkHg==
+ dependencies:
+ core-js-pure "^3.0.0"
+ regenerator-runtime "^0.13.4"
+
+"@babel/runtime@^7.10.2", "@babel/runtime@^7.7.4":
+ version "7.10.2"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.2.tgz#d103f21f2602497d38348a32e008637d506db839"
+ integrity sha512-6sF3uQw2ivImfVIl62RZ7MXhO2tap69WeWK57vAaimT6AZbE4FbqjdEJIN1UqoD6wI6B+1n9UiagafH1sxjOtg==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2":
version "7.2.2"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907"
@@ -379,6 +394,16 @@
"@types/istanbul-lib-coverage" "^2.0.0"
"@types/yargs" "^12.0.9"
+"@jest/types@^25.5.0":
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.5.0.tgz#4d6a4793f7b9599fc3680877b856a97dbccf2a9d"
+ integrity sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==
+ dependencies:
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^1.1.1"
+ "@types/yargs" "^15.0.0"
+ chalk "^3.0.0"
+
"@lerna/add@3.13.3":
version "3.13.3"
resolved "https://registry.yarnpkg.com/@lerna/add/-/add-3.13.3.tgz#f4c1674839780e458f0426d4f7b6d0a77b9a2ae9"
@@ -1160,6 +1185,25 @@
highlight.js "^9.15.6"
marked "^0.6.1"
+"@testing-library/dom@^7.9.0":
+ version "7.9.0"
+ resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.9.0.tgz#3805cda47ab691645775c59c93c9044a2cad4c91"
+ integrity sha512-WYnJx9I94cYKib/Ber2BU3v1dUB+4n5wnJpvWJLTiwgERRTSElsivEtfX5S0LSljS122One6Bewhx2kgoZKXzA==
+ dependencies:
+ "@babel/runtime" "^7.10.2"
+ aria-query "^4.0.2"
+ dom-accessibility-api "^0.4.4"
+ pretty-format "^25.5.0"
+
+"@testing-library/react@^10.0.6":
+ version "10.0.6"
+ resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-10.0.6.tgz#e1e569135badb7367cc6ac9823e376eccb0280e0"
+ integrity sha512-7cZ2sHN6zTW1b/pNKzA0icZozshOOuiEQq/zCcf4LUCNGKAOnGCxZDQI7qjpO6lMITmi4Qs0VU1j9Cd4Z36e+w==
+ dependencies:
+ "@babel/runtime" "^7.10.2"
+ "@testing-library/dom" "^7.9.0"
+ "@types/testing-library__react" "^10.0.1"
+
"@tootallnate/once@1":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.0.0.tgz#9c13c2574c92d4503b005feca8f2e16cc1611506"
@@ -1215,6 +1259,11 @@
resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.7.tgz#1b8e33b61a8c09cbe1f85133071baa0dbf9fa71a"
integrity sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==
+"@types/color-name@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
+ integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
+
"@types/connect@*":
version "3.4.32"
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28"
@@ -1303,11 +1352,31 @@
"@types/react" "*"
hoist-non-react-statics "^3.3.0"
+"@types/istanbul-lib-coverage@*":
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.2.tgz#79d7a78bad4219f4c03d6557a1c72d9ca6ba62d5"
+ integrity sha512-rsZg7eL+Xcxsxk2XlBt9KcG8nOp9iYdKCOikY9x2RFJCyOdNj4MKPQty0e8oZr29vVAzKXr1BmR+kZauti3o1w==
+
"@types/istanbul-lib-coverage@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz#1eb8c033e98cf4e1a4cedcaf8bcafe8cb7591e85"
integrity sha512-eAtOAFZefEnfJiRFQBGw1eYqa5GTLCZ1y86N0XSI/D6EB+E8z6VPV/UL7Gi5UEclFqoQk+6NRqEDsfmDLXn8sg==
+"@types/istanbul-lib-report@*":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686"
+ integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+
+"@types/istanbul-reports@^1.1.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz#e875cc689e47bce549ec81f3df5e6f6f11cfaeb2"
+ integrity sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+ "@types/istanbul-lib-report" "*"
+
"@types/jest-diff@*":
version "20.0.1"
resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89"
@@ -1377,10 +1446,10 @@
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
-"@types/react-test-renderer@^16.9.2":
- version "16.9.2"
- resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.2.tgz#e1c408831e8183e5ad748fdece02214a7c2ab6c5"
- integrity sha512-4eJr1JFLIAlWhzDkBCkhrOIWOvOxcCAfQh+jiKg7l/nNZcCIL2MHl2dZhogIFKyHzedVWHaVP1Yydq/Ruu4agw==
+"@types/react-dom@*":
+ version "16.9.8"
+ resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423"
+ integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==
dependencies:
"@types/react" "*"
@@ -1435,15 +1504,43 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
+"@types/testing-library__dom@*":
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/@types/testing-library__dom/-/testing-library__dom-7.0.2.tgz#2906f8a0dce58b0746c6ab606f786bd06fe6940e"
+ integrity sha512-8yu1gSwUEAwzg2OlPNbGq+ixhmSviGurBu1+ivxRKq1eRcwdjkmlwtPvr9VhuxTq2fNHBWN2po6Iem3Xt5A6rg==
+ dependencies:
+ pretty-format "^25.1.0"
+
+"@types/testing-library__react@^10.0.1":
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/@types/testing-library__react/-/testing-library__react-10.0.1.tgz#92bb4a02394bf44428e35f1da2970ed77f803593"
+ integrity sha512-RbDwmActAckbujLZeVO/daSfdL1pnjVqas25UueOkAY5r7vriavWf0Zqg7ghXMHa8ycD/kLkv8QOj31LmSYwww==
+ dependencies:
+ "@types/react-dom" "*"
+ "@types/testing-library__dom" "*"
+ pretty-format "^25.1.0"
+
"@types/tough-cookie@*":
version "2.3.4"
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.4.tgz#821878b81bfab971b93a265a561d54ea61f9059f"
+"@types/yargs-parser@*":
+ version "15.0.0"
+ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
+ integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
+
"@types/yargs@^12.0.2", "@types/yargs@^12.0.9":
version "12.0.9"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-12.0.9.tgz#693e76a52f61a2f1e7fb48c0eef167b95ea4ffd0"
integrity sha512-sCZy4SxP9rN2w30Hlmg5dtdRwgYQfYRiLo9usw8X9cxlf+H4FqM1xX7+sNH7NNKVdbXMJWqva7iyy+fxh/V7fA==
+"@types/yargs@^15.0.0":
+ version "15.0.5"
+ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.5.tgz#947e9a6561483bdee9adffc983e91a6902af8b79"
+ integrity sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==
+ dependencies:
+ "@types/yargs-parser" "*"
+
"@webassemblyjs/ast@1.8.5":
version "1.8.5"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
@@ -1786,6 +1883,11 @@ ansi-regex@^4.1.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
+ansi-regex@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
+ integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==
+
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -1797,6 +1899,14 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
+ integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
+ dependencies:
+ "@types/color-name" "^1.1.1"
+ color-convert "^2.0.1"
+
ansi-wrap@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
@@ -1877,6 +1987,14 @@ argv@0.0.2:
resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab"
integrity sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=
+aria-query@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.0.2.tgz#250687b4ccde1ab86d127da0432ae3552fc7b145"
+ integrity sha512-S1G1V790fTaigUSM/Gd0NngzEfiMy9uTUfMyHhKhVyy4cH5O/eTuR01ydhGL0z4Za1PXFTRGH3qL8VhUQuEO5w==
+ dependencies:
+ "@babel/runtime" "^7.7.4"
+ "@babel/runtime-corejs3" "^7.7.4"
+
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
@@ -3151,6 +3269,14 @@ chalk@^2.1.0, chalk@^2.4.1:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
+chalk@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4"
+ integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
chardet@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
@@ -3349,11 +3475,23 @@ color-convert@^1.9.0:
dependencies:
color-name "1.1.3"
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
colors@^1.1.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.2.tgz#2df8ff573dfbf255af562f8ce7181d6b971a359b"
@@ -3663,6 +3801,11 @@ copy-descriptor@^0.1.0:
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
+core-js-pure@^3.0.0:
+ version "3.6.5"
+ resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
+ integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
+
core-js@^2.2.0, core-js@^2.4.0, core-js@^2.5.0:
version "2.5.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e"
@@ -4137,6 +4280,11 @@ dir-glob@2.0.0:
arrify "^1.0.1"
path-type "^3.0.0"
+dom-accessibility-api@^0.4.4:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.4.4.tgz#c2f9fb8b591bc19581e7ef3e6fe35baf1967c498"
+ integrity sha512-XBM62jdDc06IXSujkqw6BugEWiDkp6jphtzVJf1kgPQGvfzaU7/jRtRSF/mxc8DBCIm2LS3bN1dCa5Sfxx982A==
+
dom-serialize@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b"
@@ -5396,6 +5544,11 @@ has-flag@^3.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
has-symbols@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44"
@@ -8889,6 +9042,16 @@ pretty-format@^24.7.0:
ansi-styles "^3.2.0"
react-is "^16.8.4"
+pretty-format@^25.1.0, pretty-format@^25.5.0:
+ version "25.5.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.5.0.tgz#7873c1d774f682c34b8d48b6743a2bf2ac55791a"
+ integrity sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==
+ dependencies:
+ "@jest/types" "^25.5.0"
+ ansi-regex "^5.0.0"
+ ansi-styles "^4.0.0"
+ react-is "^16.12.0"
+
private@^0.1.6, private@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
@@ -9140,7 +9303,7 @@ react-dom@^16.0.0:
prop-types "^15.6.2"
scheduler "^0.19.1"
-react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6:
+react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
@@ -9150,16 +9313,6 @@ react-is@^16.8.4:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2"
integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA==
-react-test-renderer@^16.13.1:
- version "16.13.1"
- resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1"
- integrity sha512-Sn2VRyOK2YJJldOqoh8Tn/lWQ+ZiKhyZTPtaO0Q6yNj+QDbmRkVFap6pZPy3YQk8DScRDfyqm/KxKYP9gCMRiQ==
- dependencies:
- object-assign "^4.1.1"
- prop-types "^15.6.2"
- react-is "^16.8.6"
- scheduler "^0.19.1"
-
react@^16.0.0:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e"
@@ -9355,6 +9508,11 @@ regenerator-runtime@^0.12.0:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
+regenerator-runtime@^0.13.4:
+ version "0.13.5"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697"
+ integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==
+
regenerator-transform@^0.10.0:
version "0.10.1"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"
@@ -10551,6 +10709,13 @@ supports-color@^6.0.0, supports-color@^6.1.0:
dependencies:
has-flag "^3.0.0"
+supports-color@^7.1.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
+ integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
+ dependencies:
+ has-flag "^4.0.0"
+
supports-hyperlinks@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz#71daedf36cc1060ac5100c351bb3da48c29c0ef7"
From 3e9c8f9d4fdf81588d219f5d2b7373093c50a760 Mon Sep 17 00:00:00 2001
From: Abhijeet Prasad
Date: Thu, 4 Jun 2020 14:13:57 -0400
Subject: [PATCH 04/17] feat(react): Setup basic error boundary props
---
packages/react/src/errorboundary.tsx | 67 +++++++++++++++-------
packages/react/src/profiler.tsx | 4 +-
packages/react/test/errorboundary.test.tsx | 24 ++++++--
packages/react/tslint.json | 3 +-
4 files changed, 70 insertions(+), 28 deletions(-)
diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx
index 545d97b86bb3..dee96bd6238b 100644
--- a/packages/react/src/errorboundary.tsx
+++ b/packages/react/src/errorboundary.tsx
@@ -1,34 +1,61 @@
-import * as React from 'react';
import * as Sentry from '@sentry/browser';
+import * as React from 'react';
-interface ErrorBoundaryProps {}
+export type ErrorBoundaryProps = {
+ fallback?: React.ReactNode;
+ fallbackRender?(error: Error | null, componentStack: string | null, resetErrorBoundary: () => void): React.ReactNode;
+ onError?(error: Error, componentStack: string): void;
+ onReset?(error: Error | null, componentStack: string | null): void;
+};
-interface ErrorBoundaryState {
- hasError: boolean;
-}
+type ErrorBoundaryState = {
+ error: Error | null;
+ componentStack: string | null;
+};
+
+const INITIAL_STATE = {
+ componentStack: null,
+ error: null,
+};
class ErrorBoundary extends React.Component {
- constructor(props: ErrorBoundaryProps) {
- super(props);
- this.state = {
- hasError: false,
- };
- }
+ public state: ErrorBoundaryState = INITIAL_STATE;
- public static getDerivedStateFromError(_: Error): ErrorBoundaryState {
- return { hasError: true };
+ public componentDidCatch(error: Error, { componentStack }: React.ErrorInfo): void {
+ Sentry.withScope(scope => {
+ scope.setExtra('componentStack', componentStack);
+ Sentry.captureException(error);
+ });
+ const { onError } = this.props;
+ if (onError) {
+ onError(error, componentStack);
+ }
+ this.setState({ error, componentStack });
}
- public componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
- Sentry.captureException(Error);
- console.log(error);
- console.log(errorInfo.componentStack);
- }
+ public resetErrorBoundary = () => {
+ const { onReset } = this.props;
+ if (onReset) {
+ onReset(this.state.error, this.state.componentStack);
+ }
+ this.setState(INITIAL_STATE);
+ };
public render(): React.ReactNode {
- if (this.state.hasError) {
- return null;
+ const { fallback, fallbackRender } = this.props;
+ const { error, componentStack } = this.state;
+
+ if (error) {
+ if (typeof fallbackRender === 'function') {
+ return fallbackRender(error, componentStack, this.resetErrorBoundary);
+ }
+ if (React.isValidElement(fallback)) {
+ return fallback;
+ }
+
+ throw new Error('No fallback component has been set');
}
+
return this.props.children;
}
}
diff --git a/packages/react/src/profiler.tsx b/packages/react/src/profiler.tsx
index c8435ba9a6d2..00f404ce5a65 100644
--- a/packages/react/src/profiler.tsx
+++ b/packages/react/src/profiler.tsx
@@ -56,9 +56,9 @@ const getInitActivity = (name: string): number | null => {
return null;
};
-interface ProfilerProps {
+export type ProfilerProps = {
name: string;
-}
+};
class Profiler extends React.Component {
public activity: number | null;
diff --git a/packages/react/test/errorboundary.test.tsx b/packages/react/test/errorboundary.test.tsx
index 8379c3f86246..8b2646837ae0 100644
--- a/packages/react/test/errorboundary.test.tsx
+++ b/packages/react/test/errorboundary.test.tsx
@@ -1,14 +1,28 @@
+import { render } from '@testing-library/react';
import * as React from 'react';
-import { ErrorBoundary } from '../src/errorboundary';
+import { ErrorBoundary, ErrorBoundaryProps } from '../src/errorboundary';
describe('ErrorBoundary', () => {
- it('Does not fail', () => {
- function Bomb() {
- return {new Error('💥 CABOOM 💥')}
;
+ const DEFAULT_PROPS: ErrorBoundaryProps = {
+ fallback: Error Component
,
+ fallbackRender: (error: Error, componentStack: string, resetErrorBoundary: () => void) => (
+
+ {error.toString()}
+ {componentStack}
+
+
+ ),
+ onError: jest.fn(),
+ onReset: jest.fn(),
+ };
+
+ it('Renders children with no failure', () => {
+ function Bomb(): JSX.Element {
+ return Testing children
;
}
- create(
+ render(
,
diff --git a/packages/react/tslint.json b/packages/react/tslint.json
index 1c16df5658e5..98081b906e4d 100644
--- a/packages/react/tslint.json
+++ b/packages/react/tslint.json
@@ -6,6 +6,7 @@
"dev"
],
"variable-name": false,
- "completed-docs": false
+ "completed-docs": false,
+ "interface-over-type-literal": false
}
}
From 271f895726ae148301cfbce70da34d8f3f16d30b Mon Sep 17 00:00:00 2001
From: Abhijeet Prasad
Date: Thu, 4 Jun 2020 17:25:08 -0400
Subject: [PATCH 05/17] feat(react): Add lifecycle hooks to errorboundary
---
packages/react/src/errorboundary.tsx | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx
index dee96bd6238b..10b1e91a267d 100644
--- a/packages/react/src/errorboundary.tsx
+++ b/packages/react/src/errorboundary.tsx
@@ -1,11 +1,15 @@
import * as Sentry from '@sentry/browser';
import * as React from 'react';
+export const FALLBACK_ERR_MESSAGE = 'No fallback component has been set';
+
export type ErrorBoundaryProps = {
fallback?: React.ReactNode;
fallbackRender?(error: Error | null, componentStack: string | null, resetErrorBoundary: () => void): React.ReactNode;
onError?(error: Error, componentStack: string): void;
+ onMount?(error: Error | null, componentStack: string | null): void;
onReset?(error: Error | null, componentStack: string | null): void;
+ onUnmount?(error: Error | null, componentStack: string | null): void;
};
type ErrorBoundaryState = {
@@ -33,6 +37,22 @@ class ErrorBoundary extends React.Component {
const { onReset } = this.props;
if (onReset) {
@@ -53,7 +73,7 @@ class ErrorBoundary extends React.Component
Date: Thu, 4 Jun 2020 20:18:14 -0400
Subject: [PATCH 06/17] feat(react): Add report dialog
---
packages/react/src/errorboundary.tsx | 12 +-
packages/react/test/errorboundary.test.tsx | 251 +++++++++++++++++++--
packages/react/tslint.json | 4 +-
3 files changed, 241 insertions(+), 26 deletions(-)
diff --git a/packages/react/src/errorboundary.tsx b/packages/react/src/errorboundary.tsx
index 10b1e91a267d..5af3b130f528 100644
--- a/packages/react/src/errorboundary.tsx
+++ b/packages/react/src/errorboundary.tsx
@@ -4,10 +4,12 @@ import * as React from 'react';
export const FALLBACK_ERR_MESSAGE = 'No fallback component has been set';
export type ErrorBoundaryProps = {
+ showDialog?: boolean;
+ dialogOptions?: Sentry.ReportDialogOptions;
fallback?: React.ReactNode;
fallbackRender?(error: Error | null, componentStack: string | null, resetErrorBoundary: () => void): React.ReactNode;
onError?(error: Error, componentStack: string): void;
- onMount?(error: Error | null, componentStack: string | null): void;
+ onMount?(): void;
onReset?(error: Error | null, componentStack: string | null): void;
onUnmount?(error: Error | null, componentStack: string | null): void;
};
@@ -30,18 +32,20 @@ class ErrorBoundary extends React.Component ({
+ captureException: (err: any) => {
+ mockCaptureException(err);
+ },
+ showReportDialog: (options: any) => {
+ mockShowReportDialog(options);
+ },
+ withScope: (callback: Function) => {
+ callback({
+ setExtra: mockSetExtra,
+ });
+ },
+}));
+
+const TestApp: React.FC = ({ children, ...props }) => {
+ const [isError, setError] = React.useState(false);
+ return (
+
+ {isError ? : children}
+
+ );
+};
+
+function Bam(): JSX.Element {
+ throw new Error('boom');
+}
describe('ErrorBoundary', () => {
- const DEFAULT_PROPS: ErrorBoundaryProps = {
- fallback: Error Component
,
- fallbackRender: (error: Error, componentStack: string, resetErrorBoundary: () => void) => (
-
- {error.toString()}
- {componentStack}
-
-
- ),
- onError: jest.fn(),
- onReset: jest.fn(),
- };
-
- it('Renders children with no failure', () => {
- function Bomb(): JSX.Element {
- return Testing children
;
- }
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
+ afterEach(() => {
+ consoleErrorSpy.mockClear();
+ mockSetExtra.mockClear();
+ mockCaptureException.mockClear();
+ mockShowReportDialog.mockClear();
+ });
+
+ it('throws an error if not given a valid `fallbackRender` prop', () => {
+ expect(() => {
+ render(
+ // @ts-ignore
+
+
+ ,
+ );
+ }).toThrowError(FALLBACK_ERR_MESSAGE);
+ expect(consoleErrorSpy).toHaveBeenCalled();
+ });
+
+ it('throws an error if not given a valid `fallback` prop', () => {
+ expect(() => {
+ render(
+
+
+ ,
+ );
+ }).toThrowError(FALLBACK_ERR_MESSAGE);
+ expect(consoleErrorSpy).toHaveBeenCalled();
+ });
+
+ it('does not throw an error if a fallback is given', () => {
+ expect(() => {
+ render(
+ Error Component}>
+ children
+ ,
+ );
+ }).not.toThrowError();
+ });
+
+ it('calls `onMount` when mounted', () => {
+ const mockOnMount = jest.fn();
render(
-
-
+ Error Component} onMount={mockOnMount}>
+ children
,
);
+
+ expect(mockOnMount).toHaveBeenCalledTimes(1);
+ });
+
+ it('calls `onUnmount` when unmounted', () => {
+ const mockOnUnmount = jest.fn();
+ const { unmount } = render(
+ Error Component} onUnmount={mockOnUnmount}>
+ children
+ ,
+ );
+
+ expect(mockOnUnmount).toHaveBeenCalledTimes(0);
+ unmount();
+ expect(mockOnUnmount).toHaveBeenCalledTimes(1);
+ expect(mockOnUnmount).toHaveBeenCalledWith(null, null);
+ });
+
+ it('renders children correctly when there is no error', () => {
+ const { baseElement } = render(
+ Error Component}>
+ children
+ ,
+ );
+
+ expect(baseElement.outerHTML).toContain('children
');
+ });
+
+ describe('fallback', () => {
+ it('renders a fallback component', async () => {
+ const { baseElement } = render(
+ You have hit an error
}>
+ children
+ ,
+ );
+
+ expect(baseElement.outerHTML).toContain('children
');
+
+ const btn = screen.getByTestId('errorBtn');
+ fireEvent.click(btn);
+
+ expect(baseElement.outerHTML).not.toContain('children
');
+ expect(baseElement.outerHTML).toContain('You have hit an error
');
+ });
+
+ it('renders a fallbackRender component', async () => {
+ let errorString = '';
+ let compStack = '';
+ const { baseElement } = render(
+ {
+ errorString = error.toString();
+ compStack = componentStack;
+ return Fallback here
;
+ }}
+ >
+ children
+ ,
+ );
+
+ expect(baseElement.outerHTML).toContain('children
');
+
+ const btn = screen.getByTestId('errorBtn');
+ fireEvent.click(btn);
+
+ expect(baseElement.outerHTML).not.toContain('children
Fallback here');
+
+ expect(errorString).toBe('Error: boom');
+ expect(compStack).toBe(`
+ in Bam (created by TestApp)
+ in ErrorBoundary (created by TestApp)
+ in TestApp`);
+ });
+ });
+
+ describe('error', () => {
+ it('calls `componentDidCatch() when an error occurs`', () => {
+ const mockOnError = jest.fn();
+ render(
+ You have hit an error
} onError={mockOnError}>
+ children
+ ,
+ );
+
+ expect(mockOnError).toHaveBeenCalledTimes(0);
+ expect(mockCaptureException).toHaveBeenCalledTimes(0);
+ expect(mockSetExtra).toHaveBeenCalledTimes(0);
+
+ const btn = screen.getByTestId('errorBtn');
+ fireEvent.click(btn);
+
+ expect(mockOnError).toHaveBeenCalledTimes(1);
+ expect(mockOnError).toHaveBeenCalledWith(expect.any(Error), expect.any(String));
+
+ expect(mockCaptureException).toHaveBeenCalledTimes(1);
+ expect(mockCaptureException).toHaveBeenCalledWith(expect.any(Error));
+
+ expect(mockSetExtra).toHaveBeenCalledTimes(1);
+ expect(mockSetExtra).toHaveBeenCalledWith('componentStack', expect.any(String));
+ });
+
+ it('shows a Sentry Report Dialog with correct options', () => {
+ const options = { title: 'custom title' };
+ render(
+ You have hit an error} showDialog dialogOptions={options}>
+ children
+ ,
+ );
+
+ expect(mockShowReportDialog).toHaveBeenCalledTimes(0);
+
+ const btn = screen.getByTestId('errorBtn');
+ fireEvent.click(btn);
+
+ expect(mockShowReportDialog).toHaveBeenCalledTimes(1);
+ expect(mockShowReportDialog).toHaveBeenCalledWith(options);
+ });
+
+ it('it resets to initial state when reset', async () => {
+ const mockOnReset = jest.fn();
+ const { baseElement, debug } = render(
+ void) => (
+
+ )}
+ >
+ children
+ ,
+ );
+
+ expect(baseElement.outerHTML).toContain('children
');
+ expect(mockOnReset).toHaveBeenCalledTimes(0);
+
+ const btn = screen.getByTestId('errorBtn');
+ fireEvent.click(btn);
+
+ expect(baseElement.outerHTML).toContain('