From 61c99b9b4051f0269114555655b4e28ee71e41cf Mon Sep 17 00:00:00 2001
From: connorhaugh <49422820+connorhaugh@users.noreply.github.com>
Date: Tue, 14 Feb 2023 15:21:43 -0500
Subject: [PATCH] feat: add error boundary (#246)
* feat: add error boundary
---
src/editors/EditorPage.jsx | 27 ++++---
.../__snapshots__/EditorPage.test.jsx.snap | 80 ++++++++++---------
.../ErrorBoundary/ErrorPage.jsx | 62 ++++++++++++++
.../sharedComponents/ErrorBoundary/index.jsx | 46 +++++++++++
.../ErrorBoundary/index.test.jsx | 39 +++++++++
5 files changed, 204 insertions(+), 50 deletions(-)
create mode 100644 src/editors/sharedComponents/ErrorBoundary/ErrorPage.jsx
create mode 100644 src/editors/sharedComponents/ErrorBoundary/index.jsx
create mode 100644 src/editors/sharedComponents/ErrorBoundary/index.test.jsx
diff --git a/src/editors/EditorPage.jsx b/src/editors/EditorPage.jsx
index 1ea1533db..1bd030ca1 100644
--- a/src/editors/EditorPage.jsx
+++ b/src/editors/EditorPage.jsx
@@ -4,6 +4,7 @@ import { Provider } from 'react-redux';
import store from './data/store';
import Editor from './Editor';
+import ErrorBoundary from './sharedComponents/ErrorBoundary';
export const EditorPage = ({
courseId,
@@ -13,18 +14,20 @@ export const EditorPage = ({
studioEndpointUrl,
onClose,
}) => (
-
-
-
+
+
+
+
+
);
EditorPage.defaultProps = {
blockId: null,
diff --git a/src/editors/__snapshots__/EditorPage.test.jsx.snap b/src/editors/__snapshots__/EditorPage.test.jsx.snap
index 0d67afe3b..256b94d4c 100644
--- a/src/editors/__snapshots__/EditorPage.test.jsx.snap
+++ b/src/editors/__snapshots__/EditorPage.test.jsx.snap
@@ -1,47 +1,51 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Editor Page snapshots props besides blockType default to null 1`] = `
-
+
-
-
+ >
+
+
+
`;
exports[`Editor Page snapshots rendering correctly with expected Input 1`] = `
-
+
-
-
+ >
+
+
+
`;
diff --git a/src/editors/sharedComponents/ErrorBoundary/ErrorPage.jsx b/src/editors/sharedComponents/ErrorBoundary/ErrorPage.jsx
new file mode 100644
index 000000000..c67e6fa56
--- /dev/null
+++ b/src/editors/sharedComponents/ErrorBoundary/ErrorPage.jsx
@@ -0,0 +1,62 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import {
+ Button, Container, Row, Col,
+} from '@edx/paragon';
+
+import { FormattedMessage } from '@edx/frontend-platform/i18n';
+
+/**
+ * An error page that displays a generic message for unexpected errors. Also contains a "Try
+ * Again" button to refresh the page.
+ *
+ * @memberof module:React
+ * @extends {Component}
+ */
+class ErrorPage extends Component {
+ /* istanbul ignore next */
+ reload() {
+ global.location.reload();
+ }
+
+ render() {
+ const { message } = this.props;
+ return (
+
+
+
+
+
+
+ {message && (
+
+ )}
+
+
+
+
+ );
+ }
+}
+
+ErrorPage.propTypes = {
+ message: PropTypes.string,
+};
+
+ErrorPage.defaultProps = {
+ message: null,
+};
+
+export default ErrorPage;
diff --git a/src/editors/sharedComponents/ErrorBoundary/index.jsx b/src/editors/sharedComponents/ErrorBoundary/index.jsx
new file mode 100644
index 000000000..ecdbe8c08
--- /dev/null
+++ b/src/editors/sharedComponents/ErrorBoundary/index.jsx
@@ -0,0 +1,46 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+
+import {
+ logError,
+} from '@edx/frontend-platform/logging';
+
+import ErrorPage from './ErrorPage';
+
+/**
+ * Error boundary component used to log caught errors and display the error page.
+ *
+ * @memberof module:React
+ * @extends {Component}
+ */
+export default class ErrorBoundary extends Component {
+ constructor(props) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError() {
+ // Update state so the next render will show the fallback UI.
+ return { hasError: true };
+ }
+
+ componentDidCatch(error, info) {
+ logError(error, { stack: info.componentStack });
+ }
+
+ render() {
+ if (this.state.hasError) {
+ return ;
+ }
+
+ return this.props.children;
+ }
+}
+
+ErrorBoundary.propTypes = {
+ children: PropTypes.node,
+};
+
+ErrorBoundary.defaultProps = {
+ children: null,
+};
diff --git a/src/editors/sharedComponents/ErrorBoundary/index.test.jsx b/src/editors/sharedComponents/ErrorBoundary/index.test.jsx
new file mode 100644
index 000000000..ed4c8c674
--- /dev/null
+++ b/src/editors/sharedComponents/ErrorBoundary/index.test.jsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import { mount } from 'enzyme';
+
+import {
+ logError,
+} from '@edx/frontend-platform/logging';
+import ErrorBoundary from './index';
+
+jest.mock('@edx/frontend-platform/logging', () => ({
+ logError: jest.fn(),
+}));
+
+describe('ErrorBoundary', () => {
+ it('should render children if no error', () => {
+ const component = (
+
+ Yay
+
+ );
+ const wrapper = mount(component);
+
+ const element = wrapper.find('div');
+ expect(element.text()).toEqual('Yay');
+ });
+
+ it('should render ErrorPage if it has an error', () => {
+ const ExplodingComponent = () => {
+ throw new Error('booyah');
+ };
+ const component = (
+
+
+
+ );
+ mount(component);
+ expect(logError).toHaveBeenCalledTimes(1);
+ expect(logError).toHaveBeenCalledWith(new Error('booyah'), { stack: '\n in ExplodingComponent\n in ErrorBoundary (created by WrapperComponent)\n in WrapperComponent' });
+ });
+});