diff --git a/docs/reference-guides/actions/editor-actions.md b/docs/reference-guides/actions/editor-actions.md new file mode 100644 index 0000000000000..d5e37744036ee --- /dev/null +++ b/docs/reference-guides/actions/editor-actions.md @@ -0,0 +1,24 @@ +# Editor Actions + +To help you hook into the editor lifecycle and extend it, the following Actions are exposed: + +### Error Boundaries + +#### `editor.ErrorBoundary.errorLogged` + +Allows you to hook into the editor Error Boundaries' `componentDidCatch` and gives you access to the error object. + +You can use if you want to get hold of the error object that's handled by the boundaries, i.e to send them to an external error tracking tool. + +_Example_: + +```js +addAction( + 'editor.ErrorBoundary.errorLogged', + 'mu-plugin/error-capture-setup', + ( error ) => { + // error is the exception's error object + ErrorCaptureTool.captureError( error ); + } +); +``` diff --git a/packages/customize-widgets/src/components/error-boundary/index.js b/packages/customize-widgets/src/components/error-boundary/index.js index 17047e7f546ea..d1aece49b9173 100644 --- a/packages/customize-widgets/src/components/error-boundary/index.js +++ b/packages/customize-widgets/src/components/error-boundary/index.js @@ -6,6 +6,7 @@ import { __ } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; import { Warning } from '@wordpress/block-editor'; import { useCopyToClipboard } from '@wordpress/compose'; +import { doAction } from '@wordpress/hooks'; function CopyButton( { text, children } ) { const ref = useCopyToClipboard( text ); @@ -26,6 +27,8 @@ export default class ErrorBoundary extends Component { componentDidCatch( error ) { this.setState( { error } ); + + doAction( 'editor.ErrorBoundary.errorLogged', error ); } render() { diff --git a/packages/customize-widgets/src/components/test/error-boundary.js b/packages/customize-widgets/src/components/test/error-boundary.js new file mode 100644 index 0000000000000..7b9977e10f131 --- /dev/null +++ b/packages/customize-widgets/src/components/test/error-boundary.js @@ -0,0 +1,38 @@ +/** + * WordPress dependencies + */ +import * as wpHooks from '@wordpress/hooks'; +/** + * Internal dependencies + */ +import ErrorBoundary from '../error-boundary'; +/** + * External dependencies + */ +import { render } from '@testing-library/react'; + +const theError = new Error( 'Kaboom' ); + +const ChildComponent = () => { + throw theError; +}; + +describe( 'Error Boundary', () => { + describe( 'when error is thrown from a Child component', () => { + it( 'calls the `editor.ErrorBoundary.errorLogged` hook action with the error object', () => { + const doAction = jest.spyOn( wpHooks, 'doAction' ); + + render( + + + + ); + + expect( doAction ).toHaveBeenCalledWith( + 'editor.ErrorBoundary.errorLogged', + theError + ); + expect( console ).toHaveErrored(); + } ); + } ); +} ); diff --git a/packages/edit-site/src/components/error-boundary/index.js b/packages/edit-site/src/components/error-boundary/index.js index 35387fe5399c1..9e07afafd04a4 100644 --- a/packages/edit-site/src/components/error-boundary/index.js +++ b/packages/edit-site/src/components/error-boundary/index.js @@ -3,6 +3,7 @@ */ import { Component } from '@wordpress/element'; import { __ } from '@wordpress/i18n'; +import { doAction } from '@wordpress/hooks'; /** * Internal dependencies @@ -20,6 +21,10 @@ export default class ErrorBoundary extends Component { }; } + componentDidCatch( error ) { + doAction( 'editor.ErrorBoundary.errorLogged', error ); + } + static getDerivedStateFromError( error ) { return { error }; } diff --git a/packages/edit-site/src/components/test/error-boundary.js b/packages/edit-site/src/components/test/error-boundary.js new file mode 100644 index 0000000000000..7b9977e10f131 --- /dev/null +++ b/packages/edit-site/src/components/test/error-boundary.js @@ -0,0 +1,38 @@ +/** + * WordPress dependencies + */ +import * as wpHooks from '@wordpress/hooks'; +/** + * Internal dependencies + */ +import ErrorBoundary from '../error-boundary'; +/** + * External dependencies + */ +import { render } from '@testing-library/react'; + +const theError = new Error( 'Kaboom' ); + +const ChildComponent = () => { + throw theError; +}; + +describe( 'Error Boundary', () => { + describe( 'when error is thrown from a Child component', () => { + it( 'calls the `editor.ErrorBoundary.errorLogged` hook action with the error object', () => { + const doAction = jest.spyOn( wpHooks, 'doAction' ); + + render( + + + + ); + + expect( doAction ).toHaveBeenCalledWith( + 'editor.ErrorBoundary.errorLogged', + theError + ); + expect( console ).toHaveErrored(); + } ); + } ); +} ); diff --git a/packages/edit-widgets/src/components/error-boundary/index.js b/packages/edit-widgets/src/components/error-boundary/index.js index cf2098a30844d..8b941630b347c 100644 --- a/packages/edit-widgets/src/components/error-boundary/index.js +++ b/packages/edit-widgets/src/components/error-boundary/index.js @@ -6,6 +6,7 @@ import { __ } from '@wordpress/i18n'; import { Button } from '@wordpress/components'; import { Warning } from '@wordpress/block-editor'; import { useCopyToClipboard } from '@wordpress/compose'; +import { doAction } from '@wordpress/hooks'; function CopyButton( { text, children } ) { const ref = useCopyToClipboard( text ); @@ -29,6 +30,8 @@ export default class ErrorBoundary extends Component { componentDidCatch( error ) { this.setState( { error } ); + + doAction( 'editor.ErrorBoundary.errorLogged', error ); } reboot() { diff --git a/packages/edit-widgets/src/components/test/error-boundary.js b/packages/edit-widgets/src/components/test/error-boundary.js new file mode 100644 index 0000000000000..7b9977e10f131 --- /dev/null +++ b/packages/edit-widgets/src/components/test/error-boundary.js @@ -0,0 +1,38 @@ +/** + * WordPress dependencies + */ +import * as wpHooks from '@wordpress/hooks'; +/** + * Internal dependencies + */ +import ErrorBoundary from '../error-boundary'; +/** + * External dependencies + */ +import { render } from '@testing-library/react'; + +const theError = new Error( 'Kaboom' ); + +const ChildComponent = () => { + throw theError; +}; + +describe( 'Error Boundary', () => { + describe( 'when error is thrown from a Child component', () => { + it( 'calls the `editor.ErrorBoundary.errorLogged` hook action with the error object', () => { + const doAction = jest.spyOn( wpHooks, 'doAction' ); + + render( + + + + ); + + expect( doAction ).toHaveBeenCalledWith( + 'editor.ErrorBoundary.errorLogged', + theError + ); + expect( console ).toHaveErrored(); + } ); + } ); +} ); diff --git a/packages/editor/src/components/error-boundary/index.js b/packages/editor/src/components/error-boundary/index.js index c7d0824a3dee2..72e6a7680421f 100644 --- a/packages/editor/src/components/error-boundary/index.js +++ b/packages/editor/src/components/error-boundary/index.js @@ -7,6 +7,7 @@ import { Button } from '@wordpress/components'; import { select } from '@wordpress/data'; import { Warning } from '@wordpress/block-editor'; import { useCopyToClipboard } from '@wordpress/compose'; +import { doAction } from '@wordpress/hooks'; /** * Internal dependencies @@ -36,6 +37,8 @@ class ErrorBoundary extends Component { componentDidCatch( error ) { this.setState( { error } ); + + doAction( 'editor.ErrorBoundary.errorLogged', error ); } reboot() {