Merge pull request #1053 from Safelite/feature/CSR-1088

Feature/csr 1088
This commit is contained in:
Leah Schumann 2023-04-07 10:48:18 -04:00 committed by GitHub
commit 34ca460d50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 187 additions and 76 deletions

View file

@ -59,7 +59,6 @@ export default {
}, },
setup() { setup() {
const modalId = `modal-${crypto.randomUUID()}`; const modalId = `modal-${crypto.randomUUID()}`;
const { meta, validate, resetForm } = useForm(); const { meta, validate, resetForm } = useForm();
return { return {

View file

@ -1,7 +1,7 @@
<template> <template>
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<div class="mobile-location-questions"> <div class="mobile-location-questions">
<div class="text-center"> <div class="text-center" :id="componentId">
<label <label
for="mobileLocationLinkPromptId" for="mobileLocationLinkPromptId"
:aria-label="mobileLocationLinkPromptText" :aria-label="mobileLocationLinkPromptText"
@ -21,6 +21,11 @@
:customText="mobileFeeText" :customText="mobileFeeText"
cmsWidgetName="MobileFeeDisclaimerWidget" cmsWidgetName="MobileFeeDisclaimerWidget"
typeStyle="caption" /> typeStyle="caption" />
<div v-show="errorMessage" class="row my-1 form-test-error">
<span class="d-inline-flex small mt-0 center-error-message" role="alert">
{{ errorMessage }}
</span>
</div>
</div> </div>
<modal <modal
:ref="modalName" :ref="modalName"
@ -61,12 +66,14 @@ import vehicleProtectedQuestion from "@/layouts/service-location/mobile-location
// Helpers // Helpers
import { deepClone } from "@/layouts/service-location/helpers/object-cloning-helper/object-cloning-helper"; import { deepClone } from "@/layouts/service-location/helpers/object-cloning-helper/object-cloning-helper";
import { import {
getPricedMobileFeePart, getPricedMobileFeePart,
getServiceabilityDetails, getServiceabilityDetails,
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper"; } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
// Validation
import { useField } from "vee-validate";
export default { export default {
name: "mobile-location-modal-questions", name: "mobile-location-modal-questions",
emits: ["update:modelValue", "updated-mobile-fee-part", "updated-contains-military-base"], emits: ["update:modelValue", "updated-mobile-fee-part", "updated-contains-military-base"],
@ -76,6 +83,35 @@ export default {
displayInvalidZipAlert: false, displayInvalidZipAlert: false,
}; };
}, },
setup(props) {
const componentId = !props.customComponentId
? `component-${crypto.randomUUID()}`
: props.customComponentId;
// Integrate this component as a single field with an object for it's value into the page level validation
const modelValue = deepClone(props).modelValue;
const initialValue = modelValue;
const fieldOptions = {
value: modelValue,
initialValue: initialValue,
};
const { errorMessage, handleChange, meta, validate, errors } = useField(
componentId,
props.validationRules,
fieldOptions
);
return {
componentId,
errorMessage,
handleChange,
validate,
meta,
errors,
};
},
props: { props: {
modelValue: { modelValue: {
type: Object, type: Object,
@ -98,6 +134,8 @@ export default {
modalWidgetName: String, modalWidgetName: String,
alertNonServiceableZipWidgetName: String, alertNonServiceableZipWidgetName: String,
alertInvalidZipWidgetName: String, alertInvalidZipWidgetName: String,
customComponentId: String,
validationRules: String,
}, },
computed: { computed: {
mobileLocationLinkPromptText() { mobileLocationLinkPromptText() {
@ -161,7 +199,7 @@ export default {
}, },
onModalClosed() { onModalClosed() {
this.internalModel = deepClone(this.modelValue); this.internalModel = deepClone(this.modelValue);
this.resetAlerts(); this.resetValidation();
}, },
resetComponent(updatedServiceZipCodeInfo) { resetComponent(updatedServiceZipCodeInfo) {
// Reset the validation form, setting the initial values // Reset the validation form, setting the initial values
@ -180,8 +218,18 @@ export default {
resetModalButtonStyle() { resetModalButtonStyle() {
this.$refs[this.modalName].resetButtonStyle(); this.$refs[this.modalName].resetButtonStyle();
}, },
resetAlerts() { resetValidation() {
this.$refs.addressQuestions.resetAlerts(); this.$refs.addressQuestions.resetAlerts();
this.$refs[this.modalName].resetForm({
values: {
autocomplete: this.internalModel.addressQuestions.streetAddress,
city: this.internalModel.addressQuestions.city,
state: this.internalModel.addressQuestions.state,
zipCode: this.internalModel.addressQuestions.zipCode,
isVehicleProtected: this.internalModel.isVehicleProtected,
},
});
}, },
async setMobileLocation() { async setMobileLocation() {
// Validate the Zip Code // Validate the Zip Code
@ -217,6 +265,8 @@ export default {
handler(newValue) { handler(newValue) {
this.internalModel = deepClone(newValue); this.internalModel = deepClone(newValue);
this.handleChange(newValue);
this.resetComponent({ this.resetComponent({
streetAddress: newValue.addressQuestions.streetAddress, streetAddress: newValue.addressQuestions.streetAddress,
city: newValue.addressQuestions.city, city: newValue.addressQuestions.city,
@ -240,6 +290,12 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.mobile-location-questions {
.center-error-message {
justify-content: center !important;
}
}
.update-zip-text-link::before { .update-zip-text-link::before {
content: ""; content: "";
display: inline-block; display: inline-block;

View file

@ -260,7 +260,11 @@ describe("service-location.vue", () => {
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
mixins: [mockMixin], mixins: [mockMixin],
}); });
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
const mobileLocationQuestionsComponent = wrapper.findComponent({
ref: "mobileLocationQuestions",
});
mobileLocationQuestionsComponent.resetComponent = jest.fn();
const newServiceZipCodeQuestion = { const newServiceZipCodeQuestion = {
zipCode: "61606", zipCode: "61606",
@ -281,8 +285,16 @@ describe("service-location.vue", () => {
test("resets mobile location when service zip code is updated", () => { test("resets mobile location when service zip code is updated", () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
wrapper.vm.$refs.serviceZipCodeQuestion.resetMobileFeePart = jest.fn(); const mobileLocationQuestionsComponent = wrapper.findComponent({
ref: "mobileLocationQuestions",
});
mobileLocationQuestionsComponent.resetComponent = jest.fn();
const serviceZipCodeComponent = wrapper.findComponent({
ref: "serviceZipCodeQuestion",
});
serviceZipCodeComponent.resetMobileFeePart = jest.fn();
const newServiceZipCodeQuestion = { const newServiceZipCodeQuestion = {
zipCode: "61606", zipCode: "61606",
@ -312,10 +324,6 @@ describe("service-location.vue", () => {
isVehicleProtected: null, isVehicleProtected: null,
}; };
const serviceZipCodeComponent = wrapper.findComponent({
ref: "serviceZipCodeQuestion",
});
// Act // Act
serviceZipCodeComponent.vm.$emit("update:modelValue", newServiceZipCodeQuestion); serviceZipCodeComponent.vm.$emit("update:modelValue", newServiceZipCodeQuestion);
@ -326,8 +334,16 @@ describe("service-location.vue", () => {
test("resets appointment type selection when service zip code is updated by service zip modal", () => { test("resets appointment type selection when service zip code is updated by service zip modal", () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
wrapper.vm.$refs.serviceZipCodeQuestion.resetMobileFeePart = jest.fn(); const mobileLocationQuestionsComponent = wrapper.findComponent({
ref: "mobileLocationQuestions",
});
mobileLocationQuestionsComponent.resetComponent = jest.fn();
const serviceZipCodeComponent = wrapper.findComponent({
ref: "serviceZipCodeQuestion",
});
serviceZipCodeComponent.resetMobileFeePart = jest.fn();
const newServiceZipCodeQuestion = { const newServiceZipCodeQuestion = {
zipCode: "61606", zipCode: "61606",
@ -336,10 +352,6 @@ describe("service-location.vue", () => {
wrapper.vm.selectedAppointmentType = "Inshop"; wrapper.vm.selectedAppointmentType = "Inshop";
const serviceZipCodeComponent = wrapper.findComponent({
ref: "serviceZipCodeQuestion",
});
// Act // Act
serviceZipCodeComponent.vm.$emit("update:modelValue", newServiceZipCodeQuestion); serviceZipCodeComponent.vm.$emit("update:modelValue", newServiceZipCodeQuestion);
@ -350,7 +362,17 @@ describe("service-location.vue", () => {
test("displays military zip message when zip is updated", () => { test("displays military zip message when zip is updated", () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
const mobileLocationQuestionsComponent = wrapper.findComponent({
ref: "mobileLocationQuestions",
});
mobileLocationQuestionsComponent.resetComponent = jest.fn();
const serviceZipCodeComponent = wrapper.findComponent({
ref: "serviceZipCodeQuestion",
});
serviceZipCodeComponent.resetMobileFeePart = jest.fn();
expect(wrapper.vm.zipContainsMilitaryBase).toBe(false); expect(wrapper.vm.zipContainsMilitaryBase).toBe(false);
const newServiceZipCodeQuestion = { const newServiceZipCodeQuestion = {
@ -358,10 +380,6 @@ describe("service-location.vue", () => {
state: "OH", state: "OH",
}; };
const serviceZipCodeComponent = wrapper.findComponent({
ref: "serviceZipCodeQuestion",
});
// Act // Act
serviceZipCodeComponent.vm.$emit("updated-contains-military-base", true); serviceZipCodeComponent.vm.$emit("updated-contains-military-base", true);
@ -371,10 +389,23 @@ describe("service-location.vue", () => {
}); });
describe("updating mobile location", () => { describe("updating mobile location", () => {
test("updates the page model after providing the mobile location", () => { test("updates the page model after providing the mobile location", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
await wrapper.setData({
selectedAppointmentType: "Mobile",
});
const mobileLocationQuestionsComponent = wrapper.findComponent({
ref: "mobileLocationQuestions",
});
mobileLocationQuestionsComponent.resetComponent = jest.fn();
const serviceZipCodeComponent = wrapper.findComponent({
ref: "serviceZipCodeQuestion",
});
serviceZipCodeComponent.resetMobileFeePart = jest.fn();
const mobileLocationQuestions = { const mobileLocationQuestions = {
addressQuestions: { addressQuestions: {
@ -399,12 +430,11 @@ describe("service-location.vue", () => {
isVehicleProtected: true, isVehicleProtected: true,
}; };
const mobileLocationComponent = wrapper.findComponent({
ref: "mobileLocationModalQuestions",
});
// Act // Act
mobileLocationComponent.vm.$emit("update:modelValue", newMobileLocationQuestions); mobileLocationQuestionsComponent.vm.$emit(
"update:modelValue",
newMobileLocationQuestions
);
// Assert // Assert
expect(wrapper.vm.mobileLocationQuestions).toStrictEqual(newMobileLocationQuestions); expect(wrapper.vm.mobileLocationQuestions).toStrictEqual(newMobileLocationQuestions);
@ -413,8 +443,20 @@ describe("service-location.vue", () => {
test("resets service zip code when mobile location is updated", async () => { test("resets service zip code when mobile location is updated", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
wrapper.vm.$refs.serviceZipCodeQuestion.resetMobileFeePart = jest.fn(); await wrapper.setData({
selectedAppointmentType: "Mobile",
});
const mobileLocationQuestionsComponent = wrapper.findComponent({
ref: "mobileLocationQuestions",
});
mobileLocationQuestionsComponent.resetComponent = jest.fn();
const serviceZipCodeComponent = wrapper.findComponent({
ref: "serviceZipCodeQuestion",
});
serviceZipCodeComponent.resetMobileFeePart = jest.fn();
const mobileLocationQuestions = { const mobileLocationQuestions = {
addressQuestions: { addressQuestions: {
@ -441,10 +483,6 @@ describe("service-location.vue", () => {
mobileFeePart: null, mobileFeePart: null,
}; };
const mobileLocationComponent = wrapper.findComponent({
ref: "mobileLocationModalQuestions",
});
wrapper.vm.serviceZipCodeQuestion = { wrapper.vm.serviceZipCodeQuestion = {
zipCode: "61606", zipCode: "61606",
state: "IL", state: "IL",
@ -456,29 +494,38 @@ describe("service-location.vue", () => {
}; };
// Act // Act
mobileLocationComponent.vm.$emit("update:modelValue", newMobileLocationQuestions); mobileLocationQuestionsComponent.vm.$emit(
"update:modelValue",
newMobileLocationQuestions
);
// Assert // Assert
expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeInfo); expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeInfo);
}); });
test("resets appointment type selection when service zip code is updated by mobile location modal when Mobile is not selected", () => { test("resets appointment type selection when service zip code is updated by mobile location modal when Mobile is not selected", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn();
wrapper.vm.$refs.serviceZipCodeQuestion.resetMobileFeePart = jest.fn(); await wrapper.setData({
selectedAppointmentType: "Dropoff",
});
const mobileLocationQuestionsComponent = wrapper.findComponent({
ref: "mobileLocationQuestions",
});
mobileLocationQuestionsComponent.resetComponent = jest.fn();
const serviceZipCodeComponent = wrapper.findComponent({
ref: "serviceZipCodeQuestion",
});
serviceZipCodeComponent.resetMobileFeePart = jest.fn();
const newServiceZipCodeQuestion = { const newServiceZipCodeQuestion = {
zipCode: "61606", zipCode: "61606",
state: "IL", state: "IL",
}; };
wrapper.vm.selectedAppointmentType = "Dropoff";
const serviceZipCodeComponent = wrapper.findComponent({
ref: "serviceZipCodeQuestion",
});
// Act // Act
serviceZipCodeComponent.vm.$emit("update:modelValue", newServiceZipCodeQuestion); serviceZipCodeComponent.vm.$emit("update:modelValue", newServiceZipCodeQuestion);
@ -486,12 +533,23 @@ describe("service-location.vue", () => {
expect(wrapper.vm.selectedAppointmentType).toStrictEqual(null); expect(wrapper.vm.selectedAppointmentType).toStrictEqual(null);
}); });
test("does not reset appointment type selection when service zip code is updated by mobile location modal when Mobile is selected", () => { test("does not reset appointment type selection when service zip code is updated by mobile location modal when Mobile is selected", async () => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
const appointmentTypeMobile = "Mobile";
wrapper.vm.$refs.mobileLocationModalQuestions.resetComponent = jest.fn(); await wrapper.setData({
wrapper.vm.$refs.serviceZipCodeQuestion.resetMobileFeePart = jest.fn(); selectedAppointmentType: "Mobile",
});
const mobileLocationQuestionsComponent = wrapper.findComponent({
ref: "mobileLocationQuestions",
});
mobileLocationQuestionsComponent.resetComponent = jest.fn();
const serviceZipCodeComponent = wrapper.findComponent({
ref: "serviceZipCodeQuestion",
});
serviceZipCodeComponent.resetMobileFeePart = jest.fn();
const mobileLocationQuestions = { const mobileLocationQuestions = {
addressQuestions: { addressQuestions: {
@ -504,17 +562,11 @@ describe("service-location.vue", () => {
isVehicleProtected: "YesAnswer", isVehicleProtected: "YesAnswer",
}; };
wrapper.vm.selectedAppointmentType = "Mobile";
const mobileLocationComponent = wrapper.findComponent({
ref: "mobileLocationModalQuestions",
});
// Act // Act
mobileLocationComponent.vm.$emit("update:modelValue", mobileLocationQuestions); mobileLocationQuestionsComponent.vm.$emit("update:modelValue", mobileLocationQuestions);
// Assert // Assert
expect(wrapper.vm.selectedAppointmentType).toStrictEqual(appointmentTypeMobile); expect(wrapper.vm.selectedAppointmentType).toStrictEqual("Mobile");
}); });
}); });

View file

@ -53,22 +53,17 @@
cmsWidgetName="AppointmentTypeQuestionWidget" cmsWidgetName="AppointmentTypeQuestionWidget"
validationRules="option-required" /> validationRules="option-required" />
<mobileLocationModalQuestions <mobileLocationModalQuestions
v-show="selectedAppointmentType === 'Mobile'" customComponentId="mobileLocationQuestions"
v-if="selectedAppointmentType === 'Mobile'"
v-model="mobileLocationQuestions" v-model="mobileLocationQuestions"
:mobileFeePart="mobileFeePart" :mobileFeePart="mobileFeePart"
@updated-mobile-fee-part="setMobileFeePart" @updated-mobile-fee-part="setMobileFeePart"
@updated-serviceability="setServiceabilityDetails" @updated-serviceability="setServiceabilityDetails"
@updated-contains-military-base="setContainsMilitaryBase" @updated-contains-military-base="setContainsMilitaryBase"
ref="mobileLocationModalQuestions" validationRules="mobile-location-required"
ref="mobileLocationQuestions"
linkWidgetName="MobileLocationLinkWidget" linkWidgetName="MobileLocationLinkWidget"
modalWidgetName="MobileLocationModalWidget" /> modalWidgetName="MobileLocationModalWidget" />
<textboxQuestion
v-show="selectedAppointmentType === 'Mobile'"
ref="mobileLocationQuestionsError"
v-model="mobileLocationValidationField"
validationRules="mobile-location-required"
hideInput
centerErrorMessage />
<contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" /> <contentGroupModal ref="RecalModal" cmsWidgetName="RecalModal" />
<funnel-footer <funnel-footer
cmsWidgetName="FunnelFooterWidget" cmsWidgetName="FunnelFooterWidget"
@ -90,8 +85,7 @@ import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer"; import funnelFooter from "@/fmg-components/funnel-footer/funnel-footer";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header"; import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue"; import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import { Form, defineRule } from "vee-validate"; import { Form } from "vee-validate";
import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal"; import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
// Supporting files // Supporting files
@ -105,10 +99,24 @@ import {
getServiceabilityDetails, getServiceabilityDetails,
} from "@/layouts/service-location/helpers/service-location-helper/service-location-helper"; } from "@/layouts/service-location/helpers/service-location-helper/service-location-helper";
import store from "@/store"; import store from "@/store";
import { errorMessages } from "@/constants/error-messages";
import { required } from "@/helpers/validation-rules";
defineRule("mobile-location-required", required(errorMessages.MOBILE_LOCATION_REQUIRED)); // Validation
import { defineRule } from "vee-validate";
import { errorMessages } from "@/constants/error-messages";
// DEFINE VALIDATION RULES
defineRule("mobile-location-required", (value) => {
if (
value.addressQuestions.streetAddress == "" ||
value.addressQuestions.city == "" ||
value.addressQuestions.state == "" ||
value.addressQuestions.zipCode == "" ||
value.isVehicleProtected == null
) {
return errorMessages.MOBILE_LOCATION_REQUIRED;
}
return true;
});
export default { export default {
name: "service-location", name: "service-location",
@ -127,7 +135,6 @@ export default {
mobileFeePart: null, mobileFeePart: null,
zipContainsMilitaryBase: false, zipContainsMilitaryBase: false,
selectedAppointmentType: null, selectedAppointmentType: null,
mobileLocationValidationField: null,
}; };
}, },
async beforeRouteEnter(to, from, next) { async beforeRouteEnter(to, from, next) {
@ -213,8 +220,6 @@ export default {
this.zipCode = newValue.addressQuestions.zipCode; this.zipCode = newValue.addressQuestions.zipCode;
this.isVehicleProtected = newValue.isVehicleProtected; this.isVehicleProtected = newValue.isVehicleProtected;
this.mobileLocationValidationField = "isValid";
if ( if (
newValue.zipCode !== this.zipCode && newValue.zipCode !== this.zipCode &&
!this.selectedAppointmentType == "Mobile" !this.selectedAppointmentType == "Mobile"
@ -343,7 +348,6 @@ export default {
funnelSubHeader, funnelSubHeader,
Form, Form,
loadingModal, loadingModal,
textboxQuestion,
contentGroupModal, contentGroupModal,
}, },
}; };