Merge branch 'develop' into SSR-1201-style-updates

This commit is contained in:
bmauger 2024-04-25 16:55:50 -04:00 committed by GitHub
commit 087c746735
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 199 additions and 126 deletions

View file

@ -4,7 +4,7 @@ const experimentUniverses = Object.freeze({
const experimentSettings = Object.freeze({
GOOGLE_CUSTOM_DIMENSION_INDEX: 'Google Custom Dimension Index',
ISS_DISPLAY_PAY_IN_ADVANCE: 'ISSDisplayPIAInsurance_ISS'
ISS_DISPLAY_PAY_IN_ADVANCE: 'DisplayPIAInsurance'
});
const experimentTriggers = Object.freeze({

View file

@ -123,9 +123,7 @@ describe('cart-dropdown component', () => {
const storeData = {
order: {
payment: {
insuranceCoverage: {
coverageStatus: coverageStatuses.PENDING
}
insuranceCoverage: { isVerified: true }
},
policy: {
isITAC: false
@ -147,14 +145,18 @@ describe('cart-dropdown component', () => {
const initialData = { isExpanded };
const storeData = {
order: {
currentDeductible: 0,
lineItems: { },
payment: {
insuranceCoverage: {
coverageStatus: coverageStatuses.PENDING
}
insuranceCoverage: { isVerified: true }
},
policy: {
isITAC: true
isITAC: true,
policyLookupSuccessful: true
}
},
issConfig: {
isClaimRegistrationRequired: true
}
};
const { wrapper } = getMountedComponent(storeData, {}, initialData);
@ -296,14 +298,18 @@ describe('cart-dropdown component', () => {
const initialData = { isExpanded };
const storeData = {
order: {
currentDeductible: 0,
lineItems: { },
payment: {
insuranceCoverage: {
coverageStatus: coverageStatuses.PENDING
}
insuranceCoverage: { isVerified: true }
},
policy: {
isITAC: true
isITAC: true,
policyLookupSuccessful: true
}
},
issConfig: {
isClaimRegistrationRequired: true
}
};
const { wrapper } = getMountedComponent(storeData, {}, initialData);
@ -322,9 +328,7 @@ describe('cart-dropdown component', () => {
const storeData = {
order: {
payment: {
insuranceCoverage: {
coverageStatus: coverageStatuses.PENDING
}
insuranceCoverage: { isVerified: true }
},
policy: {
isITAC: false
@ -636,7 +640,9 @@ describe('cart-dropdown component', () => {
const storeData = {
order: {
policy: {
policyLookupSuccessful: true
policyLookupSuccessful: true,
noCoverage: false,
isITAC: false
},
currentDeductible: 250,
lineItems: {
@ -652,13 +658,7 @@ describe('cart-dropdown component', () => {
vaps: null
},
payment: {
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED
}
},
policy: {
noCoverage: false,
isITAC: false
insuranceCoverage: { isVerified: true }
}
},
issConfig: {
@ -696,9 +696,7 @@ describe('cart-dropdown component', () => {
]
},
payment: {
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED
}
insuranceCoverage: { isVerified: true }
}
},
issConfig: {
@ -1761,12 +1759,12 @@ describe('cart-dropdown component', () => {
const storeData = {
order: {
payment: {
insuranceCoverage: {
coverageStatus: coverageStatuses.PENDING
}
insuranceCoverage: { isVerified: true }
},
policy: {
isITAC: true
isITAC: true,
noCoverage: false,
policyLookupSuccessful: true
},
currentDeductible: 321
}
@ -1782,15 +1780,22 @@ describe('cart-dropdown component', () => {
// Assert
expect(result).toBe(dollarAmount);
});
test('returns dollar amount when no comp', () => {
test('returns dollar amount when no comp and enableNoCompQuotes true', () => {
// Arrange
const storeData = {
order: {
payment: {
insuranceCoverage: { isVerified: true }
},
policy: {
isITAC: false,
noCoverage: true
noCoverage: true,
policyLookupSuccessful: true
},
currentDeductible: 321
},
issConfig: {
enableNoCompQuote: true
}
};
const { wrapper } = getMountedComponent(storeData);
@ -1804,21 +1809,46 @@ describe('cart-dropdown component', () => {
// Assert
expect(result).toBe(dollarAmount);
});
test('returns "Verifying Coverage" when no comp and enableNoCompQuotes false', () => {
// Arrange
const storeData = {
order: {
payment: {
insuranceCoverage: { isVerified: true }
},
policy: {
isITAC: false,
noCoverage: true,
policyLookupSuccessful: true
},
currentDeductible: 321
},
issConfig: {
enableNoCompQuote: false
}
};
const { wrapper } = getMountedComponent(storeData);
const amount = 123;
// Act
const result = wrapper.vm.getDisplayed(amount);
// Assert
expect(result).toBe(VERIFYING_COVERAGE);
});
test('returns dollar amount when deductible set, not itac, not no comp, and verified', () => {
// Arrange
const storeData = {
order: {
policy: {
isITAC: false,
isNoComp: false,
noCoverage: false,
policyLookupSuccessful: true
},
currentDeductible: 321,
policyLookupSuccessful: true,
payment: {
insuranceCoverage: {
coverageStatus: coverageStatuses.VERIFIED
}
insuranceCoverage: { isVerified: true }
}
}
};

View file

@ -211,7 +211,8 @@ export default {
return this.submittedOrder ? this.submittedOrder.currentDeductible : useMainStore().order.currentDeductible;
},
showDeductibleCartItem() {
return !this.isNoComp && !this.isITAC;
const { isUnverified } = useMainStore();
return isUnverified || (!this.isNoComp && !this.isITAC);
},
lineItems() {
return this.submittedOrder ? this.submittedOrder.lineItems : useMainStore().lineItems;
@ -232,19 +233,13 @@ export default {
baseServicePrice() {
return getPriceOfLineItems(this.baseServiceLineItems) ?? 0;
},
isVerifiedCoverageStatus() {
return this.submittedOrder ? this.submittedOrder.payment.insuranceCoverage.isVerified : useMainStore().isVerifiedCoverageStatus;
},
isUnverified() {
return !this.isNoComp && !this.isITAC
&& (this.deductible == null || !this.isVerifiedCoverageStatus);
},
isITAC() {
return this.submittedOrder ? this.submittedOrder.policy.isITAC : useMainStore().isITAC;
},
isNoComp() {
return this.submittedOrder ? this.submittedOrder.policy.noCoverage : useMainStore().isNoComp;
},
// TODO fix rounding
subTotal() {
const { supportingItems, glassParts, otherParts, vaps, mobileFee } = this.lineItems;
const allLineItems = [
@ -270,13 +265,14 @@ export default {
return result;
},
salesTax() {
const { isUnverified } = useMainStore();
function sumTax(lineItems) {
return lineItems?.reduce((accumulator, lineItem) => accumulator + (lineItem.salesTax ?? 0), 0) ?? 0;
}
let result = 0;
if (!this.isUnverified) {
if (!isUnverified) {
if (this.isITAC || this.isNoComp) {
result += sumTax(this.baseServiceLineItems);
} else {
@ -451,9 +447,8 @@ export default {
this.isExpanded = !this.isExpanded;
},
getDisplayed(amount) {
return this.isUnverified
&& !this.isNoComp
&& !this.isITAC
const { isUnverified } = useMainStore();
return isUnverified
? VERIFYING_COVERAGE
: formatAmountInDollars(amount);
},

View file

@ -81,16 +81,14 @@ export default {
// load client customization overrides
const clientOverrideClass = this.mainStore.issConfig.styleSheet.trim();
if (
clientOverrideClass &&
clientOverrideClass !== '' &&
!document
clientOverrideClass
&& clientOverrideClass !== ''
&& !document
.getElementsByTagName('body')[0]
.className.split(' ')
.includes(clientOverrideClass)
) {
document.getElementsByTagName(
'body'
)[0].className += ` ${clientOverrideClass}`;
document.getElementsByTagName('body')[0].className += ` ${clientOverrideClass}`;
}
},
methods: {
@ -100,18 +98,14 @@ export default {
'Answers'
);
if (
overrideCmsHeaderAnswers &&
Array.isArray(overrideCmsHeaderAnswers)
overrideCmsHeaderAnswers
&& Array.isArray(overrideCmsHeaderAnswers)
) {
const { parentAccountNumber } = useMainStore().issConfig;
let headerOverrideToUse = overrideCmsHeaderAnswers.find(
(answer) =>
answer.AccountNumber === parentAccountNumber.toString()
);
let headerOverrideToUse = overrideCmsHeaderAnswers.find((answer) =>
answer.AccountNumber === parentAccountNumber.toString());
if (headerOverrideToUse == null) {
headerOverrideToUse = overrideCmsHeaderAnswers.find(
(answer) => answer.AccountNumber === '0'
);
headerOverrideToUse = overrideCmsHeaderAnswers.find((answer) => answer.AccountNumber === '0');
}
if (headerOverrideToUse) {

View file

@ -70,20 +70,22 @@
:cmsWidgetName="widget.notesQuestion"
maxLength="250"
:inputRows="4" />
<p ref="disclaimerText" class="caption dark-gray mt-6">
<p
ref="disclaimerText"
class="caption dark-gray mt-6">
{{ textUpdateDisclaimerText }} I also agree to
Safelite's
<textLink
ref="privacyPolicyLink"
class="normal-line-height"
linkType="text"
linkType="newWindowLink"
text="Privacy Policy"
href="//www.safelite.com/privacy-center" />
and
<textLink
ref="termsOfUseLink"
class="normal-line-height"
linkType="text"
linkType="newWindowLink"
text="Terms of Use"
href="//www.safelite.com/terms-of-use" />.
</p>
@ -128,7 +130,7 @@ export default {
siteFooter,
// eslint-disable-next-line vue/no-reserved-component-names
Form,
textLink,
textLink
},
mixins: [BaseFormMixin],
async beforeRouteEnter(to, from, next) {
@ -145,7 +147,7 @@ export default {
emailAddress,
servicePhone,
requestTextUpdates,
notesForTechnician,
notesForTechnician
} = useMainStore().contactInfo;
return {
firstName,
@ -164,14 +166,14 @@ export default {
requestTextUpdates: 'TextContentWidget',
notesQuestion: 'NotesQuestionWidget',
disclaimer: 'TextUpdateDisclaimerWidget',
siteFooter: 'SiteFooterWidget',
siteFooter: 'SiteFooterWidget'
},
rules: {
firstName: globalRules.FIRST_NAME_REQUIRED,
lastName: globalRules.LAST_NAME_REQUIRED,
emailAddress: `${globalRules.EMAIL_ADDRESS_REQUIRED}|${globalRules.EMAIL_ADDRESS_FORMAT}`,
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`,
},
phoneNumber: `${globalRules.PHONE_NUMBER_REQUIRED}|${globalRules.PHONE_NUMBER_FORMAT}`
}
};
},
computed: {
@ -192,7 +194,7 @@ export default {
},
phoneMask() {
return MaskaFormattedMasks.PHONE_NUMBER;
},
}
},
methods: {
/**
@ -204,32 +206,32 @@ export default {
lastName: this.lastName,
emailAddress: this.emailAddress,
requestTextUpdates: this.requestTextUpdates,
notesForTechnician: this.notesForTechnician,
notesForTechnician: this.notesForTechnician
};
useMainStore().updateContactInfo(contactInfo);
if (this.requestTextUpdates) {
useMainStore().updatePhoneNumbers({
service: this.phoneNumber,
alternative: this.phoneNumber,
alternative: this.phoneNumber
});
} else {
useMainStore().updatePhoneNumbers({
home: this.phoneNumber,
service: this.phoneNumber,
service: this.phoneNumber
});
}
const scenario =
useMainStore().order.serviceLocation.IsSafeliteProvider ===
false
useMainStore().order.serviceLocation.IsSafeliteProvider
=== false
? this.navigationScenarios
.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP
.CLICKED_FORWARD_WITH_NON_SAFELITE_SHOP
: this.navigationScenarios
.CLICKED_FORWARD_WITH_SAFELITE_SHOP;
.CLICKED_FORWARD_WITH_SAFELITE_SHOP;
this.$router.navigate(scenario, this.$route);
},
},
}
}
};
</script>

View file

@ -774,8 +774,7 @@ describe('coverageStatement.vue-working', () => {
order: {
payment: {
insuranceCoverage: {
claimNumber: null,
isVerified: true // Added
claimNumber: null
}
},
policy: {
@ -854,19 +853,6 @@ describe('coverageStatement.vue-working', () => {
// Assert
expect(result).toBeFalsy();
});
test('returns false when no comp', () => {
// Arrange
const mainInitialState = shouldRegisterClaimStoreStateItac;
mainInitialState.order.policy.noCoverage = true;
const { wrapper } = getMountedComponent(mainInitialState);
// Act
const result = wrapper.vm.shouldRegisterClaim;
// Assert
expect(result).toBeFalsy();
});
test('returns false when insuranceCoverage not verified', () => {});
describe('returns true when policy lookup success, vehicleId set to %p, claim reg req, claim not yet reg', () => {
test('and itac', () => {
// Arrange

View file

@ -381,7 +381,6 @@ export default {
&& policyVehicleId >= 0
&& isClaimRegistrationRequired
&& !isClaimAlreadyRegistered
&& !this.isNoCompQuoteVisible
);
}
},

View file

@ -10,6 +10,12 @@ import issPageValues from '@/router/router-constants/issPage-values';
import { paymentMethods } from '@/constants/payment-method-constants';
import queryStrings from '@/constants/query-strings';
import { experimentSettings } from '@/constants/experiments';
import { submitWorkOrder } from '@/helpers/order-helper.js';
// Mock so it can be used as an assertion
jest.mock('@/helpers/order-helper.js', () => ({
submitWorkOrder: jest.fn()
}));
function setupMocks({ customMountOptions = {}, queryString }, mainInitialState = {}, customMixin = null) {
const mountOptions = getMountOptions({
@ -251,4 +257,25 @@ describe('payment-method.vue', () => {
expect(result).toBeTruthy();
});
});
describe('forwardButtonAction', () => {
test('Pay at the time of service is selected, should call saveWorkOrder', async () => {
// Arrange
const store = {
order: {
payment: {
isPayInAdvance: false,
payInAdvanceType: null
}
}
};
const wrapper = setupMocks({}, store);
// Act
await wrapper.vm.forwardButtonAction();
// Assert
expect(submitWorkOrder).toHaveBeenCalledTimes(1);
});
});
});

View file

@ -88,6 +88,8 @@ import issPageValues from '@/router/router-constants/issPage-values';
import bailoutMessage from '@/constants/bailoutMessage';
import { AppointmentTypeStrings } from '@/constants/schedule-constants';
import VehicleBanner from '@/iss-components/vehicle-banner/vehicle-banner.vue';
import { submitWorkOrder } from '@/helpers/order-helper.js';
import { experimentSettings } from '@/constants/experiments';
export default {
name: 'payment-method',
@ -182,7 +184,10 @@ export default {
return this.$route.query[queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT];
},
isPayInAdvanceDisabled() {
return useMainStore().isUnverified;
const piaExperience = this.getSettingValue(experimentSettings.ISS_DISPLAY_PAY_IN_ADVANCE);
const isEnabled = piaExperience === 'true';
return !isEnabled || useMainStore().isUnverified;
},
paymentMethod() {
return this.paymentMethodInternalModel;
@ -296,10 +301,21 @@ export default {
await useMainStore().savePaymentMethodChoice(this.paymentMethod);
if (this.paymentMethod === paymentMethods.PAY_AT_TIME_OF_SERVICE) {
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
try {
await submitWorkOrder({
pageNameToLog: 'payment-method',
submitAfterSave: true
});
useMainStore().resetSubmittedOrder();
this.$router.navigate(
this.navigationScenarios.CLICKED_FORWARD,
this.$route
);
} catch (error) {
console.error(`error: response from submit work order:${error.message}`);
}
} else {
this.$router.navigate(
this.navigationScenarios.CLICKED_PAY_NOW,

View file

@ -29,7 +29,8 @@ export default {
];
},
addressInfo() {
if (this.serviceLocation?.appointmentType === AppointmentTypeStrings.MOBILE) {
if (this.serviceLocation?.appointmentType === AppointmentTypeStrings.MOBILE
|| this.serviceLocation?.appointmentType === AppointmentTypeStrings.MOBILE_NOT_ITAC_AND_NOT_NOCOMP) {
return {
address: this.serviceLocation?.address,
address2: this.serviceLocation?.address2 ?? '',

View file

@ -20,6 +20,7 @@ function setupMocks({ experimentSettings }) {
const mockExperimentData = [
{
settings: experimentSettings ?? testExperimentSettings,
isActive: true,
variationName: 'test',
universeName: 'testUniverse'
}

View file

@ -375,29 +375,31 @@ export const useMainStore = defineStore({
};
},
experimentOrder: (state) => ({
issVehicleYear: state.order.vehicle.year,
issVehicleMake: state.order.vehicle.make,
issVehicleModel: state.order.vehicle.model,
issVehicleStyle: state.order.vehicle.style,
issIsRepair: state.order.damage.isRepair,
issNumberOfChips: state.order.damage.numberOfChips,
issCarId: state.order.vehicle.carId,
issServiceCity: state.order.serviceLocation.city,
issServiceState: state.order.serviceLocation.state,
issServiceZipCode: state.order.serviceLocation.zipCode,
issParentAccountNumber: state.order.accountNumber,
issIsCoverageVerified: state.order.payment.insuranceCoverage.isVerified,
issHasRecalibrationPart: getHasRecalibrationPart(state),
issSelectedMultiGlass: state.order.damage.glassToReplace?.length > 1,
issSelectedWindshieldGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, 'glassLocation')
funnelVehicleYear: state.order.vehicle.year,
funnelVehicleMake: state.order.vehicle.make,
funnelVehicleModel: state.order.vehicle.model,
funnelVehicleStyle: state.order.vehicle.style,
funnelIsRepair: state.order.damage.isRepair,
funnelNumberOfChips: state.order.damage.numberOfChips,
funnelCarId: state.order.vehicle.carId,
funnelServiceCity: state.order.serviceLocation.city,
funnelServiceState: state.order.serviceLocation.state,
funnelServiceZipCode: state.order.serviceLocation.zipCode,
funnelServiceZipCodeCtu: state.order.serviceLocation.zipCodeCtu,
funnelParentAccountNumber: state.order.accountNumber,
funnelProviderNumber: state.order.serviceLocation.provider.providerNumber,
funnelIsCoverageVerified: state.order.payment.insuranceCoverage.isVerified,
funnelHasRecalibrationPart: getHasRecalibrationPart(state),
funnelSelectedMultiGlass: state.order.damage.glassToReplace?.length > 1,
funnelSelectedWindshieldGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, 'glassLocation')
.includes(damageLocationsSelected.WINDSHIELD),
issSelectedBackGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, 'glassLocation')
funnelSelectedBackGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, 'glassLocation')
.includes(damageLocationsSelected.REAR),
issSelectedDriverSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, 'glassLocation')
funnelSelectedDriverSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, 'glassLocation')
.includes(damageLocationsSelected.DRIVER),
issSelectedPassengerSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, 'glassLocation')
funnelSelectedPassengerSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, 'glassLocation')
.includes(damageLocationsSelected.PASSENGER),
issOrderPartNumbers: [
funnelOrderPartNumbers: [
...getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.lineItems.glassParts,
'partNumber'
@ -407,7 +409,7 @@ export const useMainStore = defineStore({
'partNumber'
)
],
issOrderPartTypes: [
funnelOrderPartTypes: [
...getNonFalseValuesOfPropertyInArrayOfObjects(
state.order.lineItems.glassParts,
'recalibrationType'
@ -419,9 +421,10 @@ export const useMainStore = defineStore({
]
}),
experimentSettings: (state) => state.applicationUser.experiments
.filter((x) => !!x.isActive)
.map((x) => x.settings)
.reduce((r, c) => Object.assign(r, c), {}) ?? {},
submittedOrder: () => JSON.parse(window.sessionStorage.getItem('submittedOrder'))
submittedOrder: () => JSON.parse(window.sessionStorage.getItem(webStorageConstants.SUBMITTED_ORDER))
},
actions:
{

View file

@ -1,12 +1,15 @@
// Common/Global Styles
// Use this file for global styles that don't or won't have their own stylesheet
html, body {
html,
body {
height: 100%;
}
body {
font-size: 16px;
background-color: #fff;
color: #4d5151;
.container-fluid {
.prevent-squish {
overflow-x: unset;
@ -16,39 +19,50 @@ body {
display: flex;
flex-direction: column;
}
// Set max-width on columns to prevent overly-wide
// components on extra wide screens.
.col-md-6 {
max-width: 472px;
@include media-breakpoint-up(xl) {
max-width: 708px;
}
.col {
max-width: 236px;
&.one-list-card-width {
max-width: 472px;
@include media-breakpoint-up(xl) {
max-width: 66.6666666%;
}
}
}
.shop-question {
.col {
max-width: 472px;
}
}
}
.col-xl-4 {
max-width: 472px;
.col {
max-width: 100%;
}
}
//END set max-width on columns
}
.pointer {
cursor: pointer;
}
.container,
.container-fluid {
overflow-x: hidden;
@ -71,7 +85,12 @@ body {
height: 1px;
overflow: hidden;
}
.pac-container {
z-index: 10000 !important;
}
}
.modal-open {
.container-fluid {
&.fade-on-route-transition {
@ -79,4 +98,4 @@ body {
height: auto;
}
}
}
}