Skip to content

Commit d0870b9

Browse files
authored
Merge pull request #280 from rvsia/commonMultipleChoiceList
Common multiple choice list
2 parents c144501 + 8beb3df commit d0870b9

File tree

8 files changed

+1051
-641
lines changed

8 files changed

+1051
-641
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { composeValidators } from '@data-driven-forms/react-form-renderer';
4+
5+
import { formGroup } from './prop-types-templates';
6+
7+
const MultipleChoiceList = ({ validate, FieldProvider, Wrapper, Checkbox, ...props }) => (
8+
<FieldProvider
9+
{ ...props }
10+
validate={ composeValidators(props.validate || []) }
11+
render={ ({
12+
label,
13+
isRequired,
14+
helperText,
15+
meta,
16+
options,
17+
isDisabled,
18+
isReadOnly,
19+
description,
20+
...rest
21+
}) => {
22+
const { error, touched } = meta;
23+
const showError = touched && error;
24+
const groupValues = rest.input.value;
25+
return (
26+
<Wrapper
27+
showError={ showError }
28+
isRequired={ isRequired }
29+
label={ label }
30+
helperText={ helperText }
31+
meta={ meta }
32+
description={ description }
33+
rest={ rest }
34+
error={ error }
35+
>
36+
{ options.map(option =>
37+
(<FieldProvider
38+
formOptions={ rest.formOptions }
39+
id={ `${rest.id}-${option.value}` }
40+
key={ option.value }
41+
{ ...option }
42+
name={ props.name }
43+
type="checkbox"
44+
render={ ({ input, meta, formOptions, componentType, ...rest }) => {
45+
const indexValue = groupValues.indexOf(input.value);
46+
return (
47+
<Checkbox
48+
aria-label={ option['aria-label'] || option.label }
49+
{ ...input }
50+
{ ...rest }
51+
isDisabled={ isDisabled || isReadOnly }
52+
onChange={ () => (indexValue === -1
53+
? input.onChange([ ...groupValues, input.value ])
54+
: input.onChange([ ...groupValues.slice(0, indexValue), ...groupValues.slice(indexValue + 1) ])) }
55+
label={ rest.label }
56+
/>
57+
);
58+
} }
59+
/>)) }
60+
</Wrapper>);
61+
} }
62+
/>
63+
);
64+
65+
MultipleChoiceList.propTypes = {
66+
validate: PropTypes.func,
67+
FieldProvider: PropTypes.oneOfType([ PropTypes.node, PropTypes.func ]),
68+
name: PropTypes.string.isRequired,
69+
Wrapper: PropTypes.oneOfType([ PropTypes.node, PropTypes.func ]),
70+
Checkbox: PropTypes.oneOfType([ PropTypes.node, PropTypes.func ]),
71+
};
72+
73+
export default MultipleChoiceList;
74+
75+
export const wrapperProps = {
76+
...formGroup,
77+
children: PropTypes.oneOfType([
78+
PropTypes.arrayOf(PropTypes.node),
79+
PropTypes.node,
80+
]).isRequired,
81+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
11
import PropTypes from 'prop-types';
22

33
export const optionsPropType = PropTypes.arrayOf(PropTypes.shape({ label: PropTypes.string.isRequired, value: PropTypes.any }));
4+
5+
export const meta = PropTypes.shape({
6+
active: PropTypes.bool,
7+
dirty: PropTypes.bool,
8+
dirtySinceLastSubmit: PropTypes.bool,
9+
error: PropTypes.any,
10+
initial: PropTypes.any,
11+
invalid: PropTypes.bool,
12+
modified: PropTypes.bool,
13+
pristine: PropTypes.bool,
14+
submitError: PropTypes.any,
15+
submitFailed: PropTypes.bool,
16+
submitSucceeded: PropTypes.bool,
17+
submitting: PropTypes.bool,
18+
touched: PropTypes.bool,
19+
valid: PropTypes.bool,
20+
validating: PropTypes.bool,
21+
visited: PropTypes.bool,
22+
});
23+
24+
export const formGroup = {
25+
isRequired: PropTypes.bool,
26+
label: PropTypes.node,
27+
helperText: PropTypes.node,
28+
meta,
29+
description: PropTypes.node,
30+
};
Lines changed: 43 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from 'react';
2-
import { composeValidators } from '@data-driven-forms/react-form-renderer';
2+
import PropTypes from 'prop-types';
3+
34
import Grid from '@material-ui/core/Grid';
45
import Checkbox from '@material-ui/core/Checkbox';
56
import FormControlLabel from '@material-ui/core/FormControlLabel';
@@ -8,65 +9,47 @@ import FormGroup from '@material-ui/core/FormGroup';
89
import FormControl from '@material-ui/core/FormControl';
910
import FormHelperText from '@material-ui/core/FormHelperText';
1011

11-
const MultipleChoiceList = ({ validate, FieldProvider, ...props }) => (
12-
<FieldProvider { ...props } validate={ composeValidators(props.validate || []) }>
13-
{ ({
14-
label,
15-
isRequired,
16-
helperText,
17-
meta,
18-
options,
19-
isDisabled,
20-
formOptions,
21-
componentType,
22-
...rest
23-
}) => {
24-
const { error, touched } = meta;
25-
const showError = touched && error;
26-
const groupValues = Array.isArray(rest.input.value) ? rest.input.value : [];
27-
return (
28-
<Grid container >
29-
<FormControl component="fieldset" >
30-
<FormLabel>{ label }</FormLabel>
31-
<FormGroup>
32-
{ options.map(option =>
33-
(<FieldProvider
34-
{ ...rest }
35-
id={ `${rest.id}-${option.value}` }
36-
key={ option.value }
37-
{ ...option }
38-
name={ props.name }
39-
type="checkbox"
40-
render={ ({ input, meta, value, formOptions, ...rest }) => {
41-
const indexValue = groupValues.indexOf(option.value);
42-
return (
43-
<FormControlLabel
44-
control={ <Checkbox
45-
label={ rest.label }
46-
aria-label={ option['aria-label'] || option.label }
47-
{ ...input }
48-
{ ...rest }
49-
checked={ indexValue !== -1 }
50-
disabled={ isDisabled }
51-
onChange={ () => {
52-
return (indexValue === -1
53-
? input.onChange([ ...groupValues, option.value ])
54-
: input.onChange([ ...groupValues.slice(0, indexValue), ...groupValues.slice(indexValue + 1) ]));} }
55-
>
56-
{ option.label }
57-
</Checkbox> }
58-
label={ option.label }
59-
/>
60-
);
61-
} }
62-
/>)) }
63-
</FormGroup>
64-
<FormHelperText>{ showError ? error : null }</FormHelperText>
65-
</FormControl>
66-
</Grid>
67-
);
68-
} }
69-
</FieldProvider>
12+
import MultipleChoiceListCommon, { wrapperProps } from '@data-driven-forms/common/src/multiple-choice-list';
13+
14+
const FinalCheckbox = ({ isDisabled, label, ...props }) => (
15+
<FormControlLabel
16+
control={ <Checkbox
17+
{ ...props }
18+
disabled={ isDisabled }
19+
>
20+
{ label }
21+
</Checkbox> }
22+
label={ label }
23+
/>
24+
);
25+
26+
FinalCheckbox.propTypes = {
27+
isDisabled: PropTypes.bool,
28+
label: PropTypes.node,
29+
};
30+
31+
const Wrapper = ({ showError, label, error, children }) =>(
32+
<Grid container >
33+
<FormControl component="fieldset" >
34+
<FormLabel>{ label }</FormLabel>
35+
<FormGroup>
36+
{ children }
37+
</FormGroup>
38+
<FormHelperText>{ showError ? error : null }</FormHelperText>
39+
</FormControl>
40+
</Grid>
41+
);
42+
43+
Wrapper.propTypes = {
44+
...wrapperProps,
45+
};
46+
47+
const MultipleChoiceList = (props) => (
48+
<MultipleChoiceListCommon
49+
{ ...props }
50+
Wrapper={ Wrapper }
51+
Checkbox={ FinalCheckbox }
52+
/>
7053
);
7154

7255
export default MultipleChoiceList;
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React from 'react';
2+
import { mount } from 'enzyme';
3+
4+
import Grid from '@material-ui/core/Grid';
5+
import Checkbox from '@material-ui/core/Checkbox';
6+
import FormControlLabel from '@material-ui/core/FormControlLabel';
7+
import FormLabel from '@material-ui/core/FormLabel';
8+
import FormGroup from '@material-ui/core/FormGroup';
9+
import FormControl from '@material-ui/core/FormControl';
10+
import FormHelperText from '@material-ui/core/FormHelperText';
11+
12+
import MultipleChoiceList from '../form-fields/multiple-choice-list';
13+
import MockFieldProvider from '../../../../__mocks__/mock-field-provider';
14+
15+
describe('<MultipleChoiceList />', () => {
16+
let initialProps;
17+
let changeSpy = jest.fn();
18+
beforeEach(() => {
19+
initialProps = {
20+
FieldProvider: props => <MockFieldProvider { ...props } input={{ onChange: changeSpy, value: props.value || []}} />,
21+
options: [{
22+
label: 'Foo',
23+
value: 0,
24+
}, {
25+
label: 'Bar',
26+
value: 1,
27+
}],
28+
};
29+
});
30+
31+
afterEach(() => {
32+
changeSpy.mockReset();
33+
});
34+
35+
it('should render correctly', () => {
36+
const wrapper = mount(<MultipleChoiceList { ...initialProps } />);
37+
38+
expect(wrapper.find(FormControl)).toHaveLength(1);
39+
expect(wrapper.find(FormGroup)).toHaveLength(1);
40+
expect(wrapper.find(FormLabel)).toHaveLength(1);
41+
expect(wrapper.find(FormControlLabel)).toHaveLength(initialProps.options.length);
42+
expect(wrapper.find(Grid)).toHaveLength(1);
43+
expect(wrapper.find(Checkbox)).toHaveLength(initialProps.options.length);
44+
});
45+
46+
it('should call FieldProvider on change method', () => {
47+
const wrapper = mount(<MultipleChoiceList { ...initialProps } />);
48+
49+
wrapper.find('input').last().simulate('change', { target: { checked: true }});
50+
expect(changeSpy).toHaveBeenCalledWith([ 1 ]);
51+
});
52+
53+
it('should call FieldProvider on change method and remove option value form all values', () => {
54+
const wrapper = mount(
55+
<MultipleChoiceList
56+
{ ...initialProps }
57+
FieldProvider={ props => <MockFieldProvider { ...props } input={{ onChange: changeSpy, value: props.value || [ 1 ]}} /> }
58+
/>
59+
);
60+
61+
wrapper.find('input').last().simulate('change', { target: { checked: true }});
62+
expect(changeSpy).toHaveBeenCalledWith([]);
63+
});
64+
65+
it('should render in error state', () => {
66+
const ERROR_MESSAGE = 'Error message';
67+
68+
const wrapper = mount(
69+
<MultipleChoiceList
70+
{ ...initialProps }
71+
FieldProvider={ props => (
72+
<MockFieldProvider
73+
{ ...props }
74+
input={{ onChange: changeSpy, value: []}}
75+
meta={{
76+
error: ERROR_MESSAGE,
77+
touched: true,
78+
}}
79+
/>) }
80+
/>
81+
);
82+
83+
expect(wrapper.find(FormHelperText).text()).toEqual(ERROR_MESSAGE);
84+
});
85+
});

0 commit comments

Comments
 (0)