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

View file

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

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>