import { mount } from '@vue/test-utils'; 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(() => { wrapper.destroy(); wrapper = null; textElement = null; hiddenElement = null; }); const findComponents = () => { textElement = wrapper.find('input[type=text]').element; hiddenElement = wrapper.find('input[type=hidden]').element; }; const createComponent = (props = {}) => { if (wrapper) { throw new Error('There should only be one wrapper created per test'); } 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 wrapper.vm.$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 wrapper.vm.$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 wrapper.vm.$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 wrapper.vm.$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 wrapper.vm.$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 wrapper.vm.$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 wrapper.vm.$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 wrapper.vm.$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 wrapper.vm.$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: '