387 lines
13 KiB
JavaScript
387 lines
13 KiB
JavaScript
import { mount } from '@vue/test-utils';
|
|
import { nextTick } from 'vue';
|
|
import ChronicDurationInput from '~/vue_shared/components/chronic_duration_input.vue';
|
|
|
|
const MOCK_VALUE = 2 * 3600 + 20 * 60;
|
|
|
|
describe('vue_shared/components/chronic_duration_input', () => {
|
|
let wrapper;
|
|
let textElement;
|
|
let hiddenElement;
|
|
|
|
afterEach(() => {
|
|
textElement = null;
|
|
hiddenElement = null;
|
|
});
|
|
|
|
const findComponents = () => {
|
|
textElement = wrapper.find('input[type=text]').element;
|
|
hiddenElement = wrapper.find('input[type=hidden]').element;
|
|
};
|
|
|
|
const createComponent = (props = {}) => {
|
|
wrapper = mount(ChronicDurationInput, { propsData: props });
|
|
findComponents();
|
|
};
|
|
|
|
describe('value', () => {
|
|
it('has human-readable output with value', () => {
|
|
createComponent({ value: MOCK_VALUE });
|
|
|
|
expect(textElement.value).toBe('2 hrs 20 mins');
|
|
expect(hiddenElement.value).toBe(MOCK_VALUE.toString());
|
|
});
|
|
|
|
it('has empty output with no value', () => {
|
|
createComponent({ value: null });
|
|
|
|
expect(textElement.value).toBe('');
|
|
expect(hiddenElement.value).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('change', () => {
|
|
const createAndDispatch = async (initialValue, humanReadableInput) => {
|
|
createComponent({ value: initialValue });
|
|
await nextTick();
|
|
textElement.value = humanReadableInput;
|
|
textElement.dispatchEvent(new Event('input'));
|
|
};
|
|
|
|
describe('when starting with no value and receiving human-readable input', () => {
|
|
beforeEach(() => {
|
|
createAndDispatch(null, '2hr20min');
|
|
});
|
|
|
|
it('updates hidden field', () => {
|
|
expect(textElement.value).toBe('2hr20min');
|
|
expect(hiddenElement.value).toBe(MOCK_VALUE.toString());
|
|
});
|
|
|
|
it('emits change event', () => {
|
|
expect(wrapper.emitted('change')).toEqual([[MOCK_VALUE]]);
|
|
});
|
|
});
|
|
|
|
describe('when starting with a value and receiving empty input', () => {
|
|
beforeEach(() => {
|
|
createAndDispatch(MOCK_VALUE, '');
|
|
});
|
|
|
|
it('updates hidden field', () => {
|
|
expect(textElement.value).toBe('');
|
|
expect(hiddenElement.value).toBe('');
|
|
});
|
|
|
|
it('emits change event', () => {
|
|
expect(wrapper.emitted('change')).toEqual([[null]]);
|
|
});
|
|
});
|
|
|
|
describe('when starting with a value and receiving invalid input', () => {
|
|
beforeEach(() => {
|
|
createAndDispatch(MOCK_VALUE, 'gobbledygook');
|
|
});
|
|
|
|
it('does not update hidden field', () => {
|
|
expect(textElement.value).toBe('gobbledygook');
|
|
expect(hiddenElement.value).toBe(MOCK_VALUE.toString());
|
|
});
|
|
|
|
it('does not emit change event', () => {
|
|
expect(wrapper.emitted('change')).toBeUndefined();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('valid', () => {
|
|
describe('initial value', () => {
|
|
beforeEach(() => {
|
|
createComponent({ value: MOCK_VALUE });
|
|
});
|
|
|
|
it('emits valid with initial value', () => {
|
|
expect(wrapper.emitted('valid')).toEqual([[{ valid: true, feedback: '' }]]);
|
|
expect(textElement.validity.valid).toBe(true);
|
|
expect(textElement.validity.customError).toBe(false);
|
|
expect(textElement.validationMessage).toBe('');
|
|
expect(hiddenElement.validity.valid).toBe(true);
|
|
expect(hiddenElement.validity.customError).toBe(false);
|
|
expect(hiddenElement.validationMessage).toBe('');
|
|
});
|
|
|
|
it('emits valid with user input', async () => {
|
|
textElement.value = '1m10s';
|
|
textElement.dispatchEvent(new Event('input'));
|
|
await nextTick();
|
|
|
|
expect(wrapper.emitted('valid')).toEqual([
|
|
[{ valid: true, feedback: '' }],
|
|
[{ valid: true, feedback: '' }],
|
|
]);
|
|
expect(textElement.validity.valid).toBe(true);
|
|
expect(textElement.validity.customError).toBe(false);
|
|
expect(textElement.validationMessage).toBe('');
|
|
expect(hiddenElement.validity.valid).toBe(true);
|
|
expect(hiddenElement.validity.customError).toBe(false);
|
|
expect(hiddenElement.validationMessage).toBe('');
|
|
|
|
textElement.value = '';
|
|
textElement.dispatchEvent(new Event('input'));
|
|
await nextTick();
|
|
|
|
expect(wrapper.emitted('valid')).toEqual([
|
|
[{ valid: true, feedback: '' }],
|
|
[{ valid: true, feedback: '' }],
|
|
[{ valid: null, feedback: '' }],
|
|
]);
|
|
expect(textElement.validity.valid).toBe(true);
|
|
expect(textElement.validity.customError).toBe(false);
|
|
expect(textElement.validationMessage).toBe('');
|
|
expect(hiddenElement.validity.valid).toBe(true);
|
|
expect(hiddenElement.validity.customError).toBe(false);
|
|
expect(hiddenElement.validationMessage).toBe('');
|
|
});
|
|
|
|
it('emits invalid with user input', async () => {
|
|
textElement.value = 'gobbledygook';
|
|
textElement.dispatchEvent(new Event('input'));
|
|
await nextTick();
|
|
|
|
expect(wrapper.emitted('valid')).toEqual([
|
|
[{ valid: true, feedback: '' }],
|
|
[{ valid: false, feedback: ChronicDurationInput.i18n.INVALID_INPUT_FEEDBACK }],
|
|
]);
|
|
expect(textElement.validity.valid).toBe(false);
|
|
expect(textElement.validity.customError).toBe(true);
|
|
expect(textElement.validationMessage).toBe(
|
|
ChronicDurationInput.i18n.INVALID_INPUT_FEEDBACK,
|
|
);
|
|
expect(hiddenElement.validity.valid).toBe(false);
|
|
expect(hiddenElement.validity.customError).toBe(true);
|
|
// Hidden elements do not have validationMessage
|
|
expect(hiddenElement.validationMessage).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('no initial value', () => {
|
|
beforeEach(() => {
|
|
createComponent({ value: null });
|
|
});
|
|
|
|
it('emits valid with no initial value', () => {
|
|
expect(wrapper.emitted('valid')).toEqual([[{ valid: null, feedback: '' }]]);
|
|
expect(textElement.validity.valid).toBe(true);
|
|
expect(textElement.validity.customError).toBe(false);
|
|
expect(textElement.validationMessage).toBe('');
|
|
expect(hiddenElement.validity.valid).toBe(true);
|
|
expect(hiddenElement.validity.customError).toBe(false);
|
|
expect(hiddenElement.validationMessage).toBe('');
|
|
});
|
|
|
|
it('emits valid with updated value', async () => {
|
|
wrapper.setProps({ value: MOCK_VALUE });
|
|
await nextTick();
|
|
|
|
expect(wrapper.emitted('valid')).toEqual([
|
|
[{ valid: null, feedback: '' }],
|
|
[{ valid: true, feedback: '' }],
|
|
]);
|
|
expect(textElement.validity.valid).toBe(true);
|
|
expect(textElement.validity.customError).toBe(false);
|
|
expect(textElement.validationMessage).toBe('');
|
|
expect(hiddenElement.validity.valid).toBe(true);
|
|
expect(hiddenElement.validity.customError).toBe(false);
|
|
expect(hiddenElement.validationMessage).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('decimal input', () => {
|
|
describe('when integerRequired is false', () => {
|
|
beforeEach(() => {
|
|
createComponent({ value: null, integerRequired: false });
|
|
});
|
|
|
|
it('emits valid when input is integer', async () => {
|
|
textElement.value = '2hr20min';
|
|
textElement.dispatchEvent(new Event('input'));
|
|
await nextTick();
|
|
|
|
expect(wrapper.emitted('change')).toEqual([[MOCK_VALUE]]);
|
|
expect(wrapper.emitted('valid')).toEqual([
|
|
[{ valid: null, feedback: '' }],
|
|
[{ valid: true, feedback: '' }],
|
|
]);
|
|
expect(textElement.validity.valid).toBe(true);
|
|
expect(textElement.validity.customError).toBe(false);
|
|
expect(textElement.validationMessage).toBe('');
|
|
expect(hiddenElement.validity.valid).toBe(true);
|
|
expect(hiddenElement.validity.customError).toBe(false);
|
|
expect(hiddenElement.validationMessage).toBe('');
|
|
});
|
|
|
|
it('emits valid when input is decimal', async () => {
|
|
textElement.value = '1.5s';
|
|
textElement.dispatchEvent(new Event('input'));
|
|
await nextTick();
|
|
|
|
expect(wrapper.emitted('change')).toEqual([[1.5]]);
|
|
expect(wrapper.emitted('valid')).toEqual([
|
|
[{ valid: null, feedback: '' }],
|
|
[{ valid: true, feedback: '' }],
|
|
]);
|
|
expect(textElement.validity.valid).toBe(true);
|
|
expect(textElement.validity.customError).toBe(false);
|
|
expect(textElement.validationMessage).toBe('');
|
|
expect(hiddenElement.validity.valid).toBe(true);
|
|
expect(hiddenElement.validity.customError).toBe(false);
|
|
expect(hiddenElement.validationMessage).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('when integerRequired is unspecified', () => {
|
|
beforeEach(() => {
|
|
createComponent({ value: null });
|
|
});
|
|
|
|
it('emits valid when input is integer', async () => {
|
|
textElement.value = '2hr20min';
|
|
textElement.dispatchEvent(new Event('input'));
|
|
await nextTick();
|
|
|
|
expect(wrapper.emitted('change')).toEqual([[MOCK_VALUE]]);
|
|
expect(wrapper.emitted('valid')).toEqual([
|
|
[{ valid: null, feedback: '' }],
|
|
[{ valid: true, feedback: '' }],
|
|
]);
|
|
expect(textElement.validity.valid).toBe(true);
|
|
expect(textElement.validity.customError).toBe(false);
|
|
expect(textElement.validationMessage).toBe('');
|
|
expect(hiddenElement.validity.valid).toBe(true);
|
|
expect(hiddenElement.validity.customError).toBe(false);
|
|
expect(hiddenElement.validationMessage).toBe('');
|
|
});
|
|
|
|
it('emits invalid when input is decimal', async () => {
|
|
textElement.value = '1.5s';
|
|
textElement.dispatchEvent(new Event('input'));
|
|
await nextTick();
|
|
|
|
expect(wrapper.emitted('change')).toBeUndefined();
|
|
expect(wrapper.emitted('valid')).toEqual([
|
|
[{ valid: null, feedback: '' }],
|
|
[
|
|
{
|
|
valid: false,
|
|
feedback: ChronicDurationInput.i18n.INVALID_DECIMAL_FEEDBACK,
|
|
},
|
|
],
|
|
]);
|
|
expect(textElement.validity.valid).toBe(false);
|
|
expect(textElement.validity.customError).toBe(true);
|
|
expect(textElement.validationMessage).toBe(
|
|
ChronicDurationInput.i18n.INVALID_DECIMAL_FEEDBACK,
|
|
);
|
|
expect(hiddenElement.validity.valid).toBe(false);
|
|
expect(hiddenElement.validity.customError).toBe(true);
|
|
// Hidden elements do not have validationMessage
|
|
expect(hiddenElement.validationMessage).toBe('');
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('v-model', () => {
|
|
beforeEach(() => {
|
|
wrapper = mount({
|
|
data() {
|
|
return { value: 1 * 60 + 10 };
|
|
},
|
|
components: { ChronicDurationInput },
|
|
template: '<div><chronic-duration-input v-model="value"/></div>',
|
|
});
|
|
findComponents();
|
|
});
|
|
|
|
describe('value', () => {
|
|
it('passes initial prop via v-model', () => {
|
|
expect(textElement.value).toBe('1 min 10 secs');
|
|
expect(hiddenElement.value).toBe((1 * 60 + 10).toString());
|
|
});
|
|
|
|
it('passes updated prop via v-model', async () => {
|
|
textElement.value = '2hr20min';
|
|
textElement.dispatchEvent(new Event('input'));
|
|
await nextTick();
|
|
|
|
expect(textElement.value).toBe('2hr20min');
|
|
expect(hiddenElement.value).toBe(MOCK_VALUE.toString());
|
|
});
|
|
});
|
|
|
|
describe('change', () => {
|
|
it('passes user input to parent via v-model', async () => {
|
|
textElement.value = '2hr20min';
|
|
textElement.dispatchEvent(new Event('input'));
|
|
await nextTick();
|
|
|
|
expect(wrapper.findComponent(ChronicDurationInput).props('value')).toBe(MOCK_VALUE);
|
|
expect(textElement.value).toBe('2hr20min');
|
|
expect(hiddenElement.value).toBe(MOCK_VALUE.toString());
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('name', () => {
|
|
beforeEach(() => {
|
|
createComponent({ name: 'myInput' });
|
|
});
|
|
|
|
it('sets name of hidden field', () => {
|
|
expect(hiddenElement.name).toBe('myInput');
|
|
});
|
|
|
|
it('does not set name of text field', () => {
|
|
expect(textElement.name).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('form submission', () => {
|
|
beforeEach(() => {
|
|
wrapper = mount({
|
|
template: `<form data-testid="myForm"><chronic-duration-input name="myInput" :value="${MOCK_VALUE}"/></form>`,
|
|
components: {
|
|
ChronicDurationInput,
|
|
},
|
|
});
|
|
findComponents();
|
|
});
|
|
|
|
it('creates form data with initial value', () => {
|
|
const formData = new FormData(wrapper.find('[data-testid=myForm]').element);
|
|
const iter = formData.entries();
|
|
|
|
expect(iter.next()).toEqual({
|
|
value: ['myInput', MOCK_VALUE.toString()],
|
|
done: false,
|
|
});
|
|
expect(iter.next()).toEqual({ value: undefined, done: true });
|
|
});
|
|
|
|
it('creates form data with user-specified value', async () => {
|
|
textElement.value = '1m10s';
|
|
textElement.dispatchEvent(new Event('input'));
|
|
await nextTick();
|
|
|
|
const formData = new FormData(wrapper.find('[data-testid=myForm]').element);
|
|
const iter = formData.entries();
|
|
|
|
expect(iter.next()).toEqual({
|
|
value: ['myInput', (1 * 60 + 10).toString()],
|
|
done: false,
|
|
});
|
|
expect(iter.next()).toEqual({ value: undefined, done: true });
|
|
});
|
|
});
|
|
});
|