Merge branch 'release/2024.02.08' into rlsmerge/2024.02.08-to-develop

This commit is contained in:
CarlNation 2024-01-29 09:30:26 -05:00
commit 05090b4345
15 changed files with 2244 additions and 32 deletions

View file

@ -30,7 +30,7 @@ module.exports = {
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"], testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
coverageThreshold: { coverageThreshold: {
global: { global: {
statements: 73, statements: 78,
}, },
}, },
// Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit // Uncomment this to avoid the massive amount of warnings we are getting for onSubmit and onInvalidSubmit

View file

@ -22,6 +22,7 @@ const applicationConfig = {
"//" + "//" +
location.host + location.host +
"/fmg/?fmgPage=payment-method&src=concept-funnel", "/fmg/?fmgPage=payment-method&src=concept-funnel",
CONFIRMATION_URL: location.protocol + "//" + location.host + "/fmg/?fmgPage=confirmation",
GOOGLE_CALENDAR: "https://www.google.com/calendar/render?action=TEMPLATE", GOOGLE_CALENDAR: "https://www.google.com/calendar/render?action=TEMPLATE",
YAHOO_CALENDAR: "https://calendar.yahoo.com/?v=60", YAHOO_CALENDAR: "https://calendar.yahoo.com/?v=60",
OUTLOOK_CALENDAR: OUTLOOK_CALENDAR:

View file

@ -53,6 +53,7 @@ const storeMutations = {
UPDATE_IS_PIA: "updateIsPia", UPDATE_IS_PIA: "updateIsPia",
UPDATE_PIA_TYPE: "updatePiaType", UPDATE_PIA_TYPE: "updatePiaType",
WORK_ORDER_NUMBER: "updateWorkOrderNumber", WORK_ORDER_NUMBER: "updateWorkOrderNumber",
WORK_ORDER_ID: "updateWorkOrderId",
Customer_Portal_Login_Token: "updateCustomerPortalLoginToken", Customer_Portal_Login_Token: "updateCustomerPortalLoginToken",
LOCK_TOKEN: "updateLockToken", LOCK_TOKEN: "updateLockToken",
UPDATE_SETTLED_TENDER_AMOUNT: "updateSettledTenderAmount", UPDATE_SETTLED_TENDER_AMOUNT: "updateSettledTenderAmount",

View file

@ -118,8 +118,7 @@ export default {
left: 0; left: 0;
height: 100%; height: 100%;
width: 100%; width: 100%;
background-color: $black; background-color: $white;
opacity: 0.4;
z-index: 1056; z-index: 1056;
} }
@ -129,7 +128,7 @@ export default {
overflow-x: hidden; overflow-x: hidden;
overflow-y: auto; overflow-y: auto;
outline: 0; outline: 0;
background: $gray-100; background: $white;
&.full-screen { &.full-screen {
top: 0; top: 0;

View file

@ -162,6 +162,7 @@ async function saveSessionHelper(
crmCustomerId: savedSessionInfo.data.crmCustomerId?.toString(), crmCustomerId: savedSessionInfo.data.crmCustomerId?.toString(),
eon: savedSessionInfo.data.eon, eon: savedSessionInfo.data.eon,
workOrderNumber: savedSessionInfo.data.workOrderNumber, workOrderNumber: savedSessionInfo.data.workOrderNumber,
workOrderId: savedSessionInfo.data.workOrderId,
customerPortalLoginToken: savedSessionInfo.data.customerPortalLoginToken, customerPortalLoginToken: savedSessionInfo.data.customerPortalLoginToken,
lockToken: savedSessionInfo.data.lockToken, lockToken: savedSessionInfo.data.lockToken,
settledTenderAmount: savedSessionInfo.data.settledTenderAmount, settledTenderAmount: savedSessionInfo.data.settledTenderAmount,

View file

@ -34,7 +34,7 @@ const stackingPromoErrorCodes = [
promoErrorCodes.SIMILAR_PROMO_ALREADY_ON_ORDER, promoErrorCodes.SIMILAR_PROMO_ALREADY_ON_ORDER,
]; ];
const excludeFromInactivePromoErrorCodes = [ export const excludeFromInactivePromoErrorCodes = [
promoErrorCodes.UNKNOWN, promoErrorCodes.UNKNOWN,
promoErrorCodes.PROMO_NOT_YET_IN_USE, promoErrorCodes.PROMO_NOT_YET_IN_USE,
promoErrorCodes.PROMO_USAGE_COUNT_EXCEEDED, promoErrorCodes.PROMO_USAGE_COUNT_EXCEEDED,
@ -45,7 +45,7 @@ const excludeFromInactivePromoErrorCodes = [
export const pagesToStripPromoQueryStringFrom = ["quote", "payment-method"]; export const pagesToStripPromoQueryStringFrom = ["quote", "payment-method"];
export function getPromosWithAddableVaps(promoList) { export function getPromosWithAddableVaps(promoList) {
if (!promoList.length) { if (promoList == null || !promoList.length) {
return []; return [];
} }
@ -53,8 +53,14 @@ export function getPromosWithAddableVaps(promoList) {
} }
export function getLineItemsThatMatchPromos(promos, availableLineItems) { export function getLineItemsThatMatchPromos(promos, availableLineItems) {
if (promos == null || availableLineItems == null) {
return [];
}
const matchingLineItems = []; const matchingLineItems = [];
promos.forEach((promo) => { promos.forEach((promo) => {
if (promo.discountedLineItemIds == null) {
return;
}
promo.discountedLineItemIds.forEach((discountedLineItemId) => { promo.discountedLineItemIds.forEach((discountedLineItemId) => {
matchingLineItems.push( matchingLineItems.push(
...availableLineItems.filter((lineItem) => lineItem.id === discountedLineItemId) ...availableLineItems.filter((lineItem) => lineItem.id === discountedLineItemId)
@ -180,6 +186,9 @@ export function buildToastMessagesFromRevalidateOrValidatePromoResponse(
oldActivePromos = [], oldActivePromos = [],
oldInactivePromos = [] oldInactivePromos = []
) { ) {
if (promoResponse == null) {
return [];
}
const alerts = []; const alerts = [];
// Revalidate always has an "errors" array // Revalidate always has an "errors" array
if (promoResponse.errors) { if (promoResponse.errors) {
@ -213,19 +222,24 @@ export function buildToastMessagesFromRevalidateOrValidatePromoResponse(
); );
alerts.push(createPromoSuccessAlert(promoCodeToDisplay)); alerts.push(createPromoSuccessAlert(promoCodeToDisplay));
} else { } else {
alerts.push( if (promoResponse.promoCode) {
createPromoErrorAlert( alerts.push(
promoResponse.promoCode, createPromoErrorAlert(
promoResponse.errorCode, promoResponse.promoCode,
promoResponse.additionalInfo promoResponse.errorCode,
) promoResponse.additionalInfo
); )
);
}
} }
} }
return alerts; return alerts;
} }
export function getPromosThatMatchLineItemsOnOrder(promos, lineItemsOnOrder) { export function getPromosThatMatchLineItemsOnOrder(promos, lineItemsOnOrder) {
if (promos == null || lineItemsOnOrder == null) {
return [];
}
const matchingPromoCodes = []; const matchingPromoCodes = [];
const matchingPromos = []; const matchingPromos = [];
const consolidatedPromosWithIds = {}; const consolidatedPromosWithIds = {};
@ -266,7 +280,7 @@ export function getVapsThatNeedToBeAddedToSatisfyPromos(
availableLineItems, availableLineItems,
lineItemsOnOrder lineItemsOnOrder
) { ) {
const clonedVaps = deepClone(lineItemsOnOrder.vaps ?? []); const clonedVaps = deepClone(lineItemsOnOrder?.vaps ?? []);
const promosWithAddableVaps = getPromosWithAddableVaps(promos); const promosWithAddableVaps = getPromosWithAddableVaps(promos);
if (promosWithAddableVaps.length) { if (promosWithAddableVaps.length) {
const matchingLineItems = getLineItemsThatMatchPromos( const matchingLineItems = getLineItemsThatMatchPromos(
@ -311,7 +325,7 @@ export function getNewlyInactivatedPromos(oldInactivePromos, newInactivePromos)
const inactivePromoCodesFromResponse = const inactivePromoCodesFromResponse =
getPromoCodesFromPromoObjectsWithoutDuplicates(newInactivePromos); getPromoCodesFromPromoObjectsWithoutDuplicates(newInactivePromos);
return inactivePromoCodesFromResponse.filter( return inactivePromoCodesFromResponse.filter(
(errorPromoCode) => !oldInactivePromos.includes(errorPromoCode) (errorPromoCode) => !oldInactivePromos?.includes(errorPromoCode)
); );
} }
@ -365,8 +379,9 @@ export function getPromoCodesFromPromoObjectsWithoutDuplicates(promos) {
// private methods // private methods
function findLineItemsWithPartType(typeToFind, itemsToSearch) { function findLineItemsWithPartType(typeToFind, itemsToSearch) {
const partTypeMatches = itemsToSearch?.filter( const partTypeMatches =
(lineItem) => lineItem.partType.toUpperCase() === typeToFind.toUpperCase() itemsToSearch?.filter(
); (lineItem) => lineItem.partType.toUpperCase() === typeToFind.toUpperCase()
) ?? [];
return partTypeMatches; return partTypeMatches;
} }

File diff suppressed because it is too large Load diff

View file

@ -75,6 +75,7 @@ import cart from "@/fmg-components/cart/cart";
//Supporting files //Supporting files
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import analyticsMixin from "@/mixins/analytics-mixin";
import store from "@/store"; import store from "@/store";
import { storeActions } from "@/constants/store-actions"; import { storeActions } from "@/constants/store-actions";
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper"; import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
@ -134,6 +135,7 @@ export default {
vm.setCmsContent(resultMap.cmsContent); vm.setCmsContent(resultMap.cmsContent);
vm.lineItems = lineItemsFromSubmittedOrder; vm.lineItems = lineItemsFromSubmittedOrder;
vm.vaps = availableVaps; vm.vaps = availableVaps;
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
}); });
}, },
data() { data() {
@ -387,7 +389,9 @@ export default {
text-decoration: underline; text-decoration: underline;
} }
.header-container { .header-container {
text-align: center; display: flex;
align-items: center;
justify-content: center;
padding: 1rem 0 1.5rem 0; padding: 1rem 0 1.5rem 0;
p { p {
@ -395,6 +399,11 @@ export default {
font-weight: 400; font-weight: 400;
font-size: 1.25rem; font-size: 1.25rem;
color: $black; color: $black;
margin: 0;
}
img {
margin-right: 0.5rem;
} }
} }

View file

@ -17,6 +17,7 @@
v-if="shouldDisplayPiaCCAlert" v-if="shouldDisplayPiaCCAlert"
cmsWidgetName="PIACCErrorAlertWidget" cmsWidgetName="PIACCErrorAlertWidget"
alertClass="alert-danger" alertClass="alert-danger"
@textLinkClicked="paymentFailedPayLater"
v-bind:isDismissible="false" /> v-bind:isDismissible="false" />
</div> </div>
@ -27,6 +28,7 @@
v-if="shouldDisplayPiaAPAlert" v-if="shouldDisplayPiaAPAlert"
cmsWidgetName="PIAAPErrorAlertWidget" cmsWidgetName="PIAAPErrorAlertWidget"
alertClass="alert-danger" alertClass="alert-danger"
@textLinkClicked="paymentFailedPayLater"
v-bind:isDismissible="false" /> v-bind:isDismissible="false" />
</div> </div>
@ -216,6 +218,7 @@ import {
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
import { getQuerystringParameter } from "@/helpers/querystring-helper"; import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { deepClone } from "@/helpers/object-helper"; import { deepClone } from "@/helpers/object-helper";
import { submitWorkOrder } from "@/helpers/heritage-integration/order-helper.js";
import iframeResize from "../../../node_modules/iframe-resizer/js/iframeResizer.js"; import iframeResize from "../../../node_modules/iframe-resizer/js/iframeResizer.js";
import { routerParams } from "@/router/router-constants/router-params"; import { routerParams } from "@/router/router-constants/router-params";
@ -573,6 +576,36 @@ export default {
getDisplayAmountDue() { getDisplayAmountDue() {
return baseMixin.methods.getDisplayAmountDue(store.getters.order.lineItems); return baseMixin.methods.getDisplayAmountDue(store.getters.order.lineItems);
}, },
async paymentFailedPayLater() {
this.$refs.loadingModal.isModalVisible = true;
await this.dispatchStoreAction(
storeActions.SAVE_PAYMENT_METHOD_CHOICE,
paymentMethods.LATER,
false
);
await this.dispatchStoreAction(storeActions.RESET_SAVE_SESSION_PROMISE);
try {
await submitWorkOrder({
pageNameToLog: "payment",
submitAfterSave: true,
});
await baseMixin.methods.dispatchStoreAction(storeActions.CREATE_SUBMITTED_ORDER);
window.location = applicationConfig.CONFIRMATION_URL;
} catch (error) {
console.log("error: response from submit work order(payment pg):" + error.message);
this.$router.navigateWithoutSaving(
this.navigationScenarios.PIA_ERROR,
this.$route,
{},
{ [routerParams.DISPLAY_PIA_ALERT]: true }
);
this.$refs.loadingModal.isModalVisible = false;
return;
}
},
backButtonAction() { backButtonAction() {
// The only way I could figure out how to get navigation to work on a page with the iframe. // The only way I could figure out how to get navigation to work on a page with the iframe.
// This is also how a cancel from Paypal would work. Just a redirect to the payment-method page. // This is also how a cancel from Paypal would work. Just a redirect to the payment-method page.

View file

@ -21,6 +21,7 @@ import {
GaEvents, GaEvents,
ValueToLogTypes, ValueToLogTypes,
} from "@/constants/analytics"; } from "@/constants/analytics";
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
import store from "@/store"; import store from "@/store";
import baseMixin from "@/mixins/base-mixin"; import baseMixin from "@/mixins/base-mixin";
@ -119,6 +120,82 @@ export default {
await this.logPageView(analyticsPageEvents.ENTRY); await this.logPageView(analyticsPageEvents.ENTRY);
}, },
pushSubmittedOrderToDataLayer() {
// check if submitted order exists; exit if not.
const hasSubmittedOrder = store.getters.hasSubmittedOrder;
if (!hasSubmittedOrder) {
return;
}
const order = store.getters.submittedOrder;
// assemble data for payload
// // reduce promocode array
const promos = order.lineItems.promos ?? [];
const promoCodes = promos.map((promo) => promo.promoCode);
const promoString =
promoCodes.length === 0 ? "" : promoCodes.reduce((prev, next) => `${prev},${next}`);
// // reduce glass array
const glassToReplace = order.damage.glassToReplace ?? [];
const glassToReplaceNames = glassToReplace.map(
(glassPiece) => `${glassPiece.glassLocation}/${glassPiece.glassName}`
);
const glassString =
glassToReplaceNames.length === 0
? ""
: glassToReplaceNames.reduce((prev, next) => `${prev},${next}`);
// // calculate subtotal
const lineItems = order.lineItems;
const combinedLineItems = [
...(lineItems.glassParts ?? []),
...(lineItems.supportingItems ?? []),
...(lineItems.vaps ?? []),
...(lineItems.promos ?? []),
];
const subtotal = baseMixin.methods.getTotalPriceOfAllLineItemsAndChildParts(
combinedLineItems,
false
);
// // get correct zip code
const providerZip = order.serviceLocation.provider.address.zipCode;
const serviceZip =
order.serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE
? order.serviceLocation.zipCode
: providerZip;
// // calculate total
const total = baseMixin.methods.getTotalPriceOfAllLineItemsAndChildParts(
combinedLineItems,
true
);
const payload = {
serviceZipCode: serviceZip,
damageType: order.damage.isRepair ? "repair" : "replace",
accountType: order.payment.isInsurance ? "insurance" : "cash",
promoCodes: promoString,
vehicleYear: order.vehicle.year,
vehicleMake: order.vehicle.make,
vehicleModel: order.vehicle.model,
vehicleStyle: order.vehicle.style,
glassToReplace: glassString,
workOrderId: order.workOrderId,
providerCtu: order.serviceLocation.zipCodeCtu,
orderNumber: order.workOrderNumber,
priceTotal: total,
priceSubTotal: subtotal,
isRecalibrationOnOrder: store.getters.isRecalibrationOnSubmittedOrder,
appointmentType: order.serviceLocation.appointmentType,
};
// push to data layer.
pushToDataLayerIfDefined(payload);
},
pushExperimentsToDataLayer() { pushExperimentsToDataLayer() {
const experiments = store.getters.applicationUser.experiments; const experiments = store.getters.applicationUser.experiments;
experiments?.forEach((exp) => { experiments?.forEach((exp) => {

View file

@ -22,6 +22,46 @@ import {
getUserIdValue, getUserIdValue,
} from "@/helpers/heritage-integration/cookie-helper"; } from "@/helpers/heritage-integration/cookie-helper";
const parts = {
windshield: {
name: "windshield",
canSafeliteRecalibrate: true,
childParts: [],
color: "Green Tint",
description:
"solar, soundproofing, lane keep assist, lane departure warning system, w/adaptive cruise control",
id: "db22fd44-10dd-456f-979b-ff88cf68cca6",
partNumber: "FW04896GTYN",
partType: "WINDSHIELD",
recalibrationType: "STATIC",
requiresCapabilityQuestions: false,
requiresRecalibration: true,
salesTax: 63.86,
sellingPrice: 791.46,
},
frontWipers: {
name: "front wipers",
partNumber: "SBB16",
description: "SAFELITE BEAM BLADE 16",
partType: "FRONT WIPER",
price: 32.64,
},
rearWipers: {
name: "rear wipers",
partNumber: "SBBR12A",
description: "SAFELITE REAR BLADE 12A",
partType: "REAR WIPER",
price: 24.48,
},
rainDefense: {
name: "rain defense",
partNumber: "RAIN DEFENSE",
description: null,
partType: "RAIN DEFENSE",
price: 35.5,
},
};
describe("analyticsMixin.js", () => { describe("analyticsMixin.js", () => {
beforeEach(() => { beforeEach(() => {
removeAllTestCookies(); removeAllTestCookies();
@ -284,6 +324,130 @@ describe("analyticsMixin.js", () => {
]); ]);
}); });
describe("pushSubmittedOrderToDataLayer", () => {
beforeEach(() => {
store.getters.hasSubmittedOrder = true;
store.getters.submittedOrder = {
vehicle: {
year: "2020",
make: "acura",
model: "mdx",
style: "4-door sedan",
carId: "dummyCarId",
category: "dummyCategory",
vin: "dummyVin",
},
serviceLocation: {
address: "add1",
address2: "add2",
city: "city",
state: "state",
zipCode: "zip",
zipCodeCtu: "zipCtu",
appointmentType: "IN_SHOP",
isVehicleProtected: true,
provider: {
providerNumber: 2,
address: {
streetAddress: "add3",
city: "city2",
state: "state2",
zipCode: "zip2",
zipCodeCtu: "zipCtu2",
},
},
techNotes: "",
},
customer: {
firstName: "first",
lastName: "last",
emailAddress: "builddigitaltest@safelite.com",
phoneNumber: "555-555-5555",
isSmsOptIn: false,
},
damage: {
isRepair: false,
numberOfChips: null,
glassToReplace: [{ glassName: "single", location: "windshield" }],
},
lineItems: {
glassParts: [parts.windshield],
supportingItems: [],
vaps: [parts.frontWipers],
promos: [{ promoCode: "promoTEST" }, { promoCode: "promoTEST2" }],
},
payment: {
isInsurance: false,
insuranceCoverage: {
isVerified: null,
coverageStatus: null,
coverageVerificationType: null,
},
isPia: true,
piaType: "Afterpay",
inactivePromos: [],
},
schedule: {
date: "date",
startTime: "start",
endTime: "end",
jobMinMinutes: "30",
jobMaxMinutes: "45",
},
workOrderNumber: "01820-111111",
workOrderId: "222222222222",
};
});
test("Pushes to data layer if nominal", () => {
// Arrange
const mockDataLayerFn = jest.fn();
window.dataLayer = {
push: mockDataLayerFn,
};
// Act
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
// Assert
expect(mockDataLayerFn).toHaveBeenCalled();
});
test("Does not push to data layer if no submitted order available.", () => {
// Arrange
store.getters.hasSubmittedOrder = false;
store.getters.submittedOrder = undefined;
const mockDataLayerFn = jest.fn();
window.dataLayer = {
push: mockDataLayerFn,
};
// Act
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
// Assert
expect(mockDataLayerFn).not.toHaveBeenCalled();
});
test("Glass and Promo strings correctly formatted", () => {
// Arrange
window.dataLayer = [];
// Act
analyticsMixin.methods.pushSubmittedOrderToDataLayer();
// Assert
const glassString = window.dataLayer[0].glassToReplace;
const promoString = window.dataLayer[0].promoCodes;
expect(glassString).toMatch(/(\w+\/\w+)?(,\w+\/\w+)*/);
expect(promoString).toMatch(/(\w+)?(,\w+)*/);
});
});
test("Obj is not null after action prepended", () => { test("Obj is not null after action prepended", () => {
//Arrange //Arrange
const obj = { baseMethodName: "testMethodName", data: "testData" }; const obj = { baseMethodName: "testMethodName", data: "testData" };

View file

@ -9,16 +9,9 @@ export default {
return Object.hasOwn(store.getters.experimentSettings, settingName); return Object.hasOwn(store.getters.experimentSettings, settingName);
}, },
getSettingValue(settingName) { getSettingValue(settingName) {
if (this.hasSetting(settingName)) { return this.hasSetting(settingName)
return store.getters.experimentSettings[settingName]; ? store.getters.experimentSettings[settingName]
} else if (store.getters.hasSubmittedOrder) { : null;
const experimentSettings = store.getters.submittedOrder.experiments
.filter((x) => x.isActive)
.reduce((r, c) => ({ ...r, ...c.settings }), {});
return experimentSettings[settingName] ?? null;
} else {
return null;
}
}, },
}, },
}; };

View file

@ -132,6 +132,7 @@ const getDefaultState = () => {
referralCorrelationId: null, referralCorrelationId: null,
eon: null, eon: null,
workOrderNumber: null, workOrderNumber: null,
workOrderId: null,
customerPortalLoginToken: null, customerPortalLoginToken: null,
lockToken: null, lockToken: null,
settledTenderAmount: 0, settledTenderAmount: 0,
@ -254,6 +255,9 @@ export const mutations = {
updateWorkOrderNumber(state, workOrderNumber) { updateWorkOrderNumber(state, workOrderNumber) {
state.order.workOrderNumber = workOrderNumber; state.order.workOrderNumber = workOrderNumber;
}, },
updateWorkOrderId(state, workOrderId) {
state.order.workOrderId = workOrderId;
},
updateCustomerPortalLoginToken(state, customerPortalLoginToken) { updateCustomerPortalLoginToken(state, customerPortalLoginToken) {
state.order.customerPortalLoginToken = customerPortalLoginToken; state.order.customerPortalLoginToken = customerPortalLoginToken;
}, },
@ -1027,6 +1031,7 @@ export const actions = {
savedSessionId, savedSessionId,
crmCustomerId, crmCustomerId,
workOrderNumber, workOrderNumber,
workOrderId,
customerPortalLoginToken, customerPortalLoginToken,
lockToken, lockToken,
settledTenderAmount, settledTenderAmount,
@ -1041,6 +1046,7 @@ export const actions = {
context.commit(storeMutations.UPDATE_SAVED_SESSION_ID, savedSessionId); context.commit(storeMutations.UPDATE_SAVED_SESSION_ID, savedSessionId);
context.commit(storeMutations.UPDATE_CRM_CUSTOMER_ID, crmCustomerId); context.commit(storeMutations.UPDATE_CRM_CUSTOMER_ID, crmCustomerId);
context.commit(storeMutations.WORK_ORDER_NUMBER, workOrderNumber); context.commit(storeMutations.WORK_ORDER_NUMBER, workOrderNumber);
context.commit(storeMutations.WORK_ORDER_ID, workOrderId);
context.commit(storeMutations.LOCK_TOKEN, lockToken); context.commit(storeMutations.LOCK_TOKEN, lockToken);
context.commit(storeMutations.Customer_Portal_Login_Token, customerPortalLoginToken); context.commit(storeMutations.Customer_Portal_Login_Token, customerPortalLoginToken);
context.commit(storeMutations.UPDATE_SETTLED_TENDER_AMOUNT, settledTenderAmount); context.commit(storeMutations.UPDATE_SETTLED_TENDER_AMOUNT, settledTenderAmount);
@ -2514,14 +2520,16 @@ export const actions = {
// create a submitted order object from vuex. // create a submitted order object from vuex.
const submittedOrder = context.state.order; const submittedOrder = context.state.order;
//add experiments to submittedOrder const experiments = context.state.applicationUser.experiments;
submittedOrder.experiments = context.state.applicationUser.experiments;
// set to local storage // set to local storage
window.sessionStorage.setItem("submittedOrder", JSON.stringify(submittedOrder)); window.sessionStorage.setItem("submittedOrder", JSON.stringify(submittedOrder));
// clear vuex // clear vuex
context.commit(storeMutations.RESET_STATE); context.commit(storeMutations.RESET_STATE);
//restore user's experiments
context.commit(storeMutations.UPDATE_EXPERIMENTS, experiments);
}, },
resetSubmittedOrder(context) { resetSubmittedOrder(context) {

View file

@ -2766,6 +2766,702 @@ describe("Actions", () => {
testCapabilityQuestionAnswerDependenciesHaveBeenReset(context, true); testCapabilityQuestionAnswerDependenciesHaveBeenReset(context, true);
}); });
}); });
describe("saveActiveAndOrInactivePromos", () => {
it("should save empty arrays to promos and inactivePromos when provided with empty or null parameters", () => {
// Arrange
const context = state;
context.commit = jest.fn();
const emptyActivePromos = [];
const emptyInactivePromos = [];
const nullActivePromos = [];
const nullInactivePromos = [];
// Act
actions.saveActiveAndOrInactivePromos(context, {
activePromos: emptyActivePromos,
inactivePromos: emptyInactivePromos,
});
actions.saveActiveAndOrInactivePromos(context, {
activePromos: nullActivePromos,
inactivePromos: nullInactivePromos,
});
// Assert
expect(context.commit).toHaveBeenNthCalledWith(1, storeMutations.UPDATE_PROMOS, []);
expect(context.commit).toHaveBeenNthCalledWith(
2,
storeMutations.UPDATE_INACTIVE_PROMOS,
[]
);
expect(context.commit).toHaveBeenNthCalledWith(3, storeMutations.UPDATE_PROMOS, []);
expect(context.commit).toHaveBeenNthCalledWith(
4,
storeMutations.UPDATE_INACTIVE_PROMOS,
[]
);
});
it("should save both active and inactivePromos when method is supplied with both active and inactive", () => {
// Arrange
const context = state;
context.commit = jest.fn();
const activePromos = [{ promoCode: "activePromo" }];
const inactivePromos = ["inactivePromo"];
// Act
actions.saveActiveAndOrInactivePromos(context, {
activePromos: activePromos,
inactivePromos: inactivePromos,
});
// Assert
expect(context.commit).toBeCalledWith(storeMutations.UPDATE_PROMOS, activePromos);
expect(context.commit).toBeCalledWith(
storeMutations.UPDATE_INACTIVE_PROMOS,
inactivePromos
);
});
it("should remove any inactivePromos that are already active when supplied with both active and inactive", () => {
// Arrange
const context = state;
context.commit = jest.fn();
const activePromos = [{ promoCode: "duplicatePromo" }];
const inactivePromos = ["duplicatePromo", "uniquePromo"];
const expectedInactivePromos = ["uniquePromo"];
// Act
actions.saveActiveAndOrInactivePromos(context, {
activePromos: activePromos,
inactivePromos: inactivePromos,
});
// Assert
expect(context.commit).toBeCalledWith(
storeMutations.UPDATE_INACTIVE_PROMOS,
expectedInactivePromos
);
});
it("should save active promos from store and inactive promos from method call when only inactivePromos is provided", () => {
// Arrange
const context = state;
context.commit = jest.fn();
const activePromos = [{ promoCode: "storedPromo" }];
const inactivePromos = ["inactivePromo"];
context["getters"] = { lineItems: { promos: activePromos } };
// Act
actions.saveActiveAndOrInactivePromos(context, { inactivePromos: inactivePromos });
// Assert
expect(context.commit).toBeCalledWith(storeMutations.UPDATE_PROMOS, activePromos);
expect(context.commit).toBeCalledWith(
storeMutations.UPDATE_INACTIVE_PROMOS,
inactivePromos
);
});
it("should save active promos but remove duplicates from store and inactive promos from method call when only inactivePromos is provided", () => {
// If only inactivePromos is supplied, it will remove duplicates from active promos in the store
// Arrange
const context = state;
context.commit = jest.fn();
const activePromos = [{ promoCode: "storedPromo" }, { promoCode: "duplicatePromo" }];
const inactivePromos = ["duplicatePromo"];
context["getters"] = { lineItems: { promos: activePromos } };
const expectedSavedActivePromos = [{ promoCode: "storedPromo" }];
// Act
actions.saveActiveAndOrInactivePromos(context, { inactivePromos: inactivePromos });
// Assert
expect(context.commit).toBeCalledWith(
storeMutations.UPDATE_PROMOS,
expectedSavedActivePromos
);
expect(context.commit).toBeCalledWith(
storeMutations.UPDATE_INACTIVE_PROMOS,
inactivePromos
);
});
it("should save inactivePromos from the store and provided activePromos when only activePromos is provided", () => {
// Arrange
const context = state;
context.commit = jest.fn();
const activePromos = [{ promoCode: "activePromo" }];
const inactivePromos = ["inactivePromo"];
context["getters"] = { payment: { inactivePromos: inactivePromos } };
// Act
actions.saveActiveAndOrInactivePromos(context, { activePromos: activePromos });
// Assert
expect(context.commit).toBeCalledWith(storeMutations.UPDATE_PROMOS, activePromos);
expect(context.commit).toBeCalledWith(
storeMutations.UPDATE_INACTIVE_PROMOS,
inactivePromos
);
});
it("should save inactivePromos without duplicates from activePromos from the store and provided activePromos when only activePromos is provided", () => {
// Arrange
const context = state;
context.commit = jest.fn();
const activePromos = [{ promoCode: "activePromo" }, { promoCode: "duplicatePromo" }];
const inactivePromos = ["inactivePromo", "duplicatePromo"];
context["getters"] = { payment: { inactivePromos: inactivePromos } };
const expectedInactivePromos = ["inactivePromo"];
// Act
actions.saveActiveAndOrInactivePromos(context, { activePromos: activePromos });
// Assert
expect(context.commit).toBeCalledWith(storeMutations.UPDATE_PROMOS, activePromos);
expect(context.commit).toBeCalledWith(
storeMutations.UPDATE_INACTIVE_PROMOS,
expectedInactivePromos
);
});
});
describe("validateOrderPromoAndSaveServerData", () => {
it("should add GUIDs if not there to provided addableVaps before sending the http call", async () => {
// Arrange
const context = state;
const promoCode = "testPromo";
const lineItemsToUse = { vaps: [1], promos: [2] };
const addableVaps = [{ partNumber: "addableVap" }];
context["getters"] = {
order: {
serviceLocation: {
appointmentType: "test",
state: "test",
zipCodeCtu: "test",
},
vehicle: {
carId: "test",
year: "test",
},
referralCorrelationId: "test",
eon: "test",
damage: {
isRepair: true,
glassToReplace: null,
},
payment: {
parentAccountNumber: "test",
},
referralSequenceNumber: "test",
lineItems: {
serverData: "test",
},
},
};
globalMethods.callHttpClient = jest.fn().mockResolvedValue({
data: {},
});
crypto.randomUUID = jest.fn(() => "GUID");
// Act
actions.validateOrderPromoAndSaveServerData(context, {
payload: {
promoCode: promoCode,
lineItemsToUse: lineItemsToUse,
addableVaps: addableVaps,
},
pageNameToLog: "test",
});
const firstCallArgs = globalMethods.callHttpClient.mock.calls[0];
// Assert
expect(firstCallArgs[0].payload.addableVaps[0].id).toEqual("GUID");
});
it("should use lineItems from the store if not provided in the call", async () => {
// Arrange
const context = state;
const promoCode = "testPromo";
const lineItemsToUse = null;
const addableVaps = [{ partNumber: "addableVap" }];
context["getters"] = {
order: {
serviceLocation: {
appointmentType: "test",
state: "test",
zipCodeCtu: "test",
},
vehicle: {
carId: "test",
year: "test",
},
referralCorrelationId: "test",
eon: "test",
damage: {
isRepair: true,
glassToReplace: null,
},
payment: {
parentAccountNumber: "test",
},
referralSequenceNumber: "test",
lineItems: {
vaps: [1],
promos: [2],
serverData: "test",
},
},
};
globalMethods.callHttpClient = jest.fn().mockResolvedValue({
data: {},
});
crypto.randomUUID = jest.fn(() => "GUID");
const expectedLineItemsOnOrder = [1, 2];
// Act
actions.validateOrderPromoAndSaveServerData(context, {
payload: {
promoCode: promoCode,
lineItemsToUse: lineItemsToUse,
addableVaps: addableVaps,
},
pageNameToLog: "test",
});
const firstCallArgs = globalMethods.callHttpClient.mock.calls[0];
// Assert
expect(firstCallArgs[0].payload.order.lineItemsOnOrder).toEqual(
expectedLineItemsOnOrder
);
});
it("should not blow up if an error comes back from the http call", async () => {
// Arrange
const context = state;
const promoCode = "testPromo";
const lineItemsToUse = null;
const addableVaps = [{ partNumber: "addableVap" }];
context["getters"] = {
order: {
serviceLocation: {
appointmentType: "test",
state: "test",
zipCodeCtu: "test",
},
vehicle: {
carId: "test",
year: "test",
},
referralCorrelationId: "test",
eon: "test",
damage: {
isRepair: true,
glassToReplace: null,
},
payment: {
parentAccountNumber: "test",
},
referralSequenceNumber: "test",
lineItems: {
vaps: [1],
promos: [2],
serverData: "test",
},
},
};
globalMethods.callHttpClient = jest.fn(() =>
Promise.reject(new Error("Error message"))
);
crypto.randomUUID = jest.fn(() => "GUID");
// Act
try {
await actions.validateOrderPromoAndSaveServerData(context, {
payload: {
promoCode: promoCode,
lineItemsToUse: lineItemsToUse,
addableVaps: addableVaps,
},
pageNameToLog: "test",
});
} catch (error) {
// Assert
fail("Unhandled error occurred");
}
});
it("should always save serverData if any is received from the http call", async () => {
// Arrange
const context = state;
const promoCode = "testPromo";
const lineItemsToUse = null;
const addableVaps = [{ partNumber: "addableVap" }];
context.commit = jest.fn(() => {});
context["getters"] = {
order: {
serviceLocation: {
appointmentType: "test",
state: "test",
zipCodeCtu: "test",
},
vehicle: {
carId: "test",
year: "test",
},
referralCorrelationId: "test",
eon: "test",
damage: {
isRepair: true,
glassToReplace: null,
},
payment: {
parentAccountNumber: "test",
},
referralSequenceNumber: "test",
lineItems: {
vaps: [1],
promos: [2],
serverData: "test",
},
},
};
globalMethods.callHttpClient = jest.fn().mockResolvedValue({
data: { serverData: "serverData" },
});
crypto.randomUUID = jest.fn(() => "GUID");
// Act
await actions.validateOrderPromoAndSaveServerData(context, {
payload: {
promoCode: promoCode,
lineItemsToUse: lineItemsToUse,
addableVaps: addableVaps,
},
pageNameToLog: "test",
});
// Assert
expect(context.commit).toBeCalledWith(
storeMutations.UPDATE_LINE_ITEMS_SERVER_DATA,
expect.anything()
);
});
});
describe("revalidateOrderPromosAndSaveServerData", () => {
it("should add GUIDs if not there to provided vaps before sending the http call", async () => {
// Arrange
const context = state;
context.commit = jest.fn(() => {});
context["getters"] = {
order: {
serviceLocation: {
appointmentType: "test",
state: "test",
zipCodeCtu: "test",
},
vehicle: {
carId: "test",
year: "test",
},
referralCorrelationId: "test",
eon: "test",
damage: {
isRepair: true,
glassToReplace: null,
},
payment: {
parentAccountNumber: "test",
inactivePromos: ["inactiveTest"],
},
referralSequenceNumber: "test",
lineItems: {
serverData: "test",
promos: [{ promoCode: "test" }],
vaps: [{ partNumber: "testVap" }],
},
},
};
globalMethods.callHttpClient = jest.fn().mockResolvedValue({
data: { lineItemsServerData: "testData" },
});
crypto.randomUUID = jest.fn(() => "GUID");
// Act
actions.revalidateOrderPromosAndSaveServerData(context, {
payload: {},
pageNameToLog: "test",
});
const firstCallArgs = globalMethods.callHttpClient.mock.calls[0];
// Assert
const vapsLineItem = firstCallArgs[0].payload.order.lineItemsOnOrder.filter(
(lineItem) => {
return lineItem.partNumber == "testVap";
}
);
expect(vapsLineItem[0].id).toEqual("GUID");
});
it("uses provided parameters in favor of store values", async () => {
// Arrange
const context = state;
context.commit = jest.fn(() => {});
const activePromosToUse = [{ promoCode: "providedPromo" }];
const inactivePromosToUse = ["providedInactivePromo"];
const vapsProvided = [{ id: 123 }];
const lineItemsToUse = { vaps: vapsProvided };
context["getters"] = {
order: {
serviceLocation: {
appointmentType: "test",
state: "test",
zipCodeCtu: "test",
},
vehicle: {
carId: "test",
year: "test",
},
referralCorrelationId: "test",
eon: "test",
damage: {
isRepair: true,
glassToReplace: null,
},
payment: {
parentAccountNumber: "test",
inactivePromos: ["inactiveTest"],
},
referralSequenceNumber: "test",
lineItems: {
serverData: "test",
promos: [{ promoCode: "test" }],
vaps: [{ partNumber: "testVap" }],
},
},
};
globalMethods.callHttpClient = jest.fn().mockResolvedValue({
data: { lineItemsServerData: "testData" },
});
crypto.randomUUID = jest.fn(() => "GUID");
// Act
actions.revalidateOrderPromosAndSaveServerData(context, {
payload: {
activePromosToUse: activePromosToUse,
inactivePromosToUse: inactivePromosToUse,
lineItemsToUse: lineItemsToUse,
},
pageNameToLog: "test",
});
const firstCallArgs = globalMethods.callHttpClient.mock.calls[0];
const expectedInactivePromos = ["providedInactivePromo"];
const expectedLineItemsOnOrder = [...vapsProvided, ...activePromosToUse];
// Assert
expect(firstCallArgs[0].payload.inactivePromos).toEqual(expectedInactivePromos);
expect(firstCallArgs[0].payload.order.lineItemsOnOrder).toEqual(
expectedLineItemsOnOrder
);
});
it("can build a valid payload with store data only", async () => {
// Arrange
const context = state;
context.commit = jest.fn(() => {});
context["getters"] = {
order: {
serviceLocation: {
appointmentType: "test",
state: "test",
zipCodeCtu: "test",
},
vehicle: {
carId: "test",
year: "test",
},
referralCorrelationId: "test",
eon: "test",
damage: {
isRepair: true,
glassToReplace: null,
},
payment: {
parentAccountNumber: "test",
inactivePromos: ["inactiveTest"],
},
referralSequenceNumber: "test",
lineItems: {
serverData: "test",
promos: [{ promoCode: "test" }],
vaps: [{ partNumber: "testVap", id: "providedId" }],
},
},
};
globalMethods.callHttpClient = jest.fn().mockResolvedValue({
data: { lineItemsServerData: "testData" },
});
crypto.randomUUID = jest.fn(() => "GUID");
// Act
actions.revalidateOrderPromosAndSaveServerData(context, {
payload: {},
pageNameToLog: "test",
});
const expectedLineItemsOnOrder = [
...context.getters.order.lineItems.vaps,
...context.getters.order.lineItems.promos,
];
const firstCallArgs = globalMethods.callHttpClient.mock.calls[0];
// Assert
expect(firstCallArgs[0].payload.inactivePromos).toEqual(
context.getters.order.payment.inactivePromos
);
expect(firstCallArgs[0].payload.order.lineItemsOnOrder).toEqual(
expectedLineItemsOnOrder
);
});
it("removes any promos from inactivePromos that are already active before sending the request", async () => {
// Arrange
const context = state;
context.commit = jest.fn(() => {});
const activePromosToUse = [{ promoCode: "providedPromoDuplicate" }];
const inactivePromosToUse = ["providedPromoDuplicate"];
const vapsProvided = [{ id: 123 }];
const lineItemsToUse = { vaps: vapsProvided };
context["getters"] = {
order: {
serviceLocation: {
appointmentType: "test",
state: "test",
zipCodeCtu: "test",
},
vehicle: {
carId: "test",
year: "test",
},
referralCorrelationId: "test",
eon: "test",
damage: {
isRepair: true,
glassToReplace: null,
},
payment: {
parentAccountNumber: "test",
},
referralSequenceNumber: "test",
lineItems: {
serverData: "test",
},
},
};
globalMethods.callHttpClient = jest.fn().mockResolvedValue({
data: { lineItemsServerData: "testData" },
});
crypto.randomUUID = jest.fn(() => "GUID");
// Act
actions.revalidateOrderPromosAndSaveServerData(context, {
payload: {
activePromosToUse: activePromosToUse,
inactivePromosToUse: inactivePromosToUse,
lineItemsToUse: lineItemsToUse,
},
pageNameToLog: "test",
});
const firstCallArgs = globalMethods.callHttpClient.mock.calls[0];
// Assert
expect(firstCallArgs[0].payload.inactivePromos).toEqual([]);
});
it("saves serverData to the store", async () => {
// Arrange
const context = state;
context.commit = jest.fn(() => {});
const activePromosToUse = [{ promoCode: "providedPromoDuplicate" }];
const inactivePromosToUse = ["providedPromoDuplicate"];
const vapsProvided = [{ id: 123 }];
const lineItemsToUse = { vaps: vapsProvided };
context["getters"] = {
order: {
serviceLocation: {
appointmentType: "test",
state: "test",
zipCodeCtu: "test",
},
vehicle: {
carId: "test",
year: "test",
},
referralCorrelationId: "test",
eon: "test",
damage: {
isRepair: true,
glassToReplace: null,
},
payment: {
parentAccountNumber: "test",
},
referralSequenceNumber: "test",
lineItems: {
serverData: "test",
},
},
};
globalMethods.callHttpClient = jest.fn().mockResolvedValue({
data: { lineItemsServerData: "testData" },
});
crypto.randomUUID = jest.fn(() => "GUID");
// Act
await actions.revalidateOrderPromosAndSaveServerData(context, {
payload: {
activePromosToUse: activePromosToUse,
inactivePromosToUse: inactivePromosToUse,
lineItemsToUse: lineItemsToUse,
},
pageNameToLog: "test",
});
// Assert
expect(context.commit).toBeCalledWith(
storeMutations.UPDATE_LINE_ITEMS_SERVER_DATA,
expect.anything()
);
});
});
}); });
describe("Getters", () => { describe("Getters", () => {

View file

@ -23,7 +23,7 @@
<textLink <textLink
linkType="text" linkType="text"
:text="getRouterLinkDisplayTextFromCopy(copy)" :text="getRouterLinkDisplayTextFromCopy(copy)"
href="#!" href="javascript:void(0)"
@click-event=" @click-event="
$emit('textLinkClicked', getRouterLinkRouteFromCopy(copy)) $emit('textLinkClicked', getRouterLinkRouteFromCopy(copy))
" "