diff options
Diffstat (limited to 'spec/frontend/vue_shared/components/chronic_duration_input_spec.js')
-rw-r--r-- | spec/frontend/vue_shared/components/chronic_duration_input_spec.js | 390 |
1 files changed, 390 insertions, 0 deletions
diff --git a/spec/frontend/vue_shared/components/chronic_duration_input_spec.js b/spec/frontend/vue_shared/components/chronic_duration_input_spec.js new file mode 100644 index 00000000000..530d01402c6 --- /dev/null +++ b/spec/frontend/vue_shared/components/chronic_duration_input_spec.js @@ -0,0 +1,390 @@ +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: '<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 () => { + wrapper.setData({ value: MOCK_VALUE }); + await wrapper.vm.$nextTick(); + + expect(textElement.value).toBe('2 hrs 20 mins'); + 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 wrapper.vm.$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 wrapper.vm.$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 }); + }); + }); +}); |