From 5e661111e1e6ce778545facdcf2c8237da340a60 Mon Sep 17 00:00:00 2001 From: hiteshkumar87 Date: Tue, 11 Feb 2025 13:51:03 +0530 Subject: [PATCH 1/5] CASH-170 creating async function as the computed property can not directly handle asynchronous operations or promises. --- .../mobile-location-modal-questions.spec.js | 21 ++++++++---- .../mobile-location-modal-questions.vue | 24 ++++++++++---- .../service-location/service-location.spec.js | 32 ++++++++++++++++--- .../service-location/service-location.vue | 12 +++++-- 4 files changed, 69 insertions(+), 20 deletions(-) diff --git a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.spec.js b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.spec.js index 863a04c82..91f26b64d 100644 --- a/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.spec.js +++ b/src/layouts/service-location/mobile-location-modal-questions/mobile-location-modal-questions.spec.js @@ -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 () => { 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 d88bd53e2..dadbaff8b 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 @@ -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: { diff --git a/src/layouts/service-location/service-location.spec.js b/src/layouts/service-location/service-location.spec.js index 54f28a89c..b29ec604b 100644 --- a/src/layouts/service-location/service-location.spec.js +++ b/src/layouts/service-location/service-location.spec.js @@ -463,11 +463,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); }); @@ -532,11 +544,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); }); diff --git a/src/layouts/service-location/service-location.vue b/src/layouts/service-location/service-location.vue index 9addea705..47cf5262e 100644 --- a/src/layouts/service-location/service-location.vue +++ b/src/layouts/service-location/service-location.vue @@ -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" @@ -296,17 +297,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; }, From 80caabf5efdabba4ab37d51169c46666e99f9199 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Tue, 11 Feb 2025 09:10:00 -0500 Subject: [PATCH 2/5] CASH-201 | Add "Pay on my own" link Add the link to insurance-company Make cash auto selected on quote when this link is used --- .../insurance-company/insurance-company.vue | 16 +++++++++++++++- src/layouts/quote/quote.vue | 4 ++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/layouts/insurance-company/insurance-company.vue b/src/layouts/insurance-company/insurance-company.vue index 79e238d47..ddfffe177 100644 --- a/src/layouts/insurance-company/insurance-company.vue +++ b/src/layouts/insurance-company/insurance-company.vue @@ -16,6 +16,13 @@ v-model="parentAccountNumber" :originalList="originalList" /> + diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index 4428f6e40..99b75c9a7 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -328,6 +328,10 @@ export default { } const getIsInsuranceSelectedValue = (availableLineItems, insuranceThreshold) => { + // routeParam checked here is set on insurance-selection "Pay on my own" link click + if (from.params.forceCashSelection) { + 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 From 37cd5f01bfb92d5eaa07a4a50ac977a048e4a15b Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Tue, 11 Feb 2025 09:20:48 -0500 Subject: [PATCH 3/5] CASH-201 | Update unit test Fixed broken tests, haven't added new ones yet --- src/layouts/quote/quote.spec.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/layouts/quote/quote.spec.js b/src/layouts/quote/quote.spec.js index deca0d6d1..42e3aecd0 100644 --- a/src/layouts/quote/quote.spec.js +++ b/src/layouts/quote/quote.spec.js @@ -293,7 +293,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - undefined, + {params:{}}, (c) => c(wrapper.vm) ); @@ -345,7 +345,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - undefined, + {params:{}}, (c) => c(wrapper.vm) ); //Assert @@ -388,7 +388,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - undefined, + {params:{}}, (c) => c(wrapper.vm) ); @@ -432,7 +432,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote", isInsurance: false } }, - undefined, + {params:{}}, (c) => c(wrapper.vm) ); @@ -478,7 +478,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - undefined, + {params:{}}, (c) => c(wrapper.vm) ); @@ -523,7 +523,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - undefined, + {params:{}}, (c) => c(wrapper.vm) ); @@ -570,7 +570,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - undefined, + {params:{}}, (c) => c(wrapper.vm) ); @@ -618,7 +618,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - undefined, + {params:{}}, (c) => c(wrapper.vm) ); @@ -662,7 +662,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - undefined, + {params:{}}, (c) => c(wrapper.vm) ); //Assert @@ -783,7 +783,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - undefined, + {params:{}}, nextFunction ); await nextTick(); From 738e6a108d025e812cda4f68986e9cfdd26f3aa5 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Tue, 11 Feb 2025 10:20:34 -0500 Subject: [PATCH 4/5] CASH-201 | Switch to query parameter --- .../insurance-company/insurance-company.vue | 9 ++++++--- src/layouts/quote/quote.spec.js | 20 +++++++++---------- src/layouts/quote/quote.vue | 4 ++-- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/layouts/insurance-company/insurance-company.vue b/src/layouts/insurance-company/insurance-company.vue index ddfffe177..452484e48 100644 --- a/src/layouts/insurance-company/insurance-company.vue +++ b/src/layouts/insurance-company/insurance-company.vue @@ -101,7 +101,7 @@ export default { return !!this.parentAccountNumber; }, payWithCashButtonCopy() { - return "Pay on my own"; + return this.getCmsContent("PayWithCashWidget", "Text"); } }, methods: { @@ -122,8 +122,11 @@ export default { }, backButtonAction(forceCashSelectionOnQuote) { // Go back to Quote page - if (forceCashSelectionOnQuote) - this.$route.params['forceCashSelection'] = true; + 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() { diff --git a/src/layouts/quote/quote.spec.js b/src/layouts/quote/quote.spec.js index 42e3aecd0..deca0d6d1 100644 --- a/src/layouts/quote/quote.spec.js +++ b/src/layouts/quote/quote.spec.js @@ -293,7 +293,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - {params:{}}, + undefined, (c) => c(wrapper.vm) ); @@ -345,7 +345,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - {params:{}}, + undefined, (c) => c(wrapper.vm) ); //Assert @@ -388,7 +388,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - {params:{}}, + undefined, (c) => c(wrapper.vm) ); @@ -432,7 +432,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote", isInsurance: false } }, - {params:{}}, + undefined, (c) => c(wrapper.vm) ); @@ -478,7 +478,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - {params:{}}, + undefined, (c) => c(wrapper.vm) ); @@ -523,7 +523,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - {params:{}}, + undefined, (c) => c(wrapper.vm) ); @@ -570,7 +570,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - {params:{}}, + undefined, (c) => c(wrapper.vm) ); @@ -618,7 +618,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - {params:{}}, + undefined, (c) => c(wrapper.vm) ); @@ -662,7 +662,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - {params:{}}, + undefined, (c) => c(wrapper.vm) ); //Assert @@ -783,7 +783,7 @@ describe("quote.vue", () => { await quote.beforeRouteEnter.call( wrapper.vm, { query: { fmgPage: "quote" } }, - {params:{}}, + undefined, nextFunction ); await nextTick(); diff --git a/src/layouts/quote/quote.vue b/src/layouts/quote/quote.vue index 99b75c9a7..20dc16304 100644 --- a/src/layouts/quote/quote.vue +++ b/src/layouts/quote/quote.vue @@ -328,8 +328,8 @@ export default { } const getIsInsuranceSelectedValue = (availableLineItems, insuranceThreshold) => { - // routeParam checked here is set on insurance-selection "Pay on my own" link click - if (from.params.forceCashSelection) { + // 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; From 8b2b22b86846854765f6a5943e3041cf03516243 Mon Sep 17 00:00:00 2001 From: Scott Kiener Date: Tue, 11 Feb 2025 10:33:52 -0500 Subject: [PATCH 5/5] CASH-201 | Add unit test --- src/layouts/quote/quote.spec.js | 50 +++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/layouts/quote/quote.spec.js b/src/layouts/quote/quote.spec.js index deca0d6d1..522bc4f2e 100644 --- a/src/layouts/quote/quote.spec.js +++ b/src/layouts/quote/quote.spec.js @@ -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