diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 56e587d..a923a7c 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -28,7 +28,7 @@ jobs: filters: | validate-src: - 'src/**' - - 'tests/**' + - 'test/**' - '.github/workflows/validate.yml' component-tests: diff --git a/test/components/BaseButton.test.js b/test/components/BaseButton.test.js index b8cfb47..db00515 100644 --- a/test/components/BaseButton.test.js +++ b/test/components/BaseButton.test.js @@ -148,6 +148,7 @@ describe('BaseButton', () => { } else { // link type buttons can still be clicked (to navigate the to the link) even if disabled expect(wrapper.emitted().click, 'expected click event to have been emitted').toBeTruthy(); + expect(wrapper.emitted()['click'].length).toBe(1); } } }); diff --git a/test/components/CheckboxInput.test.js b/test/components/CheckboxInput.test.js index 6af918c..6919b1d 100644 --- a/test/components/CheckboxInput.test.js +++ b/test/components/CheckboxInput.test.js @@ -123,6 +123,7 @@ describe('CheckboxInput', () => { // click on checkbox, verify received click event await cboxInput.trigger('click'); expect(wrapper.emitted().click, 'expected click event to have been emitted').toBeTruthy(); + expect(wrapper.emitted()['click'].length).toBe(1); // checkbox should now be checked expect(cboxInput.element.checked).toBe(true); @@ -148,6 +149,7 @@ describe('CheckboxInput', () => { // click on checkbox, verify received click event await cboxInput.trigger('click'); expect(wrapper.emitted().click, 'expected click event to have been emitted').toBeTruthy(); + expect(wrapper.emitted()['click'].length).toBe(1); // checkbox should now be UNchecked expect(cboxInput.element.checked).toBe(false); diff --git a/test/components/LoadingSkeleton.test.js b/test/components/LoadingSkeleton.test.js new file mode 100644 index 0000000..bd992e0 --- /dev/null +++ b/test/components/LoadingSkeleton.test.js @@ -0,0 +1,119 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import LoadingSkeleton from '@/components/LoadingSkeleton.vue'; +import { AnimationTypes } from '@/definitions'; + + +describe('LoadingSkeleton', () => { + var wrapper; + + // repeat all tests for each availble BaseButton type + describe.each([ AnimationTypes.Scan, AnimationTypes.Pulse ])('Animation type: %s', (type) => { + + // build out test cases for variants/options to be repeated for each animation type + const testCases = [ + { + desc: 'default', // not a prop, using for test case name + type: type, + radius: null, + width: null, + height: null, + isLoading: true + }, + { + desc: 'rectangle', + type: type, + radius: null, + width: '10rem', + height: '4rem', + isLoading: true + }, + { + desc: 'rectangle with rounded edges', + type: type, + radius: '16px', + width: '10rem', + height: '4rem', + isLoading: true + }, + { + desc: 'square', + type: type, + radius: null, + width: '5rem', + height: '5rem', + isLoading: true + }, + { + desc: 'square with rounded edges', + type: type, + radius: '16px', + width: '5rem', + height: '5rem', + isLoading: true + }, + { + desc: 'circle', + type: type, + radius: '100%', + width: '5rem', + height: '5rem', + isLoading: true + }, + { + desc: 'finished loading', + type: type, + radius: null, + width: null, + height: null, + isLoading: false + }, + ]; + + afterEach(() => { + wrapper.unmount(); + }); + + it.each(testCases)('$desc renders correctly', + async ({ type, radius, width, height, isLoading}) => { + const ourProps = { + animationType: type, + borderRadius: radius, + width: width, + height: height, + isLoading: isLoading + }; + + wrapper = mount(LoadingSkeleton, { + propsData: ourProps, + }); + + expect(wrapper.props()).toEqual(ourProps); + const loader = wrapper.find('.skeleton'); + + // verify skeleton loader is displayed as expected (unless isLoading is false) + if (ourProps['isLoading']) { + expect(loader.exists()).toBe(true); + expect(loader.isVisible()).toBe(true); + expect(loader.attributes().class).toBe(`skeleton ${ourProps['animationType']}`); + + var expStyle = ''; + if (ourProps['width']) { + expStyle += `width: ${ourProps['width']}; `; + } + if (ourProps['height']) { + expStyle += `height: ${ourProps['height']}; `; + } + if (ourProps['borderRadius']) { + expStyle += `border-radius: ${ourProps['borderRadius']};` + } + if (expStyle.length) { + expect(loader.attributes().style).toBe(expStyle.trim()); + } + + } else { + expect(loader.exists()).toBe(false); + } + }); + }); +}); diff --git a/test/components/ModalDialog.test.js b/test/components/ModalDialog.test.js new file mode 100644 index 0000000..c9a2f07 --- /dev/null +++ b/test/components/ModalDialog.test.js @@ -0,0 +1,271 @@ +import { describe, it, expect, afterEach, test } from 'vitest'; +import { mount } from '@vue/test-utils'; +import ModalDialog from '@/components/ModalDialog.vue'; + + +describe('ModalDialog', () => { + var wrapper; + + const ourProps = { + closeOutside: true, + dataTestid: 'our-test-modal', + }; + + const basicSlots = { + header: 'Basic Test Modal Header!', + default: 'This is the body text of our basic test modal.', + }; + + const btn1Label = 'OK!'; + const btn2Label = 'Cancel!'; + + const standardSlots = { + header: 'Standard Test Modal Header!', + default: 'This is the body text of our standard test modal.', + actions: ``, + footer: 'Standard test modal footer', + }; + + const modalSel = `[data-testid=${ourProps['dataTestid']}]`; + const modalHdrSel = 'div.modal-header'; + const modalBodySel = 'div.modal-body'; + const modalFooterSel = 'div.footer'; + const modalActionsSel = 'div.modal-actions'; + const modalActionBtn1Sel = '.button-1'; + const modalActionBtn2Sel = '.button-2'; + const modalCloseBtnSel = 'button.modal-close'; + + async function verifyBasicModal(wrapper, testSlots) { + // verify modal exists and is visible + const testModal = wrapper.find(modalSel); + expect(testModal.exists()).toBe(true); + expect(testModal.isVisible()).toBe(true); + expect(testModal.attributes().class).toBe('overlay'); + expect(testModal.attributes().role).toBe('dialog'); + + // verify the header is displayed and is correct + const modalHdr = wrapper.find(modalHdrSel); + expect(modalHdr.exists()).toBe(true); + expect(modalHdr.isVisible()).toBe(true); + expect(modalHdr.attributes().class).toBe('modal-header'); + expect(modalHdr.text()).toBe(testSlots['header']); + + // verify the body text is displayed and correct + const modalBody = wrapper.find(modalBodySel); + expect(modalBody.exists()).toBe(true); + expect(modalBody.isVisible()).toBe(true); + expect(modalBody.attributes().class).toBe('modal-body'); + expect(modalBody.text()).toBe(testSlots['default']); + + // verify dialog close button exists (will be tested in separate test) + const closeBtn = wrapper.find(modalCloseBtnSel); + expect(closeBtn.exists()).toBe(true); + expect(closeBtn.isVisible()).toBe(true); + }; + + afterEach(() => { + wrapper.unmount(); + }); + + it('basic modal renders correctly', async () => { + // modal with only a title and body text + wrapper = mount(ModalDialog, { + propsData: ourProps, + slots: basicSlots, + }); + + expect(wrapper.props()).toEqual(ourProps); + + // now call the exposed show method to have the modal be displayed; need 'await' + // to give time for the DOM to be updated (via happy-dom) otherwise won't be found + await wrapper.vm.show(); + + // verify modal is displayed as expected + await verifyBasicModal(wrapper, basicSlots); + }); + + it('standard modal renders correctly', async () => { + // modal with header/title, body text, action buttons, and a footer + wrapper = mount(ModalDialog, { + propsData: ourProps, + slots: standardSlots, + }); + + expect(wrapper.props()).toEqual(ourProps); + + // now call the exposed show method to have the modal be displayed; need 'await' + // to give time for the DOM to be updated (via happy-dom) otherwise won't be found + await wrapper.vm.show(); + + // verify modal is visible with header, body, and close button + await verifyBasicModal(wrapper, standardSlots); + + // verify action buttons are displayed; modal actions element contains the buttons + const modalActions = wrapper.find(modalActionsSel); + expect(modalActions.exists()).toBe(true); + expect(modalActions.isVisible()).toBe(true); + + const modalActionBtn1 = wrapper.find(modalActionBtn1Sel); + expect(modalActionBtn1.exists()).toBe(true); + expect(modalActionBtn1.isVisible()).toBe(true); + expect(modalActionBtn1.text()).toBe(btn1Label); + + const modalActionBtn2 = wrapper.find(modalActionBtn2Sel); + expect(modalActionBtn2.exists()).toBe(true); + expect(modalActionBtn2.isVisible()).toBe(true); + expect(modalActionBtn2.text()).toBe(btn2Label); + + // verify the footer text is displayed and correct + const modalFooter = wrapper.find(modalFooterSel); + expect(modalFooter.exists()).toBe(true); + expect(modalFooter.isVisible()).toBe(true); + expect(modalFooter.attributes().class).toBe('footer'); + expect(modalFooter.text()).toBe(standardSlots['footer']); + }); + + it('modal is displayed when call exposed show method', async () => { + // mount a basic modal + wrapper = mount(ModalDialog, { + propsData: ourProps, + slots: basicSlots, + }); + + // verify modal is not found + var testModal = wrapper.find(modalSel); + expect(testModal.exists()).toBe(false); + + // call show exposed method, need await for dom to render + await wrapper.vm.show(); + + // verify modal is now found/displayed + testModal = wrapper.find(modalSel); + expect(testModal.exists()).toBe(true); + expect(testModal.isVisible()).toBe(true); + }); + + it('modal is hidden when call exposed hide method', async () => { + // mount a basic modal + wrapper = mount(ModalDialog, { + propsData: ourProps, + slots: basicSlots, + }); + + // call show exposed method, need await for dom to render + await wrapper.vm.show(); + + // verify modal is now found/displayed + var testModal = wrapper.find(modalSel); + expect(testModal.exists()).toBe(true); + expect(testModal.isVisible()).toBe(true); + + // call hide exposed method, need await for dom to render + await wrapper.vm.hide(); + + // verify modal is no longer displayed + testModal = wrapper.find(modalSel); + expect(testModal.exists()).toBe(false); + }); + + it('clicking a modal action button generates an event', async () => { + // modal with header/title, body text, action buttons, and a footer + wrapper = mount(ModalDialog, { + propsData: ourProps, + slots: standardSlots, + }); + + expect(wrapper.props()).toEqual(ourProps); + + // now call the exposed show method to have the modal be displayed; need 'await' + // to give time for the DOM to be updated (via happy-dom) otherwise won't be found + await wrapper.vm.show(); + + // verify modal is displayed + const testModal = wrapper.find(modalSel); + expect(testModal.exists()).toBe(true); + expect(testModal.isVisible()).toBe(true); + + // click on the first action button and verify a click event was generated + const modalActionBtn1 = wrapper.find(modalActionBtn1Sel); + await modalActionBtn1.trigger('click'); + expect(wrapper.emitted().click, 'expected click event to have been emitted').toBeTruthy(); + + // click on the second action button and verify a click event was generated + const modalActionBtn2 = wrapper.find(modalActionBtn1Sel); + await modalActionBtn2.trigger('click'); + expect(wrapper.emitted().click, 'expected click event to have been emitted').toBeTruthy(); + expect(wrapper.emitted()['click'].length).toBe(2); + }); + + it('able to close modal by clicking the window close button', async () => { + // mount a basic modal + wrapper = mount(ModalDialog, { + propsData: ourProps, + slots: basicSlots, + }); + + // call show exposed method, need await for dom to render + await wrapper.vm.show(); + + // verify modal is now found/displayed + var testModal = wrapper.find(modalSel); + expect(testModal.exists()).toBe(true); + expect(testModal.isVisible()).toBe(true); + + // click the mnodal window close button + const modalCloseBtn = wrapper.find(modalCloseBtnSel); + await modalCloseBtn.trigger('click'); + + // verify modal is now closed + testModal = wrapper.find(modalSel); + expect(testModal.exists()).toBe(false); + }); + + it('modal closes when click outside of it (when closeOutside is true)', async () => { + // mount a basic modal with closeOutside true and attach do document body so it has underlying element + wrapper = mount(ModalDialog, { + propsData: ourProps, + slots: basicSlots, + attachTo: document.body, + }); + + // call show exposed method, need await for dom to render + await wrapper.vm.show(); + + // verify modal is now found/displayed + var testModal = wrapper.find(modalSel); + expect(testModal.exists()).toBe(true); + expect(testModal.isVisible()).toBe(true); + + // now click outside of the modal on the document body + await document.body.dispatchEvent(new MouseEvent('click', { bubbles: true })); + + // verify modal is now closed + testModal = wrapper.find(modalSel); + expect(testModal.exists()).toBe(false); + }); + + it('modal does not close when click outside of it (when closeOutside is false)', async () => { + // mount a basic modal with closeOutside false, and attach to document body so has underlying element + ourProps['closeOutside'] = false; + wrapper = mount(ModalDialog, { + propsData: ourProps, + slots: basicSlots, + attachTo: document.body, + }); + + // call show exposed method, need await for dom to render + await wrapper.vm.show(); + + // verify modal is now found/displayed + var testModal = wrapper.find(modalSel); + expect(testModal.exists()).toBe(true); + expect(testModal.isVisible()).toBe(true); + + // now click outside of the modal on the document body + await document.body.dispatchEvent(new MouseEvent('click', { bubbles: true })); + + // verify modal is still displayed / was not closed + testModal = wrapper.find(modalSel); + expect(testModal.exists()).toBe(true); + }); +}); diff --git a/test/components/NoticeBar.test.js b/test/components/NoticeBar.test.js new file mode 100644 index 0000000..cac78c7 --- /dev/null +++ b/test/components/NoticeBar.test.js @@ -0,0 +1,98 @@ +import { describe, it, expect, afterEach, beforeEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import NoticeBar from '@/components/NoticeBar.vue'; +import { NoticeBarTypes } from '@/definitions'; + + +describe('NoticeBar', () => { + var wrapper; + + afterEach(() => { + wrapper.unmount(); + }); + + // repeat each test for each badge type + const testCases = [ + { type: NoticeBarTypes.Info, text: 'Info noticebar text!', dataTestid: 'info-noticebar', expIconAriaLabel: 'Information' }, + { type: NoticeBarTypes.Success, text: 'Success noticebar text!', dataTestid: 'success-noticebar', expIconAriaLabel: 'Success' }, + { type: NoticeBarTypes.Warning, text: 'Warning noticebar text!', dataTestid: 'warning-noticebar', expIconAriaLabel: 'Warning' }, + { type: NoticeBarTypes.Critical, text: 'Critical noticebar text!', dataTestid: 'critical-noticebar', expIconAriaLabel: 'Critical' }, + ]; + + it.each(testCases)('$type noticebar renders correctly', ({ type, text, dataTestid, expIconAriaLabel }) => { + const ourProps = { + type: type, + dataTestid: dataTestid, + }; + const ourSlots = { + default: text, + }; + + // mount noticebar with given opts + wrapper = mount(NoticeBar, { + propsData: ourProps, + slots: ourSlots, + }); + + expect(wrapper.props()).toEqual(ourProps); + + // verify noticebar exists, and is visible with correct text + const nbSel = `[data-testid=${ourProps['dataTestid']}]`; + const nb = wrapper.find(nbSel); + expect(nb.exists()).toBe(true); + expect(nb.attributes().class).toBe(`${ourProps['type']} notice notice-bar`); + expect(nb.isVisible()).toBe(true); + expect(nb.text()).toEqual(ourSlots['default']); + + // verify icon is displayed on noticebar and corresponds to type + const nbIconSel = 'span.icon'; + const nbIcon = wrapper.find(nbIconSel); + expect(nbIcon.exists()).toBe(true); + expect(nbIcon.isVisible()).toBe(true); + const nbIconAriaLabel = nbIcon.find('svg').attributes()['aria-label']; + expect(nbIconAriaLabel).toBe(expIconAriaLabel); + }); + + describe('NoticeBar with CTA', () => { + var wrapper; + const ourProps = { + type: NoticeBarTypes.Info, + dataTestid: `info-noticebar-with-cta`, + }; + const nbBtnLabel = 'Click me!'; + const ourSlots = { + default: 'This noticebar has a CTA!', + cta: ``, + }; + const nbBtnSel = 'button.nb-cta'; + + beforeEach(() => { + // mount a noticebar with a CTA button + wrapper = mount(NoticeBar, { + propsData: ourProps, + slots: ourSlots, + }); + + expect(wrapper.props()).toEqual(ourProps); + }); + + afterEach(() => { + wrapper.unmount(); + }); + + it('CTA button renders correctly on noticebar', async () => { + const nbBtn = wrapper.find(nbBtnSel); + expect(nbBtn.exists()).toBe(true); + expect(nbBtn.isVisible()).toBe(true); + expect(nbBtn.text()).toBe(nbBtnLabel); + }); + + it('clicking on noticebar CTA button generates a click event', async () => { + // click on button and verify event triggered + const nbBtn = wrapper.find(nbBtnSel); + await nbBtn.trigger('click'); + expect(wrapper.emitted().click, 'expected click event to have been emitted').toBeTruthy(); + expect(wrapper.emitted()['click'].length).toBe(1); + }); + }); +}); diff --git a/test/components/SegmentedControl.test.js b/test/components/SegmentedControl.test.js new file mode 100644 index 0000000..fab3757 --- /dev/null +++ b/test/components/SegmentedControl.test.js @@ -0,0 +1,227 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import SegmentedControl from '@/components/SegmentedControl.vue'; + + +describe('SegmentedControl', () => { + var wrapper; + const options = [ + { + "label": "Option 1", + "value": 1 + }, + { + "label": "Option 2", + "value": 2 + }, + { + "label": "Option 3", + "value": 3 + }, + { + "label": "Option 4", + "value": 4 + }, + { + "label": "Option 5", + "value": 5 + }, + { + "label": "Option 6", + "value": 6, + }, + { + "label": "Option 7", + "value": 7, + } + ]; + + // build out test cases for variants/options + const testCases = [ + { + label: 'Please select an option', + options: options, + modelValue: 4, + required: false, + disabled: false, + dataTestid: 'segmented-control-1', + }, + { + label: 'Please select an option', + options: options, + modelValue: 1, + required: true, + disabled: false, + dataTestid: 'segmented-control-required', + }, + { + label: 'Please select an option', + options: options, + modelValue: 7, + required: true, + disabled: true, + dataTestid: 'segmented-control-disabled', + }, + ]; + + afterEach(() => { + wrapper.unmount(); + }); + + it.each(testCases)('renders correctly with the given options', + async ({ label, options, modelValue, required, disabled, dataTestid }) => { + const ourProps = { + label: label, + options: options, + modelValue: modelValue, + required: required, + disabled: disabled, + dataTestid: dataTestid, + }; + + wrapper = mount(SegmentedControl, { + propsData: ourProps, + }); + + expect(wrapper.props()).toEqual(ourProps); + + // find segment control, verify exists and is visible + const segCtrlWrapper = wrapper.find('.segment-wrapper'); + expect(segCtrlWrapper.exists()).toBe(true); + const segCtrlLabel = segCtrlWrapper.find('.label'); + expect(segCtrlLabel.exists()).toBe(true); + expect(segCtrlLabel.isVisible()).toBe(true); + expect(segCtrlLabel.text()).toContain(ourProps['label']); + + // verify each option exists in the control and has correct label + const segCtrlList = wrapper.find('.segment-list'); + expect(segCtrlList.exists()).toBe(true); + const segCtrlButtons = segCtrlList.findAll('button'); + expect(segCtrlButtons.length).toBe(ourProps['options'].length); + + segCtrlButtons.forEach((item, index) => { + expect(item.text()).toBe(ourProps['options'][index]['label']); + expect(item.attributes()['data-testid']).toBe(ourProps['dataTestid']); + if(ourProps['disabled']) { + expect(item.attributes().class).toContain('disabled'); + } else { + expect(item.attributes().class).not.toContain('disabled'); + } + // verify only the specified default selected option is selected + if(ourProps['modelValue'] == (index + 1)) { + expect(item.attributes().class).toContain('selected'); + } else { + expect(item.attributes().class).not.toContain('selected'); + } + }); + + // if required option set, ensure required asterisk is displayed + if (ourProps['required'] == true) { + expect(segCtrlLabel.text()).toContain('*'); + } + }); + + it('clicking an option selects the option and triggers click event', async () => { + const ourProps = { + label: 'Please select an option', + options: options, + modelValue: 4, + required: false, + disabled: false, + dataTestid: 'segmented-control-click-test', + }; + + wrapper = mount(SegmentedControl, { + propsData: ourProps, + }); + + // get all the option buttons in our segment control + const segCtrlList = wrapper.find('.segment-list'); + expect(segCtrlList.exists()).toBe(true); + const segCtrlButtons = segCtrlList.findAll('button'); + expect(segCtrlButtons.length).toBe(ourProps['options'].length); + + // verify the first option is not currently selected + expect(segCtrlButtons[0].attributes().class).not.toContain('selected'); + + // now click on the first option, verify click event triggered + await segCtrlButtons[0].trigger('click'); + expect(wrapper.emitted().click, 'expected click event to have been emitted').toBeTruthy(); + expect(wrapper.emitted()['click'].length).toBe(1); + + // verify option is now selected + expect(segCtrlButtons[0].attributes().class).toContain('selected'); + + // now click on the last option in the control + await segCtrlButtons[segCtrlButtons.length - 1].trigger('click'); + expect(wrapper.emitted().click, 'expected click event to have been emitted').toBeTruthy(); + expect(wrapper.emitted()['click'].length).toBe(2); + + // now verify the first option is no longer selected, and the last option is + expect(segCtrlButtons[0].attributes().class).not.toContain('selected'); + expect(segCtrlButtons[segCtrlButtons.length - 1].attributes().class).toContain('selected'); + }); + + it('option badges render correctly', async () => { + const optionBadges = { '1':'45', '2':'37', '3':'18', '4': null, '5': '550', '6': null, '7': '789' }; + const ourProps = { + label: 'Please select an option', + options: options, + modelValue: 4, + required: false, + disabled: false, + dataTestid: 'segmented-control-with-option-badges', + optionBadges: optionBadges, + }; + + wrapper = mount(SegmentedControl, { + propsData: ourProps, + }); + + // find our segmented control buttons and verify each option is dislayed with correct optionBadge + const segCtrlList = wrapper.find('.segment-list'); + expect(segCtrlList.exists()).toBe(true); + const segCtrlButtons = segCtrlList.findAll('button'); + expect(segCtrlButtons.length).toBe(ourProps['options'].length); + + segCtrlButtons.forEach((item, index) => { + if (ourProps['optionBadges'][index + 1]) { + expect(item.text()).toEqual(`${ourProps['options'][index]['label']} ${ourProps['optionBadges'][index + 1]}`); + } else { + // no option badge specified/expected for that particular item + expect(item.text()).toEqual(ourProps['options'][index]['label']); + } + }); + }); + + it('no click event is emitted when disabled', async () => { + const ourProps = { + label: 'Please select an option', + options: options, + modelValue: 4, + required: false, + disabled: true, + dataTestid: 'segmented-control-disabled', + }; + + wrapper = mount(SegmentedControl, { + propsData: ourProps, + }); + + // get all the option buttons in our segment control + const segCtrlList = wrapper.find('.segment-list'); + expect(segCtrlList.exists()).toBe(true); + const segCtrlButtons = segCtrlList.findAll('button'); + expect(segCtrlButtons.length).toBe(ourProps['options'].length); + + // verify the first option is not currently selected + expect(segCtrlButtons[0].attributes().class).not.toContain('selected'); + + // now attempt to click on the first option, verify click event not triggered since control is disabled + await segCtrlButtons[0].trigger('click'); + expect(wrapper.emitted().click, 'expected click event to not have been emitted').toBeFalsy(); + + // verify option is still not selected + expect(segCtrlButtons[0].attributes().class).not.toContain('selected'); + }); +}); diff --git a/test/components/SelectInput.test.js b/test/components/SelectInput.test.js new file mode 100644 index 0000000..f27d087 --- /dev/null +++ b/test/components/SelectInput.test.js @@ -0,0 +1,244 @@ +import { describe, it, expect, afterEach } from 'vitest'; +import { mount } from '@vue/test-utils'; +import SelectInput from '@/components/SelectInput.vue'; + + +describe('SelectInput', () => { + var wrapper; + const options = [ + { + "label": "Yes absolutely", + "value": "yes", + }, + { + "label": "They have a good chance", + "value": "chance" + }, + { + "label": "Wait and see", + "value": "wait", + }, + { + "label": "Probably not", + "value": "no", + }, + { + "label": "Never going to happen", + "value": "never", + } + ]; + + // build out test cases for variants/options + const testCases = [ + { + options: options, + defaultSelected: options[2]['value'], + helpText: null, + required: false, + autoFocus: false, + disabled: false, + dataTestid: 'standard-select-input', + }, + { + options: options, + defaultSelected: null, + helpText: null, + required: false, + autoFocus: false, + disabled: false, + dataTestid: 'select-input-no-default', + }, + { + options: options, + defaultSelected: options[0]['value'], + helpText: 'This is the help text!', + required: false, + autoFocus: false, + disabled: false, + dataTestid: 'select-input-help-text', + }, + { + options: options, + defaultSelected: null, + helpText: null, + required: true, + autoFocus: false, + disabled: false, + dataTestid: 'select-input-required', + }, + { + options: options, + defaultSelected: options[0]['value'], + helpText: null, + required: false, + autoFocus: true, + disabled: false, + dataTestid: 'select-input-autofocus', + }, + { + options: options, + defaultSelected: options[0]['value'], + helpText: null, + required: false, + autoFocus: false, + disabled: true, + dataTestid: 'select-input-disabled', + }, + ]; + + afterEach(() => { + wrapper.unmount(); + }); + + it.each(testCases)('renders correctly with the given options', + async ({ options, defaultSelected, helpText, required, autoFocus, disabled, dataTestid }) => { + const ourProps = { + name: 'standard', + label: 'Will your favourite MLB team win the world series this year?', + options: options, + modelValue: defaultSelected, + help: helpText, + required: required, + autofocus: autoFocus, + disabled: disabled, + dataTestid: dataTestid, + }; + + wrapper = mount(SelectInput, { + propsData: ourProps, + }); + + expect(wrapper.props()).toEqual(ourProps); + + // find, verify exists and is visible + const selInputSel = `[data-testid=${ourProps['dataTestid']}]`; + const selInput = wrapper.find(selInputSel); + expect(selInput.exists()).toBe(true); + expect(selInput.isVisible()).toBe(true); + expect(selInput.attributes().name).toBe(ourProps['name']); + + const selInputLabel = wrapper.find('.label'); + expect(selInputLabel.exists()).toBe(true); + expect(selInputLabel.isVisible()).toBe(true); + expect(selInputLabel.text()).toContain(ourProps['label']); + + // if required option set, and no item selected by default, ensure required asterisk is displayed + if (ourProps['required'] == true && !ourProps['modelValue']) { + expect(selInputLabel.text()).toContain('*'); + } + + // if help text set, ensure it is displayed + if (ourProps['help']) { + const helpTxt = wrapper.find('.help-label'); + expect(helpTxt.isVisible()).toBe(true); + expect(helpTxt.text()).toBe(ourProps['help']); + } + + // if we set a default value ensure it is currently selected + if (ourProps['modelValue']) { + expect(selInput.element.value).toBe(ourProps['modelValue']); + } + + // if disabled set, verify + if(ourProps['disabled']) { + expect(selInput.attributes().disabled).not.toBeUndefined(); + } else { + expect(selInput.attributes().disabled).toBeUndefined(); + } + + // now verify each option exists in the pulldown + const selInputOpts = selInput.findAll('option'); + expect(selInputOpts.length).toBe(ourProps['options'].length); + + selInputOpts.forEach((item, index) => { + expect(item.text()).toBe(ourProps['options'][index]['label']); + expect(item.attributes().value).toBe(ourProps['options'][index]['value']); + }); + }); + + it('able to select an option', async () => { + const ourProps = { + name: 'standard', + label: 'Please select an option', + options: options, + modelValue: options[2]['value'], + help: null, + required: false, + autofocus: false, + disabled: true, + dataTestid: 'select-input-select-test', + }; + + wrapper = mount(SelectInput, { + propsData: ourProps, + }); + + // ensure default item is currently selected + const selInputSel = `[data-testid=${ourProps['dataTestid']}]`; + const selInput = wrapper.find(selInputSel); + expect(selInput.exists()).toBe(true); + expect(selInput.element.value).toBe(ourProps['modelValue']); + + // change to the first option + await selInput.setValue(options[0]['value']); + expect(selInput.element.value).toBe(options[0]['value']); + }); + + it('unable to select an option when disabled', async () => { + const ourProps = { + name: 'standard', + label: 'Please select an option', + options: options, + modelValue: options[2]['value'], + help: null, + required: false, + autofocus: false, + disabled: true, + dataTestid: 'select-input-disabled-test', + }; + + wrapper = mount(SelectInput, { + propsData: ourProps, + }); + + // ensure default item is currently selected + const selInputSel = `[data-testid=${ourProps['dataTestid']}]`; + const selInput = wrapper.find(selInputSel); + expect(selInput.exists()).toBe(true); + expect(selInput.element.value).toBe(ourProps['modelValue']); + + // clicking on disabled control should not cause a click event + await selInput.trigger('click'); + expect(wrapper.emitted().click, 'expected click event to have been emitted').toBeFalsy(); + }); + + it('able to reset the selected input using exposed reset method', async () => { + const ourProps = { + name: 'standard', + label: 'Please select an option', + options: options, + modelValue: options[2]['value'], + help: null, + required: false, + autofocus: false, + disabled: false, + dataTestid: 'select-input-reset-test', + }; + + wrapper = mount(SelectInput, { + propsData: ourProps, + }); + + // verify the 3rd option is selected by default + const selInputSel = `[data-testid=${ourProps['dataTestid']}]`; + const selInput = wrapper.find(selInputSel); + expect(selInput.exists()).toBe(true); + expect(selInput.element.value).toBe(ourProps['modelValue']); + + // reset the select input using the exposed reset method; need await for dom to render + await wrapper.vm.reset(); + + // verify no item is selected anymore + expect(selInput.element.value).toBe(''); + }); +});