Merge pull request #3241 from Safelite/feature/CASH-2947

Feature/CASH 2947
This commit is contained in:
Chris 2026-06-25 14:25:58 -04:00 committed by GitHub
commit b81fcaac9d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 123 additions and 59 deletions

View file

@ -132,8 +132,6 @@ const storeActions = {
UPDATE_HAS_TRIGGERED_ERROR: "updateHasTriggeredError", UPDATE_HAS_TRIGGERED_ERROR: "updateHasTriggeredError",
GET_VALID_IDEMPOTENCY_KEY: "getValidIdempotencyKey", GET_VALID_IDEMPOTENCY_KEY: "getValidIdempotencyKey",
CORRECT_IDEMPOTENCY_KEY_EXPIRY: "correctIdempotencyKeyExpiry", CORRECT_IDEMPOTENCY_KEY_EXPIRY: "correctIdempotencyKeyExpiry",
SAVE_BAILOUT_CODE: "saveBailoutCode",
}; };
export { storeActions }; export { storeActions };

View file

@ -116,9 +116,6 @@ const storeMutations = {
UPDATE_EXPERIMENTS: "updateExperiments", UPDATE_EXPERIMENTS: "updateExperiments",
UPDATE_TRIGGERED_SITE_ENTRY: "updateTriggeredSiteEntry", UPDATE_TRIGGERED_SITE_ENTRY: "updateTriggeredSiteEntry",
// BAILOUT MUTATIONS
UPDATE_BAILOUT_CODE: "updateBailoutCode",
// EXTERNAL_PARAMETER MUTATIONS // EXTERNAL_PARAMETER MUTATIONS
UPDATE_IS_EXTERNAL_PARAMETER: "updateIsExternalParameter", UPDATE_IS_EXTERNAL_PARAMETER: "updateIsExternalParameter",
UPDATE_EXTERNAL_PARAMETER_YEAR: "updateExternalParameterYear", UPDATE_EXTERNAL_PARAMETER_YEAR: "updateExternalParameterYear",

View file

@ -159,7 +159,7 @@ export default {
methods: { methods: {
getBailoutCodeFromStore() { getBailoutCodeFromStore() {
return store.getters.applicationUser.bailoutCode; return store.getters.bailoutCode;
}, },
getFirstNameFromStore() { getFirstNameFromStore() {
return store.getters.order.customer.firstName; return store.getters.order.customer.firstName;
@ -233,7 +233,6 @@ export default {
this.navigationScenarios.CLICKED_FORWARD, this.navigationScenarios.CLICKED_FORWARD,
this.pageName, this.pageName,
{ {
bailoutCode: this.bailoutCode,
submit: true, submit: true,
} }
); );
@ -244,7 +243,7 @@ export default {
} }
}, },
arePagePrerequisitesValid() { arePagePrerequisitesValid() {
return this.getBailoutCodeFromStore() !== null; return this.getBailoutCodeFromStore() != null;
}, },
}, },

View file

@ -11,8 +11,9 @@ jest.mock("@/store", () => ({
commit: jest.fn(), commit: jest.fn(),
dispatch: jest.fn(), dispatch: jest.fn(),
getters: { getters: {
applicationUser: {
bailoutCode: null, bailoutCode: null,
applicationUser: {
pageData: {},
}, },
externalParameterState: { externalParameterState: {
isExternalParameter: false, isExternalParameter: false,

View file

@ -525,7 +525,7 @@ export default {
}, },
getRequiredVinNotFound() { getRequiredVinNotFound() {
// Prevents success alert from showing // Prevents success alert from showing
var bailoutCode = this.$store.getters.applicationUser.bailoutCode; var bailoutCode = this.$store.getters.bailoutCode;
var vinRequired = this.$store.getters.vehicle.vinRequired; var vinRequired = this.$store.getters.vehicle.vinRequired;
if (vinRequired && this.vin && bailoutCode == bailoutCodes.PART_NOT_FOUND) { if (vinRequired && this.vin && bailoutCode == bailoutCodes.PART_NOT_FOUND) {
return true; return true;

View file

@ -34,6 +34,7 @@ import { getQuerystringParameter } from "@/helpers/querystring-helper";
import { containsRecalParts, getRecalPartNumbers } from "@/helpers/recal-helper"; import { containsRecalParts, getRecalPartNumbers } from "@/helpers/recal-helper";
import { getAmountDue, getSubTotal, getSalesTax } from "@/helpers/pricing-helper.js"; import { getAmountDue, getSubTotal, getSalesTax } from "@/helpers/pricing-helper.js";
import { partTypeStrings } from "@/constants/part-type-strings"; import { partTypeStrings } from "@/constants/part-type-strings";
import { bailoutCodes } from "@/constants/bailout-codes";
import router from "@/router"; import router from "@/router";
export default { export default {
@ -271,6 +272,9 @@ export default {
sessionData.totalPrice = getAmountDue(order?.lineItems, true); sessionData.totalPrice = getAmountDue(order?.lineItems, true);
sessionData.userAgent = navigator.userAgent; sessionData.userAgent = navigator.userAgent;
sessionData.cashPriceSubTotal = order?.cashPriceSubTotal; sessionData.cashPriceSubTotal = order?.cashPriceSubTotal;
sessionData.bailoutCode = this.getBailoutCodeEnum(
applicationUser?.pageData?.bailout?.bailoutCode
);
await baseMixin.methods.dispatchStoreAction( await baseMixin.methods.dispatchStoreAction(
storeActions.LOG_FMG_SESSION_DATA, storeActions.LOG_FMG_SESSION_DATA,
@ -279,6 +283,17 @@ export default {
); );
}, },
getBailoutCodeEnum(bailoutCode) {
if (bailoutCode) {
const bailoutCodeEnum = Object.keys(bailoutCodes).find(
(key) => bailoutCodes[key] === bailoutCode
);
return bailoutCodeEnum ? bailoutCode + "_" + bailoutCodeEnum : bailoutCode;
} else {
return null;
}
},
pushOrderToDataLayer() { pushOrderToDataLayer() {
// helper check for if an object is defined (but maybe falsey) // helper check for if an object is defined (but maybe falsey)
const isDefined = (x) => x !== null && x !== undefined; const isDefined = (x) => x !== null && x !== undefined;

View file

@ -22,6 +22,7 @@ import {
getUserIdValue, getUserIdValue,
} from "@/helpers/heritage-integration/cookie-helper"; } from "@/helpers/heritage-integration/cookie-helper";
import baseMixin from "./base-mixin"; import baseMixin from "./base-mixin";
import { bailoutCodes } from "@/constants/bailout-codes";
const parts = { const parts = {
windshield: { windshield: {
@ -746,6 +747,71 @@ describe("analyticsMixin.js", () => {
//Assert //Assert
expect(gaLabels).toEqual(GaLabels); expect(gaLabels).toEqual(GaLabels);
}); });
describe("getBailoutCodeEnum", () => {
test("returns null when bailout code is not provided", () => {
expect(analyticsMixin.methods.getBailoutCodeEnum(null)).toBeNull();
expect(analyticsMixin.methods.getBailoutCodeEnum(undefined)).toBeNull();
});
test("returns formatted value when bailout code matches a known enum", () => {
const result = analyticsMixin.methods.getBailoutCodeEnum(bailoutCodes.PART_NOT_FOUND);
expect(result).toBe(`${bailoutCodes.PART_NOT_FOUND}_PART_NOT_FOUND`);
});
test("returns original value when bailout code does not match a known enum", () => {
expect(analyticsMixin.methods.getBailoutCodeEnum(999)).toBe(999);
});
});
describe("pushFmgSessionData", () => {
beforeEach(() => {
baseMixin.methods.hasSubmittedOrder = jest.fn().mockReturnValue(false);
baseMixin.methods.getSubmittedOrder = jest.fn();
baseMixin.methods.hasSubmittedApplicationUser = jest.fn().mockReturnValue(false);
baseMixin.methods.getSubmittedApplicationUser = jest.fn();
store.getters.order = {
vehicle: { vinRequired: false },
payment: {
isInsurance: false,
isClaimAndCoverage: false,
isPia: false,
},
damage: { isRepair: false },
lineItems: {},
serviceLocation: {},
customer: {},
policy: {},
};
store.getters.applicationUser = {
experiments: [],
pageData: {
bailout: {
bailoutCode: bailoutCodes.PART_NOT_FOUND,
},
},
};
});
test("dispatches session data with formatted bailout code", async () => {
const mocks = setupMocksForJsFiles({
actionList: [{ actionName: storeActions.LOG_FMG_SESSION_DATA }],
});
await analyticsMixin.methods.pushFmgSessionData();
expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(
storeActions.LOG_FMG_SESSION_DATA,
expect.objectContaining({
currentPage: "mockedPageName",
bailoutCode: `${bailoutCodes.PART_NOT_FOUND}_PART_NOT_FOUND`,
}),
false
);
});
});
describe("initSession", () => { describe("initSession", () => {
test("Generates random values for userId and deviceId if not present", async () => { test("Generates random values for userId and deviceId if not present", async () => {
// Arrange // Arrange

View file

@ -1,12 +1,17 @@
import { navigationScenarios } from "@/router/constants/navigation-scenarios"; import { navigationScenarios } from "@/router/constants/navigation-scenarios";
import { routeData } from "@/router/constants/routes";
import { savePageData } from "@/router/methods/helpers/save-page-data";
export default { export default {
methods: { methods: {
navigateToBailoutPage(vm, bailoutCode) { navigateToBailoutPage(vm, bailoutCode) {
const self = vm ?? this; const self = vm ?? this;
self.dispatchStoreAction(self.storeActions.SAVE_BAILOUT_CODE, bailoutCode).then(() => { return savePageData(routeData.BAILOUT.name, { bailoutCode }).then(() => {
self.$router.navigateWithoutSaving(navigationScenarios.BAILOUT, self.pageName); return self.$router.navigateWithoutSaving(
navigationScenarios.BAILOUT,
self.pageName
);
}); });
}, },
}, },

View file

@ -1,33 +1,34 @@
import bailoutMixin from "@/mixins/bailout-mixin"; import bailoutMixin from "@/mixins/bailout-mixin";
import { storeActions } from "@/constants/store-actions.js";
import { navigationScenarios } from "@/router/constants/navigation-scenarios"; import { navigationScenarios } from "@/router/constants/navigation-scenarios";
import { bailoutCodes } from "@/constants/bailout-codes.js"; import { bailoutCodes } from "@/constants/bailout-codes.js";
import { routeData } from "@/router/constants/routes";
jest.mock("@/router/methods/helpers/save-page-data", () => ({
savePageData: jest.fn().mockResolvedValue(undefined),
}));
import { savePageData } from "@/router/methods/helpers/save-page-data";
describe("bailout-mixin.js", () => { describe("bailout-mixin.js", () => {
test("navigateToBailoutPage: dispatches SAVE_BAILOUT_CODE action with bailout code", async () => { beforeEach(() => {
// Arrange savePageData.mockClear();
});
test("navigateToBailoutPage: saves bailout code to pageData", async () => {
const mockVm = createMockVm(); const mockVm = createMockVm();
const bailoutCode = bailoutCodes.PART_NOT_FOUND; const bailoutCode = bailoutCodes.PART_NOT_FOUND;
// Act
await bailoutMixin.methods.navigateToBailoutPage(mockVm, bailoutCode); await bailoutMixin.methods.navigateToBailoutPage(mockVm, bailoutCode);
// Assert expect(savePageData).toHaveBeenCalledWith(routeData.BAILOUT.name, { bailoutCode });
expect(mockVm.dispatchStoreAction).toHaveBeenCalledWith(
storeActions.SAVE_BAILOUT_CODE,
bailoutCode
);
}); });
test("navigateToBailoutPage: navigates to bailout page after saving bailout code", async () => { test("navigateToBailoutPage: navigates to bailout page after saving bailout code", async () => {
// Arrange
const mockVm = createMockVm(); const mockVm = createMockVm();
const bailoutCode = bailoutCodes.PART_NOT_FOUND; const bailoutCode = bailoutCodes.PART_NOT_FOUND;
// Act
await bailoutMixin.methods.navigateToBailoutPage(mockVm, bailoutCode); await bailoutMixin.methods.navigateToBailoutPage(mockVm, bailoutCode);
// Assert
expect(mockVm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(mockVm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
navigationScenarios.BAILOUT, navigationScenarios.BAILOUT,
mockVm.pageName mockVm.pageName
@ -35,55 +36,39 @@ describe("bailout-mixin.js", () => {
}); });
test("navigateToBailoutPage: uses current context (this) when vm is not provided", async () => { test("navigateToBailoutPage: uses current context (this) when vm is not provided", async () => {
// Arrange
const mockRouter = { const mockRouter = {
navigateWithoutSaving: jest.fn().mockResolvedValue(undefined), navigateWithoutSaving: jest.fn().mockResolvedValue(undefined),
}; };
const mockThis = { const mockThis = {
dispatchStoreAction: jest.fn().mockResolvedValue(undefined),
$router: mockRouter, $router: mockRouter,
storeActions: storeActions,
pageName: "test-page", pageName: "test-page",
}; };
const bailoutCode = bailoutCodes.PART_NOT_FOUND; const bailoutCode = bailoutCodes.PART_NOT_FOUND;
// Act
await bailoutMixin.methods.navigateToBailoutPage.call(mockThis, undefined, bailoutCode); await bailoutMixin.methods.navigateToBailoutPage.call(mockThis, undefined, bailoutCode);
// Assert expect(savePageData).toHaveBeenCalledWith(routeData.BAILOUT.name, { bailoutCode });
expect(mockThis.dispatchStoreAction).toHaveBeenCalledWith(
storeActions.SAVE_BAILOUT_CODE,
bailoutCode
);
}); });
test("navigateToBailoutPage: passes correct bailout code to store", async () => { test("navigateToBailoutPage: passes correct bailout code to pageData", async () => {
// Arrange
const mockVm = createMockVm(); const mockVm = createMockVm();
const customBailoutCode = 999; const customBailoutCode = 999;
// Act
await bailoutMixin.methods.navigateToBailoutPage(mockVm, customBailoutCode); await bailoutMixin.methods.navigateToBailoutPage(mockVm, customBailoutCode);
// Assert expect(savePageData).toHaveBeenCalledWith(routeData.BAILOUT.name, {
expect(mockVm.dispatchStoreAction).toHaveBeenCalledWith( bailoutCode: customBailoutCode,
storeActions.SAVE_BAILOUT_CODE, });
customBailoutCode
);
}); });
test("navigateToBailoutPage: calls navigateWithoutSaving with correct parameters", async () => { test("navigateToBailoutPage: calls navigateWithoutSaving with correct parameters", async () => {
// Arrange
const mockVm = createMockVm(); const mockVm = createMockVm();
const mockPageName = "vehicle-damage"; const mockPageName = "vehicle-damage";
mockVm.pageName = mockPageName; mockVm.pageName = mockPageName;
const bailoutCode = bailoutCodes.PART_NOT_FOUND; const bailoutCode = bailoutCodes.PART_NOT_FOUND;
// Act
await bailoutMixin.methods.navigateToBailoutPage(mockVm, bailoutCode); await bailoutMixin.methods.navigateToBailoutPage(mockVm, bailoutCode);
// Assert
expect(mockVm.$router.navigateWithoutSaving).toHaveBeenCalledWith( expect(mockVm.$router.navigateWithoutSaving).toHaveBeenCalledWith(
navigationScenarios.BAILOUT, navigationScenarios.BAILOUT,
mockPageName mockPageName
@ -93,11 +78,9 @@ describe("bailout-mixin.js", () => {
function createMockVm() { function createMockVm() {
return { return {
dispatchStoreAction: jest.fn().mockResolvedValue(undefined),
$router: { $router: {
navigateWithoutSaving: jest.fn().mockResolvedValue(undefined), navigateWithoutSaving: jest.fn().mockResolvedValue(undefined),
}, },
storeActions,
pageName: "test-page", pageName: "test-page",
}; };
} }

View file

@ -193,5 +193,5 @@ export async function beforeEach(to, from) {
function getIsBailout(submittedState) { function getIsBailout(submittedState) {
const submittedStateObj = JSON.parse(submittedState); const submittedStateObj = JSON.parse(submittedState);
return !!submittedStateObj?.applicationUser?.bailoutCode; return !!submittedStateObj?.applicationUser?.pageData?.bailout?.bailoutCode;
} }

View file

@ -3,6 +3,7 @@ import { buildManualUrl } from "@/router/methods/helpers/build-manual-url";
import { getDestination } from "@/router/methods/helpers/get-destination"; import { getDestination } from "@/router/methods/helpers/get-destination";
import { savePageData } from "@/router/methods/helpers/save-page-data"; import { savePageData } from "@/router/methods/helpers/save-page-data";
import { navigationScenarios } from "@/router/constants/navigation-scenarios"; import { navigationScenarios } from "@/router/constants/navigation-scenarios";
import { routeData } from "@/router/constants/routes";
import router from "@/router"; import router from "@/router";
import store from "@/store"; import store from "@/store";
@ -79,9 +80,13 @@ export async function navigateWithSaving(scenario, currentPageName) {
export async function navigateWithPageData(scenario, currentPageName, pageData = {}) { export async function navigateWithPageData(scenario, currentPageName, pageData = {}) {
const nextPage = getDestination(currentPageName, scenario); const nextPage = getDestination(currentPageName, scenario);
if (pageData && pageData.bailoutCode) { if (currentPageName === routeData.BAILOUT.name) {
pageData.AppName = "FixMyGlass"; const existingPageData = store.getters.pageData(routeData.BAILOUT.name) ?? {};
await savePageData(currentPageName, pageData); await savePageData(routeData.BAILOUT.name, {
...existingPageData,
...pageData,
AppName: "FixMyGlass",
});
} else { } else {
await savePageData(nextPage.name, pageData); await savePageData(nextPage.name, pageData);
} }

View file

@ -227,7 +227,6 @@ const getDefaultState = () => {
affiliateCookies: [], affiliateCookies: [],
loggingOption: false, loggingOption: false,
hasAlreadyTriggeredError: false, hasAlreadyTriggeredError: false,
bailoutCode: null,
}, },
idempotencyKeyFields: { idempotencyKeyFields: {
referralCorrelationId: null, referralCorrelationId: null,
@ -1030,9 +1029,6 @@ export const mutations = {
state.idempotencyKeyFields.totalInCents = totalInCents; state.idempotencyKeyFields.totalInCents = totalInCents;
state.idempotencyKeyFields.expiryTime = expiryTime; state.idempotencyKeyFields.expiryTime = expiryTime;
}, },
updateBailoutCode(state, bailoutCode) {
state.applicationUser.bailoutCode = bailoutCode;
},
}; };
// Export Getters // Export Getters
@ -1155,6 +1151,7 @@ export const getters = {
pageData: (state) => (page) => { pageData: (state) => (page) => {
return state.applicationUser.pageData[page]; return state.applicationUser.pageData[page];
}, },
bailoutCode: (state) => state.applicationUser.pageData.bailout?.bailoutCode,
applicationUser: (state) => state.applicationUser, applicationUser: (state) => state.applicationUser,
order: (state) => state.order, order: (state) => state.order,
payment: (state) => state.order.payment, payment: (state) => state.order.payment,
@ -1844,6 +1841,7 @@ export const actions = {
totalPrice, totalPrice,
userAgent, userAgent,
cashPriceSubTotal, cashPriceSubTotal,
bailoutCode,
} }
) { ) {
var payload = { var payload = {
@ -1898,6 +1896,7 @@ export const actions = {
userAgent: userAgent, userAgent: userAgent,
cashPriceSubTotal: cashPriceSubTotal, cashPriceSubTotal: cashPriceSubTotal,
billToAccountNumber: billToAccountNumber, billToAccountNumber: billToAccountNumber,
bailoutCode: bailoutCode,
}; };
return globalMethods.callHttpClient({ return globalMethods.callHttpClient({
@ -3959,10 +3958,6 @@ export const actions = {
context.commit(storeMutations.UPDATE_IDEMPOTENCY_KEY, newKey); context.commit(storeMutations.UPDATE_IDEMPOTENCY_KEY, newKey);
} }
}, },
saveBailoutCode(context, bailoutCode) {
context.commit(storeMutations.UPDATE_BAILOUT_CODE, bailoutCode);
},
}; };
export default createStore({ export default createStore({