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('');
+ });
+});