diff --git a/client/src/app/tabs/EditorTab.js b/client/src/app/tabs/EditorTab.js index 8722ad015..4cf8562ab 100644 --- a/client/src/app/tabs/EditorTab.js +++ b/client/src/app/tabs/EditorTab.js @@ -7,6 +7,7 @@ import React, { PureComponent } from 'react'; +import ErrorTab from './ErrorTab'; import MultiSheetTab from './MultiSheetTab'; @@ -14,9 +15,19 @@ export function createTab(tabName, providers) { class EditorTab extends PureComponent { + static getDerivedStateFromError(error) { + return { + hasError: true + }; + } + constructor(props) { super(props); + this.state = { + hasError: false + }; + this.tabRef = React.createRef(); } @@ -39,13 +50,15 @@ export function createTab(tabName, providers) { } = this.props; return ( - + this.state.hasError ? + : + ); } diff --git a/client/src/app/tabs/ErrorTab.js b/client/src/app/tabs/ErrorTab.js new file mode 100644 index 000000000..561b440ff --- /dev/null +++ b/client/src/app/tabs/ErrorTab.js @@ -0,0 +1,41 @@ +/** + * Copyright (c) Camunda Services GmbH. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React, { PureComponent } from 'react'; + +import css from './ErrorTab.less'; + +import { + TabContainer +} from '../primitives'; + + +export default class ErrorTab extends PureComponent { + + triggerAction() {} + + render() { + return ( + +
+

+ Unexpected Error +

+

+ An unexpected error occurred in this tab. Please click the link below to report + an issue on GitHub. You can also save the latest known state of the tab. +

+

+ + Report an issue. + +

+
+
+ ); + } +} diff --git a/client/src/app/tabs/ErrorTab.less b/client/src/app/tabs/ErrorTab.less new file mode 100644 index 000000000..0de152f74 --- /dev/null +++ b/client/src/app/tabs/ErrorTab.less @@ -0,0 +1,18 @@ +:local(.ErrorTab) { + + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + flex: 1; + + h1 { + font-size: 26px; + } + + p { + font-size: 16px; + max-width: 60%; + text-align: justify; + } +} \ No newline at end of file diff --git a/client/src/app/tabs/__tests__/EditorTabSpec.js b/client/src/app/tabs/__tests__/EditorTabSpec.js index d5d85d423..f19060e57 100644 --- a/client/src/app/tabs/__tests__/EditorTabSpec.js +++ b/client/src/app/tabs/__tests__/EditorTabSpec.js @@ -7,26 +7,55 @@ import React from 'react'; -import { createTab } from './../EditorTab'; +import TestRenderer from 'react-test-renderer'; -import { mount } from 'enzyme'; +import { createTab } from './../EditorTab'; +import ErrorTab from './../ErrorTab'; import { + Editor as MockEditor, providers as defaultProviders, tab as defaultTab } from './mocks'; +/* global sinon */ + describe('', function() { describe('render', function() { it('should render', function() { + + // when const { instance } = renderEditorTab(); - expect(instance).to.exist; + // then + expect(instance, 'did not render').to.exist; + }); + + }); + + + describe('error handling', function() { + + afterEach(sinon.restore); + + + it('should display ErrorTab when editor fails', function() { + + // given + sinon.stub(MockEditor.prototype, 'render').throwsException(); + + // when + const { + wrapper + } = renderEditorTab(); + + // then + verifyChildIsPresent(wrapper, ErrorTab); }); }); @@ -39,14 +68,15 @@ describe('', function() { function noop() {} function renderEditorTab({ - tab = defaultTab, onError = noop, - onShown = noop + onShown = noop, + providers = defaultProviders, + tab = defaultTab } = {}) { - const EditorTab = createTab(defaultTab.name, defaultProviders); + const EditorTab = createTab(tab.name, providers); - const wrapper = mount( + const testRenderer = TestRenderer.create( ); - const instance = wrapper.instance(); + const instance = testRenderer.getInstance(); return { instance, - wrapper + wrapper: testRenderer.root }; -} \ No newline at end of file +} + +function verifyChildIsPresent(wrapper, childType) { + function isChildPresent() { + return wrapper.findByType(childType); + } + + expect(isChildPresent, `did not display ${childType.name}`).to.not.throw(); +} diff --git a/client/src/app/tabs/__tests__/mocks/index.js b/client/src/app/tabs/__tests__/mocks/index.js index c0411be85..109571bd1 100644 --- a/client/src/app/tabs/__tests__/mocks/index.js +++ b/client/src/app/tabs/__tests__/mocks/index.js @@ -5,49 +5,49 @@ * LICENSE file in the root directory of this source tree. */ -import React, { Component } from 'react'; - -export class Editor extends Component { +import React, { Component } from 'react'; + +export class Editor extends Component { constructor(props) { super(props); - - this.xml = null; - } - - triggerAction() {} - - setXML(xml) { - this.xml = xml; - } - - getXML() { - return this.xml; - } - - render() { - return
; - } -} - -export const providers = [{ - type: 'editor', - editor: Editor, - defaultName: 'Editor' -}, { - type: 'fallback', - editor: Editor, - defaultName: 'Fallback', - isFallback: true -}]; - -export const tab = { - id: 42, - name: 'foo.bar', - type: 'bar', - title: 'unsaved', - file: { - name: 'foo.bar', - contents: '', - path: null - } + + this.xml = null; + } + + triggerAction() {} + + setXML(xml) { + this.xml = xml; + } + + getXML() { + return this.xml; + } + + render() { + return
; + } +} + +export const providers = [{ + type: 'editor', + editor: Editor, + defaultName: 'Editor' +}, { + type: 'fallback', + editor: Editor, + defaultName: 'Fallback', + isFallback: true +}]; + +export const tab = { + id: 42, + name: 'foo.bar', + type: 'bar', + title: 'unsaved', + file: { + name: 'foo.bar', + contents: '', + path: null + } }; \ No newline at end of file