├── .npmignore ├── .gitignore ├── src ├── index.js ├── setupTests.js ├── getValue.js ├── getValue.spec.js ├── DependentField.js ├── DependentInput.js ├── DependentField.spec.js └── DependentInput.spec.js ├── .babelrc ├── .travis.yml ├── .eslintrc ├── Makefile ├── LICENSE ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | .babelrc 3 | .eslintrc 4 | Makefile -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | .nvmrc 4 | .nyc_output 5 | .coverage-cache 6 | coverage 7 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export DependentInput from './DependentInput'; 2 | export DependentField from './DependentField'; 3 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | import Enzyme from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | Enzyme.configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /src/getValue.js: -------------------------------------------------------------------------------- 1 | import get from 'lodash.get'; 2 | 3 | export default (value, path) => { 4 | if (typeof value === 'object') { 5 | return get(value, path); 6 | } 7 | 8 | return value; 9 | }; 10 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "stage-0", 5 | "react" 6 | ], 7 | "plugins": [ 8 | [ 9 | "transform-runtime", 10 | { 11 | "polyfill": false, 12 | "regenerator": true 13 | } 14 | ] 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "7.7.2" 5 | 6 | env: 7 | global: 8 | - NODE_ENV=test 9 | 10 | dist: trusty 11 | 12 | cache: 13 | yarn: true 14 | directories: 15 | - node_modules 16 | 17 | before_install: 18 | - npm install -g yarn 19 | 20 | install: 21 | - "make --silent install" 22 | -------------------------------------------------------------------------------- /src/getValue.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect'; 2 | import getValue from './getValue'; 3 | 4 | describe('getValue', () => { 5 | it('returns directly the value if it is not an object', () => { 6 | expect(getValue(10, 'foo.bar')).toEqual(10); 7 | }); 8 | it('returns the value at specified path if it is an object', () => { 9 | expect(getValue({ foo: { bar: 10 } }, 'foo.bar')).toEqual(10); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "es6": true, 5 | "jest": true, 6 | "node": true 7 | }, 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:import/errors", 11 | "plugin:import/warnings", 12 | "plugin:react/recommended", 13 | "prettier", 14 | "prettier/react" 15 | ], 16 | "plugins": [ 17 | "react", 18 | "prettier" 19 | ], 20 | "rules": { 21 | "prettier/prettier": ["error", { 22 | "printWidth": 120, 23 | "singleQuote": true, 24 | "tabWidth": 4, 25 | "trailingComma": "all" 26 | }], 27 | "import/no-extraneous-dependencies": "off", 28 | "no-console": ["error", { "allow": ["warn", "error"] }], 29 | "no-unused-vars": ["error", { "ignoreRestSiblings": true }] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build help 2 | 3 | help: 4 | @grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 5 | 6 | install: package.json ## Install dependencies 7 | @yarn 8 | 9 | clean: ## Clean up the lib folder for building 10 | @rm -rf lib 11 | 12 | build: clean ## Compile ES6 files to JS 13 | @NODE_ENV=production ./node_modules/.bin/babel \ 14 | --out-dir=lib \ 15 | --ignore=*.spec.js \ 16 | ./src 17 | 18 | watch: ## continuously compile ES6 files to JS 19 | @NODE_ENV=production ./node_modules/.bin/babel \ 20 | --out-dir=lib \ 21 | --ignore='*.spec.js' \ 22 | --watch \ 23 | ./src 24 | 25 | test: ## Launch unit tests 26 | @NODE_ENV=test ./node_modules/.bin/jest --setupTestFrameworkScriptFile ./src/setupTests.js 27 | 28 | 29 | watch-test: ## Launch unit tests and watch for changes 30 | @NODE_ENV=test ./node_modules/.bin/jest --watch --setupTestFrameworkScriptFile ./src/setupTests.js 31 | 32 | format: ## Format the source code 33 | @./node_modules/.bin/eslint --fix ./src 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 marmelab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aor-dependent-input", 3 | "version": "1.2.0", 4 | "description": "A component for displaying input depending on other inputs values in Admin-on-rest", 5 | "main": "./lib/index.js", 6 | "repository": "https://github.com/marmelab/aor-dependent-input.git", 7 | "authors": [ 8 | "Gildas Garcia" 9 | ], 10 | "license": "MIT", 11 | "scripts": { 12 | "build": "make build", 13 | "clean": "make clean", 14 | "format": "make format", 15 | "precommit": "lint-staged", 16 | "prepublish": "make build", 17 | "test": "make test", 18 | "watch-test": "make watch-test" 19 | }, 20 | "lint-staged": { 21 | "*.js": [ 22 | "eslint --fix ./src", 23 | "git add" 24 | ] 25 | }, 26 | "devDependencies": { 27 | "admin-on-rest": "^1.3.2", 28 | "babel-cli": "~6.26.0", 29 | "babel-core": "~6.26.0", 30 | "babel-eslint": "8.0.1", 31 | "babel-jest": "21.2.0", 32 | "babel-preset-es2015": "~6.24.1", 33 | "babel-preset-react": "~6.24.1", 34 | "babel-preset-stage-0": "~6.24.1", 35 | "enzyme": "~3.1.0", 36 | "enzyme-adapter-react-16": "~1.0.1", 37 | "eslint": "~4.8.0", 38 | "eslint-config-prettier": "~2.6.0", 39 | "eslint-plugin-import": "~2.7.0", 40 | "eslint-plugin-jsx-a11y": "~6.0.2", 41 | "eslint-plugin-mocha": "~4.11.0", 42 | "eslint-plugin-prettier": "~2.3.1", 43 | "eslint-plugin-react": "~7.4.0", 44 | "expect": "~21.2.1", 45 | "husky": "~0.14.3", 46 | "istanbul-cobertura-badger": "~1.3.0", 47 | "jest": "~21.2.1", 48 | "lint-staged": "~4.2.3", 49 | "prettier": "~1.7.4", 50 | "react": "^15.5.0", 51 | "react-dom": "^15.5.0", 52 | "react-test-renderer": "~16.0.0", 53 | "regenerator-runtime": "~0.11.0" 54 | }, 55 | "dependencies": { 56 | "babel-plugin-transform-runtime": "~6.23.0", 57 | "lodash.get": "~4.4.2", 58 | "prop-types": "~15.6.0" 59 | }, 60 | "peerDependencies": { 61 | "admin-on-rest": "^1.3.2", 62 | "react": "^15.6.2", 63 | "react-dom": "^15.6.2" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/DependentField.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | import get from 'lodash.get'; 5 | import set from 'lodash.set'; 6 | import FormField from 'admin-on-rest/lib/mui/form/FormField'; 7 | import getValue from './getValue'; 8 | 9 | export const DependentFieldComponent = ({ children, show, dependsOn, value, resolve, ...props }) => { 10 | if (!show) { 11 | return null; 12 | } 13 | 14 | if (Array.isArray(children)) { 15 | return ( 16 |
17 | {React.Children.map(children, child => ( 18 |
23 | 24 |
25 | ))} 26 |
27 | ); 28 | } 29 | 30 | return ( 31 |
32 | 33 |
34 | ); 35 | }; 36 | 37 | DependentFieldComponent.propTypes = { 38 | children: PropTypes.node.isRequired, 39 | dependsOn: PropTypes.any, 40 | record: PropTypes.object, 41 | resolve: PropTypes.func, 42 | show: PropTypes.bool.isRequired, 43 | value: PropTypes.any, 44 | }; 45 | 46 | export const mapStateToProps = (state, { record, resolve, dependsOn, value }) => { 47 | if (resolve && (dependsOn === null || typeof dependsOn === 'undefined')) { 48 | return { show: resolve(record, dependsOn, value) }; 49 | } 50 | 51 | if (resolve && !Array.isArray(dependsOn)) { 52 | return { show: resolve(getValue(record, dependsOn)) }; 53 | } 54 | 55 | if (resolve && Array.isArray(dependsOn)) { 56 | const obj = dependsOn.reduce((acc, path) => { 57 | const value = get(record, path); 58 | return set(acc, path, value); 59 | }, {}); 60 | return { show: resolve(obj) }; 61 | } 62 | 63 | if (Array.isArray(dependsOn) && Array.isArray(value)) { 64 | return { 65 | show: dependsOn.reduce((acc, s, index) => acc && getValue(record, s) === value[index], true), 66 | }; 67 | } 68 | 69 | if (typeof value === 'undefined') { 70 | if (Array.isArray(dependsOn)) { 71 | return { 72 | show: dependsOn.reduce((acc, s) => acc && !!getValue(record, s), true), 73 | }; 74 | } 75 | 76 | return { show: !!getValue(record, dependsOn) }; 77 | } 78 | 79 | return { show: getValue(record, dependsOn) === value }; 80 | }; 81 | 82 | export default connect(mapStateToProps)(DependentFieldComponent); 83 | -------------------------------------------------------------------------------- /src/DependentInput.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | import { formValueSelector, getFormValues } from 'redux-form'; 5 | import get from 'lodash.get'; 6 | import FormField from 'admin-on-rest/lib/mui/form/FormField'; 7 | import getValue from './getValue'; 8 | 9 | const REDUX_FORM_NAME = 'record-form'; 10 | 11 | export const DependentInputComponent = ({ children, show, dependsOn, value, resolve, ...props }) => { 12 | if (!show) { 13 | return null; 14 | } 15 | 16 | if (Array.isArray(children)) { 17 | return ( 18 |
19 | {React.Children.map(children, child => ( 20 |
25 | 26 |
27 | ))} 28 |
29 | ); 30 | } 31 | 32 | return ( 33 |
34 | 35 |
36 | ); 37 | }; 38 | 39 | DependentInputComponent.propTypes = { 40 | children: PropTypes.node.isRequired, 41 | show: PropTypes.bool.isRequired, 42 | dependsOn: PropTypes.any, 43 | value: PropTypes.any, 44 | resolve: PropTypes.func, 45 | formName: PropTypes.string, 46 | }; 47 | 48 | export const mapStateToProps = (state, { resolve, dependsOn, value, formName = REDUX_FORM_NAME }) => { 49 | if (resolve && (dependsOn === null || typeof dependsOn === 'undefined')) { 50 | const values = getFormValues(formName)(state); 51 | return { dependsOnValue: values, show: resolve(values, dependsOn, value) }; 52 | } 53 | 54 | let formValue; 55 | // get the current form values from redux-form 56 | if (Array.isArray(dependsOn)) { 57 | // We have to destructure the array here as redux-form does not accept an array of fields 58 | formValue = formValueSelector(formName)(state, ...dependsOn); 59 | } else { 60 | formValue = formValueSelector(formName)(state, dependsOn); 61 | } 62 | 63 | if (resolve) { 64 | return { 65 | dependsOnValue: formValue, 66 | show: resolve(formValue, dependsOn), 67 | }; 68 | } 69 | 70 | if (Array.isArray(dependsOn) && Array.isArray(value)) { 71 | return { 72 | dependsOnValue: formValue, 73 | show: dependsOn.reduce((acc, s, index) => acc && get(formValue, s) === value[index], true), 74 | }; 75 | } 76 | 77 | if (typeof value === 'undefined') { 78 | if (Array.isArray(dependsOn)) { 79 | return { 80 | dependsOnValue: formValue, 81 | show: dependsOn.reduce((acc, s) => acc && !!getValue(formValue, s), true), 82 | }; 83 | } 84 | 85 | return { dependsOnValue: formValue, show: !!formValue }; 86 | } 87 | 88 | return { dependsOnValue: formValue, show: formValue === value }; 89 | }; 90 | 91 | export default connect(mapStateToProps)(DependentInputComponent); 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 |
archivedArchived Repository
5 | This code is no longer maintained. Feel free to fork it, but use it at your own risks. 6 |
9 | 10 | # aor-dependent-input 11 | 12 | [![Build Status](https://travis-ci.org/marmelab/aor-dependent-input.svg?branch=master)](https://travis-ci.org/marmelab/aor-dependent-input) 13 | 14 | A component for displaying inputs and fields depending on other inputs or fields values in [Admin-on-rest](https://github.com/marmelab/admin-on-rest). 15 | 16 | - [Installation](#installation) 17 | - [Usage](#installation) 18 | - [API](#api) 19 | 20 | ## Installation 21 | 22 | Install with: 23 | 24 | ```sh 25 | npm install --save aor-dependent-input 26 | ``` 27 | 28 | or 29 | 30 | ```sh 31 | yarn add aor-dependent-input 32 | ``` 33 | 34 | ## Usage 35 | 36 | Check that the field specified by `dependsOn` has a value (a truthy value): 37 | 38 | ```js 39 | import { DependentInput } from 'aor-dependent-input'; 40 | 41 | export const UserCreate = (props) => ( 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | ); 53 | ``` 54 | 55 | Check that the field specified by `dependsOn` has a specific value: 56 | 57 | ```js 58 | import { DependentInput } from 'aor-dependent-input'; 59 | 60 | export const PostCreate = (props) => ( 61 | 62 | 63 | 64 | 65 | 70 | 71 | 72 | 77 | 78 | 79 | 80 | 83 | 84 | 85 | 86 | 89 | 90 | 91 | 92 | ); 93 | ``` 94 | 95 | Check that the field specified by `dependsOn` matches a custom constraint: 96 | 97 | ```js 98 | import { DependentInput } from 'aor-dependent-input'; 99 | 100 | const checkCustomConstraint = (value) => value.startsWith('programming')); 101 | 102 | export const PostCreate = (props) => ( 103 | 104 | 105 | 106 | 113 | 114 | 115 | 120 | 121 | 122 | 123 | ); 124 | ``` 125 | 126 | All powers! Check whether the current full record matches your constraints: 127 | 128 | ```js 129 | import { DependentInput } from 'aor-dependent-input'; 130 | 131 | const checkRecord = (record) => record.firstName && record.lastName); 132 | 133 | export const UserCreate = (props) => ( 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | ); 145 | ``` 146 | 147 | ## API 148 | 149 | The `DependentInput` and `DependentField` components accepts the following props: 150 | 151 | ### dependsOn 152 | 153 | Either a string indicating the name of the field to check (eg: `hasEmail`) or an array of fields to check (eg: `['firstName', 'lastName']`). 154 | 155 | You can specify deep paths such as `author.firstName`. 156 | 157 | ### value 158 | 159 | If not specified, only check that the field(s) specified by `dependsOn` have a truthy value. 160 | 161 | You may specify a single value or an array of values. Deep paths will be correctly retrieved and compared to the specified values. 162 | 163 | If both `value` and `resolve` are specified, `value` will be ignored. 164 | 165 | ### resolve 166 | 167 | The `resolve` prop accepts a function which must return either `true` to display the child input or `false` to hide it. 168 | 169 | If the `dependsOn` prop is specified, `resolve` will be called with either the value of the field specified by `dependsOn` (when a single field name was specified as `dependsOn`) or with an object matching the specified paths. 170 | 171 | **Note**: When specifying deep paths (eg: `author.firstName`), `resolve` will be called with an object matching the specified structure. For example, when passing `['author.firstName', 'author.lastName']` as `dependsOn`, the `resolve` function will be passed the following object: 172 | 173 | ```js 174 | { author: { firstName: 'bValue', lastName: 'cValue' } } 175 | ``` 176 | 177 | If `dependsOn` is not specified, `resolve` will be called with the current form values (`DependentInput`) or the full record (`DependentField`). 178 | 179 | If both `value` and `resolve` are specified, `value` will be ignored. 180 | 181 | ## Re-rendering the DependentInput children when the values of the dependencies change 182 | 183 | This could be necessary to implement cascaded select. For example, a song may have a genre and a sub genre, which are retrieved with calls to an external service not hosted in our API. 184 | This is how we could display only the sub genres for the selected genre: 185 | 186 | ```js 187 | // in SubGenreInput.js 188 | import React, { Component } from 'react'; 189 | import { translate, SelectInput } from 'admin-on-rest'; 190 | import fetchSubGenres from './fetchSubGenres'; 191 | 192 | class SubGenreInput extends Component { 193 | state = { 194 | subgenres: [], 195 | } 196 | 197 | // This is necessary so that the SelectInput receive all its required props 198 | // from redux-form 199 | static defaultProps = { addField: true }; 200 | 201 | componentDidMount() { 202 | this.fetchData(this.props); 203 | } 204 | 205 | componentWillReceiveProps(nextProps) { 206 | if (nextProps.dependsOnValue !== this.props.dependsOnValue) { 207 | this.fetchData(nextProps); 208 | } 209 | } 210 | 211 | fetchData(props) { 212 | fetchSubGenres(props.dependsOnValue).then(subgenres => { 213 | this.setState({ subgenres }); 214 | }) 215 | } 216 | 217 | render() { 218 | return 219 | } 220 | } 221 | 222 | SubGenreInput.propTypes = SelectInput.propTypes; 223 | SubGenreInput.defaultProps = SelectInput.defaultProps; 224 | 225 | export default SubGenreInput; 226 | ``` 227 | 228 | ## Contributing 229 | 230 | Run the tests with this command: 231 | 232 | ```sh 233 | make test 234 | ``` 235 | 236 | Coverage data is available in `./coverage` after executing `make test`. 237 | 238 | An HTML report is generated in `./coverage/lcov-report/index.html`. 239 | -------------------------------------------------------------------------------- /src/DependentField.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import expect from 'expect'; 3 | import { shallow } from 'enzyme'; 4 | import { DependentFieldComponent as DependentField, mapStateToProps } from './DependentField'; 5 | 6 | describe('mapStateToProps', () => { 7 | describe('with resolve function', () => { 8 | test('returns { show: false } if the resolve returns false', () => { 9 | const resolve = jest.fn(() => false); 10 | expect(mapStateToProps({}, { resolve })).toEqual({ show: false }); 11 | }); 12 | 13 | test('returns { show: true } if the resolve returns true', () => { 14 | const resolve = jest.fn(() => true); 15 | expect(mapStateToProps({}, { resolve })).toEqual({ show: true }); 16 | }); 17 | }); 18 | 19 | describe('with only dependsOn specified as a string', () => { 20 | test('returns { show: false } if the form does not have a truthy value for the field matching dependsOn', () => { 21 | expect( 22 | mapStateToProps( 23 | { 24 | form: { 25 | 'record-form': { 26 | values: { 27 | lastName: 'blublu', 28 | }, 29 | }, 30 | }, 31 | }, 32 | { 33 | dependsOn: 'firstName', 34 | }, 35 | ), 36 | ).toEqual({ show: false }); 37 | }); 38 | 39 | test('returns { show: true } if the form has a truthy value for the field matching dependsOn', () => { 40 | expect( 41 | mapStateToProps( 42 | {}, 43 | { 44 | dependsOn: 'firstName', 45 | record: { 46 | firstName: 'blublu', 47 | }, 48 | }, 49 | ), 50 | ).toEqual({ show: true }); 51 | }); 52 | }); 53 | 54 | describe('with only dependsOn specified as a deep path string', () => { 55 | test('returns { show: false } if the form does not have a truthy value for the field matching dependsOn', () => { 56 | expect( 57 | mapStateToProps( 58 | {}, 59 | { 60 | dependsOn: 'author.firstName', 61 | record: { 62 | author: { 63 | lastName: 'blublu', 64 | }, 65 | }, 66 | }, 67 | ), 68 | ).toEqual({ show: false }); 69 | }); 70 | 71 | test('returns { show: true } if the form has a truthy value for the field matching dependsOn', () => { 72 | expect( 73 | mapStateToProps( 74 | {}, 75 | { 76 | dependsOn: 'author.firstName', 77 | record: { 78 | author: { 79 | firstName: 'blublu', 80 | }, 81 | }, 82 | }, 83 | ), 84 | ).toEqual({ show: true }); 85 | }); 86 | }); 87 | 88 | describe('with dependsOn specified as a string and a specific value', () => { 89 | test('returns { show: false } if the form does not have the specific value for the field matching dependsOn', () => { 90 | expect( 91 | mapStateToProps( 92 | {}, 93 | { 94 | dependsOn: 'firstName', 95 | value: 'foo', 96 | record: { 97 | firstName: 'bar', 98 | }, 99 | }, 100 | ), 101 | ).toEqual({ show: false }); 102 | }); 103 | 104 | test('returns { show: true } if the form have the specific value for the field matching dependsOn', () => { 105 | expect( 106 | mapStateToProps( 107 | {}, 108 | { 109 | dependsOn: 'firstName', 110 | value: 'foo', 111 | record: { 112 | firstName: 'foo', 113 | }, 114 | }, 115 | ), 116 | ).toEqual({ show: true }); 117 | }); 118 | }); 119 | 120 | describe('with only dependsOn specified as an array', () => { 121 | test('returns { show: false } if the form does not have a truthy value for the fields matching dependsOn', () => { 122 | expect( 123 | mapStateToProps( 124 | {}, 125 | { 126 | dependsOn: ['firstName', 'lastName'], 127 | record: { 128 | lastName: 'blublu', 129 | }, 130 | }, 131 | ), 132 | ).toEqual({ show: false }); 133 | }); 134 | 135 | test('returns { show: true } if the form has a truthy value for the fields matching dependsOn', () => { 136 | expect( 137 | mapStateToProps( 138 | {}, 139 | { 140 | dependsOn: ['firstName', 'lastName'], 141 | record: { 142 | firstName: 'blublu', 143 | lastName: 'blublu', 144 | }, 145 | }, 146 | ), 147 | ).toEqual({ show: true }); 148 | }); 149 | }); 150 | 151 | describe('with only dependsOn specified as an array with deep path strings', () => { 152 | test('returns { show: false } if the form does not have a truthy value for the fields matching dependsOn', () => { 153 | expect( 154 | mapStateToProps( 155 | {}, 156 | { 157 | dependsOn: ['author.firstName', 'date'], 158 | record: { 159 | date: new Date().toDateString(), 160 | author: { 161 | lastName: 'blublu', 162 | }, 163 | }, 164 | }, 165 | ), 166 | ).toEqual({ show: false }); 167 | }); 168 | 169 | test('returns { show: true } if the form has a truthy value for the fields matching dependsOn', () => { 170 | expect( 171 | mapStateToProps( 172 | {}, 173 | { 174 | dependsOn: ['author.firstName', 'date'], 175 | record: { 176 | date: new Date().toDateString(), 177 | author: { 178 | firstName: 'blublu', 179 | }, 180 | }, 181 | }, 182 | ), 183 | ).toEqual({ show: true }); 184 | }); 185 | }); 186 | 187 | describe('with dependsOn specified as an array and specific values as an array', () => { 188 | test('returns { show: false } if the form does not have the specific values for the fields matching dependsOn', () => { 189 | expect( 190 | mapStateToProps( 191 | {}, 192 | { 193 | dependsOn: ['author.firstName', 'category'], 194 | record: { 195 | category: 'bar', 196 | author: { 197 | firstName: 'bar', 198 | }, 199 | }, 200 | value: ['foo', 'bar'], 201 | }, 202 | ), 203 | ).toEqual({ show: false }); 204 | }); 205 | 206 | test('returns { show: true } if the form have the specific values for the fields matching dependsOn', () => { 207 | expect( 208 | mapStateToProps( 209 | {}, 210 | { 211 | dependsOn: ['author.firstName', 'category'], 212 | record: { 213 | category: 'bar', 214 | author: { 215 | firstName: 'foo', 216 | }, 217 | }, 218 | value: ['foo', 'bar'], 219 | }, 220 | ), 221 | ).toEqual({ show: true }); 222 | }); 223 | }); 224 | 225 | describe('with dependsOn specified as an array and resolve', () => { 226 | test('returns { show: false } if the resolve function returns false', () => { 227 | expect( 228 | mapStateToProps( 229 | {}, 230 | { 231 | dependsOn: ['author.firstName', 'category'], 232 | record: { 233 | category: 'bar', 234 | author: { 235 | firstName: 'bar', 236 | lastName: 'bar', 237 | }, 238 | }, 239 | resolve: values => { 240 | return values.author.firstName === 'foo' && values.category === 'bar'; 241 | }, 242 | }, 243 | ), 244 | ).toEqual({ show: false }); 245 | }); 246 | 247 | test('returns { show: true } if the resolve function returns true', () => { 248 | expect( 249 | mapStateToProps( 250 | {}, 251 | { 252 | dependsOn: ['author.firstName', 'category'], 253 | record: { 254 | category: 'bar', 255 | author: { 256 | firstName: 'foo', 257 | lastName: 'bar', 258 | }, 259 | }, 260 | resolve: values => { 261 | return values.author.firstName === 'foo' && values.category === 'bar'; 262 | }, 263 | }, 264 | ), 265 | ).toEqual({ show: true }); 266 | }); 267 | }); 268 | }); 269 | 270 | describe('', () => { 271 | test('returns null when show prop is false', () => { 272 | const wrapper = shallow( 273 | 274 | 275 | , 276 | ); 277 | expect(wrapper.type() === null); 278 | }); 279 | 280 | test('returns a unique FormField element when passed a unique child', () => { 281 | const wrapper = shallow( 282 | 283 | 284 | , 285 | ); 286 | 287 | expect(wrapper.name()).toEqual('div'); 288 | expect(wrapper.prop('className')).toEqual('aor-input-aSource'); 289 | const formFields = wrapper.find('FormField'); 290 | expect(formFields.length).toEqual(1); 291 | expect(formFields.at(0).prop('input')).toEqual(); 292 | }); 293 | 294 | test('returns a span with FormField children for each passed child', () => { 295 | const wrapper = shallow( 296 | 297 | 298 | 299 | 300 | , 301 | ); 302 | 303 | expect(wrapper.at(0).type()).toEqual('div'); 304 | const formFields = wrapper.find('FormField'); 305 | expect(formFields.length).toEqual(3); 306 | 307 | expect(formFields.at(0).prop('input')).toEqual(); 308 | expect(formFields.at(1).prop('input')).toEqual(); 309 | expect(formFields.at(2).prop('input')).toEqual(); 310 | }); 311 | }); 312 | -------------------------------------------------------------------------------- /src/DependentInput.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import expect from 'expect'; 3 | import { shallow } from 'enzyme'; 4 | import { DependentInputComponent as DependentInput, mapStateToProps } from './DependentInput'; 5 | 6 | describe('mapStateToProps', () => { 7 | describe('with resolve function', () => { 8 | test('returns { show: false } if the resolve returns false', () => { 9 | const resolve = jest.fn(() => false); 10 | expect( 11 | mapStateToProps( 12 | { 13 | form: { 14 | 'record-form': { 15 | values: { 16 | lastName: 'blublu', 17 | }, 18 | }, 19 | }, 20 | }, 21 | { resolve }, 22 | ), 23 | ).toEqual({ 24 | show: false, 25 | dependsOnValue: { 26 | lastName: 'blublu', 27 | }, 28 | }); 29 | }); 30 | 31 | test('returns { show: true } if the resolve returns true', () => { 32 | const resolve = jest.fn(() => true); 33 | expect( 34 | mapStateToProps( 35 | { 36 | form: { 37 | 'record-form': { 38 | values: { 39 | lastName: 'blublu', 40 | }, 41 | }, 42 | }, 43 | }, 44 | { resolve }, 45 | ), 46 | ).toEqual({ 47 | show: true, 48 | dependsOnValue: { 49 | lastName: 'blublu', 50 | }, 51 | }); 52 | }); 53 | }); 54 | 55 | describe('with only dependsOn specified as a string', () => { 56 | test('returns { show: false } if the form does not have a truthy value for the field matching dependsOn', () => { 57 | expect( 58 | mapStateToProps( 59 | { 60 | form: { 61 | 'record-form': { 62 | values: { 63 | lastName: 'blublu', 64 | }, 65 | }, 66 | }, 67 | }, 68 | { 69 | dependsOn: 'firstName', 70 | }, 71 | ), 72 | ).toEqual({ show: false, dependsOnValue: undefined }); 73 | }); 74 | 75 | test('returns { show: true } if the form has a truthy value for the field matching dependsOn', () => { 76 | expect( 77 | mapStateToProps( 78 | { 79 | form: { 80 | 'record-form': { 81 | values: { 82 | firstName: 'blublu', 83 | }, 84 | }, 85 | }, 86 | }, 87 | { dependsOn: 'firstName' }, 88 | ), 89 | ).toEqual({ show: true, dependsOnValue: 'blublu' }); 90 | }); 91 | }); 92 | 93 | describe('with only dependsOn specified as a deep path string', () => { 94 | test('returns { show: false } if the form does not have a truthy value for the field matching dependsOn', () => { 95 | expect( 96 | mapStateToProps( 97 | { 98 | form: { 99 | 'record-form': { 100 | values: { 101 | author: { 102 | lastName: 'blublu', 103 | }, 104 | }, 105 | }, 106 | }, 107 | }, 108 | { dependsOn: 'author.firstName' }, 109 | ), 110 | ).toEqual({ show: false, dependsOnValue: undefined }); 111 | }); 112 | 113 | test('returns { show: true } if the form has a truthy value for the field matching dependsOn', () => { 114 | expect( 115 | mapStateToProps( 116 | { 117 | form: { 118 | 'record-form': { 119 | values: { 120 | author: { 121 | firstName: 'blublu', 122 | }, 123 | }, 124 | }, 125 | }, 126 | }, 127 | { dependsOn: 'author.firstName' }, 128 | ), 129 | ).toEqual({ show: true, dependsOnValue: 'blublu' }); 130 | }); 131 | }); 132 | 133 | describe('with dependsOn specified as a string and a specific value', () => { 134 | test('returns { show: false } if the form does not have the specific value for the field matching dependsOn', () => { 135 | expect( 136 | mapStateToProps( 137 | { 138 | form: { 139 | 'record-form': { 140 | values: { 141 | firstName: 'bar', 142 | }, 143 | }, 144 | }, 145 | }, 146 | { dependsOn: 'firstName', value: 'foo' }, 147 | ), 148 | ).toEqual({ show: false, dependsOnValue: 'bar' }); 149 | }); 150 | 151 | test('returns { show: true } if the form have the specific value for the field matching dependsOn', () => { 152 | expect( 153 | mapStateToProps( 154 | { 155 | form: { 156 | 'record-form': { 157 | values: { 158 | firstName: 'foo', 159 | }, 160 | }, 161 | }, 162 | }, 163 | { dependsOn: 'firstName', value: 'foo' }, 164 | ), 165 | ).toEqual({ show: true, dependsOnValue: 'foo' }); 166 | }); 167 | }); 168 | 169 | describe('with dependsOn specified as a string and resolve', () => { 170 | test('returns { show: false } if the resolve function returns false', () => { 171 | expect( 172 | mapStateToProps( 173 | { 174 | form: { 175 | 'record-form': { 176 | values: { 177 | firstName: 'bar', 178 | }, 179 | }, 180 | }, 181 | }, 182 | { dependsOn: 'firstName', resolve: value => value === 'foo' }, 183 | ), 184 | ).toEqual({ show: false, dependsOnValue: 'bar' }); 185 | }); 186 | 187 | test('returns { show: true } if the resolve function returns true', () => { 188 | expect( 189 | mapStateToProps( 190 | { 191 | form: { 192 | 'record-form': { 193 | values: { 194 | firstName: 'foo', 195 | }, 196 | }, 197 | }, 198 | }, 199 | { dependsOn: 'firstName', resolve: value => value === 'foo' }, 200 | ), 201 | ).toEqual({ show: true, dependsOnValue: 'foo' }); 202 | }); 203 | }); 204 | 205 | describe('with only dependsOn specified as an array', () => { 206 | test('returns { show: false } if the form does not have a truthy value for the fields matching dependsOn', () => { 207 | expect( 208 | mapStateToProps( 209 | { 210 | form: { 211 | 'record-form': { 212 | values: { 213 | lastName: 'blublu', 214 | }, 215 | }, 216 | }, 217 | }, 218 | { dependsOn: ['firstName', 'lastName'] }, 219 | ), 220 | ).toEqual({ 221 | show: false, 222 | dependsOnValue: { 223 | lastName: 'blublu', 224 | }, 225 | }); 226 | }); 227 | 228 | test('returns { show: true } if the form has a truthy value for the fields matching dependsOn', () => { 229 | expect( 230 | mapStateToProps( 231 | { 232 | form: { 233 | 'record-form': { 234 | values: { 235 | firstName: 'blublu', 236 | lastName: 'blublu', 237 | }, 238 | }, 239 | }, 240 | }, 241 | { dependsOn: ['firstName', 'lastName'] }, 242 | ), 243 | ).toEqual({ 244 | show: true, 245 | dependsOnValue: { 246 | firstName: 'blublu', 247 | lastName: 'blublu', 248 | }, 249 | }); 250 | }); 251 | }); 252 | 253 | describe('with only dependsOn specified as an array with deep path strings', () => { 254 | const date = new Date().toDateString(); 255 | 256 | test('returns { show: false } if the form does not have a truthy value for the fields matching dependsOn', () => { 257 | expect( 258 | mapStateToProps( 259 | { 260 | form: { 261 | 'record-form': { 262 | values: { 263 | date, 264 | author: { 265 | lastName: 'blublu', 266 | }, 267 | }, 268 | }, 269 | }, 270 | }, 271 | { dependsOn: ['author.firstName', 'date'] }, 272 | ), 273 | ).toEqual({ 274 | show: false, 275 | dependsOnValue: { date }, 276 | }); 277 | }); 278 | 279 | test('returns { show: true } if the form has a truthy value for the fields matching dependsOn', () => { 280 | expect( 281 | mapStateToProps( 282 | { 283 | form: { 284 | 'record-form': { 285 | values: { 286 | date, 287 | author: { 288 | firstName: 'blublu', 289 | }, 290 | }, 291 | }, 292 | }, 293 | }, 294 | { dependsOn: ['author.firstName', 'date'] }, 295 | ), 296 | ).toEqual({ 297 | show: true, 298 | dependsOnValue: { 299 | date, 300 | author: { 301 | firstName: 'blublu', 302 | }, 303 | }, 304 | }); 305 | }); 306 | }); 307 | 308 | describe('with dependsOn specified as an array and specific values as an array', () => { 309 | test('returns { show: false } if the form does not have the specific values for the fields matching dependsOn', () => { 310 | expect( 311 | mapStateToProps( 312 | { 313 | form: { 314 | 'record-form': { 315 | values: { 316 | category: 'bar', 317 | author: { 318 | firstName: 'bar', 319 | }, 320 | }, 321 | }, 322 | }, 323 | }, 324 | { 325 | dependsOn: ['author.firstName', 'category'], 326 | value: ['foo', 'bar'], 327 | }, 328 | ), 329 | ).toEqual({ 330 | show: false, 331 | dependsOnValue: { 332 | category: 'bar', 333 | author: { 334 | firstName: 'bar', 335 | }, 336 | }, 337 | }); 338 | }); 339 | 340 | test('returns { show: true } if the form have the specific values for the fields matching dependsOn', () => { 341 | expect( 342 | mapStateToProps( 343 | { 344 | form: { 345 | 'record-form': { 346 | values: { 347 | category: 'bar', 348 | author: { 349 | firstName: 'foo', 350 | }, 351 | }, 352 | }, 353 | }, 354 | }, 355 | { 356 | dependsOn: ['author.firstName', 'category'], 357 | value: ['foo', 'bar'], 358 | }, 359 | ), 360 | ).toEqual({ 361 | show: true, 362 | dependsOnValue: { 363 | category: 'bar', 364 | author: { 365 | firstName: 'foo', 366 | }, 367 | }, 368 | }); 369 | }); 370 | }); 371 | 372 | describe('with dependsOn specified as an array and resolve', () => { 373 | test('returns { show: false } if the resolve function returns false', () => { 374 | expect( 375 | mapStateToProps( 376 | { 377 | form: { 378 | 'record-form': { 379 | values: { 380 | category: 'bar', 381 | author: { 382 | firstName: 'bar', 383 | }, 384 | }, 385 | }, 386 | }, 387 | }, 388 | { 389 | dependsOn: ['author.firstName', 'category'], 390 | resolve: values => { 391 | return values.author.firstName === 'foo' && values.category === 'bar'; 392 | }, 393 | }, 394 | ), 395 | ).toEqual({ 396 | show: false, 397 | dependsOnValue: { 398 | category: 'bar', 399 | author: { 400 | firstName: 'bar', 401 | }, 402 | }, 403 | }); 404 | }); 405 | 406 | test('returns { show: true } if the resolve function returns true', () => { 407 | expect( 408 | mapStateToProps( 409 | { 410 | form: { 411 | 'record-form': { 412 | values: { 413 | category: 'bar', 414 | author: { 415 | firstName: 'foo', 416 | }, 417 | }, 418 | }, 419 | }, 420 | }, 421 | { 422 | dependsOn: ['author.firstName', 'category'], 423 | resolve: values => { 424 | return values.author.firstName === 'foo' && values.category === 'bar'; 425 | }, 426 | }, 427 | ), 428 | ).toEqual({ 429 | show: true, 430 | dependsOnValue: { 431 | category: 'bar', 432 | author: { 433 | firstName: 'foo', 434 | }, 435 | }, 436 | }); 437 | }); 438 | }); 439 | }); 440 | 441 | describe('', () => { 442 | test('returns null when show prop is false', () => { 443 | const wrapper = shallow( 444 | 445 | 446 | , 447 | ); 448 | expect(wrapper.type() === null); 449 | }); 450 | 451 | test('returns a unique FormField element when passed a unique child', () => { 452 | const wrapper = shallow( 453 | 454 | 455 | , 456 | ); 457 | 458 | expect(wrapper.name()).toEqual('div'); 459 | expect(wrapper.prop('className')).toEqual('aor-input-aSource'); 460 | const formFields = wrapper.find('FormField'); 461 | expect(formFields.length).toEqual(1); 462 | expect(formFields.at(0).prop('input')).toEqual(); 463 | }); 464 | 465 | test('returns a span with FormField children for each passed child', () => { 466 | const wrapper = shallow( 467 | 468 | 469 | 470 | 471 | , 472 | ); 473 | 474 | expect(wrapper.at(0).type()).toEqual('div'); 475 | const formFields = wrapper.find('FormField'); 476 | expect(formFields.length).toEqual(3); 477 | 478 | expect(formFields.at(0).prop('input')).toEqual(); 479 | expect(formFields.at(1).prop('input')).toEqual(); 480 | expect(formFields.at(2).prop('input')).toEqual(); 481 | }); 482 | }); 483 | --------------------------------------------------------------------------------