import { shallowMount } from '@vue/test-utils'; import textboxQuestion from '@/digital-components/textbox-question/textbox-question.vue'; import { useField, validate } from 'vee-validate'; // Mock CMS content const questionText = 'Question Text'; const mockMixin = { methods: { getCmsContent: jest.fn().mockImplementation(() => questionText) } }; jest.mock('vee-validate', () => ({ useField: jest.fn(), validate: jest.fn() })); useField.mockImplementation(() => Promise.resolve({})); validate.mockImplementation(() => Promise.resolve({})); const searchButtonSelector = 'button[type="submit"][aria-label="Search button"]'; const selectButtonSelector = 'button[type="submit"][aria-label="Select button"]'; const errorMessageSelector = { ref: 'errorMessageDiv' }; describe('TextboxQuestion', () => { describe('should render', () => { describe('an input element', () => { test('that exists with expected classes', () => { // Arrange const wrapper = shallowMount(textboxQuestion, { mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.exists()).toBeTruthy(); expect(input.classes()).toContain('form-control'); }); describe('with correct attributes and classes given prop', () => { test('type', () => { // Arrange const type = 'crocodile'; const wrapper = shallowMount(textboxQuestion, { propsData: { type }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.attributes().type).toBe(type); }); test('placeholderText', () => { // Arrange const placeholderText = 'text in place'; const wrapper = shallowMount(textboxQuestion, { propsData: { placeholderText }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.attributes().placeholder).toBe(placeholderText); }); test('inputId', () => { // Arrange const inputId = '123009'; const wrapper = shallowMount(textboxQuestion, { propsData: { inputId }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.attributes('id')).toBe(inputId); expect(input.attributes('name')).toBe(inputId); }); describe('isDisabled', () => { test('with value "true"', () => { // Arrange const isDisabled = true; const wrapper = shallowMount(textboxQuestion, { propsData: { isDisabled }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.attributes('aria-disabled')).toBe(String(isDisabled)); expect(input.attributes().disabled).toBeDefined(); }); test('with value "false"', () => { // Arrange const isDisabled = false; const wrapper = shallowMount(textboxQuestion, { propsData: { isDisabled }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.attributes('aria-disabled')).toBe(String(isDisabled)); expect(input.attributes().disabled).not.toBeDefined(); }); }); describe('isRequired', () => { test('with value "true"', () => { // Arrange const isRequired = true; const wrapper = shallowMount(textboxQuestion, { propsData: { isRequired }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.attributes().required).toBeDefined(); }); test('with value "false"', () => { // Arrange const isRequired = false; const wrapper = shallowMount(textboxQuestion, { propsData: { isRequired }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.attributes().required).not.toBeDefined(); }); }); describe('hasIcon', () => { test('with value "true"', () => { // Arrange const hasIcon = true; const wrapper = shallowMount(textboxQuestion, { propsData: { hasIcon }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.classes()).toContain('has-icon'); }); test('with value "false"', () => { // Arrange const hasIcon = false; const wrapper = shallowMount(textboxQuestion, { propsData: { hasIcon }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.classes()).not.toContain('has-icon'); }); }); describe('iconRight', () => { test('with value "true"', () => { // Arrange const iconRight = true; const wrapper = shallowMount(textboxQuestion, { propsData: { iconRight }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.classes()).toContain('icon-right'); }); test('with value "false"', () => { // Arrange const iconRight = false; const wrapper = shallowMount(textboxQuestion, { propsData: { iconRight }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.classes()).not.toContain('icon-right'); }); }); test('validationRules', () => { // Arrange const validationRules = 'random validation rules'; const wrapper = shallowMount(textboxQuestion, { propsData: { validationRules }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.attributes().validationrules).toBe(validationRules); }); test('cmsWidgetName', () => { // Arrange const cmsWidgetName = 'name of widget'; const wrapper = shallowMount(textboxQuestion, { propsData: { cmsWidgetName }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); const expected = '#name of widget'; // Act const input = wrapper.find('input'); // Assert expect(input.attributes('data-bs-target')).toBe(expected); }); describe('maxLength', () => { test('with no value', () => { // Arrange const wrapper = shallowMount(textboxQuestion, { mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); const expected = '999'; // Act const input = wrapper.find('input'); // Assert expect(input.attributes('maxlength')).toBe(expected); }); test('with some value', () => { // Arrange const maxLength = '123'; const wrapper = shallowMount(textboxQuestion, { propsData: { maxLength }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.attributes('maxlength')).toBe(maxLength); }); }); describe('cornerStyle', () => { test('with value "rounded"', () => { // Arrange const cornerStyle = 'rounded'; const wrapper = shallowMount(textboxQuestion, { propsData: { cornerStyle }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.classes()).toContain('rounded-pill'); }); test('with value other than "rounded"', () => { // Arrange const cornerStyle = 'decidedly not rounded'; const wrapper = shallowMount(textboxQuestion, { propsData: { cornerStyle }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.classes()).not.toContain('rounded-pill'); }); }); describe('includeSelectIcon', () => { test('with value "true"', () => { // Arrange const includeSelectIcon = true; const wrapper = shallowMount(textboxQuestion, { propsData: { includeSelectIcon }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.attributes('data-bs-toggle')).toBe('modal'); }); test('with value "false"', () => { // Arrange const includeSelectIcon = false; const wrapper = shallowMount(textboxQuestion, { propsData: { includeSelectIcon }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.attributes('data-bs-toggle')).toBe(''); }); }); test('min', () => { // Arrange const min = 'some value'; const wrapper = shallowMount(textboxQuestion, { propsData: { min }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.attributes().min).toBe(min); }); test('max', () => { // Arrange const max = 'some value'; const wrapper = shallowMount(textboxQuestion, { propsData: { max }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const input = wrapper.find('input'); // Assert expect(input.attributes().max).toBe(max); }); }); }); describe('a label element given that displayQuestionText is true with correct attributes and classes set with prop', () => { const displayQuestionText = true; test('inputId', () => { // Arrange const inputId = '123758'; const wrapper = shallowMount(textboxQuestion, { propsData: { displayQuestionText, inputId }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const label = wrapper.find('label'); // Assert expect(label.exists()).toBeTruthy(); expect(label.attributes().for).toBe(inputId); expect(label.attributes('aria-label')).toBe(questionText); expect(label.classes()).toContain('form-label'); }); describe('questionAlignment', () => { test('having value "center"', () => { // Arrange const questionAlignment = 'center'; const wrapper = shallowMount(textboxQuestion, { propsData: { displayQuestionText, questionAlignment }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const label = wrapper.find('label'); // Assert expect(label.exists()).toBeTruthy(); expect(label.attributes('aria-label')).toBe(questionText); expect(label.classes()).toContain('form-label'); expect(label.classes()).toContain('text-center'); expect(label.classes()).toContain('w-100'); expect(label.classes()).toContain('mb-5'); }); test('having value other than "center"', () => { // Arrange const questionAlignment = 'not center'; const wrapper = shallowMount(textboxQuestion, { propsData: { displayQuestionText, questionAlignment }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const label = wrapper.find('label'); // Assert expect(label.exists()).toBeTruthy(); expect(label.attributes('aria-label')).toBe(questionText); expect(label.classes()).toContain('form-label'); expect(label.classes()).not.toContain('text-center'); expect(label.classes()).not.toContain('w-100'); expect(label.classes()).not.toContain('mb-5'); }); }); }); test('a search button with the correct attributes and classes if includeSearchIcon is true', () => { // Arrange const includeSearchIcon = true; const wrapper = shallowMount(textboxQuestion, { propsData: { includeSearchIcon }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const searchButton = wrapper.find(searchButtonSelector); // Assert expect(searchButton.exists()).toBeTruthy(); expect(searchButton.attributes('type')).toBe('submit'); expect(searchButton.attributes('aria-label')).toBe('Search button'); }); describe('a select button with the correct attributes and classes if includeSelectIcon is true', () => { const includeSelectIcon = true; test('where cmsWidgetName defined', () => { // Arrange const cmsWidgetName = 'name of cms widget'; const wrapper = shallowMount(textboxQuestion, { propsData: { includeSelectIcon, cmsWidgetName }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); const expectedDataBsTarget = '#name of cms widget'; // Act const selectButton = wrapper.find(selectButtonSelector); // Assert expect(selectButton.exists()).toBeTruthy(); expect(selectButton.attributes().type).toBe('submit'); expect(selectButton.attributes('data-bs-toggle')).toBe('modal'); expect(selectButton.attributes('data-bs-target')).toBe(expectedDataBsTarget); expect(selectButton.attributes('aria-label')).toBe('Select button'); }); test('where cmsWidgetName not defined', () => { // Arrange const wrapper = shallowMount(textboxQuestion, { propsData: { includeSelectIcon }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); const expectedDataBsTarget = '#'; // Act const selectButton = wrapper.find(selectButtonSelector); // Assert expect(selectButton.exists()).toBeTruthy(); expect(selectButton.attributes().type).toBe('submit'); expect(selectButton.attributes('data-bs-toggle')).toBe('modal'); expect(selectButton.attributes('data-bs-target')).toBe(expectedDataBsTarget); expect(selectButton.attributes('aria-label')).toBe('Select button'); }); }); test('an error with the correct attributes and classes if errorMessage is not empty', () => { // Arrange const errorMessage = 'some not empty message'; useField.mockReturnValueOnce({ errorMessage }); const wrapper = shallowMount(textboxQuestion, { mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const error = wrapper.find(errorMessageSelector); // Assert expect(error.exists()).toBeTruthy(); expect(error.attributes().style).not.toBeDefined(); expect(error.classes()).toContain('form-test-error'); expect(error.classes()).toContain('row'); expect(error.classes()).toContain('my-1'); }); }); describe('does not render', () => { test('a label element if displayQuestionText is false', () => { // Arrange const displayQuestionText = false; const wrapper = shallowMount(textboxQuestion, { propsData: { displayQuestionText }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const label = wrapper.find('label'); // Assert expect(label.exists()).not.toBeTruthy(); }); test('a search button element if includeSearchIcon is false', () => { // Arrange const includeSearchIcon = false; const wrapper = shallowMount(textboxQuestion, { propsData: { includeSearchIcon }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const searchButton = wrapper.find(searchButtonSelector); // Assert expect(searchButton.exists()).not.toBeTruthy(); }); test('a select button if includeSelectIcon is false', () => { // Arrange const includeSelectIcon = false; const wrapper = shallowMount(textboxQuestion, { propsData: { includeSelectIcon }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const selectButton = wrapper.find(selectButtonSelector); // Assert expect(selectButton.exists()).not.toBeTruthy(); }); test('an error span if errorMessage is empty', () => { // Arrange const errorMessage = ''; const wrapper = shallowMount(textboxQuestion, { propsData: { errorMessage }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act const errorSpan = wrapper.find(errorMessageSelector); // Assert expect(errorSpan.attributes().style).toContain('display: none;'); }); }); test('should return the errorMessage, handleBlur, handleChange, meta, validate and errors values from the useField hook', () => { // Arrange const errorMessage = 'some not empty message'; const handleBlur = () => { console.log('blur'); }; const handleChange = () => { console.log('change'); }; const meta = 'meta content'; const setValidate = () => { console.log('validate'); }; const errors = 'content of errors'; useField.mockReturnValueOnce({ errorMessage, handleBlur, handleChange, meta, errors, validate: setValidate }); const wrapper = shallowMount(textboxQuestion, { mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Assert expect(wrapper.vm.errorMessage).toBe(errorMessage); expect(wrapper.vm.handleBlur).toBe(handleBlur); expect(wrapper.vm.handleChange).toBe(handleChange); expect(wrapper.vm.meta).toBe(meta); expect(wrapper.vm.validate).toBe(setValidate); expect(wrapper.vm.errors).toBe(errors); }); describe('computed', () => { test('"questionText" should return value based on getCmsContent result', () => { // Arrange const cmsContent = 'content from cms'; mockMixin.methods.getCmsContent.mockReturnValueOnce(cmsContent); const wrapper = shallowMount(textboxQuestion, { mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Assert expect(wrapper.vm.questionText).toBe(cmsContent); }); describe('"value" should', () => { test('get the modelValue prop', () => { // Arrange const modelValue = 'value of modelValue'; const wrapper = shallowMount(textboxQuestion, { propsData: { modelValue }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Assert expect(wrapper.vm.value).toBe(modelValue); }); test('emit the "update:modelValue" event when updated', () => { // Arrange const wrapper = shallowMount(textboxQuestion, { mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); const newValue = 'updated value'; // Act wrapper.vm.value = newValue; // Assert expect(wrapper.emitted('update:modelValue')).toBeTruthy(); expect(wrapper.emitted('update:modelValue').length).toBe(1); // event emitted once expect(wrapper.emitted('update:modelValue')[0]).toEqual([newValue]); // emitted with expected payload }); }); describe('labelText', () => { test('should return the question text with no break characters if disableAutoFill is true', () => { // Arrange const disableAutoFill = true; const wrapper = shallowMount(textboxQuestion, { propsData: { disableAutoFill }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // TODO this seems like strange behavior to me const expectedLabelText = 'Q⁠uestion T⁠ext'; // Assert expect(wrapper.vm.labelText).toBe(expectedLabelText); }); test('should return the question text as it is if disableAutoFill is false', () => { // Arrange const disableAutoFill = false; const wrapper = shallowMount(textboxQuestion, { propsData: { disableAutoFill }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Assert expect(wrapper.vm.labelText).toBe(questionText); }); }); }); describe('watch', () => { test('should validate the new value and trigger full validation if valid', async () => { // Arrange const handleChange = jest.fn(); const modelValue = 'initial value'; const validationRules = 'required'; validate.mockReturnValueOnce({ valid: true }); useField.mockReturnValueOnce({ handleChange }); const wrapper = shallowMount(textboxQuestion, { propsData: { modelValue, validationRules }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); const newValue = 'new value'; // Act await wrapper.vm.$options.watch.value.call(wrapper.vm, newValue); // Assert expect(validate).toHaveBeenCalledWith(newValue, validationRules); expect(handleChange).toHaveBeenCalledWith(newValue); }); test('should not trigger full validation if invalid', async () => { // Arrange const handleChange = jest.fn(); const modelValue = 'initial value'; const validationRules = 'required'; validate.mockReturnValueOnce({ valid: false }); useField.mockReturnValueOnce({ handleChange }); const wrapper = shallowMount(textboxQuestion, { propsData: { modelValue, validationRules }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); const newValue = 'failing value'; // Act await wrapper.vm.$options.watch.value.call(wrapper.vm, newValue); // Assert expect(validate).toHaveBeenCalledWith(newValue, validationRules); expect(handleChange).not.toHaveBeenCalled(); }); test('should emit new value when modelValue is changed', async () => { // Act const wrapper = shallowMount(textboxQuestion, { global: { directives: { maska: jest.fn() } }, propsData: { modelValue: 'val' }, mixins: [mockMixin] }); await wrapper.find('input').setValue('val2'); // Assert expect(wrapper.emitted()).toHaveProperty('change'); }); }); describe('mounted hook', () => { test('should emit an inputId assigned event with the inputId prop as an argument', () => { // Arrange const inputId = '126743'; const wrapper = shallowMount(textboxQuestion, { propsData: { inputId }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Assert expect(wrapper.emitted()['textboxQuestionEvent.inputIdAssigned']).toBeTruthy(); expect(wrapper.emitted()['textboxQuestionEvent.inputIdAssigned'][0]).toEqual([inputId]); }); }); describe('method', () => { describe('trimOnPaste', () => { test('should trim the pasted text and update the model value', () => { // Arrange document.getElementById = jest.fn(() => ({ value: ' hello ', selectionStart: 7, selectionEnd: 7 })); const stopPropagation = jest.fn(); const preventDefault = jest.fn(); const data = { getData: jest.fn(() => ' world ') }; const inputId = '123758'; const pasteEvent = { type: 'paste', srcElement: { id: inputId }, clipboardData: data, stopPropagation, preventDefault }; const wrapper = shallowMount(textboxQuestion, { propsData: { inputId }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act wrapper.vm.trimOnPaste(pasteEvent); // Assert expect(stopPropagation).toHaveBeenCalled(); expect(preventDefault).toHaveBeenCalled(); expect(data.getData).toHaveBeenCalledWith('Text'); expect(wrapper.emitted()['update:modelValue']).toBeTruthy(); expect(wrapper.emitted()['update:modelValue'][0]).toEqual([' hello world ']); }); test('should trim the dropped text and update the model value', () => { // Arrange document.getElementById = jest.fn(() => ({ value: ' hello ', selectionStart: 7, selectionEnd: 7 })); const stopPropagation = jest.fn(); const preventDefault = jest.fn(); const data = { getData: jest.fn(() => ' world ') }; const inputId = '123758'; const dropEvent = { type: 'drop', srcElement: { id: inputId }, dataTransfer: data, stopPropagation, preventDefault }; const wrapper = shallowMount(textboxQuestion, { propsData: { inputId }, mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act wrapper.vm.trimOnPaste(dropEvent); // Assert expect(stopPropagation).toHaveBeenCalled(); expect(preventDefault).toHaveBeenCalled(); expect(data.getData).toHaveBeenCalledWith('Text'); expect(wrapper.emitted()['update:modelValue']).toBeTruthy(); expect(wrapper.emitted()['update:modelValue'][0]).toEqual([' hello world ']); }); }); describe('clickedSearch', () => { test('should emit a click event if the meta valid flag is true', () => { // Arrange const meta = { valid: true }; useField.mockReturnValueOnce({ meta }); const wrapper = shallowMount(textboxQuestion, { mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act wrapper.vm.clickedSearch(); // Assert expect(wrapper.emitted()['click-event']).toBeTruthy(); }); test('should not emit a click event if the meta valid flag is falsy', () => { // Arrange const meta = { }; useField.mockReturnValueOnce({ meta }); const wrapper = shallowMount(textboxQuestion, { mixins: [mockMixin], global: { directives: { maska: jest.fn() } } }); // Act wrapper.vm.clickedSearch(); // Assert expect(wrapper.emitted()['click-event']).not.toBeTruthy(); }); }); }); });