Merge branch 'develop' into CASH-132-dontation-widget-foster-love

This commit is contained in:
Bryan Mauger 2025-02-13 14:13:11 -05:00
commit 61abcbb5d8
16 changed files with 250 additions and 44 deletions

View file

@ -78,6 +78,7 @@ const storeActions = {
SAVE_SCHEDULE: "saveSchedule",
SAVE_EMAIL: "saveEmail",
SAVE_PHONE_NUMBER: "savePhoneNumber",
SAVE_IS_SMS_OPT_IN: "saveIsSmsOptIn",
SAVE_REGISTRATION_LICENSE_PLATE_LOOKUP: "saveRegistrationLicensePlateLookup",
SAVE_VIN: "saveVin",
SAVE_REGISTRATION_ADDRESS_LOOKUP: "saveRegistrationAddressLookup",

View file

@ -40,6 +40,7 @@ const storeMutations = {
// CUSTOMER MUTATIONS
UPDATE_CUSTOMER_EMAIL_ADDRESS: "updateCustomerEmailAddress",
UPDATE_CUSTOMER_PHONE_NUMBER: "updateCustomerPhoneNumber",
UPDATE_CUSTOMER_IS_SMS_OPT_IN: "updateCustomerIsSmsOptin",
UPDATE_CUSTOMER_DETAILS: "updateCustomerDetails",
// ORDER MUTATIONS

View file

@ -145,6 +145,20 @@ export default {
vm.setCmsContent(resultMap.cmsContent);
if (store.getters.externalParameterState?.isExternalParameter) {
if (store.getters.externalParameterCustomer?.phoneNumber) {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_PHONE_NUMBER,
store.getters.externalParameterCustomer.phoneNumber,
false
);
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_IS_SMS_OPT_IN,
true,
false
);
}
if (store.getters.externalParameterEstimate.vinSelection) {
vm.selectedVinLookupMethod = vinPagesMixin.methods.getVinlookupMethod(
store.getters.externalParameterEstimate.vinSelection

View file

@ -16,6 +16,13 @@
v-model="parentAccountNumber"
:originalList="originalList" />
</div>
<textLink
id="payOnMyOwnBackButton"
useLoadingModal
linkType="text"
:text="payWithCashButtonCopy"
href="javascript:void(0)"
@click-event="backButtonAction(foreceCashSelectionOnQuote = true)" />
<navbar
cmsWidgetName="FunnelFooterWidget"
@ -36,6 +43,7 @@ import navbar from "@/fmg-components/nav-bar/nav-bar";
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import insuranceCompanyQuestion from "@/layouts/insurance-company/insurance-company-question";
import textLink from "../../ux-components/text-link/text-link.vue";
// Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -92,6 +100,9 @@ export default {
isParentAccountNumber() {
return !!this.parentAccountNumber;
},
payWithCashButtonCopy() {
return this.getCmsContent("PayWithCashWidget", "Text");
}
},
methods: {
isCashParentAccountNumber() {
@ -109,8 +120,13 @@ export default {
arePagePrerequisitesValid() {
return store.getters.order.payment.isInsurance === true;
},
backButtonAction() {
backButtonAction(forceCashSelectionOnQuote) {
// Go back to Quote page
if (forceCashSelectionOnQuote) {
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route, {"isCashSelected": true});
return;
}
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
},
async forwardButtonAction() {
@ -157,6 +173,7 @@ export default {
Form,
loadingModal,
insuranceCompanyQuestion,
textLink,
},
};
</script>

View file

@ -78,6 +78,20 @@ export default {
);
}
if (store.getters.externalParameterCustomer?.phoneNumber) {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_PHONE_NUMBER,
store.getters.externalParameterCustomer.phoneNumber,
false
);
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_IS_SMS_OPT_IN,
true,
false
);
}
baseMixin.methods.ResetExternalParamsAndHideModal();
}
});

View file

@ -79,6 +79,21 @@ export default {
false
);
}
if (store.getters.externalParameterCustomer?.phoneNumber) {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_PHONE_NUMBER,
store.getters.externalParameterCustomer.phoneNumber,
false
);
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_IS_SMS_OPT_IN,
true,
false
);
}
baseMixin.methods.ResetExternalParamsAndHideModal();
}
});

View file

@ -577,6 +577,56 @@ describe("quote.vue", () => {
//Assert
expect(wrapper.vm.isInsuranceSelected).toBe(false);
});
test("should default to cash if isCashSelected is true, despite all other checks pointing to insurance selection", async () => {
// Also needs no query parameter or previous selection in store to be present
//Arrange
store.getters = {
lineItems: {
glassParts: ["item", "item2"],
},
applicationUser: {
experiments: [],
},
order: {
lineItems: {
glassParts: ["item", "item2"],
},
payment: {
isInsurance: true,
},
customer: {
emailAddress: "test@test.com",
},
serviceLocation: {
state: null,
},
},
vehicle: {
cardId: "123",
},
experimentSettings: {
settingName: "SERVICE_PACKAGE_DISCOUNT",
},
};
mockTierOnePrice = 505;
const { wrapper } = setupMocks({});
//Act
await quote.beforeRouteEnter.call(
wrapper.vm,
{ query: { fmgPage: "quote",
isInsuranceSelected: true,
isCashSelected: true,
} },
undefined,
(c) => c(wrapper.vm)
);
//Assert
expect(wrapper.vm.isInsuranceSelected).toBe(false);
});
test("should default to insurance if total economy package price is over $500", async () => {
// Also needs no query parameter or previous selection in store to be present
//Arrange

View file

@ -328,6 +328,10 @@ export default {
}
const getIsInsuranceSelectedValue = (availableLineItems, insuranceThreshold) => {
// query param checked here is set on insurance-selection "Pay on my own" link click
if (to.query?.isCashSelected || to.query?.iscashselected) {
return false;
}
const serviceLocationState = store.getters.order.serviceLocation.state;
// usually true when returning from heritage but can be false when returning from heritage on a save quote

View file

@ -203,7 +203,7 @@ describe("mobile-location-modal-questions.vue", () => {
expect(wrapper.vm.$refs.MobileLocationModalWidget.openModal).toHaveBeenCalled();
});
it("Should emit update:modelValue on setMobileLocation for a valid address and vehicle protection answer", async () => {
it("Should emit updated-mobile-location-questions on setMobileLocation for a valid address and vehicle protection answer", async () => {
// Arrange
const mobileLocationQuestions = {
addressQuestions: {
@ -240,17 +240,26 @@ describe("mobile-location-modal-questions.vue", () => {
});
wrapper.vm.$refs.MobileLocationModalWidget.closeModal = jest.fn();
// Mock the emitEvent function
wrapper.vm.emitEvent = jest.fn(async (eventName, payload) => {
return new Promise((resolve) => {
wrapper.vm.$emit(eventName, newMobileLocationQuestions, resolve);
resolve(newMobileLocationQuestions);
});
});
// Act
wrapper.vm.internalModel = newMobileLocationQuestions;
await wrapper.vm.setMobileLocation();
let expectedEmit = [[newMobileLocationQuestions]];
// Assert
expect(wrapper.emitted("update:modelValue")).toEqual(expectedEmit);
expect(wrapper.emitted()["updated-mobile-location-questions"]).toBeTruthy();
expect(wrapper.emitted()["updated-mobile-location-questions"][0][0]).toBe(
newMobileLocationQuestions
);
});
it("Should not emit update:modelValue on setMobileLocation for an invalid address zip code", async () => {
it("Should not emit updated-mobile-location-questions on setMobileLocation for an invalid address zip code", async () => {
// Arrange
const mobileLocationQuestions = {
addressQuestions: {
@ -293,7 +302,7 @@ describe("mobile-location-modal-questions.vue", () => {
await wrapper.vm.setMobileLocation();
// Assert
expect(wrapper.emitted("update:modelValue")).not.toBeTruthy();
expect(wrapper.emitted("updated-mobile-location-questions")).not.toBeTruthy();
});
it("Should display invalid zip alert for invalid address zip inputs", async () => {

View file

@ -98,6 +98,7 @@ export default {
"updated-contains-military-base",
"updated-bill-to-account-number",
"mobileLocationSelected",
"updated-mobile-location-questions",
],
data() {
return {
@ -251,10 +252,15 @@ export default {
if (!zipCodeData.isValid) {
this.displayInvalidZipAlert = true;
this.resetModalButtonStyle();
return;
} else if (zipCodeData.state != this.internalModel.addressQuestions.state) {
this.displayMismatchStateAndZipAlert = true;
this.resetModalButtonStyle();
} else {
return;
} else if (
this.internalModel.addressQuestions.zipCode !==
this.modelValue.addressQuestions.zipCode
) {
// retrieve mobile fee part
const serviceZipCode = this.internalModel.addressQuestions.zipCode;
const mobileFeePart = await getPricedMobileFeePart(
@ -279,13 +285,17 @@ export default {
this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase);
this.$emit("updated-mobile-ctu", zipCodeData.zipCodeCtu);
this.$emit("updated-bill-to-account-number", billToAccountNumber);
// update the page level model
this.$emit("update:modelValue", this.internalModel);
//Page advance to Schedule page
this.$emit("mobileLocationSelected");
}
// update the page level model
await this.emitEvent("updated-mobile-location-questions", this.internalModel);
this.$emit("mobileLocationSelected");
},
//creating emitEvent function to raise events with resolve callback to handle events sequencing
async emitEvent(eventName, payload) {
return new Promise((resolve) => {
this.$emit(eventName, payload, resolve);
});
},
},
watch: {

View file

@ -460,11 +460,23 @@ describe("service-location.vue", () => {
};
// Act
mobileLocationQuestionsComponent.vm.$emit(
"update:modelValue",
// Mock the emitEvent function
mobileLocationQuestionsComponent.vm.emitEvent = jest.fn(async (eventName, payload) => {
return new Promise((resolve) => {
mobileLocationQuestionsComponent.vm.$emit(eventName, payload, resolve);
});
});
// Trigger the event
await mobileLocationQuestionsComponent.vm.emitEvent(
"updated-mobile-location-questions",
newMobileLocationQuestions
);
await wrapper.vm.$nextTick(); // Wait for DOM updates
await new Promise((resolve) => setTimeout(resolve, 0)); // Wait for promise to resolve
// Assert
expect(wrapper.vm.mobileLocationQuestions).toStrictEqual(newMobileLocationQuestions);
});
@ -529,11 +541,23 @@ describe("service-location.vue", () => {
};
// Act
mobileLocationQuestionsComponent.vm.$emit(
"update:modelValue",
// Mock the emitEvent function
mobileLocationQuestionsComponent.vm.emitEvent = jest.fn(async (eventName, payload) => {
return new Promise((resolve) => {
mobileLocationQuestionsComponent.vm.$emit(eventName, payload, resolve);
});
});
// Trigger the event
await mobileLocationQuestionsComponent.vm.emitEvent(
"updated-mobile-location-questions",
newMobileLocationQuestions
);
await wrapper.vm.$nextTick(); // Wait for DOM updates
await new Promise((resolve) => setTimeout(resolve, 0)); // Wait for promise to resolve
// Assert
expect(wrapper.vm.serviceZipCodeQuestion).toStrictEqual(newServiceZipCodeInfo);
});

View file

@ -93,6 +93,7 @@
@updated-serviceability="setServiceabilityDetails"
@updated-contains-military-base="setContainsMilitaryBase"
@updated-mobile-ctu="setCtuForMobile"
@updated-mobile-location-questions="setMobileLocationQuestions"
validationRules="mobile-location-required"
ref="mobileLocationQuestions"
linkWidgetName="MobileLocationLinkWidget"
@ -294,17 +295,18 @@ export default {
isVehicleProtected: this.isVehicleProtected,
};
},
set: function (newValue) {
async set(newValue) {
if (newValue.addressQuestions.zipCode !== this.zipCode) {
getShopProviderData(newValue.addressQuestions.zipCode).then((result) => {
await getShopProviderData(newValue.addressQuestions.zipCode).then((result) => {
this.shopProviderData = result.data;
this.selectedProvider = new Provider(
this.shopProviderData.mobileProviderNumber
);
});
}
this.setMobileLocation(newValue);
//handle promise
newValue.resolve?.();
},
},
isServiceableMobile() {
@ -485,6 +487,10 @@ export default {
setMobileFeePart(mobileFeePart) {
this.mobileFeePart = mobileFeePart;
},
//creating async function as the computed property can not directly handle asynchronous operations or promises.
async setMobileLocationQuestions(newValue, resolve) {
this.mobileLocationQuestions = { ...newValue, resolve };
},
getServiceAddressFromStore() {
return store.getters.order.serviceLocation.address;
},

View file

@ -133,22 +133,36 @@ export default {
vm.setCmsContent(resultMap.cmsContent);
if (store.getters.externalParameterState?.isExternalParameter) {
if (store.getters.externalParameterServiceZip.zipCode) {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
zipCode: store.getters.externalParameterServiceZip.zipCode,
state: null,
zipCodeCtu: null,
},
false
);
}
if (store.getters.externalParameterCustomer?.phoneNumber) {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_PHONE_NUMBER,
store.getters.externalParameterCustomer.phoneNumber,
false
);
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_IS_SMS_OPT_IN,
true,
false
);
}
const isValid = await baseMixin.methods.isFormValid(vm.$refs.theForm);
if (isValid) {
vm.forwardButtonAction();
} else {
if (store.getters.externalParameterServiceZip.zipCode) {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,
{
zipCode: store.getters.externalParameterServiceZip.zipCode,
state: null,
zipCodeCtu: null,
},
false
);
}
baseMixin.methods.ResetExternalParamsAndHideModal();
}
}
@ -188,11 +202,18 @@ export default {
async forwardButtonAction() {
const zipCodeData = await this.getZipCodeData(this.serviceZipCode);
if (vinPagesMixin.methods.isPhoneNumber(this.emailOrSms)) {
const phone = this.emailOrSms.replace(/[()]/g, "");
await this.dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, phone, false);
} else {
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailOrSms, false);
// if no value due to field being optional, blank both phone and email address
if (!this.emailOrSms) {
await this.dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, "", false);
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, "", false);
}
else {
if (vinPagesMixin.methods.isPhoneNumber(this.emailOrSms)) {
const phone = this.emailOrSms.replace(/[()]/g, "");
await this.dispatchStoreAction(storeActions.SAVE_PHONE_NUMBER, phone, false);
} else {
await this.dispatchStoreAction(storeActions.SAVE_EMAIL, this.emailOrSms, false);
}
}
await this.dispatchStoreAction(

View file

@ -152,6 +152,20 @@ export default {
if (isValid) {
vm.forwardButtonAction();
} else {
if (store.getters.externalParameterCustomer?.phoneNumber) {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_PHONE_NUMBER,
store.getters.externalParameterCustomer.phoneNumber,
false
);
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_IS_SMS_OPT_IN,
true,
false
);
}
if (store.getters.externalParameterServiceZip.zipCode) {
await baseMixin.methods.dispatchStoreAction(
storeActions.SAVE_SERVICE_ZIP_CODE_INFO,

View file

@ -898,7 +898,6 @@ function updateExternalParameterState() {
storeMutations.UPDATE_EXTERNAL_PARAMETER_PHONE_NUMBER,
externalParameterPhoneNumber
);
store.dispatch(storeActions.SAVE_PHONE_NUMBER, externalParameterPhoneNumber);
}
}

View file

@ -342,6 +342,9 @@ export const mutations = {
updateCustomerPhoneNumber(state, phoneNumber) {
state.order.customer.phoneNumber = phoneNumber;
},
updateCustomerIsSmsOptin(state, isSmsOptIn) {
state.order.customer.isSmsOptIn = isSmsOptIn;
},
updateVehicle(state, vehicleInfo) {
state.order.vehicle.year = vehicleInfo.year;
state.order.vehicle.make = vehicleInfo.make;
@ -477,7 +480,7 @@ export const mutations = {
saveExternalParameterState(externalParameterState);
},
updateExternalParameterEmailAddress(state, emailAddress) {
externalParameterState.serviceZip.emailAddress = emailAddress;
externalParameterState.customer.emailAddress = emailAddress;
saveExternalParameterState(externalParameterState);
},
updateExternalParameterIsInsurance(state, isInsurance) {
@ -518,7 +521,6 @@ export const mutations = {
},
resetExternalParameterServiceZipState(state) {
externalParameterState.serviceZip.zipCode = null;
externalParameterState.serviceZip.emailAddress = null;
saveExternalParameterState(externalParameterState);
},
resetExternalParameterQuoteState(state) {
@ -530,6 +532,7 @@ export const mutations = {
saveExternalParameterState(externalParameterState);
},
resetExternalParameterCustomerState(state) {
externalParameterState.customer.emailAddress = null;
externalParameterState.customer.phoneNumber = null;
saveExternalParameterState(externalParameterState);
},
@ -998,8 +1001,9 @@ export const getters = {
emailOrSms: (state) => {
const email =
externalParameterState.serviceZip.emailAddress ?? state.order.customer.emailAddress;
const phone = state.order.customer.phoneNumber;
externalParameterState.customer.emailAddress ?? state.order.customer.emailAddress;
const phone =
externalParameterState.customer.phoneNumber ?? state.order.customer.phoneNumber;
if (!phone && !email) {
return null;
@ -3062,6 +3066,9 @@ export const actions = {
phoneNumber === "" ? null : phoneNumber
);
},
saveIsSmsOptIn(context, isSmsOptIn) {
context.commit(storeMutations.UPDATE_CUSTOMER_IS_SMS_OPT_IN, isSmsOptIn);
},
savePageData(context, emailOrSms) {
context.commit(storeMutations.UPDATE_PAGE_DATA, emailOrSms);
@ -3749,7 +3756,6 @@ function createExternalParameterDefaultState() {
vinSelection: null,
},
serviceZip: {
emailAddress: null,
zipCode: null,
},
quote: {
@ -3757,6 +3763,7 @@ function createExternalParameterDefaultState() {
servicePackage: null,
},
customer: {
emailAddress: null,
phoneNumber: null,
},
};