Merge pull request #1254 from Safelite/feature/humphries/INSR-9940

INSR-9940: Improve logic for showing/hiding MSR related copy
This commit is contained in:
AHumphriesSL 2026-06-11 14:22:35 -04:00 committed by GitHub
commit 7a30595d4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 205 additions and 21 deletions

View file

@ -16,7 +16,8 @@ const experimentSettings = Object.freeze({
ISS_MOBILE_FIRST_MAX_MOBILE_DAYS: 'MaxMobileDays', ISS_MOBILE_FIRST_MAX_MOBILE_DAYS: 'MaxMobileDays',
ISS_MOBILE_FIRST_MAX_PM_MOBILE_DAYS: 'MaxPmMobileDays', ISS_MOBILE_FIRST_MAX_PM_MOBILE_DAYS: 'MaxPmMobileDays',
ISS_MOBILE_FIRST_SHOW_FIRST_MOBILE_APPOINTMENT: 'ShowMobileFirstAppointment', ISS_MOBILE_FIRST_SHOW_FIRST_MOBILE_APPOINTMENT: 'ShowMobileFirstAppointment',
ISS_ENABLE_ADYEN_V1: 'ISS_Enable_Adyen_V1' ISS_ENABLE_ADYEN_V1: 'ISS_Enable_Adyen_V1',
MSR_SPLIT_PAY_ENABLED: 'EnableMSRSplitPay'
}); });
const experimentTest = Object.freeze({ const experimentTest = Object.freeze({

View file

@ -1,3 +1,4 @@
import partNumberStrings from '@/constants/part-number-strings';
import partTypeStrings from '@/constants/part-type-strings'; import partTypeStrings from '@/constants/part-type-strings';
import { deepClone, getNonFalseValuesOfPropertyInArrayOfObjects } from '@/helpers/object-helper'; import { deepClone, getNonFalseValuesOfPropertyInArrayOfObjects } from '@/helpers/object-helper';
@ -117,3 +118,7 @@ export function anyPartWithRequiresRecalFlag(lineItems) {
return flattened.some((li) => li.requiresRecalibration === true); return flattened.some((li) => li.requiresRecalibration === true);
} }
export function hasMSRPart(lineItems) {
return lineItems?.feeItems?.some((item) => item.partNumber === partNumberStrings.RECAL_MOBILEDUAL || item.partNumber === partNumberStrings.RECAL_MOBILE) ?? false
}

View file

@ -13,6 +13,7 @@ import { getPriceOfLineItems } from '@/helpers/price-calculator.js';
import coverageStatuses from '@/constants/coverage-statuses'; import coverageStatuses from '@/constants/coverage-statuses';
import coverageType from '@/constants/coverage-type'; import coverageType from '@/constants/coverage-type';
import * as cartHelper from '@/helpers/cart-helper'; import * as cartHelper from '@/helpers/cart-helper';
import { experimentSettings } from '@/constants/experiments';
const VERIFYING_COVERAGE = 'Verifying coverage'; const VERIFYING_COVERAGE = 'Verifying coverage';
@ -30,7 +31,7 @@ jest.mock('@/helpers/service-package-helper', () => ({
getPackageContents: jest.fn() getPackageContents: jest.fn()
})); }));
function getMountedComponent(mainInitialState = {}, initialData = {}, propsData = {}) { function getMountedComponent(mainInitialState = {}, initialData = {}, propsData = {}, experimentMockFunction = jest.fn(() => 'false')) {
const mountOptions = getMountOptions({ const mountOptions = getMountOptions({
router: { router: {
navigate: jest.fn() navigate: jest.fn()
@ -44,7 +45,7 @@ function getMountedComponent(mainInitialState = {}, initialData = {}, propsData
}); });
useMainStore(testingPinia); useMainStore(testingPinia);
mountOptions.global.mixins[0].methods.getSettingValue = jest.fn(() => 'false'); mountOptions.global.mixins[0].methods.getSettingValue = experimentMockFunction;
mountOptions.global.plugins = [testingPinia]; mountOptions.global.plugins = [testingPinia];
mountOptions.data = () => (initialData); mountOptions.data = () => (initialData);
mountOptions.propsData = propsData; mountOptions.propsData = propsData;
@ -804,12 +805,16 @@ describe('cart-dropdown component', () => {
partType: partTypeStrings.REPLACE_FEE partType: partTypeStrings.REPLACE_FEE
})); }));
}); });
test('when mobileFee, includes mobile fee', () => { test('when mobile fee should be included, includes mobile fee', () => {
// Arrange // Arrange
const storeData = { const storeData = {
order: { order: {
lineItems: { lineItems: {
feeItems: [{ partType: partTypeStrings.MOBILE_FEE }] feeItems: [{ partType: partTypeStrings.MOBILE_FEE, sellingPrice: 123}]
},
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.NO_COMP
} }
} }
}; };
@ -977,6 +982,153 @@ describe('cart-dropdown component', () => {
expect(result.name).toBe(expectedName); expect(result.name).toBe(expectedName);
}); });
}); });
describe('includeMobileFeeInCart', () => {
test('returns false with no mobile fee', () => {
// Arrange
const storeData = {
order: {
lineItems: {
feeItems: []
}
}
};
const { wrapper } = getMountedComponent(storeData);
// Act
const result = wrapper.vm.includeMobileFeeInCart;
// Assert
expect(result).toBe(false);
});
test('returns false with a mobile fee with no price', () => {
// Arrange
const storeData = {
order: {
lineItems: {
feeItems: [{ partType: partTypeStrings.MOBILE_FEE, price: 0 }]
}
}
};
const { wrapper } = getMountedComponent(storeData);
// Act
const result = wrapper.vm.includeMobileFeeInCart;
// Assert
expect(result).toBe(false);
});
test('returns true for priced mobile fee for No Comp', () => {
// Arrange
const storeData = {
order: {
lineItems: {
feeItems: [{ partType: partTypeStrings.MOBILE_FEE, price: 100 }]
},
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.NO_COMP
}
}
};
const { wrapper } = getMountedComponent(storeData);
// Act
const result = wrapper.vm.includeMobileFeeInCart;
// Assert
expect(result).toBe(true);
});
test('returns true for priced mobile fee for ITAC', () => {
// Arrange
const storeData = {
order: {
lineItems: {
feeItems: [{ partType: partTypeStrings.MOBILE_FEE, price: 100 }]
},
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.ITAC
}
}
};
const { wrapper } = getMountedComponent(storeData);
// Act
const result = wrapper.vm.includeMobileFeeInCart;
// Assert
expect(result).toBe(true);
});
test('returns false for priced mobile fee for Deductible without Split Pay', () => {
// Arrange
const storeData = {
order: {
lineItems: {
feeItems: [{ partType: partTypeStrings.MOBILE_FEE, price: 100 }]
},
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.Deductible
}
}
};
const { wrapper } = getMountedComponent(storeData);
// Act
const result = wrapper.vm.includeMobileFeeInCart;
// Assert
expect(result).toBe(false);
});
test('returns false for priced mobile fee for Deductible with Split Pay and isInsurable=true', () => {
// Arrange
const storeData = {
order: {
lineItems: {
feeItems: [{ partType: partTypeStrings.MOBILE_FEE, price: 100, isInsurable: true }]
},
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.Deductible
}
}
};
const { wrapper } = getMountedComponent(storeData, {}, {}, (settingName) => (settingName === experimentSettings.MSR_SPLIT_PAY_ENABLED).toString());
// Act
const result = wrapper.vm.includeMobileFeeInCart;
// Assert
expect(result).toBe(false);
});
test('returns true for priced mobile fee for Deductible with Split Pay and isInsurable=false', () => {
// Arrange
const storeData = {
order: {
lineItems: {
feeItems: [{ partType: partTypeStrings.MOBILE_FEE, price: 100, isInsurable: false }]
},
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED,
coverageType: coverageType.Deductible
}
}
};
const { wrapper } = getMountedComponent(storeData, {}, {}, (settingName) => (settingName === experimentSettings.MSR_SPLIT_PAY_ENABLED).toString());
// Act
const result = wrapper.vm.includeMobileFeeInCart;
// Assert
expect(result).toBe(true);
});
});
}); });
describe('method', () => { describe('method', () => {
const dollarAmount = '$84.00'; const dollarAmount = '$84.00';

View file

@ -146,6 +146,8 @@ import {
} from '@/helpers/cart-helper'; } from '@/helpers/cart-helper';
import { getPriceOfLineItem, getPriceOfLineItems, getTaxOfLineItems } from '@/helpers/price-calculator'; import { getPriceOfLineItem, getPriceOfLineItems, getTaxOfLineItems } from '@/helpers/price-calculator';
import { processIfStatements } from '@/helpers/cms-content-helper'; import { processIfStatements } from '@/helpers/cms-content-helper';
import partNumberStrings from '@/constants/part-number-strings';
import { hasMSRPart } from '@/helpers/recal-helper';
const VERIFYING_COVERAGE = 'Verifying coverage'; const VERIFYING_COVERAGE = 'Verifying coverage';
const ADVANCED_MOBILE_MODAL_REF_NAME = 'advancedMobileModal'; const ADVANCED_MOBILE_MODAL_REF_NAME = 'advancedMobileModal';
@ -229,7 +231,8 @@ export default {
servicePrice() { servicePrice() {
const price = getPriceOfLineItems(getServiceLineItems(this.cartOrder)) ?? 0; const price = getPriceOfLineItems(getServiceLineItems(this.cartOrder)) ?? 0;
const recycleFee = this.recycleFeeLineItem ? getPriceOfLineItem(this.recycleFeeLineItem) : 0; const recycleFee = this.recycleFeeLineItem ? getPriceOfLineItem(this.recycleFeeLineItem) : 0;
return price - this.recalibrationPrice - recycleFee; const mobileFee = this.mobileFeeCartItem ? this.mobileFeeCartItem.subTotal : 0;
return price - this.recalibrationPrice - recycleFee - mobileFee;
}, },
isUnverified() { isUnverified() {
return isOrderUnverified(this.cartOrder); return isOrderUnverified(this.cartOrder);
@ -270,7 +273,6 @@ export default {
cartItems() { cartItems() {
const items = []; const items = [];
const isRecycleFeeHidden = this.getSettingValue(experimentSettings.ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_HIDDEN) === 'true'; const isRecycleFeeHidden = this.getSettingValue(experimentSettings.ISS_FEATURE_TOGGLE_IS_RECYCLE_FEE_HIDDEN) === 'true';
const isMobileFeeHidden = this.getSettingValue(experimentSettings.ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_HIDDEN) === 'true';
if (this.isITAC || this.isNoComp) { if (this.isITAC || this.isNoComp) {
items.push(this.warrantyCartItem); items.push(this.warrantyCartItem);
} }
@ -291,7 +293,7 @@ export default {
} }
}); });
items.push(...vapsCartItems.filter((item) => item != null)); items.push(...vapsCartItems.filter((item) => item != null));
if (this.mobileFeeCartItem && !isMobileFeeHidden && !this.mobileFeeCartItem.isInsurable) { if (this.includeMobileFeeInCart) {
items.push(this.mobileFeeCartItem); items.push(this.mobileFeeCartItem);
} }
return items; return items;
@ -380,6 +382,34 @@ export default {
return { return {
feeAmount: this.mobileFeeCartItem ? formatAmountInDollars(this.mobileFeeCartItem.subTotal) : '' feeAmount: this.mobileFeeCartItem ? formatAmountInDollars(this.mobileFeeCartItem.subTotal) : ''
} }
},
hasMSRPart() {
return hasMSRPart(this.cartOrder.lineItems);
},
includeMobileFeeInCart() {
if (!this.mobileFeeCartItem || this.mobileFeeCartItem.subTotal === 0) {
return false;
}
const isMobileFeeHidden = this.getSettingValue(experimentSettings.ISS_FEATURE_TOGGLE_IS_MOBILE_FEE_HIDDEN) === 'true';
if (isMobileFeeHidden) {
return false;
}
if (this.isITAC || this.isNoComp) {
return true;
}
const isSplitPayEnabled = this.getSettingValue(experimentSettings.MSR_SPLIT_PAY_ENABLED) === 'true';
if (!isSplitPayEnabled) {
return false;
}
if (!this.mobileFeeCartItem.isInsurable) {
return true;
}
return false;
} }
}, },
methods: { methods: {

View file

@ -17,12 +17,11 @@
ref="siteSubHeader" ref="siteSubHeader"
class="subheader" class="subheader"
:cmsWidgetName="widget.siteSubHeader" /> :cmsWidgetName="widget.siteSubHeader" />
<!-- TO DO: Use MSR appointment information for MSR -->
<alert <alert
ref="appointmentInformationAlert" ref="appointmentInformationAlert"
isCollapsible isCollapsible
alertClass="alert-warning" alertClass="alert-warning"
:cmsWidgetName="widget.appointmentInformation" /> :cmsWidgetName="appointmentInformationWidget" />
<checkbox <checkbox
v-if="showSameAsPolicyAddressQuestion" v-if="showSameAsPolicyAddressQuestion"
ref="sameAsPolicyAddressQuestion" ref="sameAsPolicyAddressQuestion"
@ -135,6 +134,7 @@ import widgetFields from '@/constants/cms-widget-fields';
import states from '@/constants/states'; import states from '@/constants/states';
import applicationConfig from '@/constants/application-config'; import applicationConfig from '@/constants/application-config';
import { vehicleProtectedAnswers } from '@/constants/contact-details'; import { vehicleProtectedAnswers } from '@/constants/contact-details';
import { hasMSRPart } from '@/helpers/recal-helper';
// DEFINE VALIDATION RULES // DEFINE VALIDATION RULES
defineRule('street-address-required', required(errorMessages.SERVICE_ADDRESS_REQUIRED)); defineRule('street-address-required', required(errorMessages.SERVICE_ADDRESS_REQUIRED));
@ -228,6 +228,9 @@ export default {
return acc; return acc;
}, {}); }, {});
}, },
appointmentInformationWidget() {
return hasMSRPart(useMainStore().lineItems) ? this.widget.MSRAppointmentInformation : this.widget.appointmentInformation;
}
}, },
watch: { watch: {
isSameAsPolicyAddress(newValue) { isSameAsPolicyAddress(newValue) {

View file

@ -97,8 +97,6 @@
class="cart-dropdown" class="cart-dropdown"
:showAsPaid="payment.isPayInAdvance" :showAsPaid="payment.isPayInAdvance"
:readOnly="true" :readOnly="true"
recyclingModalCmsWidgetName="RecycleModal"
servicePackageTitleWidgetName="ServicePackageTitle"
:submittedOrder="submittedOrder" /> :submittedOrder="submittedOrder" />
</div> </div>
</div> </div>

View file

@ -33,8 +33,6 @@
<cartDropdown <cartDropdown
:showAsPaid="false" :showAsPaid="false"
:readOnly="false" :readOnly="false"
recyclingModalCmsWidgetName="RecycleModal"
servicePackageTitleWidgetName="ServicePackageTitle"
@switchToInShop="handleSwitchToInShop" /> @switchToInShop="handleSwitchToInShop" />
</div> </div>
<alert <alert

View file

@ -26,9 +26,7 @@
ref="cart" ref="cart"
class="cart-dropdown-component" class="cart-dropdown-component"
:readOnly="true" :readOnly="true"
:showAsPaid="false" :showAsPaid="false" />
recyclingModalCmsWidgetName="RecycleModal"
servicePackageTitleWidgetName="ServicePackageTitle" />
</div> </div>
</div> </div>
</div> </div>

View file

@ -45,9 +45,7 @@
ref="cart" ref="cart"
class="cart-dropdown-component" class="cart-dropdown-component"
:readOnly="true" :readOnly="true"
:showAsPaid="false" :showAsPaid="false" />
recyclingModalCmsWidgetName="RecycleModal"
servicePackageTitleWidgetName="ServicePackageTitle" />
</div> </div>
<div class="d-none d-lg-block"> <div class="d-none d-lg-block">
<buttonMain <buttonMain

View file

@ -573,7 +573,8 @@ export const useMainStore = defineStore({
policyNumber: policy.policyNumber, policyNumber: policy.policyNumber,
dateOfLoss: policy.dateOfLoss, dateOfLoss: policy.dateOfLoss,
zipCode: policy.policyZipCode, zipCode: policy.policyZipCode,
referralCorrelationId: order.referralCorrelationId referralCorrelationId: order.referralCorrelationId,
referralNumber: order.referralNumber
}, },
bailoutOnError: false bailoutOnError: false
}); });