CSR-1384: add validation on modal when multiple wiper options

This commit is contained in:
Adam Caouette 2023-10-31 10:53:33 -04:00
parent f8811c6984
commit bbf4b083e8
4 changed files with 271 additions and 65 deletions

View file

@ -92,6 +92,7 @@ import listButton from "@/ux-components/list-button/list-button";
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal"; import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
import listCard from "@/ux-components/list-card/list-card"; import listCard from "@/ux-components/list-card/list-card";
import radio from "@/ux-components/radio/radio"; import radio from "@/ux-components/radio/radio";
import checkbox from "@/ux-components/checkbox/checkbox";
import { useField, ErrorMessage } from "vee-validate"; import { useField, ErrorMessage } from "vee-validate";
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
@ -209,6 +210,9 @@ export default {
case "radio": case "radio":
classes = "ui-radio d-flex"; classes = "ui-radio d-flex";
break; break;
case "checkbox":
classes = "ui-checkbox d-flex";
break;
case "servicePackageRadio": case "servicePackageRadio":
classes = "package-main"; classes = "package-main";
break; break;
@ -317,6 +321,7 @@ export default {
listCard, listCard,
ErrorMessage, ErrorMessage,
radio, radio,
checkbox,
}, },
}; };
</script> </script>

View file

@ -26,30 +26,24 @@
v-if="wipersModal" v-if="wipersModal"
@footer-button-action="addWipersToCart('WipersModal', wipersModal)" @footer-button-action="addWipersToCart('WipersModal', wipersModal)"
footerButtonActionName="footer-button-action"> footerButtonActionName="footer-button-action">
<div v-if="wipersToDisplay === wipersToDisplayStrings.BOTH" class="wiper-options"> <div v-if="wipersOffered === wipersOfferedStrings.BOTH" class="wiper-options">
<div> <buttonQuestion
<checkboxQuestion ref="buttonQuestion"
class="mb-5" buttonTypeString="checkbox"
cmsWidgetName="AddFrontBladesQuestionWidget" class="mt-5"
v-model="selectedFrontWipers" /> :answers="wipersOptions"
<p class="price">+${{ wipersModal.totalFrontPrice }}</p> groupName="wipers-options"
</div> textPosition="text-center"
<div> v-model="selectedWipers"
<checkboxQuestion isMultiSelect
class="mb-5" isRequired
cmsWidgetName="AddRearBladesQuestionWidget" validationRules="checkbox-required"
v-model="selectedRearWipers" /> :additionalButtonData="additionalButtonData" />
<p class="price">+${{ wipersModal.totalRearPrice }}</p>
</div>
</div> </div>
<div <div v-else-if="wipersOffered === wipersOfferedStrings.FRONT" class="wiper-options">
v-else-if="wipersToDisplay === wipersToDisplayStrings.FRONT"
class="wiper-options">
<p class="price">+${{ wipersModal.totalFrontPrice }}</p> <p class="price">+${{ wipersModal.totalFrontPrice }}</p>
</div> </div>
<div <div v-else-if="wipersOffered === wipersOfferedStrings.REAR" class="wiper-options">
v-else-if="wipersToDisplay === wipersToDisplayStrings.REAR"
class="wiper-options">
<p class="price">+${{ wipersModal.totalRearPrice }}</p> <p class="price">+${{ wipersModal.totalRearPrice }}</p>
</div> </div>
</contentGroupModal> </contentGroupModal>
@ -62,12 +56,17 @@
import buttonQuestion from "@/digital-components/button-question/button-question"; import buttonQuestion from "@/digital-components/button-question/button-question";
import addVapsButton from "./add-vaps-button/add-vaps-button"; import addVapsButton from "./add-vaps-button/add-vaps-button";
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal"; import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
// SUPPORTING // SUPPORTING
import { partTypeStrings } from "@/constants/part-type-strings"; import { partTypeStrings } from "@/constants/part-type-strings";
import { defineRule } from "vee-validate";
import { required } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
const wipersToDisplayStrings = { // DEFINE VALIDATION RULES
defineRule("checkbox-required", required(errorMessages.OPTION_REQUIRED));
const wipersOfferedStrings = {
FRONT: "FRONT", FRONT: "FRONT",
REAR: "REAR", REAR: "REAR",
BOTH: "BOTH", BOTH: "BOTH",
@ -83,15 +82,22 @@ export default {
data() { data() {
return { return {
addVapsButton: addVapsButton, addVapsButton: addVapsButton,
selectedFrontWipers: false, wipersToAddToCart: [],
selectedRearWipers: false, wipersOffered: null,
wipersToDisplay: null, isModalOpened: false,
selectedWipers: this.getSelectedWipers(),
}; };
}, },
methods: { methods: {
openModal(modalName) { openModal(modalName) {
this.$refs[modalName].openModal(); this.$refs[modalName].openModal();
}, },
closeModal(modalName) {
this.$refs[modalName].closeModal();
},
formatString(str) {
return String(str).replaceAll(" ", "-");
},
addRainDefenseToCart(modalName, part) { addRainDefenseToCart(modalName, part) {
const alreadyInCart = this.currentCartItems.vaps.some((item) => { const alreadyInCart = this.currentCartItems.vaps.some((item) => {
return item.partType === part.partType; return item.partType === part.partType;
@ -99,9 +105,11 @@ export default {
if (!alreadyInCart) { if (!alreadyInCart) {
this.currentCartItems.vaps.push(part); this.currentCartItems.vaps.push(part);
} }
this.$refs[modalName].closeModal(); this.closeModal(modalName);
}, },
addWipersToCart(modalName, part) { addWipersToCart(modalName, part) {
const itemsForCart = [];
const frontWipersAlreadyInCart = this.currentCartItems.vaps.some((item) => { const frontWipersAlreadyInCart = this.currentCartItems.vaps.some((item) => {
return item.partType === part.frontWiperLineItems[0].partType; return item.partType === part.frontWiperLineItems[0].partType;
}); });
@ -109,36 +117,43 @@ export default {
return item.partType === part.rearWiperLineItems[0]?.partType; return item.partType === part.rearWiperLineItems[0]?.partType;
}); });
if (this.wipersToDisplay === wipersToDisplayStrings.FRONT) { if (this.wipersOffered === wipersOfferedStrings.BOTH) {
this.selectedFrontWipers = true; // set as true if front wipers is only option this.selectedWipers.forEach((wiper) => {
itemsForCart.push(wiper);
});
} else {
if (
!frontWipersAlreadyInCart &&
this.wipersOffered === wipersOfferedStrings.FRONT
) {
itemsForCart.push(wipersOfferedStrings.FRONT);
}
if (!rearWipersAlreadyInCart && this.wipersOffered === wipersOfferedStrings.REAR) {
itemsForCart.push(wipersOfferedStrings.REAR);
}
} }
if (this.wipersToDisplay === wipersToDisplayStrings.REAR) {
this.selectedRearWipers = true; // set as true if rear wipers is only option itemsForCart.forEach((type) => {
} if (type === wipersOfferedStrings.FRONT) {
if (!frontWipersAlreadyInCart && this.selectedFrontWipers) {
if (part.frontWiperLineItems) {
// for wipers, a "combined" part (frontWiperLineItems)
part.frontWiperLineItems.forEach((part) => { part.frontWiperLineItems.forEach((part) => {
this.currentCartItems.vaps.push(part); this.currentCartItems.vaps.push(part);
}); });
} }
} if (type === wipersOfferedStrings.REAR) {
if (!rearWipersAlreadyInCart && this.selectedRearWipers) {
if (part.rearWiperLineItems) {
part.rearWiperLineItems.forEach((part) => { part.rearWiperLineItems.forEach((part) => {
this.currentCartItems.vaps.push(part); this.currentCartItems.vaps.push(part);
}); });
} }
} });
this.$refs[modalName].closeModal();
if (this.wipersToDisplay === wipersToDisplayStrings.BOTH) { this.closeModal(modalName);
this.selectedFrontWipers = false; // set as false if both options are given this.selectedWipers;
this.selectedRearWipers = false; // set as false if both options are given
}
}, },
updateWipersToDisplay(value) { updateWipersOffered(value) {
this.wipersToDisplay = value; this.wipersOffered = value;
},
getSelectedWipers() {
return null;
}, },
}, },
computed: { computed: {
@ -229,31 +244,72 @@ export default {
]; ];
prices.sort((a, b) => a - b); prices.sort((a, b) => a - b);
modalData.SubText = "+$" + prices[0] + " - $" + prices[prices.length - 1]; modalData.SubText = "+$" + prices[0] + " - $" + prices[prices.length - 1];
this.updateWipersToDisplay(wipersToDisplayStrings.BOTH); this.updateWipersOffered(wipersOfferedStrings.BOTH);
buttons.push(modalData); buttons.push(modalData);
} else { } else {
// NO FRONT WIPERS IN CART; SHOW ONLY FRONT WIPERS BUTTON // NO FRONT WIPERS IN CART; SHOW ONLY FRONT WIPERS BUTTON
modalData.SubText = "+$" + this.wipersModal?.totalFrontPrice; modalData.SubText = "+$" + this.wipersModal?.totalFrontPrice;
this.updateWipersToDisplay(wipersToDisplayStrings.FRONT); this.updateWipersOffered(wipersOfferedStrings.FRONT);
buttons.push(modalData); buttons.push(modalData);
} }
} else if (!rearWipersInCart && this.wipersModal?.rearWiperLineItems.length > 0) { } else if (!rearWipersInCart && this.wipersModal?.rearWiperLineItems.length > 0) {
// NO REAR WIPERS IN CART; SHOW ONLY REAR WIPERS BUTTON // NO REAR WIPERS IN CART; SHOW ONLY REAR WIPERS BUTTON
modalData.SubText = "+$" + this.wipersModal?.totalRearPrice; modalData.SubText = "+$" + this.wipersModal?.totalRearPrice;
this.updateWipersToDisplay(wipersToDisplayStrings.REAR); this.updateWipersOffered(wipersOfferedStrings.REAR);
buttons.push(modalData); buttons.push(modalData);
} }
return buttons; return buttons;
}, },
wipersToDisplayStrings() { wipersOfferedStrings() {
return wipersToDisplayStrings; return wipersOfferedStrings;
},
wipersOptions() {
const wipersOptions = [];
const wipersFrontCopy = this.getCmsContent(
"AddFrontBladesQuestionWidget",
"QuestionText"
);
const wipersRearCopy = this.getCmsContent(
"AddRearBladesQuestionWidget",
"QuestionText"
);
const frontWipersLineItems = this.wipersModal.frontWiperLineItems;
const rearWipersLineItems = this.wipersModal.rearWiperLineItems;
if (frontWipersLineItems) {
const wipersOption = {
buttonLabel: wipersFrontCopy,
groupName: "wipers-options",
value: wipersOfferedStrings.FRONT,
buttonLabelSubCopy: "buttonLabelSubCopy",
buttonBodyCopy: "buttonBodyCopy",
buttonAuxillaryCopy: "buttonAuxillaryCopy",
buttonFooterCopy: "buttonFooterCopy",
additionalButtonData: "+$" + this.wipersModal.totalFrontPrice,
};
wipersOptions.push(wipersOption);
}
if (rearWipersLineItems) {
const wipersOption = {
buttonLabel: wipersRearCopy,
groupName: "wipers-options",
value: wipersOfferedStrings.REAR,
buttonLabelSubCopy: "buttonLabelSubCopy",
buttonBodyCopy: "buttonBodyCopy",
buttonAuxillaryCopy: "buttonAuxillaryCopy",
buttonFooterCopy: "buttonFooterCopy",
additionalButtonData: "+$" + this.wipersModal.totalRearPrice,
};
wipersOptions.push(wipersOption);
}
return wipersOptions;
}, },
}, },
components: { components: {
buttonQuestion, buttonQuestion,
contentGroupModal, contentGroupModal,
checkboxQuestion,
}, },
}; };
</script> </script>
@ -286,26 +342,31 @@ export default {
text-align: center; text-align: center;
} }
.wiper-options { .wiper-options {
& > div { .ui-checkbox {
display: flex; display: block;
justify-content: space-between;
margin-bottom: 1rem;
&:last-child { & > div.col {
margin-bottom: 0; margin-bottom: 1rem;
&:last-child {
margin-bottom: 0;
}
} }
.checkbox {
display: flex;
justify-content: space-between;
.form-check { .additional-button-data {
margin-bottom: 0; font-size: 0.875rem;
line-height: 1.75;
padding-left: 0.5rem;
color: $green;
}
} }
} }
p { p {
margin-bottom: 0; margin-bottom: 0;
} line-height: 1.75;
p.price {
font-size: 0.875rem;
padding-left: 0.5rem;
color: $green;
} }
} }
} }

View file

@ -0,0 +1,66 @@
import { mount } from "@vue/test-utils";
import checkbox from "./checkbox";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
describe("checkbox.vue", () => {
it("Should have correct group name", async () => {
// Arrange
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: {
groupName: "checkbox-button-test",
value: "test value",
},
},
});
// Act
const input = wrapper.find("input");
// Assert
expect(input.attributes().name).toEqual("checkbox-button-test");
});
it("Should have correct label text", async () => {
// Act
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: {
buttonLabel: "label text",
value: "test value",
},
},
});
// Arrange
const paragraph = wrapper.find("p");
// Assert
expect(paragraph.text()).toEqual("label text");
});
it("Should have correct screenreader-only text", async () => {
// Act
let { wrapper } = setupMocks({});
// Arrange
await wrapper.setProps({
screenReaderOnlyText: "screenreader text",
value: "test value",
});
const paragraph = wrapper.find(".sr-only");
// Assert
expect(paragraph.text()).toEqual("screenreader text");
});
});
function setupMocks({ mountOptionsMockData = {} }) {
const wrapper = mount(checkbox, {
...mountOptionsMockData,
mixins: [inputButtonWrapperMixin],
});
return { wrapper };
}

View file

@ -0,0 +1,74 @@
<template>
<baseInputButton
v-bind="$props"
buttonWrapperClasses="form-check base-input-button checkbox"
inputClasses="form-check-input"
v-model="selectedValue">
<div class="d-flex align-items-start form-check-label">
<p v-if="buttonLabel" class="m-0">{{ buttonLabel }}</p>
<span v-if="screenReaderOnlyText" class="sr-only">{{ screenReaderOnlyText }}</span>
</div>
<div
v-if="additionalButtonData"
v-html="additionalButtonData"
class="additional-button-data"></div>
</baseInputButton>
</template>
<script>
import baseInputButton from "@/digital-components/base-input-button/base-input-button";
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
export default {
name: "checkbox",
mixins: [inputButtonWrapperMixin],
components: {
baseInputButton,
},
};
</script>
<style lang="scss">
.form-check {
position: relative;
.form-check-input {
border: 1px solid $gray-500; //
border-radius: 2px; //
margin-right: 0.5rem;
opacity: 1;
height: 1em;
width: 1em;
&:checked {
background-color: $blue; //
background-size: contain;
background-position: center;
border: 1px solid $blue;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23ffffff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e");
+ .form-check-label {
p {
font-weight: 400;
font-size: 0.875rem;
color: $gray-600;
}
}
}
&:focus {
box-shadow: 0 0 0 2.5px $blue;
}
}
&:hover {
.form-check-input:not(:checked) {
box-shadow: 0 0 0 4px $blue-300;
}
}
p {
font-weight: 400;
font-size: 0.875rem;
color: $gray-600;
}
}
</style>