├── .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 | Archived Repository
5 | This code is no longer maintained. Feel free to fork it, but use it at your own risks.
6 | |
7 |
8 |
9 |
10 | # aor-dependent-input
11 |
12 | [](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 |
--------------------------------------------------------------------------------