Merge branch 'develop' into feature/SSR-129
This commit is contained in:
commit
5d56f6ed7a
5 changed files with 307 additions and 69 deletions
|
|
@ -112,10 +112,12 @@ describe("analyticsMixin.js", () => {
|
|||
// Assert
|
||||
expect(expectedDataLayer).toEqual(expect.arrayContaining(window.dataLayer));
|
||||
});
|
||||
/*
|
||||
|
||||
test("Experiments, should push to dataLayer with default Google Custom Dimension Index", () => {
|
||||
// Arrange
|
||||
window.dataLayer = [];
|
||||
const store = useMainStore();
|
||||
|
||||
window.dataLayer = [];
|
||||
|
||||
const mockExperimentData = [
|
||||
{
|
||||
|
|
@ -125,26 +127,7 @@ describe("analyticsMixin.js", () => {
|
|||
},
|
||||
];
|
||||
|
||||
// Mock store
|
||||
jest.mock(
|
||||
"@/store",
|
||||
() => {
|
||||
return {};
|
||||
},
|
||||
{ virtual: true }
|
||||
);
|
||||
|
||||
useMainStore.getters = {
|
||||
applicationUserObj: {
|
||||
experiments: [
|
||||
{
|
||||
settings: {},
|
||||
variationName: "test",
|
||||
universeName: "testUniverse",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
store.applicationUserObj.experiments = mockExperimentData;
|
||||
|
||||
// Act
|
||||
analyticsMixin.methods.pushExperimentsToDataLayer(mockExperimentData);
|
||||
|
|
@ -163,7 +146,9 @@ describe("analyticsMixin.js", () => {
|
|||
|
||||
test("Experiments, should push to dataLayer with custom Google Custom Dimension Index", () => {
|
||||
// Arrange
|
||||
window.dataLayer = [];
|
||||
const store = useMainStore();
|
||||
|
||||
window.dataLayer = [];
|
||||
|
||||
const mockExperimentData = [
|
||||
{
|
||||
|
|
@ -173,26 +158,7 @@ describe("analyticsMixin.js", () => {
|
|||
},
|
||||
];
|
||||
|
||||
// Mock store
|
||||
jest.mock(
|
||||
"@/store",
|
||||
() => {
|
||||
return {};
|
||||
},
|
||||
{ virtual: true }
|
||||
);
|
||||
|
||||
useMainStore.getters = {
|
||||
applicationUserObj: {
|
||||
experiments: [
|
||||
{
|
||||
settings: { "Google Custom Dimension Index": "5" },
|
||||
variationName: "test",
|
||||
universeName: "testUniverse",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
store.applicationUserObj.experiments = mockExperimentData;
|
||||
|
||||
// Act
|
||||
analyticsMixin.methods.pushExperimentsToDataLayer(mockExperimentData);
|
||||
|
|
@ -208,7 +174,7 @@ describe("analyticsMixin.js", () => {
|
|||
},
|
||||
]);
|
||||
});
|
||||
*/
|
||||
|
||||
test("Obj is not null after action prepended", () => {
|
||||
//Arrange
|
||||
const obj = { baseMethodName: "testMethodName", data: "testData" };
|
||||
|
|
|
|||
|
|
@ -3,14 +3,14 @@ import { useMainStore } from "@/store";
|
|||
export default {
|
||||
methods: {
|
||||
hasSettingEqualTo(settingName, settingValue) {
|
||||
return useMainStore.experimentSettings[settingName] == settingValue;
|
||||
return useMainStore().experimentSettings[settingName] == settingValue;
|
||||
},
|
||||
hasSetting(settingName) {
|
||||
return Object.hasOwn( useMainStore.experimentSettings, settingName);
|
||||
return Object.hasOwn( useMainStore().experimentSettings, settingName);
|
||||
},
|
||||
getSettingValue(settingName) {
|
||||
return this.hasSetting(settingName)
|
||||
? useMainStore.experimentSettings[settingName]
|
||||
? useMainStore().experimentSettings[settingName]
|
||||
: null;
|
||||
},
|
||||
},
|
||||
|
|
|
|||
155
src/mixins/experiment-mixin.spec.js
Normal file
155
src/mixins/experiment-mixin.spec.js
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
import experimentMixin from "@/mixins/experiment-mixin";
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import { useMainStore } from "@/store";
|
||||
|
||||
describe("experiment-mixin", () => {
|
||||
describe("hasSettingEqualTo", () => {
|
||||
test("setting exists and value matches => return true", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.hasSettingEqualTo("Setting2", "Value2");
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
test("setting exists and value does not match => return false", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.hasSettingEqualTo("Setting2", "Value3");
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
test("setting does not exist => return false", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.hasSettingEqualTo("SettingBoogly", "Woogly");
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
test("there are no settings => return false", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({ experimentSettings: {} });
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.hasSettingEqualTo("Setting2", "Value3");
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("hasSetting", () => {
|
||||
test("has setting => return true", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.hasSetting("Setting4");
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(true);
|
||||
});
|
||||
|
||||
test("does not have setting => return false", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.hasSetting("BooglyWoogly");
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
test("experimentSettings is empty => return false", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({ experimentSettings: {} });
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.hasSetting("Setting4");
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSettingValue", () => {
|
||||
test("has setting => return correct value", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.getSettingValue("Setting3");
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual("Value3");
|
||||
});
|
||||
|
||||
test("does not have setting => return null", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.getSettingValue("Hello");
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
test("experimentSettings is empty => return null", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({ experimentSettings: {} });
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.getSettingValue("Hello");
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks({ experimentSettings }) {
|
||||
const store = useMainStore();
|
||||
const mocks = getMountOptions({});
|
||||
|
||||
const testExperimentSettings = {
|
||||
Setting1: "Value1",
|
||||
Setting2: "Value2",
|
||||
Setting3: "Value3",
|
||||
Setting4: "Value1",
|
||||
Setting5: "Value2",
|
||||
Setting6: "Value3",
|
||||
};
|
||||
|
||||
const mockExperimentData = [
|
||||
{
|
||||
settings: experimentSettings ?? testExperimentSettings,
|
||||
variationName: "test",
|
||||
universeName: "testUniverse",
|
||||
},
|
||||
];
|
||||
|
||||
store.applicationUser.experiments = mockExperimentData;
|
||||
|
||||
const mockComponent = {
|
||||
template: "<div></div>",
|
||||
mixins: [experimentMixin],
|
||||
};
|
||||
|
||||
const wrapper = shallowMount(mockComponent, mocks);
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
@ -6,6 +6,13 @@ import { useMainStore } from '@/store';
|
|||
import eventBus from "@/helpers/event-bus/event-bus";
|
||||
import { globalEvents, globalEventTypes } from "@/constants/events";
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
import {
|
||||
getDeviceIdValue,
|
||||
updateOrCreateISSCookie,
|
||||
updateSessionIdCookie,
|
||||
} from "@/helpers/cookie-helper";
|
||||
import { experimentTriggers } from "@/constants/experiments";
|
||||
import { applicationConfig } from "@/constants/application-config";
|
||||
|
||||
import analyticsMixin from "@/mixins/analytics-mixin";
|
||||
|
||||
|
|
@ -14,8 +21,19 @@ const routes = [
|
|||
path: '/',
|
||||
name: 'root',
|
||||
async beforeEnter(to, from, next) {
|
||||
try {
|
||||
try {
|
||||
to.query.issPage = !to.query.issPage ? issPageValues.VEHICLE_YEAR : to.query.issPage;
|
||||
|
||||
if (analyticsMixin.methods.noSession()) {
|
||||
await analyticsMixin.methods.initSession();
|
||||
} else {
|
||||
updateSessionIdCookie();
|
||||
}
|
||||
|
||||
await runExperiments(to.query.issPage);
|
||||
|
||||
// Process ISS cookie.
|
||||
updateOrCreateISSCookie();
|
||||
|
||||
if (router.hasRoute(to.query.issPage)) {
|
||||
return next({ name: to.query.issPage, query: to.query, params: to.params });
|
||||
|
|
@ -195,4 +213,23 @@ function GoToStartOn404(next) {
|
|||
|
||||
};
|
||||
|
||||
// Run SiteEntry and PageEntry triggers for experiments
|
||||
async function runExperiments(nextPage) {
|
||||
const store = useMainStore();
|
||||
|
||||
if (!store.applicationUser.triggeredSiteEntry) {
|
||||
await store.runExperimentsForTrigger({
|
||||
userId: getDeviceIdValue(),
|
||||
triggerEvent: experimentTriggers.SITE_ENTRY,
|
||||
triggerValue: applicationConfig.SITE_ENTRY_TRIGGER_VALUE,
|
||||
});
|
||||
}
|
||||
|
||||
await store.runExperimentsForTrigger({
|
||||
userId: getDeviceIdValue(),
|
||||
triggerEvent: experimentTriggers.PAGE_ENTRY,
|
||||
triggerValue: nextPage,
|
||||
});
|
||||
}
|
||||
|
||||
export default router;
|
||||
|
|
@ -2,8 +2,10 @@ import { defineStore } from "pinia";
|
|||
import { endpoints } from "@/constants/endpoints.js";
|
||||
import { getDateForSavedSessionTimeout } from "@/helpers/session-helper";
|
||||
import globalMethods from "@/global-methods";
|
||||
import { experimentTriggers } from "@/constants/experiments";
|
||||
import { applicationConfig } from "@/constants/application-config";
|
||||
import { issPageValues } from "@/router/router-constants/issPage-values";
|
||||
import { damageLocationsSelected } from "@/constants/damage-locations-selected";
|
||||
|
||||
const storeId = 'main';
|
||||
|
||||
|
|
@ -49,6 +51,18 @@ const getDefaultState = () => {
|
|||
glassParts: null,
|
||||
otherParts: null
|
||||
},
|
||||
payment: {
|
||||
isInsurance: true,
|
||||
insuranceCoverage: {
|
||||
isVerified: false,
|
||||
},
|
||||
},
|
||||
serviceLocation: {
|
||||
address: null,
|
||||
city: null,
|
||||
state: null,
|
||||
zipCode: null,
|
||||
},
|
||||
referralNumber: null,
|
||||
referralDate: null,
|
||||
accountNumber: 0,
|
||||
|
|
@ -92,38 +106,37 @@ export const useMainStore = defineStore({
|
|||
},
|
||||
experimentOrder: (state) => {
|
||||
return {
|
||||
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,
|
||||
funnelParentAccountNumber: state.order.accountNumber,
|
||||
funnelIsCoverageVerified: state.order.payment.insuranceCoverage.isVerified,
|
||||
funnelHasRecalibrationPart: getHasRecalibrationPart(state),
|
||||
funnelSelectedMultiGlass: state.order.damage.glassToReplace?.length > 1,
|
||||
funnelSelectedWindshieldGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||
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"
|
||||
).includes(damageLocationsSelected.WINDSHIELD),
|
||||
funnelSelectedBackGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||
issSelectedBackGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||
state.order.damage.glassToReplace,
|
||||
"glassLocation"
|
||||
).includes(damageLocationsSelected.REAR),
|
||||
funnelSelectedDriverSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||
issSelectedDriverSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||
state.order.damage.glassToReplace,
|
||||
"glassLocation"
|
||||
).includes(damageLocationsSelected.DRIVER),
|
||||
funnelSelectedPassengerSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||
issSelectedPassengerSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||
state.order.damage.glassToReplace,
|
||||
"glassLocation"
|
||||
).includes(damageLocationsSelected.PASSENGER),
|
||||
|
||||
funnelOrderPartNumbers: [
|
||||
issOrderPartNumbers: [
|
||||
...getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||
state.order.lineItems.glassParts,
|
||||
"partNumber"
|
||||
|
|
@ -134,7 +147,7 @@ export const useMainStore = defineStore({
|
|||
),
|
||||
],
|
||||
|
||||
funnelOrderPartTypes: [
|
||||
issOrderPartTypes: [
|
||||
...getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||
state.order.lineItems.glassParts,
|
||||
"recalibrationType"
|
||||
|
|
@ -582,6 +595,13 @@ export const useMainStore = defineStore({
|
|||
this.applicationUser.lastPageVisited = lastPageVisited;
|
||||
},
|
||||
|
||||
updateExperiments(experiments) {
|
||||
this.applicationUser.experiments = experiments;
|
||||
},
|
||||
updateTriggeredSiteEntry(wasSiteEntryTriggered) {
|
||||
this.applicationUser.triggeredSiteEntry = wasSiteEntryTriggered;
|
||||
},
|
||||
|
||||
GetExperimentsByUser(userId) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetExperimentsByUser.method,
|
||||
|
|
@ -589,12 +609,72 @@ export const useMainStore = defineStore({
|
|||
payload: {},
|
||||
});
|
||||
},
|
||||
|
||||
async runExperimentsForTrigger({ userId, triggerEvent, triggerValue }) {
|
||||
if (triggerEvent == experimentTriggers.SITE_ENTRY) {
|
||||
this.updateTriggeredSiteEntry(true);
|
||||
}
|
||||
|
||||
var payload = {
|
||||
applicationName: applicationConfig.APPLICATION_NAME,
|
||||
userId: userId,
|
||||
triggerEvent: triggerEvent,
|
||||
triggerValue: triggerValue,
|
||||
experimentOrder: this.experimentOrder,
|
||||
};
|
||||
|
||||
const response = await globalMethods.callHttpClient({
|
||||
method: endpoints.RunExperimentsForTrigger.method,
|
||||
endpoint: endpoints.RunExperimentsForTrigger.url,
|
||||
payload: payload,
|
||||
});
|
||||
|
||||
this.updateExperiments(response.data.experiments);
|
||||
},
|
||||
|
||||
},
|
||||
persist: true
|
||||
});
|
||||
|
||||
|
||||
// Private Functions
|
||||
|
||||
function getHasRecalibrationPart(state) {
|
||||
var hasRequiresRecalibration =
|
||||
getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||
state.order.lineItems.glassParts,
|
||||
"requiresRecalibration"
|
||||
)?.length > 0;
|
||||
var hasRecalibrationType =
|
||||
getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||
state.order.lineItems.glassParts,
|
||||
"recalibrationType"
|
||||
)?.length > 0;
|
||||
|
||||
if (hasRequiresRecalibration) {
|
||||
if (hasRecalibrationType) {
|
||||
// Has both 'requiresRecalibration' and 'recalibrationType' and 'recalibrationType'
|
||||
return (
|
||||
getNonFalseValuesOfPropertyInArrayOfObjects(
|
||||
state.order.lineItems.glassParts,
|
||||
"recalibrationType"
|
||||
)[0].toLowerCase() != "unknown"
|
||||
);
|
||||
} else {
|
||||
// Has 'requiresRecalibration' but no 'recalibrationType' at all
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Does not have 'requiresRecalibration'
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
|
||||
return (array ?? []).map((x) => x[propertyName]).filter((x) => x);
|
||||
}
|
||||
|
||||
|
||||
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
|
||||
if (!arrayOfObjects) return null;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue