From 85a34031aca09714d2025f805b4ae29538763ed4 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 7 Dec 2017 16:59:26 -0800 Subject: [PATCH 1/6] Add React 16 as a peer dependency. --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 1e53f0bdf6a..8da7631ddc9 100644 --- a/package.json +++ b/package.json @@ -89,5 +89,8 @@ "webpack-dev-server": "2.9.2", "yeoman-generator": "2.0.1", "yo": "2.0.0" + }, + "peerDependencies": { + "react": "^16.0.0-rc || ^16.0" } } From 9d12ce1f70f17cb179b62f8ff9190731e18b144f Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 7 Dec 2017 17:08:04 -0800 Subject: [PATCH 2/6] Export test module. --- scripts/compile-eui.sh | 4 ++-- src/index.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/compile-eui.sh b/scripts/compile-eui.sh index 9df1e2dc035..ebcd05a63bb 100755 --- a/scripts/compile-eui.sh +++ b/scripts/compile-eui.sh @@ -6,7 +6,7 @@ compile_lib() { local color_green="\033[0;32m" local color_reset="\033[0m" - mkdir -p lib/components lib/services + mkdir -p lib/components lib/services lib/test # We use tput below to save the cursor position, then restore it later in # order to overwrite "processing" messages. @@ -16,7 +16,7 @@ compile_lib() { babel \ --quiet \ --out-dir=lib \ - --ignore "**/webpack.config.js,**/test/*.js,**/*.test.js" \ + --ignore "**/webpack.config.js,**/*.test.js" \ src tput rc echo -e "${color_green}✔ Finished compiling src/ to lib/${color_reset}" >&2 diff --git a/src/index.js b/src/index.js index 80e3bea6755..a336caf9032 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,3 @@ export * from './components'; export * from './services'; +export * from './test'; From dd8c4a603723ce5186a248744864cb57aa364176 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 7 Dec 2017 20:17:18 -0800 Subject: [PATCH 3/6] Add EuiErrorBoundary. --- .../components/guide_section/guide_section.js | 19 ++++--- src-docs/src/services/routes/routes.js | 18 ++++-- .../src/services/string/render_to_html.js | 20 ++++--- src-docs/src/views/app_view.js | 23 ++++---- .../views/error_boundary/error_boundary.js | 15 +++++ .../error_boundary/error_boundary_example.js | 35 ++++++++++++ .../__snapshots__/error_boundary.test.js.snap | 30 ++++++++++ .../error_boundary/_error_boundary.scss | 19 +++++++ src/components/error_boundary/_index.scss | 1 + .../error_boundary/error_boundary.js | 56 +++++++++++++++++++ .../error_boundary/error_boundary.test.js | 43 ++++++++++++++ src/components/error_boundary/index.js | 3 + src/components/index.js | 4 ++ src/components/index.scss | 1 + 14 files changed, 258 insertions(+), 29 deletions(-) create mode 100644 src-docs/src/views/error_boundary/error_boundary.js create mode 100644 src-docs/src/views/error_boundary/error_boundary_example.js create mode 100644 src/components/error_boundary/__snapshots__/error_boundary.test.js.snap create mode 100644 src/components/error_boundary/_error_boundary.scss create mode 100644 src/components/error_boundary/_index.scss create mode 100644 src/components/error_boundary/error_boundary.js create mode 100644 src/components/error_boundary/error_boundary.test.js create mode 100644 src/components/error_boundary/index.js diff --git a/src-docs/src/components/guide_section/guide_section.js b/src-docs/src/components/guide_section/guide_section.js index 21d4928781a..f87fbdda09b 100644 --- a/src-docs/src/components/guide_section/guide_section.js +++ b/src-docs/src/components/guide_section/guide_section.js @@ -6,12 +6,13 @@ import { } from '..'; import { - EuiText, - EuiTitle, + EuiCodeBlock, + EuiErrorBoundary, EuiSpacer, - EuiTabs, EuiTab, - EuiCodeBlock, + EuiTabs, + EuiText, + EuiTitle, } from '../../../../src/components'; import { @@ -132,10 +133,12 @@ export class GuideSection extends Component { } return ( -
-
- {this.props.demo} -
+ +
+
+ {this.props.demo} +
+ ); } diff --git a/src-docs/src/services/routes/routes.js b/src-docs/src/services/routes/routes.js index 4970ea61e65..056c2e3b127 100644 --- a/src-docs/src/services/routes/routes.js +++ b/src-docs/src/services/routes/routes.js @@ -10,6 +10,10 @@ import { GuideSection, } from '../../components'; +import { + EuiErrorBoundary, +} from '../../../../src/components'; + import Slugify from '../string/slugify'; // Guidelines @@ -52,6 +56,9 @@ import { ContextMenuExample } import { DescriptionListExample } from '../../views/description_list/description_list_example'; +import { ErrorBoundaryExample } + from '../../views/error_boundary/error_boundary_example'; + import { ExpressionExample } from '../../views/expression/expression_example'; @@ -155,10 +162,12 @@ const createExample = ({ title, intro, sections }) => { })); const component = () => ( - - {intro} - {renderedSections} - + + + {intro} + {renderedSections} + + ); return { @@ -181,6 +190,7 @@ const components = [ CodeExample, ContextMenuExample, DescriptionListExample, + ErrorBoundaryExample, ExpressionExample, FlexExample, FormExample, diff --git a/src-docs/src/services/string/render_to_html.js b/src-docs/src/services/string/render_to_html.js index f61e603b550..1cd47b401c0 100644 --- a/src-docs/src/services/string/render_to_html.js +++ b/src-docs/src/services/string/render_to_html.js @@ -12,11 +12,17 @@ import html from 'html'; configure({ adapter: new EnzymeAdapter() }); export function renderToHtml(componentReference, props = {}) { - // Create the React element, render it and get its HTML, then format it prettily. - const element = React.createElement(componentReference, props); - const htmlString = render(element).html(); - return html.prettyPrint(htmlString, { - indent_size: 2, - unformatted: [], // Expand all tags, including spans - }); + // If there's a failure, just return an empty string. The actual component itself should + // trip an error boundary in the UI when it fails. + try { + // Create the React element, render it and get its HTML, then format it prettily. + const element = React.createElement(componentReference, props); + const htmlString = render(element).html(); + return html.prettyPrint(htmlString, { + indent_size: 2, + unformatted: [], // Expand all tags, including spans + }); + } catch(e) { + return ''; + } } diff --git a/src-docs/src/views/app_view.js b/src-docs/src/views/app_view.js index 7948bd51db3..87968405fce 100644 --- a/src-docs/src/views/app_view.js +++ b/src-docs/src/views/app_view.js @@ -11,6 +11,7 @@ import { } from '../components'; import { + EuiErrorBoundary, EuiPage, EuiPageBody, EuiPageContent, @@ -67,16 +68,18 @@ export class AppView extends Component { return ( - - - + + + + + diff --git a/src-docs/src/views/error_boundary/error_boundary.js b/src-docs/src/views/error_boundary/error_boundary.js new file mode 100644 index 00000000000..ec35c034ad6 --- /dev/null +++ b/src-docs/src/views/error_boundary/error_boundary.js @@ -0,0 +1,15 @@ +import React from 'react'; + +import { + EuiErrorBoundary, +} from '../../../../src/components'; + +const BadComponent = () => { + throw new Error('There was a terrible error!'); +}; + +export default () => ( + + + +); diff --git a/src-docs/src/views/error_boundary/error_boundary_example.js b/src-docs/src/views/error_boundary/error_boundary_example.js new file mode 100644 index 00000000000..c186e7cc0ed --- /dev/null +++ b/src-docs/src/views/error_boundary/error_boundary_example.js @@ -0,0 +1,35 @@ +import React from 'react'; + +import { renderToHtml } from '../../services'; + +import { + GuideSectionTypes, +} from '../../components'; + +import { + EuiCode, +} from '../../../../src/components'; + +import ErrorBoundary from './error_boundary'; +const errorBoundarySource = require('!!raw-loader!./error_boundary'); +const errorBoundaryHtml = renderToHtml(ErrorBoundary); + +export const ErrorBoundaryExample = { + title: 'ErrorBoundary', + sections: [{ + title: 'ErrorBoundary', + source: [{ + type: GuideSectionTypes.JS, + code: errorBoundarySource, + }, { + type: GuideSectionTypes.HTML, + code: errorBoundaryHtml, + }], + text: ( +

+ Use EuiErrorBoundary to prevent errors from taking down the entire app. +

+ ), + demo: , + }], +}; diff --git a/src/components/error_boundary/__snapshots__/error_boundary.test.js.snap b/src/components/error_boundary/__snapshots__/error_boundary.test.js.snap new file mode 100644 index 00000000000..323e08e4f6d --- /dev/null +++ b/src/components/error_boundary/__snapshots__/error_boundary.test.js.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`EuiErrorBoundary is rendered with an error 1`] = ` +
+
+
+

+ Error +

+

+ Error: Terrible error! +

+
+
+
+`; + +exports[`EuiErrorBoundary is rendered without an error 1`] = ` +
+ No error +
+`; diff --git a/src/components/error_boundary/_error_boundary.scss b/src/components/error_boundary/_error_boundary.scss new file mode 100644 index 00000000000..17fb958d5d3 --- /dev/null +++ b/src/components/error_boundary/_error_boundary.scss @@ -0,0 +1,19 @@ +.euiErrorBoundary { + $color1: transparentize($euiColorDanger, .75); + $color2: transparentize($euiColorDanger, .95); + + background: repeating-linear-gradient( + 45deg, + $color1, + $color1 1px, + $color2 1px, + $color2 20px + ); + overflow: auto; + padding: $euiSize; +} + + .euiErrorBoundary__text { + background-color: $euiColorEmptyShade; + padding: $euiSizeS; + } diff --git a/src/components/error_boundary/_index.scss b/src/components/error_boundary/_index.scss new file mode 100644 index 00000000000..e0dab3ec511 --- /dev/null +++ b/src/components/error_boundary/_index.scss @@ -0,0 +1 @@ +@import 'error_boundary'; diff --git a/src/components/error_boundary/error_boundary.js b/src/components/error_boundary/error_boundary.js new file mode 100644 index 00000000000..edf9f5bdf07 --- /dev/null +++ b/src/components/error_boundary/error_boundary.js @@ -0,0 +1,56 @@ +import React, { + Component, +} from 'react'; +import PropTypes from 'prop-types'; + +import { + EuiText, +} from '../text'; + +export class EuiErrorBoundary extends Component { + static propTypes = { + children: PropTypes.node, + } + + constructor(props) { + super(props); + + this.state = { + hasError: false, + error: undefined, + }; + } + + componentDidCatch(error) { + // Display fallback UI + this.setState({ + hasError: true, + error + }); + } + + render() { + const { + children, + ...rest + } = this.props; + + if (this.state.hasError) { + // You can render any custom fallback UI + return ( +
+
+ +

Error

+

+ {this.state.error && this.state.error.toString()} +

+
+
+
+ ); + } + + return children; + } +} diff --git a/src/components/error_boundary/error_boundary.test.js b/src/components/error_boundary/error_boundary.test.js new file mode 100644 index 00000000000..989e0ccfc11 --- /dev/null +++ b/src/components/error_boundary/error_boundary.test.js @@ -0,0 +1,43 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import { + requiredProps, + takeMountedSnapshot, +} from '../../test'; + +import { EuiErrorBoundary } from './error_boundary'; + +const GoodComponent = () => ( +
No error
+); + +const BadComponent = () => { + throw new Error('Terrible error!'); +}; + +describe('EuiErrorBoundary', () => { + test('is rendered without an error', () => { + const component = mount( + + + + ); + + expect(takeMountedSnapshot(component)) + .toMatchSnapshot(); + }); + + test('is rendered with an error', () => { + // Prevent the React boundary error from appearing in the terminal. + spyOn(console, 'error'); // eslint-disable-line no-undef + + const component = mount( + + + + ); + + expect(takeMountedSnapshot(component)) + .toMatchSnapshot(); + }); +}); diff --git a/src/components/error_boundary/index.js b/src/components/error_boundary/index.js new file mode 100644 index 00000000000..8cd31133ba6 --- /dev/null +++ b/src/components/error_boundary/index.js @@ -0,0 +1,3 @@ +export { + EuiErrorBoundary, +} from './error_boundary'; diff --git a/src/components/index.js b/src/components/index.js index cc0db64f9f1..4e6797d386c 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -50,6 +50,10 @@ export { EuiDescriptionListDescription, } from './description_list'; +export { + EuiErrorBoundary, +} from './error_boundary'; + export { EuiExpression, EuiExpressionButton, diff --git a/src/components/index.scss b/src/components/index.scss index d794e289452..aa702e66ea2 100644 --- a/src/components/index.scss +++ b/src/components/index.scss @@ -12,6 +12,7 @@ @import 'code_editor/index'; @import 'context_menu/index'; @import 'description_list/index'; +@import 'error_boundary/index'; @import 'expression/index'; @import 'flex/index'; @import 'form/index'; From b220aa86506d821f0c6284fbd02ef8dde206d779 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 7 Dec 2017 20:25:04 -0800 Subject: [PATCH 4/6] Fix test template requiredProps import. --- generator-eui/component/templates/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator-eui/component/templates/test.js b/generator-eui/component/templates/test.js index 4f384d6c2d3..ab9c6b7a939 100644 --- a/generator-eui/component/templates/test.js +++ b/generator-eui/component/templates/test.js @@ -1,6 +1,6 @@ import React from 'react'; import { render } from 'enzyme'; -import { requiredProps } from '../../test/required_props'; +import { requiredProps } from '../../test'; import { <%= componentName %> } from './<%= fileName %>'; From 9267cce1fddd535820ad5b2b2ff23694f1429674 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Thu, 7 Dec 2017 20:25:25 -0800 Subject: [PATCH 5/6] Update CHANGELOG. --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f47bf8b1bfe..98dd5303ce8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - Add `` component ([#174](https://github.com/elastic/eui/pull/174), [#193](https://github.com/elastic/eui/pull/193)) - Add a bold weight of 700 and apply it to `` elements by default ([#193](https://github.com/elastic/eui/pull/193)) - Icon size prop now accepts `s`. Adjusted coloring of sidenav arrows ([#178](https://github.com/elastic/eui/pull/197)) +- Add `` ([#198](https://github.com/elastic/eui/pull/198)) +- Export `test` module, which includes `findTestSubject`, `startThrowingReactWarnings`, `stopThrowingReactWarnings`, `requiredProps`, and `takeMountedSnapshot` helpers ([#198](https://github.com/elastic/eui/pull/198)) **Bug fixes** @@ -27,6 +29,7 @@ **Breaking** - Fixed a bug where table cell classes were being applied twice [(#167)](https://github.com/elastic/eui/pull/167) +- React ^16.0 is now a peer dependency ([#198](https://github.com/elastic/eui/pull/198)) # [`0.0.3`](https://github.com/elastic/eui/tree/v0.0.3) From bc3a76b3097d97c25088eb8d4c95f4bef49f62b8 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Fri, 8 Dec 2017 16:36:05 -0800 Subject: [PATCH 6/6] Move react and react-router to devDependencies. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 8da7631ddc9..ed551fbd81f 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,7 @@ "keymirror": "0.1.1", "lodash": "4.17.4", "prop-types": "15.6.0", - "react": "16.0.0", "react-ace": "5.5.0", - "react-router": "3.2.0", "serve": "6.3.1", "tabbable": "1.1.0", "uuid": "3.1.0" @@ -76,8 +74,10 @@ "postcss-loader": "2.0.8", "pre-commit": "1.2.2", "raw-loader": "0.5.1", + "react": "16.0.0", "react-dom": "16.0.0", "react-redux": "5.0.6", + "react-router": "3.2.0", "react-router-redux": "4.0.8", "react-test-renderer": "16.0.0", "redux": "3.7.2",