diff --git a/App.js b/App.js index 515ad839..b5089187 100644 --- a/App.js +++ b/App.js @@ -12,6 +12,7 @@ import SnippetEditor from "./app/SnippetEditorExample"; // Required to make Material UI work with touch screens. import injectTapEventPlugin from "react-tap-event-plugin"; import Checkbox from "./composites/Plugin/Shared/components/Checkbox"; +import KeywordInput from "./composites/Plugin/Shared/components/KeywordInput"; const components = [ { @@ -62,6 +63,14 @@ const components = [ onChange={ event => console.log( event ) } />, }, + { + id: "focus-keyword", + name: "Keyword", + component: , + }, { id: "sidebar-collapsible", name: "Sidebar Collapsible", diff --git a/composites/Plugin/Shared/components/KeywordInput.js b/composites/Plugin/Shared/components/KeywordInput.js new file mode 100644 index 00000000..dddc530f --- /dev/null +++ b/composites/Plugin/Shared/components/KeywordInput.js @@ -0,0 +1,121 @@ +import React from "react"; +import styled from "styled-components"; +import PropTypes from "prop-types"; + +import colors from "../../../../style-guide/colors.json"; + +let KeywordField = styled.input` + margin-right: 0.5em; + border-color: ${ props => props.borderColor }; +`; + +const ErrorText = styled.div` + font-size: 1em; + color: ${ colors.$color_red }; + margin: 1em 0; + min-height: 1.8em; +`; + +const ErrorMessage = "Are you trying to use multiple keywords? You should add them separately below."; + +class KeywordInput extends React.Component { + /** + * Constructs a KeywordInput component + * + * @param {Object} props The props for this input field component. + * @param {String} props.id The id of the KeywordInput. + * @param {IconsButton} props.label The label of the KeywordInput. + * @param {boolean} props.keyword The initial keyword passed to the state. + * + * @returns {void} + */ + constructor( props ) { + super( props ); + + this.onChange = this.handleChange.bind( this ); + + this.state = { + showErrorMessage: false, + keyword: props.keyword, + }; + } + + /** + * Checks the keyword input for comma-separated words + * + * @param {String} keywordText The text of the input + * + * @returns {void} + */ + checkKeywordInput( keywordText ) { + let separatedWords = keywordText.split( "," ); + if ( separatedWords.length > 1 ) { + this.setState( { showErrorMessage: true } ); + } else { + this.setState( { showErrorMessage: false } ); + } + } + + /** + * Displays the error message + * + * @param {String} input The text of the input + * + * @returns {Element} ErrorText The error message element + */ + displayErrorMessage( input = "" ) { + if ( this.state.showErrorMessage && input !== "" ) { + return ( + + { ErrorMessage } + + ); + } + } + + /** + * Handles changes in the KeywordInput. + * + * @param {Event} event The onChange event. + * + * @returns {void} Calls the checkKeywordInput-function. + */ + handleChange( event ) { + this.setState( { keyword: event.target.value } ); + this.checkKeywordInput( event.target.value ); + } + + /** + * Renders an input field, a label, and if the condition is met, an error message. + * + * @returns {ReactElement} The KeywordField react component including its label and eventual error message. + */ + render() { + const color = this.state.showErrorMessage ? "red" : "white"; + return( + + + + { this.displayErrorMessage( this.state.keyword ) } + + ); + } +} + +KeywordInput.propTypes = { + id: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + keyword: PropTypes.string, +}; + +KeywordInput.defaultProps = { + keyword: "", +}; + +export default KeywordInput; + diff --git a/composites/Plugin/Shared/tests/KeywordInputTest.js b/composites/Plugin/Shared/tests/KeywordInputTest.js new file mode 100644 index 00000000..1611dd6f --- /dev/null +++ b/composites/Plugin/Shared/tests/KeywordInputTest.js @@ -0,0 +1,57 @@ +import React from "react"; +import renderer from "react-test-renderer"; +import EnzymeAdapter from "enzyme-adapter-react-16"; +import Enzyme from "enzyme/build/index"; + +import KeywordInput from "../components/KeywordInput"; + +Enzyme.configure( { adapter: new EnzymeAdapter() } ); + +describe( KeywordInput, () => { + it( "matches the snapshot by default", () => { + const component = renderer.create( + { + } } label="test label"/> + ); + + let tree = component.toJSON(); + expect( tree ).toMatchSnapshot(); + } ); + + it( "does not display the error message for a single keyword", () => { + const wrapper = Enzyme.mount( + + ); + wrapper.find( "input" ).simulate( "change", { + target: { + value: "Keyword1", + }, + } ); + expect( wrapper.state().showErrorMessage ).toEqual( false ); + } ); + + it( "does not display the error message for two words separated by whitespace", () => { + const wrapper = Enzyme.mount( + + ); + wrapper.find( "input" ).simulate( "change", { + target: { + value: "Keyword1 Keyword2", + }, + } ); + expect( wrapper.state().showErrorMessage ).toEqual( false ); + } ); + + it( "displays the error message for comma-separated words", () => { + const wrapper = Enzyme.mount( + + ); + wrapper.find( "input" ).simulate( "change", { + target: { + value: "Keyword1, Keyword2", + }, + } ); + expect( wrapper.state().showErrorMessage ).toEqual( true ); + expect( wrapper.find( "div" ).text() ).toBeTruthy(); + } ); +} ); diff --git a/composites/Plugin/Shared/tests/__snapshots__/KeywordInputTest.js.snap b/composites/Plugin/Shared/tests/__snapshots__/KeywordInputTest.js.snap new file mode 100644 index 00000000..fdc6e459 --- /dev/null +++ b/composites/Plugin/Shared/tests/__snapshots__/KeywordInputTest.js.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`KeywordInput matches the snapshot by default 1`] = ` +Array [ + , + .c0 { + margin-right: 0.5em; + border-color: white; +} + +, +] +`; diff --git a/yarn.lock b/yarn.lock index 0a13ab12..f6802b2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2195,6 +2195,12 @@ enzyme-adapter-utils@^1.3.0: object.assign "^4.0.4" prop-types "^15.6.0" +enzyme-react-intl@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/enzyme-react-intl/-/enzyme-react-intl-2.0.0.tgz#1000400c0ab2ec6e7393f3f09cf8435cd3c884bf" + dependencies: + jsonfile "^4.0.0" + enzyme-to-json@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/enzyme-to-json/-/enzyme-to-json-3.3.3.tgz#ede45938fb309cd87ebd4386f60c754525515a07" @@ -3063,7 +3069,7 @@ globule@^1.0.0: lodash "~4.17.4" minimatch "~3.0.2" -graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -4365,6 +4371,12 @@ json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"