Skip to content

Commit

Permalink
Merge pull request #196 from edx/mikix/input-validation
Browse files Browse the repository at this point in the history
feat(asinput): add "isValid" and "validationMessage" props
  • Loading branch information
mikix authored Mar 30, 2018
2 parents 561695e + 3e4ae29 commit fa56e8a
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 4 deletions.
6 changes: 6 additions & 0 deletions src/asInput/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,11 @@ Handles all necessary props that are related to Input typed components.
### `validator` (function; optional)
`validator` specifies the function to use for validation logic if the input needs to be validated. Default is undefined.

### `isValid` (boolean; optional)
`isValid` specifies whether the current input has validated correctly. Consider updating this from an `onBlur` handler. Only used if `validator` is not specified. The default is true.

### `validationMessage` (string; optional)
`validationMessage` specifies the message to display when `isValid` is false. Only used if `validator` is not specified. The default is an empty string.

### `value` (string; optional)
`value` specifies the value for the value property within the component. The default is an empty string.
72 changes: 72 additions & 0 deletions src/asInput/asInput.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,78 @@ describe('asInput()', () => {
expect(spy).toHaveBeenCalledTimes(1);
});

describe('validation properties', () => {
it('ignores props if validator is defined', () => {
const spy = jest.fn();
spy.mockReturnValue({ isValid: false });
const props = {
...baseProps,
validator: spy,
isValid: false,
};
const wrapper = mount(<InputTestComponent {...props} />);
expect(wrapper.state('isValid')).toEqual(true); // default is true, ignoring our prop

wrapper.setProps({ isValid: true });
wrapper.find('input').simulate('blur'); // trigger validation
expect(wrapper.state('isValid')).toEqual(false); // validator set false, ignoring our prop

wrapper.setProps({ isValid: true });
expect(wrapper.state('isValid')).toEqual(false); // resetting prop changes nothing
});

it('ignores validationMessage prop if validator is defined', () => {
const spy = jest.fn();
spy.mockReturnValue({ validationMessage: 'Spy' });
const props = {
...baseProps,
validator: spy,
validationMessage: 'Prop',
};
const wrapper = mount(<InputTestComponent {...props} />);
expect(wrapper.state('validationMessage')).toEqual(''); // default is '', ignoring our prop

wrapper.find('input').simulate('blur'); // trigger validation
expect(wrapper.state('validationMessage')).toEqual('Spy'); // validator set Spy, ignoring our prop

wrapper.setProps({ validationMessage: 'Reset' });
expect(wrapper.state('validationMessage')).toEqual('Spy'); // resetting prop changes nothing
});

it('uses props if validator becomes undefined', () => {
const spy = jest.fn();
spy.mockReturnValue({ validationMessage: 'Spy' });
const props = {
...baseProps,
validator: spy,
isValid: false,
validationMessage: 'Prop',
};
const wrapper = mount(<InputTestComponent {...props} />);
expect(wrapper.state('validationMessage')).toEqual('');

wrapper.setProps({ validator: null });
expect(wrapper.state('validationMessage')).toEqual('Prop');
});

it('uses isValid to display validation message', () => {
const props = {
...baseProps,
isValid: false,
validationMessage: 'Nope!',
};
const wrapper = mount(<InputTestComponent {...props} />);
const err = wrapper.find('.form-control-feedback');
expect(err.text()).toEqual('Nope!');

wrapper.setProps({ validationMessage: 'New Message' });
expect(err.text()).toEqual('New Message');

wrapper.setProps({ isValid: true });
expect(err.text()).toEqual('');
});
});

describe('validator', () => {
it('on blur', () => {
const spy = jest.fn();
Expand Down
28 changes: 24 additions & 4 deletions src/asInput/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export const inputProps = {
onChange: PropTypes.func,
onBlur: PropTypes.func,
validator: PropTypes.func,
isValid: PropTypes.bool,
validationMessage: PropTypes.string,
className: PropTypes.arrayOf(PropTypes.string),
themes: PropTypes.arrayOf(PropTypes.string),
inline: PropTypes.bool,
Expand All @@ -38,6 +40,8 @@ export const defaultProps = {
disabled: false,
required: false,
validator: undefined,
isValid: true,
validationMessage: '',
className: [],
themes: [],
inline: false,
Expand All @@ -51,21 +55,37 @@ const asInput = (WrappedComponent, inputType = undefined, labelFirst = true) =>
this.handleBlur = this.handleBlur.bind(this);

const id = this.props.id ? this.props.id : newId('asInput');
const isValid = this.props.validator ? true : this.props.isValid;
const validationMessage = this.props.validator ? '' : this.props.validationMessage;
this.state = {
id,
value: this.props.value,
isValid: true,
isValid,
validationMessage,
describedBy: [],
errorId: `error-${id}`,
descriptionId: `description-${id}`,
};
}

componentWillReceiveProps(nextProps) {
const updatedState = {};
if (nextProps.value !== this.props.value) {
this.setState({
value: nextProps.value,
});
updatedState.value = nextProps.value;
}
if (nextProps.isValid !== this.props.isValid && !nextProps.validator) {
updatedState.isValid = nextProps.isValid;
}
if (nextProps.validationMessage !== this.props.validationMessage && !nextProps.validator) {
updatedState.validationMessage = nextProps.validationMessage;
}
// If validator goes away, revert to props
if (nextProps.validator !== this.props.validator && !nextProps.validator) {
updatedState.isValid = nextProps.isValid;
updatedState.validationMessage = nextProps.validationMessage;
}
if (Object.keys(updatedState).length > 0) {
this.setState(updatedState);
}
}

Expand Down

0 comments on commit fa56e8a

Please sign in to comment.