diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js index 968478932..cce109c43 100644 --- a/src/constants/store-mutations.js +++ b/src/constants/store-mutations.js @@ -18,6 +18,7 @@ const storeMutations = { UPDATE_GLASS_TO_REPLACE: "updateGlassToReplace", UPDATE_PART_QUESTION_ANSWERS: "updatePartQuestionAnswers", UPDATE_GLASS_PARTS: "updateGlassParts", + UPDATE_OTHER_PARTS: "updateOtherParts", UPDATE_REGISTRATION_LICENSE_PLATE : "updateRegistrationLicensePlate", UPDATE_REGISTRATION_ADDRESS: "updateRegistrationAddress", diff --git a/src/router/index.js b/src/router/index.js index 2e1bca1b1..d14a692f0 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -37,13 +37,7 @@ const routes = [ return next(false); } - if (!store.getters.applicationUser.triggeredSiteEntry) { - baseMixin.methods.dispatchStoreAction(storeActions.RUN_EXPERIMENTS_FOR_TRIGGER, { - userId: getDeviceIdValue(), - triggerEvent: experimentTriggers.SITE_ENTRY, - triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE - }) - } + runExperiments(to.query.fmgPage); // If the saved session has timed out, clear the session, execute 404 logic. if (getFunnelCookie() !== null && !isSavedSessionStillActive()) { @@ -274,4 +268,19 @@ function arePagePrerequisitesValid(component) { return component.default.methods.arePagePrerequisitesValid(); } +// Run SiteEntry and PageEntry triggers for experiments +function runExperiments(nextPage) { + baseMixin.methods.dispatchStoreAction(storeActions.RUN_EXPERIMENTS_FOR_TRIGGER, { + userId: getDeviceIdValue(), + triggerEvent: experimentTriggers.SITE_ENTRY, + triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE + }) + + baseMixin.methods.dispatchStoreAction(storeActions.RUN_EXPERIMENTS_FOR_TRIGGER, { + userId: getDeviceIdValue(), + triggerEvent: experimentTriggers.PAGE_ENTRY, + triggerValue: nextPage + }) +} + export default router; diff --git a/src/store/index.js b/src/store/index.js index 64d451f63..6e97cc9f6 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -129,6 +129,9 @@ export const mutations = { updateGlassParts(state, partsData) { state.order.lineItems.glassParts = partsData; }, + updateOtherParts(state, partsData) { + state.order.lineItems.otherParts = partsData; + }, updatePageData(state, pageData) { state.applicationUser.pageData[pageData.page] = pageData.data; }, @@ -379,7 +382,7 @@ export const getters = { isCoverageVerified: state.order.payment.insuranceCoverage.isVerified, orderPartNumbers: [...getAllValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "partNumber"), ...getAllValuesOfPropertyInArrayOfObjects(state.order.lineItems.otherParts, "partNumber")], orderPartTypes: [...getAllValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "recalibrationType"), ...getAllValuesOfPropertyInArrayOfObjects(state.order.lineItems.otherParts, "recalibrationType")], - hasRecalibrationPart: (state.order.lineItems.glassParts ?? []).filter(x => x.requiresRecalibration)?.length > 0, + hasRecalibrationPart: getAllValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "requiresRecalibration")?.length > 0, selectedMultiGlass: state.order.damage.glassToReplace?.length > 1, selectedWindshieldGlass: getAllValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.WINDSHIELD), selectedBackGlass: getAllValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.REAR), @@ -645,6 +648,11 @@ export const actions = { runExperimentsForTrigger(context, { userId, triggerEvent, triggerValue }) { if (triggerEvent == experimentTriggers.SITE_ENTRY) { + // If site entry event was already triggered, don't rerun experiments + if (context.getters.applicationUser.triggeredSiteEntry) { + return; + } + context.commit(storeMutations.UPDATE_TRIGGERED_SITE_ENTRY, true); } diff --git a/src/store/store.spec.js b/src/store/store.spec.js index 84b8a46d7..c3162b878 100644 --- a/src/store/store.spec.js +++ b/src/store/store.spec.js @@ -2,6 +2,8 @@ import globalMethods from "@/global-methods"; import { mutations, state, actions, getters } from "@/store"; import { storeMutations } from "@/constants/store-mutations"; import { storeActions } from "@/constants/store-actions"; +import { experimentTriggers } from "@/constants/experiments"; + // Mock global method globalMethods.callHttpClient = jest.fn(); @@ -249,6 +251,36 @@ describe("Mutations", () => { expect(storeState.order.payment.insuranceCoverage.isVerified).toEqual(true); }); + it("updateExperiments, should set experiments", () => { + // Arrange + const storeState = state; + const mockExperimentsList = [ + { + universeName: "XYZ", + settings: { + ExperimentSetting: "ExperimentValue" + } + } + ] + + // Act + mutations.updateExperiments(storeState, mockExperimentsList); + + // Assert + expect(storeState.applicationUser.experiments).toEqual(mockExperimentsList); + }); + + it("updateTriggeredSiteEntry, should set triggeredSiteEntry", () => { + // Arrange + const storeState = state; + + // Act + mutations.updateTriggeredSiteEntry(storeState, true); + + // Assert + expect(storeState.applicationUser.triggeredSiteEntry).toEqual(true); + }); + }); describe("Actions", () => { @@ -1056,6 +1088,86 @@ describe("Actions", () => { }); + describe("runExperimentsForTrigger", () => { + beforeEach(() => { + mutations.resetState(state); + globalMethods.callHttpClient = jest.fn(); + }) + + test("triggerEvent is SiteEntry => set triggeredSiteEntry to true in store", () => { + // Arrange + const context = state; + context.commit = jest.fn().mockImplementation((storeMutation, value) => mutations[storeMutation](context, value)); + context.getters = { + ...getters, + applicationUser: getters.applicationUser(context) + }; + + // Act + actions.runExperimentsForTrigger(context, { + triggerEvent: experimentTriggers.SITE_ENTRY, + }); + + // Assert + expect(context.commit).toHaveBeenCalledWith(storeMutations.UPDATE_TRIGGERED_SITE_ENTRY, true); + expect(context.getters.applicationUser.triggeredSiteEntry).toBe(true); + expect(globalMethods.callHttpClient).toHaveBeenCalled(); + }); + + test("runExperimentsForTrigger is called twice with triggerEvent == SiteEntry => only update store's triggeredSiteEntry value once", () => { + // Arrange + const context = state; + context.commit = jest.fn().mockImplementation((storeMutation, value) => mutations[storeMutation](context, value)); + context.getters = { + ...getters, + applicationUser: getters.applicationUser(context) + }; + expect(context.commit).toHaveBeenCalledTimes(0); + + // Act + actions.runExperimentsForTrigger(context, { + triggerEvent: experimentTriggers.SITE_ENTRY, + }); + + // Assert + expect(context.commit).toHaveBeenCalledTimes(1); + expect(context.commit).toHaveBeenCalledWith(storeMutations.UPDATE_TRIGGERED_SITE_ENTRY, true); + expect(context.getters.applicationUser.triggeredSiteEntry).toBe(true); + + // Act + actions.runExperimentsForTrigger(context, { + triggerEvent: experimentTriggers.SITE_ENTRY, + }); + + // Assert + expect(context.commit).toHaveBeenCalledTimes(1); + expect(context.getters.applicationUser.triggeredSiteEntry).toBe(true); + + expect(globalMethods.callHttpClient).toHaveBeenCalledTimes(1); + }); + + test("triggerEvent is not SiteEntry => triggeredSiteEntry is false in store", () => { + // Arrange + const context = state; + context.commit = jest.fn().mockImplementation((storeMutation, value) => mutations[storeMutation](context, value)); + context.getters = { + ...getters, + applicationUser: getters.applicationUser(context) + }; + expect(context.commit).toHaveBeenCalledTimes(0); + + // Act + actions.runExperimentsForTrigger(context, { + triggerEvent: "NotSiteEntry", + }); + + // Assert + expect(context.commit).not.toHaveBeenCalled(); + expect(context.getters.applicationUser.triggeredSiteEntry).toBe(false); + + expect(globalMethods.callHttpClient).toHaveBeenCalledTimes(1); + }); + }) }); @@ -1153,4 +1265,321 @@ describe("Getters", () => { expect(getters.payment(storeState).insuranceCoverage.isVerified).toEqual(true); }); + describe("experimentOrder", () => { + test("glassToReplace, glassParts, and otherParts are null > return correct experimentOrder values", () => { + // Arrange + const storeState = state; + const mockStateValues = { + vehicleYear: 1000, + vehicleMake: "CarMake", + vehicleModel: "CarModel", + vehicleStyle: "SuperCoolStyle", + isRepair: true, + numberOfChips: 9999999, + carId: "Gibberish", + serviceCity: "Columbus", + serviceState: "OH-IO", + serviceZipCode: 43215, + parentAccountNumber: "999999", + isCoverageVerified: true, + glassParts: null, + otherParts: null, + glassToReplace: null + } + + //Act + mutations.updateYear(storeState, mockStateValues.vehicleYear); + mutations.updateMake(storeState, mockStateValues.vehicleMake); + mutations.updateModel(storeState, mockStateValues.vehicleModel); + mutations.updateStyle(storeState, mockStateValues.vehicleStyle); + mutations.updateIsRepair(storeState, mockStateValues.isRepair); + mutations.updateNumberOfChips(storeState, mockStateValues.numberOfChips); + mutations.updateCarId(storeState, mockStateValues.carId); + mutations.updateServiceLocation(storeState, { + city: mockStateValues.serviceCity, + state: mockStateValues.serviceState, + zipCode: mockStateValues.serviceZipCode + }); + mutations.updateParentAcctNumber(storeState, mockStateValues.parentAccountNumber); + mutations.updateInsuranceVerifiedStatus(storeState, mockStateValues.isCoverageVerified); + mutations.updateGlassParts(storeState, mockStateValues.glassParts); + mutations.updateOtherParts(storeState, mockStateValues.otherParts); + mutations.updateGlassToReplace(storeState, mockStateValues.glassToReplace); + + //Assert + expect(getters.experimentOrder(storeState)).toEqual({ + vehicleYear: mockStateValues.vehicleYear, + vehicleMake: mockStateValues.vehicleMake, + vehicleModel: mockStateValues.vehicleModel, + vehicleStyle: mockStateValues.vehicleStyle, + isRepair: mockStateValues.isRepair, + numberOfChips: mockStateValues.numberOfChips, + carId: mockStateValues.carId, + serviceCity: mockStateValues.serviceCity, + serviceState: mockStateValues.serviceState, + serviceZipCode: mockStateValues.serviceZipCode, + parentAccountNumber: mockStateValues.parentAccountNumber, + isCoverageVerified: mockStateValues.isCoverageVerified, + orderPartNumbers: [], + orderPartTypes: [], + hasRecalibrationPart: false, + selectedMultiGlass: false, + selectedWindshieldGlass: false, + selectedBackGlass: false, + selectedDriverSideGlass: false, + selectedPassengerSideGlass: false + }); + }); + + test("glassToReplace, glassParts, and otherParts are empty > return correct experimentOrder values", () => { + // Arrange + const storeState = state; + const mockStateValues = { + vehicleYear: 1000, + vehicleMake: "CarMake", + vehicleModel: "CarModel", + vehicleStyle: "SuperCoolStyle", + isRepair: true, + numberOfChips: 9999999, + carId: "Gibberish", + serviceCity: "Columbus", + serviceState: "OH-IO", + serviceZipCode: 43215, + parentAccountNumber: "999999", + isCoverageVerified: true, + glassParts: [], + otherParts: [], + glassToReplace: [] + } + + //Act + mutations.updateYear(storeState, mockStateValues.vehicleYear); + mutations.updateMake(storeState, mockStateValues.vehicleMake); + mutations.updateModel(storeState, mockStateValues.vehicleModel); + mutations.updateStyle(storeState, mockStateValues.vehicleStyle); + mutations.updateIsRepair(storeState, mockStateValues.isRepair); + mutations.updateNumberOfChips(storeState, mockStateValues.numberOfChips); + mutations.updateCarId(storeState, mockStateValues.carId); + mutations.updateServiceLocation(storeState, { + city: mockStateValues.serviceCity, + state: mockStateValues.serviceState, + zipCode: mockStateValues.serviceZipCode + }); + mutations.updateParentAcctNumber(storeState, mockStateValues.parentAccountNumber); + mutations.updateInsuranceVerifiedStatus(storeState, mockStateValues.isCoverageVerified); + mutations.updateGlassParts(storeState, mockStateValues.glassParts); + mutations.updateOtherParts(storeState, mockStateValues.otherParts); + mutations.updateGlassToReplace(storeState, mockStateValues.glassToReplace); + + //Assert + expect(getters.experimentOrder(storeState)).toEqual({ + vehicleYear: mockStateValues.vehicleYear, + vehicleMake: mockStateValues.vehicleMake, + vehicleModel: mockStateValues.vehicleModel, + vehicleStyle: mockStateValues.vehicleStyle, + isRepair: mockStateValues.isRepair, + numberOfChips: mockStateValues.numberOfChips, + carId: mockStateValues.carId, + serviceCity: mockStateValues.serviceCity, + serviceState: mockStateValues.serviceState, + serviceZipCode: mockStateValues.serviceZipCode, + parentAccountNumber: mockStateValues.parentAccountNumber, + isCoverageVerified: mockStateValues.isCoverageVerified, + orderPartNumbers: [], + orderPartTypes: [], + hasRecalibrationPart: false, + selectedMultiGlass: false, + selectedWindshieldGlass: false, + selectedBackGlass: false, + selectedDriverSideGlass: false, + selectedPassengerSideGlass: false + }); + }); + + test("Single windshield requiring recalibration is selected > return correct experimentOrder values", () => { + // Arrange + const storeState = state; + const mockStateValues = { + vehicleYear: 1000, + vehicleMake: "CarMake", + vehicleModel: "CarModel", + vehicleStyle: "SuperCoolStyle", + isRepair: false, + numberOfChips: 0, + carId: "Gibberish", + serviceCity: "Columbus", + serviceState: "OH-IO", + serviceZipCode: 43215, + parentAccountNumber: "999999", + isCoverageVerified: false, + glassParts: [ + { + partNumber: "WINDSHIELDPARTNUMBER", + description: "This is a windshield", + recalibrationType: "ADAS, maybe", + requiresRecalibration: true, + requiresCapabilityQuestions: false + } + ], + otherParts: [ + + ], + glassToReplace: [ + { + glassLocation: "Windshield", + glassName: "Single" + } + ] + } + + //Act + mutations.updateYear(storeState, mockStateValues.vehicleYear); + mutations.updateMake(storeState, mockStateValues.vehicleMake); + mutations.updateModel(storeState, mockStateValues.vehicleModel); + mutations.updateStyle(storeState, mockStateValues.vehicleStyle); + mutations.updateIsRepair(storeState, mockStateValues.isRepair); + mutations.updateNumberOfChips(storeState, mockStateValues.numberOfChips); + mutations.updateCarId(storeState, mockStateValues.carId); + mutations.updateServiceLocation(storeState, { + city: mockStateValues.serviceCity, + state: mockStateValues.serviceState, + zipCode: mockStateValues.serviceZipCode + }); + mutations.updateParentAcctNumber(storeState, mockStateValues.parentAccountNumber); + mutations.updateInsuranceVerifiedStatus(storeState, mockStateValues.isCoverageVerified); + mutations.updateGlassParts(storeState, mockStateValues.glassParts); + mutations.updateOtherParts(storeState, mockStateValues.otherParts); + mutations.updateGlassToReplace(storeState, mockStateValues.glassToReplace); + + //Assert + expect(getters.experimentOrder(storeState)).toEqual({ + vehicleYear: mockStateValues.vehicleYear, + vehicleMake: mockStateValues.vehicleMake, + vehicleModel: mockStateValues.vehicleModel, + vehicleStyle: mockStateValues.vehicleStyle, + isRepair: mockStateValues.isRepair, + numberOfChips: mockStateValues.numberOfChips, + carId: mockStateValues.carId, + serviceCity: mockStateValues.serviceCity, + serviceState: mockStateValues.serviceState, + serviceZipCode: mockStateValues.serviceZipCode, + parentAccountNumber: mockStateValues.parentAccountNumber, + isCoverageVerified: mockStateValues.isCoverageVerified, + orderPartNumbers: ["WINDSHIELDPARTNUMBER"], + orderPartTypes: ["ADAS, maybe"], + hasRecalibrationPart: true, + selectedMultiGlass: false, + selectedWindshieldGlass: true, + selectedBackGlass: false, + selectedDriverSideGlass: false, + selectedPassengerSideGlass: false + }); + }); + + test("Select multiglass > return correct experimentOrder values", () => { + // Arrange + const storeState = state; + const mockStateValues = { + vehicleYear: 1000, + vehicleMake: "CarMake", + vehicleModel: "CarModel", + vehicleStyle: "SuperCoolStyle", + isRepair: false, + numberOfChips: 0, + carId: "Gibberish", + serviceCity: "Columbus", + serviceState: "OH-IO", + serviceZipCode: 43215, + parentAccountNumber: "999999", + isCoverageVerified: false, + glassParts: [ + { + partNumber: "BACKGLASS_PN", + description: "This is a back glass", + recalibrationType: null, + requiresRecalibration: false, + requiresCapabilityQuestions: false + }, + { + partNumber: "DRIVERGLASS_PN", + description: "This is a driver side glass", + recalibrationType: null, + requiresRecalibration: false, + requiresCapabilityQuestions: false + }, + { + partNumber: "PASSENGERGLASS_PN", + description: "This is a passenger side glass", + recalibrationType: null, + requiresRecalibration: false, + requiresCapabilityQuestions: false + } + ], + otherParts: [ + + ], + glassToReplace: [ + { + glassLocation: "Rear", + glassName: "Stationary" + }, + { + glassLocation: "Driver", + glassName: "Front" + }, + { + glassLocation: "Passenger", + glassName: "Front" + }, + { + glassLocation: "Passenger", + glassName: "Quarter" + } + ] + } + + //Act + mutations.updateYear(storeState, mockStateValues.vehicleYear); + mutations.updateMake(storeState, mockStateValues.vehicleMake); + mutations.updateModel(storeState, mockStateValues.vehicleModel); + mutations.updateStyle(storeState, mockStateValues.vehicleStyle); + mutations.updateIsRepair(storeState, mockStateValues.isRepair); + mutations.updateNumberOfChips(storeState, mockStateValues.numberOfChips); + mutations.updateCarId(storeState, mockStateValues.carId); + mutations.updateServiceLocation(storeState, { + city: mockStateValues.serviceCity, + state: mockStateValues.serviceState, + zipCode: mockStateValues.serviceZipCode + }); + mutations.updateParentAcctNumber(storeState, mockStateValues.parentAccountNumber); + mutations.updateInsuranceVerifiedStatus(storeState, mockStateValues.isCoverageVerified); + mutations.updateGlassParts(storeState, mockStateValues.glassParts); + mutations.updateOtherParts(storeState, mockStateValues.otherParts); + mutations.updateGlassToReplace(storeState, mockStateValues.glassToReplace); + + //Assert + expect(getters.experimentOrder(storeState)).toEqual({ + vehicleYear: mockStateValues.vehicleYear, + vehicleMake: mockStateValues.vehicleMake, + vehicleModel: mockStateValues.vehicleModel, + vehicleStyle: mockStateValues.vehicleStyle, + isRepair: mockStateValues.isRepair, + numberOfChips: mockStateValues.numberOfChips, + carId: mockStateValues.carId, + serviceCity: mockStateValues.serviceCity, + serviceState: mockStateValues.serviceState, + serviceZipCode: mockStateValues.serviceZipCode, + parentAccountNumber: mockStateValues.parentAccountNumber, + isCoverageVerified: mockStateValues.isCoverageVerified, + orderPartNumbers: ["BACKGLASS_PN", "DRIVERGLASS_PN", "PASSENGERGLASS_PN"], + orderPartTypes: [], + hasRecalibrationPart: false, + selectedMultiGlass: true, + selectedWindshieldGlass: false, + selectedBackGlass: true, + selectedDriverSideGlass: true, + selectedPassengerSideGlass: true + }); + }); + }) }); \ No newline at end of file