Skip to content

Commit

Permalink
Merge pull request #280 from rvsia/commonMultipleChoiceList
Browse files Browse the repository at this point in the history
Common multiple choice list
  • Loading branch information
Hyperkid123 authored Jan 14, 2020
2 parents c144501 + 8beb3df commit d0870b9
Show file tree
Hide file tree
Showing 8 changed files with 1,051 additions and 641 deletions.
81 changes: 81 additions & 0 deletions packages/common/src/multiple-choice-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from 'react';
import PropTypes from 'prop-types';
import { composeValidators } from '@data-driven-forms/react-form-renderer';

import { formGroup } from './prop-types-templates';

const MultipleChoiceList = ({ validate, FieldProvider, Wrapper, Checkbox, ...props }) => (
<FieldProvider
{ ...props }
validate={ composeValidators(props.validate || []) }
render={ ({
label,
isRequired,
helperText,
meta,
options,
isDisabled,
isReadOnly,
description,
...rest
}) => {
const { error, touched } = meta;
const showError = touched && error;
const groupValues = rest.input.value;
return (
<Wrapper
showError={ showError }
isRequired={ isRequired }
label={ label }
helperText={ helperText }
meta={ meta }
description={ description }
rest={ rest }
error={ error }
>
{ options.map(option =>
(<FieldProvider
formOptions={ rest.formOptions }
id={ `${rest.id}-${option.value}` }
key={ option.value }
{ ...option }
name={ props.name }
type="checkbox"
render={ ({ input, meta, formOptions, componentType, ...rest }) => {
const indexValue = groupValues.indexOf(input.value);
return (
<Checkbox
aria-label={ option['aria-label'] || option.label }
{ ...input }
{ ...rest }
isDisabled={ isDisabled || isReadOnly }
onChange={ () => (indexValue === -1
? input.onChange([ ...groupValues, input.value ])
: input.onChange([ ...groupValues.slice(0, indexValue), ...groupValues.slice(indexValue + 1) ])) }
label={ rest.label }
/>
);
} }
/>)) }
</Wrapper>);
} }
/>
);

MultipleChoiceList.propTypes = {
validate: PropTypes.func,
FieldProvider: PropTypes.oneOfType([ PropTypes.node, PropTypes.func ]),
name: PropTypes.string.isRequired,
Wrapper: PropTypes.oneOfType([ PropTypes.node, PropTypes.func ]),
Checkbox: PropTypes.oneOfType([ PropTypes.node, PropTypes.func ]),
};

export default MultipleChoiceList;

export const wrapperProps = {
...formGroup,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]).isRequired,
};
27 changes: 27 additions & 0 deletions packages/common/src/prop-types-templates.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
import PropTypes from 'prop-types';

export const optionsPropType = PropTypes.arrayOf(PropTypes.shape({ label: PropTypes.string.isRequired, value: PropTypes.any }));

export const meta = PropTypes.shape({
active: PropTypes.bool,
dirty: PropTypes.bool,
dirtySinceLastSubmit: PropTypes.bool,
error: PropTypes.any,
initial: PropTypes.any,
invalid: PropTypes.bool,
modified: PropTypes.bool,
pristine: PropTypes.bool,
submitError: PropTypes.any,
submitFailed: PropTypes.bool,
submitSucceeded: PropTypes.bool,
submitting: PropTypes.bool,
touched: PropTypes.bool,
valid: PropTypes.bool,
validating: PropTypes.bool,
visited: PropTypes.bool,
});

export const formGroup = {
isRequired: PropTypes.bool,
label: PropTypes.node,
helperText: PropTypes.node,
meta,
description: PropTypes.node,
};
103 changes: 43 additions & 60 deletions packages/mui-component-mapper/src/form-fields/multiple-choice-list.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { composeValidators } from '@data-driven-forms/react-form-renderer';
import PropTypes from 'prop-types';

import Grid from '@material-ui/core/Grid';
import Checkbox from '@material-ui/core/Checkbox';
import FormControlLabel from '@material-ui/core/FormControlLabel';
Expand All @@ -8,65 +9,47 @@ import FormGroup from '@material-ui/core/FormGroup';
import FormControl from '@material-ui/core/FormControl';
import FormHelperText from '@material-ui/core/FormHelperText';

const MultipleChoiceList = ({ validate, FieldProvider, ...props }) => (
<FieldProvider { ...props } validate={ composeValidators(props.validate || []) }>
{ ({
label,
isRequired,
helperText,
meta,
options,
isDisabled,
formOptions,
componentType,
...rest
}) => {
const { error, touched } = meta;
const showError = touched && error;
const groupValues = Array.isArray(rest.input.value) ? rest.input.value : [];
return (
<Grid container >
<FormControl component="fieldset" >
<FormLabel>{ label }</FormLabel>
<FormGroup>
{ options.map(option =>
(<FieldProvider
{ ...rest }
id={ `${rest.id}-${option.value}` }
key={ option.value }
{ ...option }
name={ props.name }
type="checkbox"
render={ ({ input, meta, value, formOptions, ...rest }) => {
const indexValue = groupValues.indexOf(option.value);
return (
<FormControlLabel
control={ <Checkbox
label={ rest.label }
aria-label={ option['aria-label'] || option.label }
{ ...input }
{ ...rest }
checked={ indexValue !== -1 }
disabled={ isDisabled }
onChange={ () => {
return (indexValue === -1
? input.onChange([ ...groupValues, option.value ])
: input.onChange([ ...groupValues.slice(0, indexValue), ...groupValues.slice(indexValue + 1) ]));} }
>
{ option.label }
</Checkbox> }
label={ option.label }
/>
);
} }
/>)) }
</FormGroup>
<FormHelperText>{ showError ? error : null }</FormHelperText>
</FormControl>
</Grid>
);
} }
</FieldProvider>
import MultipleChoiceListCommon, { wrapperProps } from '@data-driven-forms/common/src/multiple-choice-list';

const FinalCheckbox = ({ isDisabled, label, ...props }) => (
<FormControlLabel
control={ <Checkbox
{ ...props }
disabled={ isDisabled }
>
{ label }
</Checkbox> }
label={ label }
/>
);

FinalCheckbox.propTypes = {
isDisabled: PropTypes.bool,
label: PropTypes.node,
};

const Wrapper = ({ showError, label, error, children }) =>(
<Grid container >
<FormControl component="fieldset" >
<FormLabel>{ label }</FormLabel>
<FormGroup>
{ children }
</FormGroup>
<FormHelperText>{ showError ? error : null }</FormHelperText>
</FormControl>
</Grid>
);

Wrapper.propTypes = {
...wrapperProps,
};

const MultipleChoiceList = (props) => (
<MultipleChoiceListCommon
{ ...props }
Wrapper={ Wrapper }
Checkbox={ FinalCheckbox }
/>
);

export default MultipleChoiceList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
import { mount } from 'enzyme';

import Grid from '@material-ui/core/Grid';
import Checkbox from '@material-ui/core/Checkbox';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormLabel from '@material-ui/core/FormLabel';
import FormGroup from '@material-ui/core/FormGroup';
import FormControl from '@material-ui/core/FormControl';
import FormHelperText from '@material-ui/core/FormHelperText';

import MultipleChoiceList from '../form-fields/multiple-choice-list';
import MockFieldProvider from '../../../../__mocks__/mock-field-provider';

describe('<MultipleChoiceList />', () => {
let initialProps;
let changeSpy = jest.fn();
beforeEach(() => {
initialProps = {
FieldProvider: props => <MockFieldProvider { ...props } input={{ onChange: changeSpy, value: props.value || []}} />,
options: [{
label: 'Foo',
value: 0,
}, {
label: 'Bar',
value: 1,
}],
};
});

afterEach(() => {
changeSpy.mockReset();
});

it('should render correctly', () => {
const wrapper = mount(<MultipleChoiceList { ...initialProps } />);

expect(wrapper.find(FormControl)).toHaveLength(1);
expect(wrapper.find(FormGroup)).toHaveLength(1);
expect(wrapper.find(FormLabel)).toHaveLength(1);
expect(wrapper.find(FormControlLabel)).toHaveLength(initialProps.options.length);
expect(wrapper.find(Grid)).toHaveLength(1);
expect(wrapper.find(Checkbox)).toHaveLength(initialProps.options.length);
});

it('should call FieldProvider on change method', () => {
const wrapper = mount(<MultipleChoiceList { ...initialProps } />);

wrapper.find('input').last().simulate('change', { target: { checked: true }});
expect(changeSpy).toHaveBeenCalledWith([ 1 ]);
});

it('should call FieldProvider on change method and remove option value form all values', () => {
const wrapper = mount(
<MultipleChoiceList
{ ...initialProps }
FieldProvider={ props => <MockFieldProvider { ...props } input={{ onChange: changeSpy, value: props.value || [ 1 ]}} /> }
/>
);

wrapper.find('input').last().simulate('change', { target: { checked: true }});
expect(changeSpy).toHaveBeenCalledWith([]);
});

it('should render in error state', () => {
const ERROR_MESSAGE = 'Error message';

const wrapper = mount(
<MultipleChoiceList
{ ...initialProps }
FieldProvider={ props => (
<MockFieldProvider
{ ...props }
input={{ onChange: changeSpy, value: []}}
meta={{
error: ERROR_MESSAGE,
touched: true,
}}
/>) }
/>
);

expect(wrapper.find(FormHelperText).text()).toEqual(ERROR_MESSAGE);
});
});
Loading

0 comments on commit d0870b9

Please sign in to comment.