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 <mark.harris@safelite.com>
This commit is contained in:
Cursor Agent 2026-04-09 19:26:04 +00:00
parent 292b626267
commit 6f09cd3e78
No known key found for this signature in database
2 changed files with 158 additions and 12 deletions

View file

@ -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() {

View file

@ -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 &&