From 6f09cd3e786583851a3d3ff58777a6b6afb257e2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 9 Apr 2026 19:26:04 +0000 Subject: [PATCH] fix: correct isMobileStaticRecalibrationApplicable in schedule.vue to gate on MSR part conditions The schedule.vue isMobileStaticRecalibrationApplicable computed property had broken operator precedence, making isCashItacNoComp and mobileFeePart?.isInsurable standalone OR branches instead of being gated behind displayMSR and the MOBILE_STATIC_RECAL_FEE part check. This meant any self-pay (cash/ITAC/NoComp) order always evaluated isMobileStaticRecalibrationApplicable as true, regardless of whether the DISPLAY_MSR experiment was enabled or whether the order even had a MOBILE_STATIC_RECAL_FEE part. In turn, isServiceableMobile returned true even when isRecalibrationServiceableMobile === false (recalibration cannot be done mobile), causing GET_MOBILE_TIME_SLOTS to be fetched for orders where mobile service should not be offered. Fix aligns the logic with service-location.vue: - require displayMSR AND mobileFeePart.partNumber === MOBILE_STATIC_RECAL_FEE AND (isCashItacNoComp OR mobileFeePart.isInsurable) Also removes the now-unused enableMSRSplitPay computed property. Adds unit tests covering isMobileStaticRecalibrationApplicable and isServiceableMobile for the recal-cannot-be-done-mobile scenario. Co-authored-by: Mark Harris --- src/layouts/schedule/schedule.spec.js | 155 ++++++++++++++++++++++++++ src/layouts/schedule/schedule.vue | 15 +-- 2 files changed, 158 insertions(+), 12 deletions(-) diff --git a/src/layouts/schedule/schedule.spec.js b/src/layouts/schedule/schedule.spec.js index 9e7952fd0..b75fed709 100644 --- a/src/layouts/schedule/schedule.spec.js +++ b/src/layouts/schedule/schedule.spec.js @@ -7,6 +7,22 @@ import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { routeData } from "@/router/constants/routes"; import store from "@/store"; import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; +import { partNumberStrings } from "@/constants/part-number-strings"; + +let mockDisplayMSR = false; +jest.mock("@/mixins/experiment-mixin.js", () => ({ + methods: { + getSettingValue(settingName) { + // "DisplayMSR" is experimentSettings.DISPLAY_MSR + if (settingName === "DisplayMSR") { + return mockDisplayMSR ? "true" : "false"; + } + return null; + }, + hasSettingEqualTo: jest.fn(), + hasSetting: jest.fn(), + }, +})); const storeMocked = { getters: { @@ -231,6 +247,145 @@ describe("schedule.vue", () => { expect(result).toBe(false); }); }); + + describe("computed: isMobileStaticRecalibrationApplicable", () => { + beforeEach(() => { + mockDisplayMSR = false; + }); + + test("returns false when displayMSR experiment is off, even for cash orders", async () => { + mockDisplayMSR = false; + const { wrapper } = setupMocks(); + await wrapper.setData({ + mobileFeePart: { partNumber: partNumberStrings.MOBILE_STATIC_RECAL_FEE, isInsurable: false }, + }); + store.getters.order.payment.isInsurance = false; + + expect(wrapper.vm.isMobileStaticRecalibrationApplicable).toBe(false); + }); + + test("returns false when mobileFeePart is null, even for cash orders with DISPLAY_MSR on", async () => { + mockDisplayMSR = true; + const { wrapper } = setupMocks(); + await wrapper.setData({ mobileFeePart: null }); + store.getters.order.payment.isInsurance = false; + + expect(wrapper.vm.isMobileStaticRecalibrationApplicable).toBe(false); + }); + + test("returns false when mobileFeePart is not MOBILE_STATIC_RECAL_FEE, even for cash orders", async () => { + mockDisplayMSR = true; + const { wrapper } = setupMocks(); + await wrapper.setData({ + mobileFeePart: { partNumber: "SOME_OTHER_PART", isInsurable: false }, + }); + store.getters.order.payment.isInsurance = false; + + expect(wrapper.vm.isMobileStaticRecalibrationApplicable).toBe(false); + }); + + test("returns true when DISPLAY_MSR is on, part is MOBILE_STATIC_RECAL_FEE, and order is cash", async () => { + mockDisplayMSR = true; + const { wrapper } = setupMocks(); + await wrapper.setData({ + mobileFeePart: { partNumber: partNumberStrings.MOBILE_STATIC_RECAL_FEE, isInsurable: false }, + }); + store.getters.order.payment.isInsurance = false; + + expect(wrapper.vm.isMobileStaticRecalibrationApplicable).toBe(true); + }); + + test("returns true when DISPLAY_MSR is on, part is MOBILE_STATIC_RECAL_FEE, and part is insurable (insurance order)", async () => { + mockDisplayMSR = true; + const { wrapper } = setupMocks(); + await wrapper.setData({ + mobileFeePart: { partNumber: partNumberStrings.MOBILE_STATIC_RECAL_FEE, isInsurable: true }, + }); + store.getters.order.payment.isInsurance = true; + + expect(wrapper.vm.isMobileStaticRecalibrationApplicable).toBe(true); + }); + + test("returns false when DISPLAY_MSR is on, part is MOBILE_STATIC_RECAL_FEE, insurance order, and part is NOT insurable", async () => { + mockDisplayMSR = true; + store.getters.payment.isInsurance = true; + const { wrapper } = setupMocks(); + await wrapper.setData({ + mobileFeePart: { partNumber: partNumberStrings.MOBILE_STATIC_RECAL_FEE, isInsurable: false }, + }); + + expect(wrapper.vm.isMobileStaticRecalibrationApplicable).toBe(false); + + store.getters.payment.isInsurance = false; + }); + }); + + describe("computed: isServiceableMobile — recalibration cannot be done mobile", () => { + beforeEach(() => { + mockDisplayMSR = false; + }); + + test("returns false when recal is NOT mobile serviceable and MSR is not applicable (cash order, no MSR part)", async () => { + const { wrapper } = setupMocks(); + // Cash order, no MSR feature, no MOBILE_STATIC_RECAL_FEE part + await wrapper.setData({ + isGlassServiceableMobile: true, + isRecalibrationServiceableMobile: false, + mobileFeePart: null, + }); + store.getters.order.payment.isInsurance = false; + + expect(wrapper.vm.isServiceableMobile).toBe(false); + }); + + test("returns false when recal is NOT mobile serviceable and MSR experiment is off (cash order)", async () => { + mockDisplayMSR = false; + const { wrapper } = setupMocks(); + await wrapper.setData({ + isGlassServiceableMobile: true, + isRecalibrationServiceableMobile: false, + mobileFeePart: { partNumber: partNumberStrings.MOBILE_STATIC_RECAL_FEE, isInsurable: false }, + }); + store.getters.order.payment.isInsurance = false; + + expect(wrapper.vm.isServiceableMobile).toBe(false); + }); + + test("returns true when both glass and recal ARE mobile serviceable", async () => { + const { wrapper } = setupMocks(); + await wrapper.setData({ + isGlassServiceableMobile: true, + isRecalibrationServiceableMobile: true, + mobileFeePart: null, + }); + + expect(wrapper.vm.isServiceableMobile).toBe(true); + }); + + test("returns true when recal is NOT mobile serviceable but MSR is applicable (DISPLAY_MSR on, MSR part, cash)", async () => { + mockDisplayMSR = true; + const { wrapper } = setupMocks(); + await wrapper.setData({ + isGlassServiceableMobile: true, + isRecalibrationServiceableMobile: false, + mobileFeePart: { partNumber: partNumberStrings.MOBILE_STATIC_RECAL_FEE, isInsurable: false }, + }); + store.getters.order.payment.isInsurance = false; + + expect(wrapper.vm.isServiceableMobile).toBe(true); + }); + + test("falls back to isGlassServiceableMobile when isRecalibrationServiceableMobile is null", async () => { + const { wrapper } = setupMocks(); + await wrapper.setData({ + isGlassServiceableMobile: true, + isRecalibrationServiceableMobile: null, + mobileFeePart: null, + }); + + expect(wrapper.vm.isServiceableMobile).toBe(true); + }); + }); }); function setupMocksForArePagePrerequisitesValid() { diff --git a/src/layouts/schedule/schedule.vue b/src/layouts/schedule/schedule.vue index f5462fb9e..497e40838 100644 --- a/src/layouts/schedule/schedule.vue +++ b/src/layouts/schedule/schedule.vue @@ -705,11 +705,9 @@ export default { }, isMobileStaticRecalibrationApplicable() { return ( - (this.displayMSR && - this.mobileFeePart?.partNumber == partNumberStrings.MOBILE_STATIC_RECAL_FEE && - this.enableMSRSplitPay) || - this.isCashItacNoComp || - this.mobileFeePart?.isInsurable + this.displayMSR && + this.mobileFeePart?.partNumber == partNumberStrings.MOBILE_STATIC_RECAL_FEE && + (this.isCashItacNoComp || this.mobileFeePart?.isInsurable) ); }, displayMSR() { @@ -719,13 +717,6 @@ export default { ?.toLowerCase() === "true" ); }, - enableMSRSplitPay() { - return ( - experimentMixin.methods - .getSettingValue(experimentSettings.ENABLE_MSR_SPLIT_PAY) - ?.toLowerCase() === "true" - ); - }, isMSRFeeNotCoveredByInsurance() { return ( this.isMobileSelected &&