From bba2631ec5722a8dc54b53f8862bf8882a10cad0 Mon Sep 17 00:00:00 2001 From: Chris Redelinghuys Date: Mon, 13 Jan 2025 09:36:16 -0500 Subject: [PATCH 1/8] CASH-69 --- src/constants/store-mutations.js | 2 ++ .../service-location/service-location.vue | 11 +++++++- src/layouts/vehicle/vehicle.vue | 8 ++++++ src/store/index.js | 25 ++++++++++++++++++- 4 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index 740c5a47e..f625e27cf 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -16,6 +16,8 @@ const storeMutations = { UPDATE_VEHICLE_IMAGE_COLOR: "updateVehicleImageColor", UPDATE_VEHICLE_VIN: "updateVehicleVin", UPDATE_VEHICLE: "updateVehicle", + UPDATE_VEHICLE_MOBILE_STATIC_RECALIBRATION_APPLICABLE: + "updateIsMobileStaticRecalibrationApplicable", UPDATE_IS_REPAIR: "updateIsRepair", UPDATE_NUMBER_OF_CHIPS: "updateNumberOfChips", diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index 4017088c4..70991f0f5 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -188,6 +188,8 @@ export default { isRecalibrationServiceableInshop: null, isGlassServiceableMobile: null, isRecalibrationServiceableMobile: null, + isVehicleMobileStaticRecalibrationApplicable: + this.getIsVehicleMobileStaticRecalibrationApplicableFromStore(), selectedAppointmentType: this.getSelectedAppointmentType(), selectedProvider: this.getSelectedProvider(), mobileFeePart: null, @@ -306,7 +308,11 @@ export default { }, isServiceableMobile() { if (this.isRecalibrationServiceableMobile !== null) { - return this.isGlassServiceableMobile && this.isRecalibrationServiceableMobile; + return ( + this.isGlassServiceableMobile && + this.isRecalibrationServiceableMobile && + this.isVehicleMobileStaticRecalibrationApplicable + ); } else { return this.isGlassServiceableMobile; } @@ -485,6 +491,9 @@ export default { getSelectedProvider() { return store.getters.order.serviceLocation.provider; }, + getIsVehicleMobileStaticRecalibrationApplicableFromStore() { + return store.getters.order.vehicle.isMobileStaticRecalibrationApplicable; + }, resetMobileLocation() { this.streetAddress = ""; this.apartmentNumberOrBusinessName = ""; diff --git a/src/layouts/vehicle/vehicle.vue b/src/layouts/vehicle/vehicle.vue index 18eee6988..6a634d826 100644 --- a/src/layouts/vehicle/vehicle.vue +++ b/src/layouts/vehicle/vehicle.vue @@ -117,6 +117,7 @@ export default { modelOptions: [], styleOptions: [], displayNoServiceAlert: false, + isMobileStaticRecalibrationApplicable: this.getIsMobileStaticRecalibrationApplicable(), }; }, @@ -438,6 +439,8 @@ export default { this.imageVifNumber = result?.data.imageVifNumber; this.imageVifColor = result?.data.imageVifColor; this.displayNoServiceAlert = !result?.data.canSafeliteService; + this.isMobileStaticRecalibrationApplicable = + result?.data.isMobileStaticRecalibrationApplicable; }, resetAlert() { this.displayNoServiceAlert = false; @@ -456,6 +459,8 @@ export default { imageUrl: this.imageUrl, imageVifNumber: this.imageVifNumber, imageVifColor: this.imageVifColor, + isMobileStaticRecalibrationApplicable: + this.isMobileStaticRecalibrationApplicable, }, false ); @@ -531,6 +536,9 @@ export default { getImageVifColorfromStore() { return store.getters.vehicle.imageVifColor; }, + getIsMobileStaticRecalibrationApplicable() { + return store.getters.vehicle.isMobileStaticRecalibrationApplicable; + }, }, components: { diff --git a/src/store/index.js b/src/store/index.js index d964a48d5..d7f7dcb06 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -68,6 +68,7 @@ const getDefaultState = () => { registration: { licensePlate: null, }, + isMobileStaticRecalibrationApplicable: false, }, serviceLocation: { address: null, @@ -225,6 +226,10 @@ export const mutations = { updateVehicleVin(state, vin) { state.order.vehicle.vin = vin; }, + updateIsMobileStaticRecalibrationApplicable(state, isMobileStaticRecalibrationApplicable) { + state.order.vehicle.isMobileStaticRecalibrationApplicable = + isMobileStaticRecalibrationApplicable; + }, updateIsRepair(state, isRepair) { state.order.damage.isRepair = isRepair; }, @@ -350,6 +355,8 @@ export const mutations = { state.order.vehicle.imageUrl = vehicleInfo.imageUrl; state.order.vehicle.imageVifNumber = vehicleInfo.imageVifNumber; state.order.vehicle.imageColor = vehicleInfo.imageVifColor; + state.order.vehicle.isMobileStaticRecalibrationApplicable = + vehicleInfo.isMobileStaticRecalibrationApplicable; }, updateRegistration(state, registrationInfo) { state.order.vehicle.registration.licensePlate = registrationInfo?.licensePlate; @@ -534,6 +541,7 @@ export const mutations = { state.order.vehicle.imageUrl = null; state.order.vehicle.imageVifNumber = null; state.order.vehicle.imageColor = null; + state.order.vehicle.isMobileStaticRecalibrationApplicable = false; }, resetDamageState(state) { state.order.damage.isRepair = null; @@ -2221,7 +2229,18 @@ export const actions = { // Vehicle saveVehicle( context, - { year, make, model, style, carId, category, imageUrl, imageVifNumber, imageVifColor } + { + year, + make, + model, + style, + carId, + category, + imageUrl, + imageVifNumber, + imageVifColor, + isMobileStaticRecalibrationApplicable, + } ) { if ( context.state.order.vehicle.year != year || @@ -2242,6 +2261,10 @@ export const actions = { context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, imageUrl); context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, imageVifNumber); context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, imageVifColor); + context.commit( + storeMutations.UPDATE_VEHICLE_MOBILE_STATIC_RECALIBRATION_APPLICABLE, + isMobileStaticRecalibrationApplicable + ); } }, From 32fc82176b7c6292aeb2cfaeea5386a88c32e44b Mon Sep 17 00:00:00 2001 From: Matt Caimi Date: Mon, 13 Jan 2025 13:57:06 -0500 Subject: [PATCH 2/8] CASH-69 fix unit test --- src/layouts/service-location/service-location.spec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/layouts/service-location/service-location.spec.js b/src/layouts/service-location/service-location.spec.js index eea946b89..6333082ec 100644 --- a/src/layouts/service-location/service-location.spec.js +++ b/src/layouts/service-location/service-location.spec.js @@ -181,6 +181,9 @@ beforeEach(() => { zipCode: "43235", state: "OH", }, + vehicle: { + isMobileStaticRecalibrationApplicable: true, + }, }, damage: { isRepair: false, From d9933d861c6db1989b16f904fecf51dff27f5114 Mon Sep 17 00:00:00 2001 From: Chris Redelinghuys Date: Thu, 16 Jan 2025 10:05:20 -0500 Subject: [PATCH 3/8] CASH-69: Get MSR fee and offer mobile service if applicable for cash and insurance --- .../service-location-helper.js | 5 ++- .../service-location/service-location.vue | 23 +++++++++++--- src/store/index.js | 31 +++++++++++++++++-- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/layouts/service-location/helpers/service-location-helper/service-location-helper.js b/src/layouts/service-location/helpers/service-location-helper/service-location-helper.js index 1e7b6bdb3..0e86c4143 100644 --- a/src/layouts/service-location/helpers/service-location-helper/service-location-helper.js +++ b/src/layouts/service-location/helpers/service-location-helper/service-location-helper.js @@ -11,7 +11,10 @@ export async function getPricedMobileFeePart(serviceZipCode, pageNameToLog) { // Get the Mobile Fee Part const mobileFeePart = await baseMixin.methods.dispatchStoreActionWithLogging( storeActions.GET_MOBILE_FEE_PART, - null, + { + serviceZipCode: serviceZipCode, + serviceZipCodeCtu: zipCodeData.zipCodeCtu, + }, pageNameToLog, false ); diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index 70991f0f5..21a123e6e 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -159,6 +159,7 @@ import { defineRule } from "vee-validate"; import { errorMessages } from "@/constants/error-messages"; const MOBILE_FEE_PART_TYPE = "MOBILE FEE"; +const MOBILE_STATIC_RECAL_FEE_PART_NUMBER = "RECAL MOBILE"; // DEFINE VALIDATION RULES defineRule("mobile-location-required", (value) => { @@ -309,14 +310,27 @@ export default { isServiceableMobile() { if (this.isRecalibrationServiceableMobile !== null) { return ( - this.isGlassServiceableMobile && - this.isRecalibrationServiceableMobile && - this.isVehicleMobileStaticRecalibrationApplicable + (this.isGlassServiceableMobile && this.isRecalibrationServiceableMobile) || + this.isMobileStaticRecalibrationApplicable ); } else { return this.isGlassServiceableMobile; } }, + isMobileStaticRecalibrationApplicable() { + if (!this.isInsurance) { + return ( + this.isVehicleMobileStaticRecalibrationApplicable && + this.mobileFeePart?.partNumber == MOBILE_STATIC_RECAL_FEE_PART_NUMBER + ); + } else { + return ( + this.isVehicleMobileStaticRecalibrationApplicable && + this.mobileFeePart?.partNumber == MOBILE_STATIC_RECAL_FEE_PART_NUMBER && + this.mobileFeePart?.isInsurable + ); + } + }, mobileFeeApplies() { if ( this.mobileFeePart?.laborAmount > 0 || @@ -355,7 +369,8 @@ export default { return ( this.isServiceableInshop && this.isGlassServiceableMobile && - this.isRecalibrationServiceableMobile === false + this.isRecalibrationServiceableMobile === false && + !this.isMobileStaticRecalibrationApplicable ); }, displayRecalibrationWarning() { diff --git a/src/store/index.js b/src/store/index.js index d7f7dcb06..2c68cab38 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1607,7 +1607,7 @@ export const actions = { return response; }, - getMobileFeePart(context, { pageNameToLog }) { + getMobileFeePart(context, { payload: { serviceZipCode, serviceZipCodeCtu }, pageNameToLog }) { const serviceType = context.getters.damage.isRepair ? "Repair" : "Install"; const facilityType = "Mobile"; const parentAccountNumber = context.getters.payment.parentAccountNumber; @@ -1619,16 +1619,31 @@ export const actions = { const coverageStatus = coverageStatusEnum(order.payment?.insuranceCoverage?.coverageStatus); const coverageType = coverageTypeEnum(order.payment?.insuranceCoverage?.coverageType); const isItacOptimized = order.policy?.isItac ?? false; + const isMobileStaticRecalibrationApplicable = + order.vehicle?.isMobileStaticRecalibrationApplicable; const zipCode = - order.serviceLocation?.provider?.address?.zipCode ?? order.serviceLocation?.zipCode; + serviceZipCode ?? + order.serviceLocation?.provider?.address?.zipCode ?? + order.serviceLocation?.zipCode; var providerNumber = - order.serviceLocation?.provider?.providerNumber ?? order.serviceLocation?.zipCodeCtu; + serviceZipCodeCtu ?? + order.serviceLocation?.provider?.providerNumber ?? + order.serviceLocation?.zipCodeCtu; if (providerNumber.startsWith("00") && providerNumber.length > 5) { providerNumber = providerNumber.substring(1); } var endPoint = `${endpoints.GetMobileFeePart.url}/?serviceType=${serviceType}&facilityType=${facilityType}&parentAccountNumber=${parentAccountNumber}&billToAccountNumber=${billToAccountNumber}&providerNumber=${providerNumber}&isItacOptimized=${isItacOptimized}&zipCode=${zipCode}`; + + if (isMobileStaticRecalibrationApplicable) { + const staticRecalPartNumber = getStaticRecalPartNumber(order.lineItems?.glassParts[0]); + const carId = order.vehicle?.carId; + if (staticRecalPartNumber && carId) { + endPoint = `${endPoint}&partNumbers=${staticRecalPartNumber}&carId=${carId}`; + } + } + if (coverageStatus) { endPoint = `${endPoint}&coverageStatus=${coverageStatus}`; } @@ -3666,3 +3681,13 @@ function getExternalParameterDefaultState() { function saveExternalParameterState(externalParameterState) { window.sessionStorage.setItem("externalParameterState", JSON.stringify(externalParameterState)); } + +//This function checks if static recalibration is available for the vehicle and returns the part number for it. +function getStaticRecalPartNumber(glassPartsArray) { + const recalPart = glassPartsArray.childParts.find((item) => item.partNumber === "RECAL STATIC"); + if (recalPart) { + return recalPart.partNumber; + } else { + return null; + } +} From 38c16b434b5365e6a5d21718c511387be5c8b95c Mon Sep 17 00:00:00 2001 From: Chris Redelinghuys Date: Fri, 17 Jan 2025 08:29:25 -0500 Subject: [PATCH 4/8] CASH-69: Simplify logic --- .../service-location/service-location.vue | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index 21a123e6e..71bfa4b01 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -318,18 +318,11 @@ export default { } }, isMobileStaticRecalibrationApplicable() { - if (!this.isInsurance) { - return ( - this.isVehicleMobileStaticRecalibrationApplicable && - this.mobileFeePart?.partNumber == MOBILE_STATIC_RECAL_FEE_PART_NUMBER - ); - } else { - return ( - this.isVehicleMobileStaticRecalibrationApplicable && - this.mobileFeePart?.partNumber == MOBILE_STATIC_RECAL_FEE_PART_NUMBER && - this.mobileFeePart?.isInsurable - ); - } + return ( + this.isVehicleMobileStaticRecalibrationApplicable && + this.mobileFeePart?.partNumber == MOBILE_STATIC_RECAL_FEE_PART_NUMBER && + (this.isInsurance ? this.mobileFeePart?.isInsurable : true) + ); }, mobileFeeApplies() { if ( From 5dede2978f060830210d1c147bb57f855d531afd Mon Sep 17 00:00:00 2001 From: Johnny shultz Date: Thu, 23 Jan 2025 16:51:53 -0500 Subject: [PATCH 5/8] CASH-109-specs CASH-109-specs clean up on return-user.spec.js --- jest.config.js | 1 - src/layouts/return-user/return-user.spec.js | 312 ++++++++++++++++++++ 2 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 src/layouts/return-user/return-user.spec.js diff --git a/jest.config.js b/jest.config.js index 052e1c47b..ce4aa3ad6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -27,7 +27,6 @@ module.exports = { "!src/layouts/insurance/*.vue", // Temp test exclusion while in development "!src/layouts/insurance-company/*.vue", // Temp test exclusion while in development "!src/layouts/insurance-company/insurance-company-question/*.vue", // Temp test exclusion while in development - "!src/layouts/return-user/*.vue", // Temp test exclusion while in development // END ], // ! means exclude from coverage. testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], diff --git a/src/layouts/return-user/return-user.spec.js b/src/layouts/return-user/return-user.spec.js new file mode 100644 index 000000000..dc631ce62 --- /dev/null +++ b/src/layouts/return-user/return-user.spec.js @@ -0,0 +1,312 @@ +// Components +import returnUser from "@/layouts/return-user/return-user.vue"; + +// Supporting Files +import funnelHeader from "@/fmg-components/funnel-header/funnel-header"; +import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header"; +import { shallowMount } from "@vue/test-utils"; +import { getMountOptions } from "@/helpers/unit-test-helper"; +import baseMixin from "@/mixins/base-mixin.js"; +import { dispatchStoreAction } from "@/mixins/base-mixin.js"; +import store from "@/store"; +import router from "@/router"; +import navbar from "@/fmg-components/nav-bar/nav-bar"; +import { Form } from "vee-validate"; +import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; +import { navigationScenarios } from "@/router/router-constants/navigation-scenarios"; +import { settleAllPromises } from "@/helpers/layout-helper"; +import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper"; +import { deleteFunnelCookie } from "@/helpers/heritage-integration/cookie-helper"; +import { saveSession } from "@/helpers/heritage-integration/order-helper.js"; + +import { experimentSettings } from "../../constants/experiments"; +import { storeActions } from "@/constants/store-actions"; +import buttonMain from "@/ux-components/button-main/button-main"; + +// Constants + +// Setup global mocks +let mockStoreActionData = {}; + +let mockStoreData = {}; +let mockExperimentSettings = { + experiments: [ + { + universeName: "ConceptFunnel", + settings: { + SuppressVinCapture: false, + }, + }, + ], +}; + +function resetMockStoreData() { + mockStoreData = { + vehicle: { + year: "2000", + make: "TestMake", + model: "TestModel", + style: "TestStyle", + carId: "TestID", + vin: null, + registration: { + licensePlate: null, + }, + }, + serviceLocation: { + address: null, + address2: null, + city: null, + state: null, + zipCode: null, + zipCodeCtu: null, + appointmentType: null, + isVehicleProtected: null, + provider: { + providerNumber: null, + address: { + streetAddress: null, + city: null, + state: null, + zipCode: null, + zipCodeCtu: null, + }, + }, + techNotes: null, + }, + customer: { + firstName: null, + lastName: null, + emailAddress: null, + phoneNumber: null, + isSmsOptIn: null, + }, + damage: { + isRepair: false, + numberOfChips: null, + glassToReplace: [{ glassLocation: "Windshield", glassName: "windshield" }], + partQuestionAnswers: null, + moldingQuestionAnswers: null, + capabilityQuestionAnswers: null, + dateOfLoss: null, + damageCause: null, + }, + lineItems: { + glassParts: [ + { + canSafeliteRecalibrate: true, + childParts: [ + { + kitPrice: 0, + laborAmount: 23.55, + partNumber: "GGG FW4896", + salesTax: 1.77, + sellingPrice: 0, + }, + ], + color: "Green Tint", + description: + "solar, soundproofing, lane keep assist, lane departure warning system, w/adaptive cruise control", + id: "db22fd44-10dd-456f-979b-ff88cf68cca6", + kitPrice: 0, + laborAmount: 60, + partNumber: "FW04896GTYN", + partType: "WINDSHIELD", + recalibrationType: "STATIC", + requiresCapabilityQuestions: false, + requiresRecalibration: true, + salesTax: 63.86, + sellingPrice: 791.46, + }, + ], + supportingItems: null, + vaps: null, + serverData: null, + promos: null, + }, + payment: { + isInsurance: null, + insuranceCoverage: { + isVerified: null, + coverageStatus: null, + coverageType: null, + coverageVerificationType: null, + }, + parentAccountNumber: 0, + billToAccountNumber: null, + isPia: null, + piaType: null, + inactivePromos: null, + paypalToken: null, + nextGenSettledAmount: 0, + ccToken: { + subscriptionId: null, + expMonth: null, + expYear: null, + cardType: null, + billToPostalCode: null, + billToFirstName: null, + billToLastName: null, + referenceNumber: null, + authCode: null, + transactionId: null, + transReferenceNumber: null, + lastFour: null, + }, + }, + policy: { + currentDeductible: 0, + policyNumber: null, + isItac: false, + additionalAuthFlag: null, + isNoComp: false, + insuranceCompanyName: null, + }, + schedule: { + date: null, + startTime: null, + endTime: null, + routeCode: null, + jobMaxMinutes: null, + jobMinMinutes: null, + }, + externalParameterServiceZip: { + zipCode: null, + emailAddress: null, + }, + externalParameterState: { isExternalParameter: false }, + }; +} + +function applyMockStoreDataToGetters() { + store.getters = { + vehicle: mockStoreData.vehicle, + }; + store.state.order = mockStoreData; + store.state.applicationUser.experiments = mockExperimentSettings; +} + +async function mockDispatchStoreAction(actionName) { + return mockStoreActionData[actionName]; +} + +jest.mock("@/mixins/base-mixin.js", () => ({ + methods: { + dispatchStoreAction: jest.fn(), + dispatchStoreActionWithLogging: jest.fn().mockImplementation(mockDispatchStoreAction), + }, +})); + +jest.mock("@/helpers/cms-content-helper", () => ({ + fetchCmsContentForPage: () => Promise.resolve("content"), +})); + +jest.mock("@/helpers/heritage-integration/order-helper.js", () => ({ + saveSession: jest.fn(), +})); + +jest.mock("@/helpers/heritage-integration/cookie-helper", () => ({ + deleteFunnelCookie: jest.fn(), + getFunnelCookie: jest.fn(), +})); + +router.navigateWithoutSaving = jest.fn(); +router.navigateWithSaving = jest.fn(); + +// Tests +describe("return-user.vue", () => { + beforeEach(() => { + resetMockStoreData(); + jest.clearAllMocks(); + }); + + describe("Test prerequisites are valid and child components are rendered", () => { + test("expect pagePrerequisites are valid to be called", () => { + // Arrange + const wrapper = setupMocks({}); + + applyMockStoreDataToGetters(); + + // Act + const pagePrerequisitesSpy = jest.spyOn(wrapper.vm, "arePagePrerequisitesValid"); + + wrapper.vm.arePagePrerequisitesValid(); + + // Assert + expect(pagePrerequisitesSpy).toBeCalled(); + expect(getFunnelCookie).toHaveBeenCalled(); + }); + + test("renders child components", () => { + const wrapper = setupMocks({}); + + expect(wrapper.findComponent(funnelHeader).exists()).toBe(true); + expect(wrapper.findComponent(funnelSubHeader).exists()).toBe(true); + expect(wrapper.findComponent(navbar).exists()).toBe(true); + }); + }); + + describe("Navigation", () => { + test("Check forwardButtonAction is working", async () => { + // Arrange + const wrapper = setupMocks({}); + // Act + + await wrapper.vm.forwardButtonAction(); + + // Assert + expect(wrapper.vm.$router.navigateWithoutSaving).toBeCalledWith( + navigationScenarios.CLICKED_FORWARD, + wrapper.vm.$route + ); + }); + test("expect functions in startOver to be called", async () => { + //Arrange + const wrapper = setupMocks({}); + + // Act + const dispatchStoreActionSpy = jest.spyOn(wrapper.vm, "dispatchStoreAction"); + + await wrapper.vm.$nextTick(); + await wrapper.vm.startOver(); + + // Assert + expect(wrapper.vm.$router.navigateWithoutSaving).toBeCalledWith( + navigationScenarios.CLICKED_FORWARD, + wrapper.vm.$route + ); + + expect(dispatchStoreActionSpy).toHaveBeenCalledWith(storeActions.RESET_STATE); + expect(deleteFunnelCookie).toHaveBeenCalled(); + }); + }); +}); + +function setupMocks({ customMountOptions }) { + const route = { query: { fmgPage: "return-user" }, params: {} }; + + baseMixin.methods.ResetExternalParamsAndHideModal = jest.fn(); + + const mountOptions = getMountOptions({ + ...customMountOptions, + route: route, + }); + + mountOptions.global.mocks["$store"] = store; + mountOptions.global.mocks["$router"] = router; + baseMixin.methods.isFormValid = jest.fn().mockReturnValue(true); + mountOptions["attachTo"] = document.body; + + const wrapper = shallowMount(returnUser, mountOptions, { + stubs: { + Form, + funnelHeader, + funnelSubHeader, + navbar, + loadingModal: true, + buttonMain, + }, + }); + + return wrapper; +} From ccc3feb15ef22a8ffd00299d2015236089b6f9b3 Mon Sep 17 00:00:00 2001 From: CarlNation Date: Fri, 24 Jan 2025 13:50:40 -0500 Subject: [PATCH 6/8] CASH-160 CASH-160 promo code lost coming from content site /cjrepair. Added all passed querystring keys to the redirect to return-user so they're not lost. --- src/helpers/querystring-helper.js | 14 ++++++++++++++ src/router/index.js | 11 +++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/helpers/querystring-helper.js b/src/helpers/querystring-helper.js index 38583b1ae..8c40c91b7 100644 --- a/src/helpers/querystring-helper.js +++ b/src/helpers/querystring-helper.js @@ -8,3 +8,17 @@ export function getQuerystringParameter(key) { return lowerCaseParams.get(key) ? lowerCaseParams.get(key) : null; } + +// if you add the fmgPage to the querystringobject before calling, then pass true for skipFmgPageName +export function buildQuerystringObject(qso, skipFmgPageName=false) { + const queryString = window.location.search; + const urlParams = new URLSearchParams(queryString); + + for (const [name, value] of urlParams) { + if (name.toLowerCase() === "fmgpage" && skipFmgPageName) { + continue; + } + qso[name] = value; + } + return qso; +} \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index d956af773..73a8545f3 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -6,7 +6,7 @@ import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js" import { routingTable } from "@/router/router-constants/routing-table.js"; import { globalEvents, globalEventTypes } from "@/constants/events"; import { queryStrings } from "@/constants/query-strings"; -import { getQuerystringParameter } from "@/helpers/querystring-helper"; +import { getQuerystringParameter, buildQuerystringObject } from "@/helpers/querystring-helper"; import { getDeviceIdValue } from "@/helpers/heritage-integration/cookie-helper"; import { showFmgLoadingModal } from "@/helpers/loading-modal-helper"; import { fmgPageValues, funnelStartPageName } from "@/router/router-constants/fmgPage-values"; @@ -108,14 +108,13 @@ const routes = [ var qso = { fmgPage: fmgPageValues.RETURN_USER, }; - const lg = getQuerystringParameter(queryStrings.LOG); - if (lg) { - qso[queryStrings.LOG] = true; - } + + const newQueryString = buildQuerystringObject(qso, true); + log(" -- returnUser add querystring: " + JSON.stringify(newQueryString)); router.push({ path: "/", - query: Object.assign({}, qso), + query: Object.assign({}, newQueryString), }); return; } From 2a97b02b040637c4a8a73493a1daa1e49eda82c1 Mon Sep 17 00:00:00 2001 From: hiteshkumar87 Date: Mon, 27 Jan 2025 17:30:40 +0530 Subject: [PATCH 7/8] CASH-57 Remove zip code matching condition causing zip code state validation --- .../mobile-location-modal-questions.vue | 79 ++++++++----------- 1 file changed, 34 insertions(+), 45 deletions(-) diff --git a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue index 84087cb6e..d88bd53e2 100644 --- a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue +++ b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.vue @@ -241,57 +241,46 @@ export default { this.displayMismatchStateAndZipAlert = false; }, async setMobileLocation() { - if ( - this.internalModel.addressQuestions.zipCode !== - this.modelValue.addressQuestions.zipCode - ) { - this.resetAlerts(); - // Validate the Zip Code - const zipCodeData = await this.getZipCodeData( - this.internalModel.addressQuestions.zipCode, + this.resetAlerts(); + // Validate the Zip Code + const zipCodeData = await this.getZipCodeData( + this.internalModel.addressQuestions.zipCode, + "service-location" + ); + + if (!zipCodeData.isValid) { + this.displayInvalidZipAlert = true; + this.resetModalButtonStyle(); + } else if (zipCodeData.state != this.internalModel.addressQuestions.state) { + this.displayMismatchStateAndZipAlert = true; + this.resetModalButtonStyle(); + } else { + // retrieve mobile fee part + const serviceZipCode = this.internalModel.addressQuestions.zipCode; + const mobileFeePart = await getPricedMobileFeePart( + serviceZipCode, "service-location" ); - if (!zipCodeData.isValid) { - this.displayInvalidZipAlert = true; - this.resetModalButtonStyle(); - } else if (zipCodeData.state != this.internalModel.addressQuestions.state) { - this.displayMismatchStateAndZipAlert = true; - this.resetModalButtonStyle(); - } else { - // retrieve mobile fee part - const serviceZipCode = this.internalModel.addressQuestions.zipCode; - const mobileFeePart = await getPricedMobileFeePart( - serviceZipCode, - "service-location" - ); + // retrieve serviceability details + const serviceabilityDetails = await getServiceabilityDetails( + serviceZipCode, + null, + "service-location" + ); - // retrieve serviceability details - const serviceabilityDetails = await getServiceabilityDetails( - serviceZipCode, - null, - "service-location" - ); + const billToAccountNumber = await getBillToAccountNumber( + this.internalModel.zipCodeCtu + ); - const billToAccountNumber = await getBillToAccountNumber( - this.internalModel.zipCodeCtu - ); + // update content related to service zip code + this.$emit("updated-mobile-fee-part", mobileFeePart); + this.$emit("updated-serviceability", serviceabilityDetails.data); + this.$emit("updated-contains-military-base", zipCodeData.containsMilitaryBase); + this.$emit("updated-mobile-ctu", zipCodeData.zipCodeCtu); + this.$emit("updated-bill-to-account-number", billToAccountNumber); - // update content related to service zip code - this.$emit("updated-mobile-fee-part", mobileFeePart); - this.$emit("updated-serviceability", serviceabilityDetails.data); - 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"); - } - } else { - // Update the page level model + // update the page level model this.$emit("update:modelValue", this.internalModel); //Page advance to Schedule page From c5ad651546aebfdf8e63ce95b9f5e2a1a94e5a5a Mon Sep 17 00:00:00 2001 From: CarlNation Date: Mon, 27 Jan 2025 13:50:21 -0500 Subject: [PATCH 8/8] CASH-109 CASH-109 do not use the funnel session cookie as it will be deleted when the browser closes. instead, look at vuex and make sure they at least have a vehicle year to determine if they have visited the site before. --- src/fmg-components/nav-bar/nav-bar.vue | 8 ++++---- src/layouts/quote/quote.vue | 2 +- src/layouts/vehicle-parts/vehicle-parts.vue | 2 +- src/mixins/analytics-mixin.js | 10 ++-------- src/router/index.js | 19 ++++++++----------- 5 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/fmg-components/nav-bar/nav-bar.vue b/src/fmg-components/nav-bar/nav-bar.vue index 6f981f9e0..f23ecd244 100644 --- a/src/fmg-components/nav-bar/nav-bar.vue +++ b/src/fmg-components/nav-bar/nav-bar.vue @@ -97,16 +97,16 @@ export default { document.onkeydown = function (e) { return false; }; - // check session expired and initSession to recreate cookies - if (analyticsMixin.methods.sessionExpired()) { + // check session expired and initSession to recreate cookies + if (analyticsMixin.methods.sessionExpired()) { this.routeReturnUser(); } else { this.$emit("ForwardClicked"); } }, linkClick() { - // check session expired and initSession to recreate cookies - if (analyticsMixin.methods.sessionExpired()) { + // check session expired and initSession to recreate cookies + if (analyticsMixin.methods.sessionExpired()) { this.routeReturnUser(); } else { this.$emit("BackClicked"); diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index f3590cbc3..860aaf087 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -298,7 +298,7 @@ export default { // to restore, uncomment the 2 lines below // vm.showSaveProgressPopup = showSaveProgressPopup; // vm.showSaveProgressModal = showSaveProgressModal; - + vm.addableVaps = addableVaps; vm.lineItems = lineItems; vm.availableLineItems = pricingResults; diff --git a/src/layouts/vehicle-parts/vehicle-parts.vue b/src/layouts/vehicle-parts/vehicle-parts.vue index 43f66c376..832589043 100644 --- a/src/layouts/vehicle-parts/vehicle-parts.vue +++ b/src/layouts/vehicle-parts/vehicle-parts.vue @@ -97,7 +97,7 @@ export default { // turned off Save Your Progress for 1/23/25 release // to restore, uncomment line below // vm.showSaveProgressModal = !(emailFromStore?.length > 0); - + // Glass Part Question dynamic component Object.keys(vm.$refs) .filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined) diff --git a/src/mixins/analytics-mixin.js b/src/mixins/analytics-mixin.js index 1c70722a1..9bb8f0cda 100644 --- a/src/mixins/analytics-mixin.js +++ b/src/mixins/analytics-mixin.js @@ -712,16 +712,10 @@ export default { return !areAllSessionCookiesSet(); }, - // sessionExpired is true when one of the analytics cookies(sid, dxdev) has expired but we still have the funnelSessionInfo cookie + // sessionExpired is true when one of the analytics cookies(sid, dxdev) has expired but we still have the vehicle year in vuex sessionExpired() { const fromHeritage = getQuerystringParameter(queryStrings.FROM_HERITAGE) === "true"; - const funnelCookieLastTouched = getFunnelCookie()?.LastTouched; - if ( - this.noSession() && - !fromHeritage && - funnelCookieLastTouched !== null && - funnelCookieLastTouched !== undefined - ) { + if (this.noSession() && !fromHeritage && store.getters.order.vehicle?.year > 0) { return true; } else { return false; diff --git a/src/router/index.js b/src/router/index.js index 49d3b0e4f..adbb2d022 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -57,7 +57,7 @@ const routes = [ log(` --from.redirectedFrom:>${JSON.stringify(from.redirectedFrom)}<`, ""); await analyticsMixin.methods.validateSession(); - + // after session is validated, remove the fromHeritage querystring if it exists so session expiration works if (to.query) { delete to.query[queryStrings.FROM_HERITAGE]; @@ -100,14 +100,12 @@ const routes = [ } // On entering the funnel "fresh", read cookie information, decide what to do next. else if (from.redirectedFrom === undefined || fromReturnUser) { - // if entering the funnel from the content site, check and see if there is already a funnel cookie. + // if entering the funnel from the content site, check and see if there is already a vehicle year in vuex. // if so, send them to return-user page. - const funnelCookieLastTouched = getFunnelCookie()?.LastTouched; if ( fromContentSite && - funnelCookieLastTouched !== null && - funnelCookieLastTouched !== undefined && - !toReturnUserPage + !toReturnUserPage && + store.getters.order.vehicle?.year > 0 ) { log(" --from content site navigate to return user"); var qso = { @@ -178,8 +176,7 @@ const routes = [ // clear part related state because heritage selected a new vehicle if ( to.query.fmgPage === fmgPageValues.VEHICLE && - eval(getFunnelCookie()?.HasDelayedClaimRegistration && - !fromReturnUser) + eval(getFunnelCookie()?.HasDelayedClaimRegistration && !fromReturnUser) ) { store.commit(storeMutations.RESET_GLASS_PARTS_STATE); } @@ -187,7 +184,7 @@ const routes = [ // if coming from the return user page, clear the destination page so implicit navigation runs log(" --to.query ", JSON.stringify(to.query)); if (fromReturnUser && to.query) { - log( " --clear to.query"); + log(" --clear to.query"); delete to.query[queryStrings.FMG_PAGE]; //to.query[queryStrings.FMG_PAGE] = ""; @@ -571,7 +568,7 @@ async function navigate( const pageError = getQuerystringParameter(queryStrings.PAGE_ERROR); const logQs = getQuerystringParameter(queryStrings.LOG); baseMixin.methods.dispatchStoreAction(storeActions.SAVE_LOGGING_OPTION, logQs, false); - + log("------------- router index.js navigate start -----------------"); log(" --scenario: ", scenario); log(" --isSavingNavigation: ", isSavingNavigation); @@ -636,7 +633,7 @@ function getNavigationMap(scenario, currentRoute) { function log(message, data) { const log = getQuerystringParameter(queryStrings.LOG); baseMixin.methods.dispatchStoreAction(storeActions.SAVE_LOGGING_OPTION, log, false); - + data = data ?? ""; const outData = typeof data === "object" ? JSON.stringify(data) : data;