Commit 1a75e1e5 authored by peterhegman's avatar peterhegman

Refactor `customValidator` to a `customErrorMessage` prop

Instead of passing a validator function, pass a error message prop and
emit a text event to run the validator.
parent 2d8de8ea
......@@ -2,7 +2,7 @@ import Vue from 'vue';
import { __, sprintf } from '~/locale';
import CommaSeparatedListTokenSelector from '../components/comma_separated_list_token_selector.vue';
export default (el, props = {}, qaSelector) => {
export default (el, props = {}, qaSelector, customValidator) => {
// eslint-disable-next-line no-new
new Vue({
el,
......@@ -20,8 +20,14 @@ export default (el, props = {}, qaSelector) => {
regexValidator,
...(regexValidator ? { regexValidator: new RegExp(regexValidator) } : {}),
...(disallowedValues ? { disallowedValues: JSON.parse(disallowedValues) } : {}),
customErrorMessage: '',
};
},
methods: {
handleTextInput(value) {
this.customErrorMessage = customValidator(value);
},
},
render(createElement) {
return createElement('comma-separated-list-token-selector', {
attrs: {
......@@ -32,8 +38,10 @@ export default (el, props = {}, qaSelector) => {
ariaLabelledby: this.labelId,
regexValidator: this.regexValidator,
disallowedValues: this.disallowedValues,
customErrorMessage: this.customErrorMessage,
...props,
},
on: customValidator ? { 'text-input': this.handleTextInput } : {},
scopedSlots: {
'user-defined-token-content': ({ inputText: value }) => {
return sprintf(__('Add "%{value}"'), { value });
......
......@@ -35,7 +35,7 @@ export default {
required: false,
default: () => '',
},
errorMessage: {
regexErrorMessage: {
type: String,
required: false,
default: '',
......@@ -45,6 +45,11 @@ export default {
required: false,
default: '',
},
customErrorMessage: {
type: String,
required: false,
default: '',
},
},
data() {
return {
......@@ -59,14 +64,14 @@ export default {
},
computedErrorMessage() {
if (this.regexValidator !== null && this.textInputValue.match(this.regexValidator) === null) {
return this.errorMessage;
return this.regexErrorMessage;
}
if (!isEmpty(this.disallowedValues) && this.disallowedValues.includes(this.textInputValue)) {
return this.disallowedValueErrorMessage;
}
return this.customValidator(this.textInputValue);
return this.customErrorMessage;
},
},
watch: {
......@@ -116,6 +121,7 @@ export default {
handleTextInput(value) {
this.hideErrorMessage = true;
this.textInputValue = value;
this.$emit('text-input', value);
},
handleBlur() {
this.hideErrorMessage = true;
......
......@@ -6,12 +6,13 @@ import { __ } from '~/locale';
document.addEventListener('DOMContentLoaded', () => {
initAccessRestrictionField('.js-allowed-email-domains', {
placeholder: __('Enter domain'),
errorMessage: __('The domain you entered is misformatted.'),
regexErrorMessage: __('The domain you entered is misformatted.'),
disallowedValueErrorMessage: __('The domain you entered is not allowed.'),
});
initAccessRestrictionField(
'.js-ip-restriction',
{ placeholder: __('Enter IP address range'), customValidator: validateRestrictedIpAddress },
{ placeholder: __('Enter IP address range') },
'ip_restriction_field',
validateRestrictedIpAddress,
);
});
......@@ -12,13 +12,16 @@ describe('CommaSeparatedListTokenSelector', () => {
const defaultProps = {
hiddenInputId: 'comma-separated-list',
ariaLabelledby: 'comma-separated-list-label',
errorMessage: 'The value entered is invalid',
regexErrorMessage: 'The value entered is invalid',
disallowedValueErrorMessage: 'The value entered is not allowed',
};
const createComponent = options => {
wrapper = mount(CommaSeparatedListTokenSelector, {
attachTo: div,
scopedSlots: {
'user-defined-token-content': '<span>Add "{{props.inputText}}"</span>',
},
...options,
propsData: {
...defaultProps,
......@@ -132,6 +135,16 @@ describe('CommaSeparatedListTokenSelector', () => {
});
});
describe('when text input is typed in', () => {
it('emits `text-input` event', async () => {
createComponent();
await setTokenSelectorInputValue('foo bar');
expect(wrapper.emitted('text-input')[0]).toEqual(['foo bar']);
});
});
describe('when enter key is pressed', () => {
it('does not submit the form if token selector text input has a value', async () => {
createComponent();
......@@ -145,7 +158,7 @@ describe('CommaSeparatedListTokenSelector', () => {
});
describe('when `regexValidator` prop is set', () => {
it('displays `errorMessage` if regex fails', async () => {
it('displays `regexErrorMessage` if regex fails', async () => {
createComponent({
propsData: {
regexValidator: /baz/,
......@@ -176,8 +189,22 @@ describe('CommaSeparatedListTokenSelector', () => {
});
});
describe('when `regexValidator` and `disallowedValues` props are set', () => {
it('displays `errorMessage` if regex fails', async () => {
describe('when `customErrorMessage` prop is set', () => {
it('displays `customErrorMessage`', () => {
createComponent({
propsData: {
customErrorMessage: 'Value is invalid',
},
});
tokenSelectorTriggerEnter();
expect(findErrorMessageText()).toBe('Value is invalid');
});
});
describe('when `regexValidator`, `disallowedValues` and `customErrorMessage` props are set', () => {
it('displays `regexErrorMessage` if regex fails', async () => {
createComponent({
propsData: {
regexValidator: /baz/,
......@@ -206,6 +233,22 @@ describe('CommaSeparatedListTokenSelector', () => {
expect(findErrorMessageText()).toBe('The value entered is not allowed');
});
it('displays `customErrorMessage` if regex passes and value is not in the disallowed list', async () => {
createComponent({
propsData: {
regexValidator: /foo bar/,
disallowedValues: ['foo', 'bar', 'baz'],
customErrorMessage: 'Value is invalid',
},
});
await setTokenSelectorInputValue('foo bar');
tokenSelectorTriggerEnter();
expect(findErrorMessageText()).toBe('Value is invalid');
});
});
});
......@@ -216,49 +259,21 @@ describe('CommaSeparatedListTokenSelector', () => {
regexValidator: /foo/,
disallowedValues: ['bar', 'baz'],
},
scopedSlots: {
'user-defined-token-content': '<span>Add "{{props.inputText}}"</span>',
},
});
await setTokenSelectorInputValue('foo');
tokenSelectorTriggerEnter();
expect(findTokenSelectorDropdown().text()).toBe('Add "foo"');
});
});
describe('when `regexValidator` and `disallowedValues` props are not set', () => {
describe('when `customValidator` prop is not set', () => {
it('allows any value to be added', async () => {
createComponent({
scopedSlots: {
'user-defined-token-content': '<span>Add "{{props.inputText}}"</span>',
},
});
await setTokenSelectorInputValue('foo');
expect(findTokenSelectorDropdown().text()).toBe('Add "foo"');
});
});
describe('when `customValidator` prop is set', () => {
it('displays error message that is returned by `customValidator`', () => {
createComponent({
propsData: {
customValidator: () => 'Value is invalid',
},
scopedSlots: {
'user-defined-token-content': '<span>Add "{{props.inputText}}"</span>',
},
});
describe('when `regexValidator`, `disallowedValues` and `customErrorMessage` props are not set', () => {
it('allows any value to be added', async () => {
createComponent();
tokenSelectorTriggerEnter();
await setTokenSelectorInputValue('foo');
expect(findErrorMessageText()).toBe('Value is invalid');
});
expect(findTokenSelectorDropdown().text()).toBe('Add "foo"');
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment