diff options
Diffstat (limited to 'web/src')
-rw-r--r-- | web/src/js/__tests__/components/Modal/OptionSpec.js | 99 | ||||
-rw-r--r-- | web/src/js/__tests__/components/Modal/__snapshots__/ModalSpec.js.snap | 196 | ||||
-rw-r--r-- | web/src/js/__tests__/components/Modal/__snapshots__/OptionSpec.js.snap | 64 | ||||
-rw-r--r-- | web/src/js/__tests__/ducks/optionsSpec.js | 32 | ||||
-rw-r--r-- | web/src/js/__tests__/ducks/tutils.js | 6 | ||||
-rw-r--r-- | web/src/js/__tests__/ducks/ui/keyboardSpec.js | 7 | ||||
-rw-r--r-- | web/src/js/__tests__/ducks/ui/optionEditorSpec.js | 32 | ||||
-rw-r--r-- | web/src/js/__tests__/ducks/ui/optionSpec.js | 39 | ||||
-rw-r--r-- | web/src/js/components/Modal/Option.jsx | 4 | ||||
-rw-r--r-- | web/src/js/ducks/options.js | 4 | ||||
-rw-r--r-- | web/src/js/ducks/ui/option.js | 2 | ||||
-rw-r--r-- | web/src/js/ducks/ui/optionsEditor.js | 2 |
12 files changed, 404 insertions, 83 deletions
diff --git a/web/src/js/__tests__/components/Modal/OptionSpec.js b/web/src/js/__tests__/components/Modal/OptionSpec.js new file mode 100644 index 00000000..a275aee6 --- /dev/null +++ b/web/src/js/__tests__/components/Modal/OptionSpec.js @@ -0,0 +1,99 @@ +import React from 'react' +import renderer from 'react-test-renderer' +import { Options, ChoicesOption } from '../../../components/Modal/Option' + +describe('BooleanOption Component', () => { + let BooleanOption = Options['bool'], + onChangeFn = jest.fn(), + booleanOption = renderer.create( + <BooleanOption value={true} onChange={onChangeFn}/> + ), + tree = booleanOption.toJSON() + + it('should render correctly', () => { + expect(tree).toMatchSnapshot() + }) + + it('should handle onChange', () => { + let input = tree.children[0].children[0], + mockEvent = { target: { checked: true }} + input.props.onChange(mockEvent) + expect(onChangeFn).toBeCalledWith(mockEvent.target.checked) + }) +}) + +describe('StringOption Component', () => { + let StringOption = Options['str'], + onChangeFn = jest.fn(), + stringOption = renderer.create( + <StringOption value="foo" onChange={onChangeFn}/> + ), + tree = stringOption.toJSON() + + it('should render correctly', () => { + expect(tree).toMatchSnapshot() + }) + + it('should handle onChange', () => { + let mockEvent = { target: { value: 'bar' }} + tree.props.onChange(mockEvent) + expect(onChangeFn).toBeCalledWith(mockEvent.target.value) + }) + +}) + +describe('NumberOption Component', () => { + let NumberOption = Options['int'], + onChangeFn = jest.fn(), + numberOption = renderer.create( + <NumberOption value={1} onChange={onChangeFn}/> + ), + tree = numberOption.toJSON() + + it('should render correctly', () => { + expect(tree).toMatchSnapshot() + }) + + it('should handle onChange', () => { + let mockEvent = {target: { value: '2'}} + tree.props.onChange(mockEvent) + expect(onChangeFn).toBeCalledWith(2) + }) +}) + +describe('ChoiceOption Component', () => { + let onChangeFn = jest.fn(), + choiceOption = renderer.create( + <ChoicesOption value='a' choices={['a', 'b', 'c']} onChange={onChangeFn}/> + ), + tree = choiceOption.toJSON() + + it('should render correctly', () => { + expect(tree).toMatchSnapshot() + }) + + it('should handle onChange', () => { + let mockEvent = { target: {value: 'b'} } + tree.props.onChange(mockEvent) + expect(onChangeFn).toBeCalledWith(mockEvent.target.value) + }) +}) + +describe('StringOption Component', () => { + let onChangeFn = jest.fn(), + StringSequenceOption = Options['sequence of str'], + stringSequenceOption = renderer.create( + <StringSequenceOption value={['a', 'b']} onChange={onChangeFn}/> + ), + tree = stringSequenceOption.toJSON() + + it('should render correctly', () => { + expect(tree).toMatchSnapshot() + }) + + it('should handle onChange', () => { + let mockEvent = { target: {value: 'a\nb\nc\n'}} + tree.props.onChange(mockEvent) + expect(onChangeFn).toBeCalledWith(['a', 'b', 'c', '']) + }) +}) diff --git a/web/src/js/__tests__/components/Modal/__snapshots__/ModalSpec.js.snap b/web/src/js/__tests__/components/Modal/__snapshots__/ModalSpec.js.snap index 0550c1fa..bfd855bd 100644 --- a/web/src/js/__tests__/components/Modal/__snapshots__/ModalSpec.js.snap +++ b/web/src/js/__tests__/components/Modal/__snapshots__/ModalSpec.js.snap @@ -47,117 +47,165 @@ exports[`Modal Component should render correctly 2`] = ` className="modal-body" > <div - className="container-fluid" + className="form-horizontal" > <div - className="row" + className="form-group" > <div - className="col-sm-8" + className="col-xs-6" > - booleanOption + <label + htmlFor="booleanOption" + > + booleanOption + </label> + <div + className="help-block small" + > + foo + </div> </div> <div - className="col-sm-4" + className="col-xs-6" > - <input - checked={false} - onBlur={[Function]} - onChange={[Function]} - onFocus={[Function]} - onMouseLeave={[Function]} - onMouseOver={[Function]} - type="checkbox" - /> + <div + className="" + > + <div + className="checkbox" + > + <label> + <input + checked={false} + name="booleanOption" + onChange={[Function]} + onKeyDown={[Function]} + type="checkbox" + /> + Enable + </label> + </div> + </div> </div> </div> <div - className="row" + className="form-group" > <div - className="col-sm-8" + className="col-xs-6" > - choiceOption + <label + htmlFor="choiceOption" + > + choiceOption + </label> + <div + className="help-block small" + > + foo + </div> </div> <div - className="col-sm-4" + className="col-xs-6" > - <select - name="choiceOption" - onBlur={[Function]} - onChange={[Function]} - onFocus={[Function]} - onMouseLeave={[Function]} - onMouseOver={[Function]} - selected="b" + <div + className="" > - <option - value="a" - > - - a - - </option> - <option - value="b" + <select + className="form-control" + name="choiceOption" + onChange={[Function]} + onKeyDown={[Function]} + selected="b" > - - b - - </option> - <option - value="c" - > - - c - - </option> - </select> + <option + value="a" + > + a + </option> + <option + value="b" + > + b + </option> + <option + value="c" + > + c + </option> + </select> + </div> </div> </div> <div - className="row" + className="form-group" > <div - className="col-sm-8" + className="col-xs-6" > - intOption + <label + htmlFor="intOption" + > + intOption + </label> + <div + className="help-block small" + > + foo + </div> </div> <div - className="col-sm-4" + className="col-xs-6" > - <input - onBlur={[Function]} - onChange={[Function]} - onFocus={[Function]} - onKeyDown={[Function]} - onMouseLeave={[Function]} - onMouseOver={[Function]} - type="number" - value={1} - /> + <div + className="" + > + <input + className="form-control" + name="intOption" + onChange={[Function]} + onKeyDown={[Function]} + type="number" + value={1} + /> + </div> </div> </div> <div - className="row" + className="form-group" > <div - className="col-sm-8" + className="col-xs-6" > - strOption + <label + htmlFor="strOption" + > + strOption + </label> + <div + className="help-block small" + > + foo + </div> </div> <div - className="col-sm-4" + className="col-xs-6" > - <input - onBlur={[Function]} - onChange={[Function]} - onFocus={[Function]} - onKeyDown={[Function]} - onMouseLeave={[Function]} - onMouseOver={[Function]} - type="text" - value="str content" + <div + className="has-error" + > + <input + className="form-control" + name="strOption" + onChange={[Function]} + onKeyDown={[Function]} + type="text" + value="str content" + /> + </div> + <div + className="small text-danger" /> </div> </div> diff --git a/web/src/js/__tests__/components/Modal/__snapshots__/OptionSpec.js.snap b/web/src/js/__tests__/components/Modal/__snapshots__/OptionSpec.js.snap new file mode 100644 index 00000000..514e0eb5 --- /dev/null +++ b/web/src/js/__tests__/components/Modal/__snapshots__/OptionSpec.js.snap @@ -0,0 +1,64 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`BooleanOption Component should render correctly 1`] = ` +<div + className="checkbox" +> + <label> + <input + checked={true} + onChange={[Function]} + type="checkbox" + /> + Enable + </label> +</div> +`; + +exports[`ChoiceOption Component should render correctly 1`] = ` +<select + onChange={[Function]} + selected="a" +> + <option + value="a" + > + a + </option> + <option + value="b" + > + b + </option> + <option + value="c" + > + c + </option> +</select> +`; + +exports[`NumberOption Component should render correctly 1`] = ` +<input + onChange={[Function]} + type="number" + value={1} +/> +`; + +exports[`StringOption Component should render correctly 1`] = ` +<input + onChange={[Function]} + type="text" + value="foo" +/> +`; + +exports[`StringOption Component should render correctly 2`] = ` +<textarea + onChange={[Function]} + rows={2} + value="a +b" +/> +`; diff --git a/web/src/js/__tests__/ducks/optionsSpec.js b/web/src/js/__tests__/ducks/optionsSpec.js index 62019715..0925fcc1 100644 --- a/web/src/js/__tests__/ducks/optionsSpec.js +++ b/web/src/js/__tests__/ducks/optionsSpec.js @@ -1,6 +1,9 @@ -jest.mock('../../utils') - import reduceOptions, * as OptionsActions from '../../ducks/options' +import configureStore from 'redux-mock-store' +import thunk from 'redux-thunk' +import * as OptionsEditorActions from '../../ducks/ui/optionsEditor' + +const mockStore = configureStore([ thunk ]) describe('option reducer', () => { it('should return initial state', () => { @@ -18,8 +21,31 @@ describe('option reducer', () => { }) }) +let store = mockStore() + describe('option actions', () => { + it('should be possible to update option', () => { - expect(reduceOptions(undefined, OptionsActions.update())).toEqual({}) + let mockResponse = { status: 200 }, + promise = Promise.resolve(mockResponse) + global.fetch = r => { return promise } + store.dispatch(OptionsActions.update('foo', 'bar')) + expect(store.getActions()).toEqual([ + { type: OptionsEditorActions.OPTION_UPDATE_START, option: 'foo', value: 'bar'} + ]) + store.clearActions() + }) +}) + +describe('sendUpdate', () => { + + it('should handle error', () => { + let mockResponse = { status: 400, text: p => Promise.resolve('error') }, + promise = Promise.resolve(mockResponse) + global.fetch = r => { return promise } + OptionsActions.pureSendUpdate('bar', 'error') + expect(store.getActions()).toEqual([ + { type: OptionsEditorActions.OPTION_UPDATE_SUCCESS, option: 'foo'} + ]) }) }) diff --git a/web/src/js/__tests__/ducks/tutils.js b/web/src/js/__tests__/ducks/tutils.js index a3e9c168..22240448 100644 --- a/web/src/js/__tests__/ducks/tutils.js +++ b/web/src/js/__tests__/ducks/tutils.js @@ -35,6 +35,12 @@ export function TStore(){ }, modal: { activeModal: undefined + }, + optionsEditor: { + booleanOption: { isUpdating: true, error: false }, + strOption: { error: true }, + intOption: {}, + choiceOption: {}, } }, settings: { diff --git a/web/src/js/__tests__/ducks/ui/keyboardSpec.js b/web/src/js/__tests__/ducks/ui/keyboardSpec.js index 500733cb..cf17943f 100644 --- a/web/src/js/__tests__/ducks/ui/keyboardSpec.js +++ b/web/src/js/__tests__/ducks/ui/keyboardSpec.js @@ -6,6 +6,7 @@ import reduceFlows from '../../../ducks/flows' import reduceUI from '../../../ducks/ui/index' import * as flowsActions from '../../../ducks/flows' import * as UIActions from '../../../ducks/ui/flow' +import * as modalActions from '../../../ducks/ui/modal' import configureStore from 'redux-mock-store' import thunk from 'redux-thunk' import { fetchApi } from '../../../utils' @@ -154,4 +155,10 @@ describe('onKeyDown', () => { expect(fetchApi).not.toBeCalled() }) + it('should close modal', () => { + store.getState().ui.modal.activeModal = true + store.dispatch(createKeyEvent(Key.ESC)) + expect(store.getActions()).toEqual([ {type: modalActions.HIDE_MODAL} ]) + }) + }) diff --git a/web/src/js/__tests__/ducks/ui/optionEditorSpec.js b/web/src/js/__tests__/ducks/ui/optionEditorSpec.js new file mode 100644 index 00000000..df9161a4 --- /dev/null +++ b/web/src/js/__tests__/ducks/ui/optionEditorSpec.js @@ -0,0 +1,32 @@ +import reduceOptionsEditor, * as optionsEditorActions from '../../../ducks/ui/optionsEditor' +import { HIDE_MODAL } from '../../../ducks/ui/modal' + +describe('optionsEditor reducer', () => { + + it('should return initial state', () => { + expect(reduceOptionsEditor(undefined, {})).toEqual({}) + }) + + let state = undefined + it('should handle option update start', () => { + state = reduceOptionsEditor(undefined, optionsEditorActions.startUpdate('foo', 'bar')) + expect(state).toEqual({ foo: {error: false, isUpdating: true, value: 'bar'}}) + }) + + it('should handle option update success', () => { + expect(reduceOptionsEditor(state, optionsEditorActions.updateSuccess('foo'))).toEqual({foo: undefined}) + }) + + it('should handle option update error', () => { + state = reduceOptionsEditor(state, optionsEditorActions.updateError('foo', 'errorMsg')) + expect(state).toEqual({ foo: {error: 'errorMsg', isUpdating: false, value: 'bar'}}) + // boolean type + state = reduceOptionsEditor(undefined, optionsEditorActions.startUpdate('foo', true)) + state = reduceOptionsEditor(state, optionsEditorActions.updateError('foo', 'errorMsg')) + expect(state).toEqual({ foo: {error: 'errorMsg', isUpdating: false, value: false}}) + }) + + it('should handle hide modal', () => { + expect(reduceOptionsEditor(undefined, {type: HIDE_MODAL})).toEqual({}) + }) +}) diff --git a/web/src/js/__tests__/ducks/ui/optionSpec.js b/web/src/js/__tests__/ducks/ui/optionSpec.js new file mode 100644 index 00000000..4b6b43cc --- /dev/null +++ b/web/src/js/__tests__/ducks/ui/optionSpec.js @@ -0,0 +1,39 @@ +import reduceOption, * as optionActions from '../../../ducks/ui/option' + +describe('option reducer', () => { + + it('should return the initial state', () => { + expect(reduceOption(undefined, {})).toEqual({}) + }) + + let state = undefined + it('should handle option update start', () => { + state = reduceOption(undefined, { + type: optionActions.OPTION_UPDATE_START, option: 'foo', value: 'bar' + }) + expect(state).toEqual({ + foo: { + error: false, + isUpdating: true, + value: 'bar' + } + }) + }) + + it('should handle option update success', () => { + expect(reduceOption(state, { + type: optionActions.OPTION_UPDATE_SUCCESS, option: 'foo' + })).toEqual({}) + }) + + it('should handle option update error', () => { + expect(reduceOption(undefined, { + type: optionActions.OPTION_UPDATE_ERROR, option: 'foo', error: 'errorMsg' + })).toEqual({ + foo: { + error: 'errorMsg', + isUpdating: false, + } + }) + }) +}) diff --git a/web/src/js/components/Modal/Option.jsx b/web/src/js/components/Modal/Option.jsx index 1a847602..58b863d1 100644 --- a/web/src/js/components/Modal/Option.jsx +++ b/web/src/js/components/Modal/Option.jsx @@ -70,7 +70,7 @@ ChoicesOption.PropTypes = { value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, } -function ChoicesOption({ value, onChange, choices, ...props }) { +export function ChoicesOption({ value, onChange, choices, ...props }) { return ( <select onChange={(e) => onChange(e.target.value)} @@ -100,7 +100,7 @@ function StringSequenceOption({ value, onChange, ...props }) { /> } -const Options = { +export const Options = { "bool": BooleanOption, "str": StringOption, "int": NumberOption, diff --git a/web/src/js/ducks/options.js b/web/src/js/ducks/options.js index 3277fb9e..06144a3c 100644 --- a/web/src/js/ducks/options.js +++ b/web/src/js/ducks/options.js @@ -25,7 +25,7 @@ export default function reducer(state = defaultState, action) { } } -let sendUpdate = (option, value, dispatch) => { +export function pureSendUpdate (option, value, dispatch) { fetchApi.put('/options', { [option]: value }).then(response => { if (response.status === 200) { dispatch(optionsEditorActions.updateSuccess(option)) @@ -36,7 +36,7 @@ let sendUpdate = (option, value, dispatch) => { } }) } -sendUpdate = _.throttle(sendUpdate, 700, { leading: true, trailing: true }) +let sendUpdate = _.throttle(pureSendUpdate, 700, { leading: true, trailing: true }) export function update(option, value) { return dispatch => { diff --git a/web/src/js/ducks/ui/option.js b/web/src/js/ducks/ui/option.js index 6aba4998..a58fde4c 100644 --- a/web/src/js/ducks/ui/option.js +++ b/web/src/js/ducks/ui/option.js @@ -12,7 +12,7 @@ export default function reducer(state = defaultState, action) { return { ...state, [action.option]: { - isUpdate: true, + isUpdating: true, value: action.value, error: false, } diff --git a/web/src/js/ducks/ui/optionsEditor.js b/web/src/js/ducks/ui/optionsEditor.js index 23dfe01a..a8a8f69e 100644 --- a/web/src/js/ducks/ui/optionsEditor.js +++ b/web/src/js/ducks/ui/optionsEditor.js @@ -14,7 +14,7 @@ export default function reducer(state = defaultState, action) { return { ...state, [action.option]: { - isUpdate: true, + isUpdating: true, value: action.value, error: false, } |