diff --git a/src/common-components/button-question/button-question.spec.js b/src/common-components/button-question/button-question.spec.js index d36b73012..591b34317 100644 --- a/src/common-components/button-question/button-question.spec.js +++ b/src/common-components/button-question/button-question.spec.js @@ -1,6 +1,9 @@ import { shallowMount } from "@vue/test-utils"; import buttonQuestion from "@/common-components/button-question/button-question"; import { nextTick } from "vue"; +import { getMountOptions } from "@/helpers/unit-test-helper.js"; +import store from "@/store"; +jest.mock("@/store",()=>{return{};},{virtual:true}); describe("buttonQuestion.vue", () => { it("Should show overflow classes on fieldset if isOverflowScrollable is true", () => { @@ -45,6 +48,65 @@ describe("buttonQuestion.vue", () => { }); }); +describe("buttonQuestion.vue", () => { + it("Fieldset classes should contain ui-radio if button type is radio", () => { + // Act + const wrapper = shallowMount(buttonQuestion, { + propsData: { + buttonType: "radio", + } + }); + // Assert + const Div = wrapper.find('fieldset div'); + expect(Div.classes()).toContain("ui-radio"); + }); +}); + + +// testing a computed property +describe("buttonQuestion.vue", () => { + it("getColLength should return '12' if prop isWide is set to true", () => { + // Act + const localThis = { isWide: true } + + expect(buttonQuestion.computed.getColLength.call(localThis)).toBe("12"); + }); +}); + +describe("buttonQuestion.vue", () => { + it("getColLength should return '' if prop isWide is set to false", () => { + // Act + const localThis = { + isWide: false, + answers: ['a', 'b'] + } + + expect(buttonQuestion.computed.getColLength.call(localThis)).toBe(""); + }); +}); + +describe("buttonQuestion.vue", () => { + it("Should return answer.Text if prop useTextForValue is true", async () => { + // Act + const localThis = { useTextForValue: true }; + const answer = { 'Name': 'testName', 'Text': 'testText' }; + + // Assert + expect(buttonQuestion.methods.getValues.call(localThis, answer)).toBe('testText'); + }); +}); + +describe("buttonQuestion.vue", () => { + it("Should return answer.Name if prop useTextForValue is false and answer.Name exists", async () => { + // Act + const localThis = { useTextForValue: false }; + const answer = { 'Name': 'testName', 'Text': 'testText' }; + + // Assert + expect(buttonQuestion.methods.getValues.call(localThis, answer)).toBe('testName'); + }); +}); + describe("buttonQuestion.vue", () => { it("Should trigger event modelValue change to new value on when radio button selected", async () => { // Act @@ -76,3 +138,54 @@ describe("buttonQuestion.vue", () => { }); }); +describe("buttonQuestion.vue", () => { + it("Should add a value to this.selectedValues if prop isMultiSelect is true, checkValue is true and this.selectedValues already exists", () => { + // Act + const wrapper = shallowMount(buttonQuestion, { + propsData: { + isMultiSelect: true, + modelValue: [ 'a', 'b' ] + } + }); + const val = { checkValue: true, value: "2021", } + wrapper.vm.handleCheckedChanged(val); + + // Assert + expect(wrapper.vm.selectedValues).toEqual(["a", "b", "2021"]); + }); +}); + +describe("buttonQuestion.vue", () => { + it("Should remove a value to this.selectedValues if prop isMultiSelect is true, checkValue is false and this.selectedValues already exists", () => { + // Act + const wrapper = shallowMount(buttonQuestion, { + propsData: { + isMultiSelect: true, + modelValue: [ 'a', 'b' ] + } + }); + const val = { checkValue: false, value: "a", } + wrapper.vm.handleCheckedChanged(val); + + // Assert + expect(wrapper.vm.selectedValues).toEqual(["b"]); + }); +}); + + +describe("buttonQuestion.vue", () => { + it("Should do nothing to this.selectedValues if this.selectedValues is not an array", () => { + // Act + const wrapper = shallowMount(buttonQuestion, { + propsData: { + isMultiSelect: true, + modelValue: 'a', + } + }); + const val = { checkValue: true, value: "c", } + wrapper.vm.handleCheckedChanged(val); + + // Assert + expect(wrapper.vm.selectedValues).toEqual("a"); + }); +}); diff --git a/src/common-components/button-question/button-question.vue b/src/common-components/button-question/button-question.vue index e29f1fd93..10c8a6f9a 100644 --- a/src/common-components/button-question/button-question.vue +++ b/src/common-components/button-question/button-question.vue @@ -5,7 +5,7 @@ {{ questionText }}
-
+
{{(isMultiSelect && answers && answers.length > 1) ? 'Select one or more options below.' : 'Select an option below.' }} diff --git a/src/layouts/vehicle-damage/damage-location-question/damage-location-question.vue b/src/layouts/vehicle-damage/damage-location-question/damage-location-question.vue index 690e7df78..7f8e1f08a 100644 --- a/src/layouts/vehicle-damage/damage-location-question/damage-location-question.vue +++ b/src/layouts/vehicle-damage/damage-location-question/damage-location-question.vue @@ -6,6 +6,7 @@ :answers="answersToDisplay" :groupName="groupName" buttonType="listCard" + isRequired v-model="selectedValues" validationRules="damage-location-required" /> diff --git a/src/layouts/vehicle-damage/replace-options-question/replace-options-question.vue b/src/layouts/vehicle-damage/replace-options-question/replace-options-question.vue index e2a6bb426..53ef1e2fa 100644 --- a/src/layouts/vehicle-damage/replace-options-question/replace-options-question.vue +++ b/src/layouts/vehicle-damage/replace-options-question/replace-options-question.vue @@ -11,6 +11,7 @@ v-model="selectedValues" :validationRules="validationRules" :suppressError="suppressError" + :isRequired=isRequired />
@@ -36,6 +37,7 @@ export default ({ validationRules: String, suppressError: Boolean, cmsWidgetName: String, + isRequired: Boolean, }, methods: { initializeComponent(replaceOptions){ diff --git a/src/layouts/vehicle-damage/side-door-options/side-door-options.vue b/src/layouts/vehicle-damage/side-door-options/side-door-options.vue index a79cdf49f..b4df1121e 100644 --- a/src/layouts/vehicle-damage/side-door-options/side-door-options.vue +++ b/src/layouts/vehicle-damage/side-door-options/side-door-options.vue @@ -10,6 +10,7 @@ buttonType="listCard" v-model="selectedDoorSidesValues" validationRules="damage-side-required" + isRequired /> @@ -22,6 +23,7 @@ filterByVehicleCategory v-model="selectedDriverSideReplaceOptionsValues" validationRules="driver-side-options-required" + isRequired /> diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue index 543a02bef..8de1f887f 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.vue +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -44,6 +44,7 @@ v-model="selectedRearReplaceOptions" groupName="BackGlassReplaceOptionsQuestion" validationRules="replace-options-required" + isRequired /> diff --git a/src/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question.vue b/src/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question.vue index 3c377ec88..455c27361 100644 --- a/src/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question.vue +++ b/src/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question.vue @@ -9,6 +9,7 @@ v-model="selectedValues" :suppressError="suppressError" :validationRules="validationRules" + isRequired /> diff --git a/src/layouts/vehicle-damage/windshield-options/windshield-options.vue b/src/layouts/vehicle-damage/windshield-options/windshield-options.vue index df2560e17..1e9cfbc0a 100644 --- a/src/layouts/vehicle-damage/windshield-options/windshield-options.vue +++ b/src/layouts/vehicle-damage/windshield-options/windshield-options.vue @@ -27,6 +27,7 @@ v-model="selectedWindshieldReplaceOptionsValues" validationRules="windshield-replace-options-required|prevent-split-and-single-together" :suppressError="hasSplitSingleConflict" + isRequired />
- +
@@ -21,12 +21,12 @@
- +
- +
{ const label = wrapper.find("label"); wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.handleClick(); + wrapper.vm.triggerButton(); await nextTick(); @@ -123,7 +123,7 @@ describe("list-button-horizontal.vue", () => { const label = wrapper.find("label"); wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.handleClick(); + wrapper.vm.triggerButton(); await nextTick(); @@ -146,7 +146,7 @@ describe("list-button-horizontal.vue", () => { const label = wrapper.find("label"); wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.handleClick(); + wrapper.vm.triggerButton(); await nextTick(); @@ -195,4 +195,54 @@ describe("list-button-horizontal.vue", () => { // Assert expect(wrapper.componentVM.checkValue).toEqual("Car-Front"); }); + + it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => { + // Act + const wrapper = shallowMount(listButtonHorizontal, { + propsData: { + selectingInitiatesLoad: false, + }, + }); + + // Assert + wrapper.vm.handleInputChange(); + + await nextTick(); + + expect(wrapper.vm.handleCheckChange).toBeCalled; + }); + + it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => { + // Act + const wrapper = shallowMount(listButtonHorizontal, { + propsData: { + isMultiSelect: true, + }, + }); + + // Assert + wrapper.vm.handleKeyupArrow(); + + await nextTick(); + + expect(wrapper.vm.handleKeyupArrow).toHaveReturned; + }); + + it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => { + // Act + const wrapper = shallowMount(listButtonHorizontal, { + propsData: { + selectingInitiatesLoad: false, + isMultiSelect: false, + }, + }); + + // Assert + wrapper.vm.handleKeyupArrow(); + + await nextTick(); + + expect(wrapper.vm.handleCheckChange).toBeCalled; + }); + }); diff --git a/src/ux-components/list-button-horizontal/list-button-horizontal.vue b/src/ux-components/list-button-horizontal/list-button-horizontal.vue index bd1977ba3..d128982eb 100644 --- a/src/ux-components/list-button-horizontal/list-button-horizontal.vue +++ b/src/ux-components/list-button-horizontal/list-button-horizontal.vue @@ -2,8 +2,11 @@
@@ -77,27 +84,47 @@ export default { checkValue: Boolean, }; }, - created(){ - if(Array.isArray(this.selectedValues)){ - this.checkValue = this.isMultiSelect ? this.selectedValues.includes(this.value) : this.selectedValues[0]; + created() { + if (Array.isArray(this.selectedValues)) { + this.checkValue = this.isMultiSelect + ? this.selectedValues.includes(this.value) + : this.selectedValues[0]; } }, methods: { displayLoader() { this.isLoaderDisplayed = true; }, - handleClick(value) { - if(this.selectingInitiatesLoad) { - this.displayLoader(); + handleInputChange() { + if(!this.selectingInitiatesLoad) { + this.handleCheckChange(); + } + }, + handleKeyupArrow() { + if (this.isMultiSelect) { + return; // Prevent arrow keys from doing anything if element is a checkbox + } + + if(!this.selectingInitiatesLoad) { this.handleCheckChange(); } - this.handleChange(value); + this.handleChange(this.value); }, - handleCheckChange(newValue, oldValue){ - const isInitialization = typeof(oldValue) === 'function'; - if (!isInitialization) { - this.$emit('isCheckedChanged', { checkValue: this.checkValue, value: this.value.toString() }); + triggerButton() { + if(this.selectingInitiatesLoad) { + this.displayLoader(); + this.handleCheckChange(); } + this.handleChange(this.value); + }, + handleCheckChange() { + const emitEvent = { + checkValue: this.checkValue, // only read on checkboxes, on handleCheckedChanged on button-question + value: this.value.toString(), + buttonId: this.buttonID && this.buttonID.toString(), + }; + this.$emit("isCheckedChanged", emitEvent); + this.$emit("update:modelValue", emitEvent); } }, components: { @@ -105,6 +132,7 @@ export default { }, setup(props) { const inputType = props.isMultiSelect ? "checkbox" : "radio"; + const fieldOptions = { type: inputType, checkedValue: props.value, @@ -118,13 +146,11 @@ export default { } const { - checked, handleChange, errors, } = useField(props.groupName, props.validationRules, fieldOptions); return { - checked, handleChange, errors, fieldOptions, // only need to expose this for unit test purposes @@ -137,10 +163,11 @@ export default { .list-button-horizontal { input[type="radio"], input[type="checkbox"] { + position: absolute; + height: 0; opacity: 0; width: 0; - height: 0; - position: absolute; + &:focus-visible + label { box-shadow: 0 0 0 2.5px $blue; z-index: 2; diff --git a/src/ux-components/list-button/list-button.spec.js b/src/ux-components/list-button/list-button.spec.js index 7cc885b49..7a244a5bf 100644 --- a/src/ux-components/list-button/list-button.spec.js +++ b/src/ux-components/list-button/list-button.spec.js @@ -96,11 +96,8 @@ describe("list-button.vue", () => { }); // Assert - - const label = wrapper.find("label"); - wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.handleClick(); + wrapper.vm.triggerButton(); await nextTick(); @@ -119,10 +116,8 @@ describe("list-button.vue", () => { }); // Assert - - const label = wrapper.find("label"); wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.handleClick(); + wrapper.vm.triggerButton(); await nextTick(); const loader = wrapper.find("loader-stub"); @@ -140,11 +135,8 @@ describe("list-button.vue", () => { }); // Assert - - const label = wrapper.find("label"); - wrapper.vm.handleCheckChange = jest.fn(); - wrapper.vm.handleClick(); + wrapper.vm.triggerButton(); await nextTick(); @@ -197,4 +189,53 @@ describe("list-button.vue", () => { expect(wrapper.componentVM.checkValue).toEqual("Car-Front"); }); + it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => { + // Act + const wrapper = shallowMount(listButton, { + propsData: { + selectingInitiatesLoad: false, + }, + }); + + // Assert + wrapper.vm.handleInputChange(); + + await nextTick(); + + expect(wrapper.vm.handleCheckChange).toBeCalled; + }); + + it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => { + // Act + const wrapper = shallowMount(listButton, { + propsData: { + isMultiSelect: true, + }, + }); + + // Assert + wrapper.vm.handleKeyupArrow(); + + await nextTick(); + + expect(wrapper.vm.handleKeyupArrow).toHaveReturned; + }); + + it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => { + // Act + const wrapper = shallowMount(listButton, { + propsData: { + selectingInitiatesLoad: false, + isMultiSelect: false, + }, + }); + + // Assert + wrapper.vm.handleKeyupArrow(); + + await nextTick(); + + expect(wrapper.vm.handleCheckChange).toBeCalled; + }); + }); diff --git a/src/ux-components/list-button/list-button.vue b/src/ux-components/list-button/list-button.vue index d48b2407d..aba30934d 100644 --- a/src/ux-components/list-button/list-button.vue +++ b/src/ux-components/list-button/list-button.vue @@ -2,8 +2,11 @@
@@ -80,33 +84,47 @@ export default { checkValue: Boolean, }; }, - created(){ - if(Array.isArray(this.selectedValues)){ - this.checkValue = this.isMultiSelect ? this.selectedValues.includes(this.value) : this.selectedValues[0]; + created() { + if (Array.isArray(this.selectedValues)) { + this.checkValue = this.isMultiSelect + ? this.selectedValues.includes(this.value) + : this.selectedValues[0]; } }, methods: { displayLoader() { this.isLoaderDisplayed = true; }, - handleClick(value) { + handleInputChange() { + if(!this.selectingInitiatesLoad) { + this.handleCheckChange(); + } + }, + handleKeyupArrow() { + if (this.isMultiSelect) { + return; // Prevent arrow keys from doing anything if element is a checkbox + } + + if(!this.selectingInitiatesLoad) { + this.handleCheckChange(); + } + this.handleChange(this.value); + }, + triggerButton() { if(this.selectingInitiatesLoad) { this.displayLoader(); this.handleCheckChange(); } - this.handleChange(value); + this.handleChange(this.value); }, - handleCheckChange(value, oldValue){ - const isInitialization = typeof(oldValue) === 'function'; - if (!isInitialization) { - const emitEvent = { - checkValue: this.checkValue, - value: this.value.toString(), - buttonId: this.buttonID.toString(), - }; - this.$emit('isCheckedChanged', emitEvent); - this.$emit("update:modelValue", emitEvent); - } + handleCheckChange() { + const emitEvent = { + checkValue: this.checkValue, // only read on checkboxes, on handleCheckedChanged on button-question + value: this.value.toString(), + buttonId: this.buttonID && this.buttonID.toString(), + }; + this.$emit("isCheckedChanged", emitEvent); + this.$emit("update:modelValue", emitEvent); }, }, components: { @@ -128,13 +146,11 @@ export default { } const { - checked, handleChange, errors, } = useField(props.groupName, props.validationRules, fieldOptions); return { - checked, handleChange, errors, fieldOptions, // only need to expose this for unit test purposes @@ -154,10 +170,10 @@ export default { opacity: 0; &:focus-visible + label { - box-shadow: 0 0 0 2.5px $blue inset; + box-shadow: 0 0 0 2.5px $blue; } &:focus + label { - box-shadow: 0 0 0 2.5px $blue inset; + box-shadow: 0 0 0 2.5px $blue; } &:checked + label { color: $black; @@ -165,6 +181,9 @@ export default { background: $blue-100; box-shadow: 0 0 0 1px $blue; } + &:checked:focus + label { + box-shadow: 0 0 0 2.5px $blue; + } &:checked + label p, &:checked + label span { font-weight: 500; diff --git a/src/ux-components/list-card/list-card.spec.js b/src/ux-components/list-card/list-card.spec.js index 3aa803cc1..d810909e8 100644 --- a/src/ux-components/list-card/list-card.spec.js +++ b/src/ux-components/list-card/list-card.spec.js @@ -1,5 +1,6 @@ import { shallowMount } from "@vue/test-utils"; import listCard from "./list-card"; +import { nextTick } from "vue"; describe("list-card.vue", () => { it("Should return input type checkbox if isMultiSelect is true", async () => { @@ -18,7 +19,6 @@ describe("list-card.vue", () => { // Assert const input = wrapper.find("input"); - expect(input.attributes().type).toEqual("checkbox"); }); @@ -38,7 +38,6 @@ describe("list-card.vue", () => { // Assert const paragraph = wrapper.find("p"); - expect(paragraph.text()).toEqual("Windshield"); }); @@ -59,7 +58,6 @@ describe("list-card.vue", () => { // Assert const paragraph = wrapper.find("p:nth-of-type(2)"); - expect(paragraph.text()).toEqual("Test"); }); @@ -80,7 +78,6 @@ describe("list-card.vue", () => { // Assert const label = wrapper.find("label"); - expect(label.attributes().for).toEqual("List Card Checkbox"); }); @@ -101,7 +98,6 @@ describe("list-card.vue", () => { // Assert const input = wrapper.find("input"); - expect(input.attributes().name).toEqual("radio 1"); }); @@ -122,7 +118,6 @@ describe("list-card.vue", () => { // Assert const input = wrapper.find("input"); - expect(input.attributes()["aria-required"]).toEqual("true"); }); @@ -247,5 +242,88 @@ describe("list-card.vue", () => { expect(wrapper.vm.fieldOptions.initialValue).toEqual([ 'Windshield' ]); }); + it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => { + // Act + const wrapper = shallowMount(listCard, { + propsData: { + selectingInitiatesLoad: false, + }, + }); + + // Assert + wrapper.vm.handleInputChange(); + + await nextTick(); + + expect(wrapper.vm.handleCheckChange).toBeCalled; + }); + + it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => { + // Act + const wrapper = shallowMount(listCard, { + propsData: { + isMultiSelect: true, + }, + }); + + // Assert + wrapper.vm.handleKeyupArrow(); + + await nextTick(); + + expect(wrapper.vm.handleKeyupArrow).toHaveReturned; + }); + + it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => { + // Act + const wrapper = shallowMount(listCard, { + propsData: { + selectingInitiatesLoad: false, + isMultiSelect: false, + }, + }); + + // Assert + wrapper.vm.handleKeyupArrow(); + + await nextTick(); + + expect(wrapper.vm.handleCheckChange).toBeCalled; + }); + + it("Should run handleChange if triggerButton is triggered", async () => { + // Act + const wrapper = shallowMount(listCard, { + propsData: { + selectingInitiatesLoad: false, + }, + }); + + // Assert + wrapper.vm.triggerButton(); + + await nextTick(); + + expect(wrapper.vm.handleChange).toBeCalled; + expect(wrapper.vm.handleCheckChange).not.toBeCalled; + expect(wrapper.vm.displayLoader).not.toBeCalled; + }); + + it("Should run handleCheckChange and displayLoader if triggerButton is triggered and seletingInitiatesLoad is true", async () => { + // Act + const wrapper = shallowMount(listCard, { + propsData: { + selectingInitiatesLoad: true, + }, + }); + + // Assert + wrapper.vm.triggerButton(); + + await nextTick(); + + expect(wrapper.vm.handleCheckChange).toBeCalled; + expect(wrapper.vm.displayLoader).toBeCalled; + }); }); diff --git a/src/ux-components/list-card/list-card.vue b/src/ux-components/list-card/list-card.vue index a178895fb..742ec4584 100644 --- a/src/ux-components/list-card/list-card.vue +++ b/src/ux-components/list-card/list-card.vue @@ -6,8 +6,11 @@ isWide ? 'horizontal' : '', (errors.length > 0 || hasError) ? 'has-error' : '', ]" - @mouseup="handleChange(value)" - @keyup.space="handleChange(value)" + @keyup.space="triggerButton()" + @keyup.up="handleKeyupArrow()" + @keyup.down="handleKeyupArrow()" + @keyup.left="handleKeyupArrow()" + @keyup.right="handleKeyupArrow()" >