Initial adds for analytics and logging.
This commit is contained in:
parent
05844d2500
commit
ce5b402cbd
14 changed files with 2151 additions and 0 deletions
36
src/constants/analytics.js
Normal file
36
src/constants/analytics.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
const analyticsPageEvents = {
|
||||
ENTRY: "ENTRY",
|
||||
EVENT: "EVENT",
|
||||
};
|
||||
|
||||
// GA Constants
|
||||
const GaEvents = {
|
||||
GENERIC_EVENT: "event",
|
||||
PAGE_VIEW_EVENT: "logPageview",
|
||||
};
|
||||
|
||||
const GaCategories = {
|
||||
API_RESPONSE: "Api_Response",
|
||||
EVOX: "Evox",
|
||||
};
|
||||
|
||||
const GaActions = {
|
||||
RESULT: "Result",
|
||||
CLICKED: "Clicked",
|
||||
VIF: "vif",
|
||||
SUBMITTED: "Submitted",
|
||||
};
|
||||
|
||||
const GaLabels = {
|
||||
SUCCESS: "Success",
|
||||
ERROR: "Error",
|
||||
LICENSE_PLATE_LOOKUP: "License_Plate_Look_Up",
|
||||
VIN_LOOKUP: "Vin_Look_Up",
|
||||
ADDRESS_LOOKUP: "Address_Look_up",
|
||||
};
|
||||
|
||||
const ValueToLogTypes = {
|
||||
LAST_5: "last_5",
|
||||
};
|
||||
|
||||
export { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents, ValueToLogTypes };
|
||||
12
src/constants/cookie-names.js
Normal file
12
src/constants/cookie-names.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { applicationConfig } from "@/constants/application-config.js";
|
||||
|
||||
const cookieNames = {
|
||||
FUNNEL_SESSION_INFO: `FunnelSessionInfo-${applicationConfig.CURRENT_ENVIRONMENT}`,
|
||||
|
||||
// Existing Safelite.com cookies
|
||||
DXDEV: "dxdev",
|
||||
SESSION_ID: "sid",
|
||||
SESSION_KEY: "skey",
|
||||
};
|
||||
|
||||
export { cookieNames };
|
||||
14
src/constants/experiments.js
Normal file
14
src/constants/experiments.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
const experimentUniverses = {
|
||||
CONCEPT_FUNNEL: "ConceptFunnel",
|
||||
};
|
||||
|
||||
const experimentSettings = {
|
||||
GOOGLE_CUSTOM_DIMENSION_INDEX: "Google Custom Dimension Index",
|
||||
};
|
||||
|
||||
const experimentTriggers = {
|
||||
SITE_ENTRY: "SiteEntry",
|
||||
PAGE_ENTRY: "PageEntry",
|
||||
};
|
||||
|
||||
export { experimentUniverses, experimentSettings, experimentTriggers };
|
||||
209
src/helpers/heritage-integration/cookie-helper.js
Normal file
209
src/helpers/heritage-integration/cookie-helper.js
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
import { cookieNames } from "@/constants/cookie-names";
|
||||
import store from "@/store";
|
||||
import { applicationConfig } from "@/constants/application-config";
|
||||
|
||||
/*
|
||||
Will update the cookie if present, or create a new one if not.
|
||||
*/
|
||||
export function updateOrCreateFunnelCookie() {
|
||||
const wasClaimRegistrationDelayed = getFunnelCookie()?.HasDelayedClaimRegistration;
|
||||
const shouldSuppressConceptFunnel = getFunnelCookie()?.SuppressConceptFunnel;
|
||||
|
||||
// Set up cookie with all the props.
|
||||
setFunnelCookieProperties({
|
||||
LastTouched: new Date().toUTCString(),
|
||||
SavedSessionTimeoutDate: store.getters.applicationUser.savedSessionTimeout,
|
||||
DidHeritageFunnelUpdateLast: false,
|
||||
ShouldResetState: false,
|
||||
ReferralNumber: store.getters.order.referralNumber,
|
||||
ReferralDate: store.getters.order.referralDate,
|
||||
ReferralCorrelationId: store.getters.order.referralCorrelationId,
|
||||
ReferralParentAccountNumber: store.getters.order.accountNumber,
|
||||
HasDelayedClaimRegistration: wasClaimRegistrationDelayed,
|
||||
SuppressConceptFunnel: shouldSuppressConceptFunnel,
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Gets the current instance of the funnel cookie.
|
||||
Returns null if cookie isn't valid JSON.
|
||||
*/
|
||||
export function getFunnelCookie() {
|
||||
const cookieJson = document.cookie
|
||||
?.split("; ")
|
||||
?.find((row) => row.startsWith(`${cookieNames.FUNNEL_SESSION_INFO}=`))
|
||||
?.split("=")[1];
|
||||
|
||||
try {
|
||||
return JSON.parse(cookieJson);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Removes cookie from browser.
|
||||
*/
|
||||
export function deleteFunnelCookie() {
|
||||
createOrUpdateCookie(cookieNames.FUNNEL_SESSION_INFO, undefined, { maxAge: 0 });
|
||||
}
|
||||
|
||||
/*
|
||||
Gets cookie domain value. Localhost will be empty "".
|
||||
*/
|
||||
export function getCookieDomainValue() {
|
||||
return isLocalhost() ? "" : `domain=${getDomainWithoutSubdomain()};`;
|
||||
}
|
||||
|
||||
/*
|
||||
Gets value of dxdev cookie, and then extracts "did" value from it.
|
||||
Returns empty string if cookie not found or "did" string not present.
|
||||
*/
|
||||
export function getDeviceIdValue() {
|
||||
// Sometimes these cookie contains more than the device ID.
|
||||
const cookieValue = getCookieValueByName(cookieNames.DXDEV);
|
||||
const cookieValuesSplit = cookieValue.split("=");
|
||||
|
||||
// If this is the only value, just use that.
|
||||
if (cookieValuesSplit.length === 2 && cookieValuesSplit[0] === "did") {
|
||||
return cookieValuesSplit[1];
|
||||
}
|
||||
|
||||
const cookieValueMatch = cookieValue.match("^did=[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}");
|
||||
if (cookieValueMatch) {
|
||||
return cookieValueMatch[0].split("=")[1];
|
||||
}
|
||||
|
||||
return "00000000-0000-0000-0000-000000000000";
|
||||
}
|
||||
|
||||
/*
|
||||
Gets value of skey cookie, returns 0 if not found.
|
||||
*/
|
||||
export function getSessionKeyValue() {
|
||||
const cookieValue = getCookieValueByName(cookieNames.SESSION_KEY);
|
||||
|
||||
if (cookieValue) {
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
Gets value of skey cookie, returns 0 if not found.
|
||||
*/
|
||||
export function getSessionIdValue() {
|
||||
const cookieValue = getCookieValueByName(cookieNames.SESSION_ID);
|
||||
|
||||
if (cookieValue) {
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
return "00000000-0000-0000-0000-000000000000";
|
||||
}
|
||||
|
||||
/*
|
||||
Updates session ID cookie with new expiration date
|
||||
*/
|
||||
export function updateSessionIdCookie() {
|
||||
createOrUpdateCookie(cookieNames.SESSION_ID, getSessionIdValue(), { maxAge: 60 * 30 });
|
||||
}
|
||||
|
||||
export function setCookieProperties(
|
||||
properties,
|
||||
{ useDefaultFunnelCookieAttributes = true, maxAge, isSecure }
|
||||
) {
|
||||
if (typeof properties == "object") {
|
||||
Object.keys(properties).forEach((key) => {
|
||||
createOrUpdateCookie(key, properties[key], {
|
||||
useDefaultFunnelCookieAttributes,
|
||||
maxAge,
|
||||
isSecure,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
===========================
|
||||
= PRIVATE FUNCTIONS =
|
||||
===========================
|
||||
*/
|
||||
|
||||
/*
|
||||
Used to set properties on the funnel cookie.
|
||||
Takes an object with properties to set. Will overwrite existing properties.
|
||||
*/
|
||||
function setFunnelCookieProperties(properties) {
|
||||
if (typeof properties == "object") {
|
||||
let cookie = getFunnelCookie();
|
||||
|
||||
if (cookie !== null) {
|
||||
Object.keys(properties).forEach((key) => {
|
||||
cookie[key] = properties[key];
|
||||
});
|
||||
}
|
||||
|
||||
const cookieValueJson = JSON.stringify(cookie ?? {});
|
||||
createOrUpdateCookie(cookieNames.FUNNEL_SESSION_INFO, cookieValueJson, {});
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Used to create a cookie.
|
||||
`useDefaultFunnelCookieAttributes` will set the path and domain to our defaults
|
||||
*/
|
||||
function createOrUpdateCookie(
|
||||
key,
|
||||
value = "",
|
||||
{ useDefaultFunnelCookieAttributes = true, maxAge, isSecure = true }
|
||||
) {
|
||||
let cookieToAdd = `${key}=${value}; `;
|
||||
|
||||
if (useDefaultFunnelCookieAttributes) {
|
||||
cookieToAdd += `path=${applicationConfig.COOKIE_PATH}; ${getCookieDomainValue()} `;
|
||||
}
|
||||
if (isSecure && !isLocalhost()) {
|
||||
cookieToAdd += `secure; `;
|
||||
}
|
||||
if (!isNaN(maxAge)) {
|
||||
cookieToAdd += `max-age=${maxAge};`;
|
||||
}
|
||||
|
||||
document.cookie = cookieToAdd;
|
||||
}
|
||||
|
||||
/*
|
||||
Gets current domain without the subdomain for cookie.
|
||||
*/
|
||||
function getDomainWithoutSubdomain() {
|
||||
let url = location.hostname;
|
||||
if (isLocalhost()) {
|
||||
return "localhost";
|
||||
}
|
||||
|
||||
const urlParts = url.split(".");
|
||||
|
||||
return `.${urlParts
|
||||
.slice(0)
|
||||
.slice(-(urlParts.length === 4 ? 3 : 2))
|
||||
.join(".")}`;
|
||||
}
|
||||
|
||||
/*
|
||||
Gets cookie value by name, returns empty string if not found.
|
||||
*/
|
||||
function getCookieValueByName(name) {
|
||||
const value = "; " + document.cookie;
|
||||
const parts = value.split("; " + name + "=");
|
||||
|
||||
if (parts.length === 2) {
|
||||
return parts.pop().split(";").shift();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function isLocalhost() {
|
||||
return location.hostname.includes("localhost");
|
||||
}
|
||||
145
src/helpers/heritage-integration/cookie-helper.spec.js
Normal file
145
src/helpers/heritage-integration/cookie-helper.spec.js
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
import {
|
||||
getFunnelCookie,
|
||||
getDeviceIdValue,
|
||||
getSessionKeyValue,
|
||||
getSessionIdValue,
|
||||
} from "@/helpers/heritage-integration/cookie-helper.js";
|
||||
import { removeAllTestCookies, setupCookies } from "@/helpers/unit-test-helper";
|
||||
|
||||
describe("cookies", () => {
|
||||
afterEach(() => {
|
||||
removeAllTestCookies();
|
||||
});
|
||||
|
||||
describe("getFunnelCookie method", () => {
|
||||
test("gets correct value when cookie is present", () => {
|
||||
// Arrange
|
||||
const testReferralNumber = 1566818;
|
||||
const testReferralDate = "2022-03-15T10:56:24.597";
|
||||
const testReferralCorrelationId = "404d2b04-f86e-45c3-b373-127b6217b060";
|
||||
const testShouldResetState = false;
|
||||
const testDidHeritageFunnelUpdateLast = true;
|
||||
|
||||
const testCookieValue = {
|
||||
ReferralNumber: testReferralNumber,
|
||||
ReferralDate: testReferralDate,
|
||||
ReferralCorrelationId: testReferralCorrelationId,
|
||||
ShouldResetState: testShouldResetState,
|
||||
DidHeritageFunnelUpdateLast: testDidHeritageFunnelUpdateLast,
|
||||
SuppressConceptFunnel: true,
|
||||
};
|
||||
|
||||
setupCookies({ funnelCookieValue: JSON.stringify(testCookieValue) });
|
||||
|
||||
// Act
|
||||
var result = getFunnelCookie();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(testCookieValue);
|
||||
expect(typeof result).toEqual("object");
|
||||
expect(result.ReferralNumber).toEqual(testReferralNumber);
|
||||
expect(result.ReferralDate).toEqual(testReferralDate);
|
||||
expect(result.ReferralCorrelationId).toEqual(testReferralCorrelationId);
|
||||
expect(result.ShouldResetState).toEqual(testShouldResetState);
|
||||
expect(result.DidHeritageFunnelUpdateLast).toEqual(testDidHeritageFunnelUpdateLast);
|
||||
});
|
||||
|
||||
test("returns empty object when value is empty object", () => {
|
||||
// Arrange
|
||||
const testCookieValue = {};
|
||||
setupCookies({ funnelCookieValue: JSON.stringify(testCookieValue) });
|
||||
|
||||
// Act
|
||||
var result = getFunnelCookie();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(testCookieValue);
|
||||
expect(typeof result).toEqual("object");
|
||||
expect(Object.keys(result)).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("returns null when value is empty string", () => {
|
||||
// Arrange
|
||||
const testCookieValue = "";
|
||||
setupCookies({ funnelCookieValue: testCookieValue });
|
||||
|
||||
// Act
|
||||
var result = getFunnelCookie();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
test("returns null when funnel cookie doesn't exist", () => {
|
||||
// Arrange
|
||||
setupCookies({ includeHeritageCookie: false });
|
||||
|
||||
// Act
|
||||
var result = getFunnelCookie();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
|
||||
test("gets correct cookie value", () => {
|
||||
// Arrange
|
||||
const testCookieValue = { test: "testValue" };
|
||||
|
||||
setupCookies({ funnelCookieValue: JSON.stringify(testCookieValue) });
|
||||
|
||||
// Act
|
||||
const actualCookieValue = getFunnelCookie();
|
||||
|
||||
// Assert
|
||||
expect(actualCookieValue).toEqual(testCookieValue);
|
||||
});
|
||||
|
||||
test("getCookieValue: Gets null cookie value", () => {
|
||||
// Arrange
|
||||
setupCookies({ includeHeritageCookie: false });
|
||||
|
||||
// Act
|
||||
const actualCookieValue = getFunnelCookie();
|
||||
|
||||
// Assert
|
||||
expect(actualCookieValue).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getDeviceIdValue", () => {
|
||||
test("getDeviceIdValue, should return GUID", () => {
|
||||
// Arrange
|
||||
setupCookies({});
|
||||
|
||||
// Act
|
||||
const result = getDeviceIdValue();
|
||||
|
||||
//Assert
|
||||
expect(result).toBe("21b9b94a-ec23-42c1-aaac-e2ae4e4dbffe");
|
||||
});
|
||||
|
||||
test("getSessionKeyValue, should return session key int", () => {
|
||||
// Arrange
|
||||
setupCookies({});
|
||||
|
||||
// Act
|
||||
const result = getSessionKeyValue();
|
||||
|
||||
//Assert
|
||||
expect(result).toBe("12345");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getSessionIdValue", () => {
|
||||
test("getSessionIdValue, should return GUID", () => {
|
||||
// Arrange
|
||||
setupCookies({});
|
||||
|
||||
// Act
|
||||
const result = getSessionIdValue();
|
||||
|
||||
//Assert
|
||||
expect(result).toBe("cba0c3d1-3c1b-4305-bb56-31aa50f58e27");
|
||||
});
|
||||
});
|
||||
});
|
||||
150
src/helpers/heritage-integration/navigation-helper.js
Normal file
150
src/helpers/heritage-integration/navigation-helper.js
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { externalUrls } from "@/router/router-constants/externalUrl-values";
|
||||
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
|
||||
import { saveSession } from "@/helpers/heritage-integration/order-helper.js";
|
||||
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
||||
import store from "@/store";
|
||||
import router from "@/router";
|
||||
|
||||
/*
|
||||
If the user has visited the funnel before this method will determine the bets place to
|
||||
drop them so they don't start at the beginning again. This method will return 'heritage' if
|
||||
the user has an existing order and they come back in from the Safelite.com CTA.
|
||||
*/
|
||||
export async function getPageToRouteExistingOrderTo(toRoute = {}, existingHeritageOrder = false) {
|
||||
// If the user is coming in via the Safelite.Com CTA
|
||||
if (toRoute.query[queryStrings.START_TYPE] === "fmg") {
|
||||
// If they have an existing order, return 'heritage' for the page name.
|
||||
if (existingHeritageOrder) {
|
||||
return fmgPageValues.HERITAGE;
|
||||
}
|
||||
|
||||
return await getLatestPageForRedirection();
|
||||
}
|
||||
|
||||
// If navigating to a specific page, and that page is not part of the vin pages.
|
||||
// Return that page, so that it can navigate like normal.
|
||||
if (toRoute.query[queryStrings.FMG_PAGE] !== undefined && !isVinRelatedPage(toRoute)) {
|
||||
return overrideYmmsDirectionIfNeeded(toRoute);
|
||||
}
|
||||
|
||||
// If this is not a direct link to a page using fmgPage, not from Safelite.com CTA or this is a vin related page.
|
||||
// Get the latest page for redirection.
|
||||
const latestPageRoute = await getLatestPageForRedirection();
|
||||
|
||||
return latestPageRoute;
|
||||
}
|
||||
|
||||
/*
|
||||
Used to navigate to the heritage funnel with the correct query string and url.
|
||||
*/
|
||||
|
||||
export async function navigateToHeritageFunnel(shouldSaveSession = true) {
|
||||
// Create the order (or save existing order) when navigating to Heritage Funnel.
|
||||
if (shouldSaveSession) {
|
||||
await saveSession();
|
||||
}
|
||||
|
||||
router.navigateToExternalUrl(externalUrls.HERITAGE_FUNNEL, {
|
||||
corid: store.getters.order.referralCorrelationId,
|
||||
src: "concept-funnel",
|
||||
conceptsqid: store.getters.applicationUser.savedSessionId,
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
Logic for getting the last "valid" page a user visited.
|
||||
*/
|
||||
async function getLatestPageForRedirection() {
|
||||
// If this is a non-CTA navigation, determine where to send the user based on page prerequisites.
|
||||
// This also works if a user has a 'fmg' start_type query string but no current order.
|
||||
// That shouldn't happen, but it's possible.
|
||||
const vehicleMakeComponent = await getLazyLoadedComponent(fmgPageValues.VEHICLE_MAKE);
|
||||
const vehicleModelComponent = await getLazyLoadedComponent(fmgPageValues.VEHICLE_MODEL);
|
||||
const vehicleStyleComponent = await getLazyLoadedComponent(fmgPageValues.VEHICLE_STYLE);
|
||||
const vehicleDamageComponent = await getLazyLoadedComponent(fmgPageValues.VEHICLE_DAMAGE);
|
||||
const estimateComponent = await getLazyLoadedComponent(fmgPageValues.ESTIMATE);
|
||||
const vinLookupComponent = await getLazyLoadedComponent(fmgPageValues.VIN_LOOKUP);
|
||||
const partQuestionsComponent = await getLazyLoadedComponent(fmgPageValues.PART_QUESTIONS);
|
||||
const vehiclePartsComponent = await getLazyLoadedComponent(fmgPageValues.VEHICLE_PARTS);
|
||||
const moldingQuestionsComponent = await getLazyLoadedComponent(fmgPageValues.MOLDING_QUESTIONS);
|
||||
const capabilityQuestionsComponent = await getLazyLoadedComponent(
|
||||
fmgPageValues.CAPABILITY_QUESTIONS
|
||||
);
|
||||
|
||||
if (!vehicleMakeComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.VEHICLE_YEAR;
|
||||
} else if (!vehicleModelComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.VEHICLE_MAKE;
|
||||
} else if (!vehicleStyleComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.VEHICLE_MODEL;
|
||||
} else if (!vehicleDamageComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.VEHICLE_STYLE;
|
||||
} else if (!estimateComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.VEHICLE_DAMAGE;
|
||||
} else {
|
||||
if (capabilityQuestionsComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.CAPABILITY_QUESTIONS;
|
||||
} else if (moldingQuestionsComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.MOLDING_QUESTIONS;
|
||||
} else if (vehiclePartsComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.VEHICLE_PARTS;
|
||||
} else if (partQuestionsComponent.methods.arePagePrerequisitesValid()) {
|
||||
return fmgPageValues.PART_QUESTIONS;
|
||||
} else if (
|
||||
vinLookupComponent.methods.arePagePrerequisitesValid() &&
|
||||
!store.getters.damage.isRepair
|
||||
) {
|
||||
return fmgPageValues.VIN_LOOKUP;
|
||||
} else {
|
||||
return fmgPageValues.ESTIMATE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Overrides functionality to go to the YMMS pages in certain cases.
|
||||
If this is not one of the cases, it returns the 'to' fmgPage value.
|
||||
*/
|
||||
|
||||
/* istanbul ignore next */
|
||||
function overrideYmmsDirectionIfNeeded(toRoute) {
|
||||
const fmgPageValue = toRoute.query[queryStrings.FMG_PAGE];
|
||||
|
||||
if (store.getters.payment.insuranceCoverage.isVerified) {
|
||||
switch (fmgPageValue) {
|
||||
case fmgPageValues.VEHICLE_YEAR:
|
||||
case fmgPageValues.VEHICLE_MAKE:
|
||||
case fmgPageValues.VEHICLE_MODEL:
|
||||
case fmgPageValues.VEHICLE_STYLE: {
|
||||
return fmgPageValues.VEHICLE_DAMAGE;
|
||||
}
|
||||
default: {
|
||||
return fmgPageValue;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmgPageValue;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Determine if the page is a vin related page.
|
||||
*/
|
||||
|
||||
/* istanbul ignore next */
|
||||
function isVinRelatedPage(toRoute) {
|
||||
const fmgPageValue = toRoute.query[queryStrings.FMG_PAGE];
|
||||
|
||||
return (
|
||||
fmgPageValue === fmgPageValues.VIN_LOOKUP ||
|
||||
fmgPageValue === fmgPageValues.LICENSE_PLATE_LOOKUP ||
|
||||
fmgPageValue === fmgPageValues.ADDRESS_LOOKUP ||
|
||||
fmgPageValue === fmgPageValues.ADDRESS_VEHICLES ||
|
||||
fmgPageValue === fmgPageValues.ESTIMATE
|
||||
);
|
||||
}
|
||||
|
||||
async function getLazyLoadedComponent(pageName) {
|
||||
return (await lazyLoadComponent(pageName)()).default;
|
||||
}
|
||||
445
src/helpers/heritage-integration/navigation-helper.spec.js
Normal file
445
src/helpers/heritage-integration/navigation-helper.spec.js
Normal file
|
|
@ -0,0 +1,445 @@
|
|||
import {
|
||||
getPageToRouteExistingOrderTo,
|
||||
navigateToHeritageFunnel,
|
||||
} from "@/helpers/heritage-integration/navigation-helper";
|
||||
import * as orderHelper from "@/helpers/heritage-integration/order-helper";
|
||||
import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import { setupMocksForJsFiles, getMockOrderInfo } from "@/helpers/unit-test-helper.js";
|
||||
import { externalUrls } from "@/router/router-constants/externalUrl-values";
|
||||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
||||
|
||||
import store from "@/store";
|
||||
import router from "@/router";
|
||||
|
||||
// Mock Lazy Load
|
||||
jest.mock("@/router/dynamic-routing/component-loader.js", () => ({
|
||||
lazyLoadComponent: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("getPageToRouteExistingOrderTo", () => {
|
||||
test("should return vehicle-year", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {},
|
||||
};
|
||||
|
||||
// Mock out the lazy load calls for all components.
|
||||
mockLazyLoadComponentReturnValues({
|
||||
[fmgPageValues.VEHICLE_MAKE]: false,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.VEHICLE_YEAR);
|
||||
});
|
||||
|
||||
test("should return vehicle-make", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {},
|
||||
};
|
||||
|
||||
// Mock out the lazy load calls for all components.
|
||||
mockLazyLoadComponentReturnValues({
|
||||
[fmgPageValues.VEHICLE_MAKE]: true,
|
||||
[fmgPageValues.VEHICLE_MODEL]: false,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.VEHICLE_MAKE);
|
||||
});
|
||||
|
||||
test("should return vehicle-model", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {},
|
||||
};
|
||||
|
||||
// Mock out the lazy load calls for all components.
|
||||
mockLazyLoadComponentReturnValues({
|
||||
[fmgPageValues.VEHICLE_MAKE]: true,
|
||||
[fmgPageValues.VEHICLE_MODEL]: true,
|
||||
[fmgPageValues.VEHICLE_STYLE]: false,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.VEHICLE_MODEL);
|
||||
});
|
||||
|
||||
test("should return vehicle-style", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {},
|
||||
};
|
||||
|
||||
// Mock out the lazy load calls for all components.
|
||||
mockLazyLoadComponentReturnValues({
|
||||
[fmgPageValues.VEHICLE_MAKE]: true,
|
||||
[fmgPageValues.VEHICLE_MODEL]: true,
|
||||
[fmgPageValues.VEHICLE_STYLE]: true,
|
||||
[fmgPageValues.VEHICLE_DAMAGE]: false,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.VEHICLE_STYLE);
|
||||
});
|
||||
|
||||
test("should return vehicle-damage", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {},
|
||||
};
|
||||
|
||||
// Mock out the lazy load calls for all components.
|
||||
mockLazyLoadComponentReturnValues({
|
||||
[fmgPageValues.VEHICLE_MAKE]: true,
|
||||
[fmgPageValues.VEHICLE_MODEL]: true,
|
||||
[fmgPageValues.VEHICLE_STYLE]: true,
|
||||
[fmgPageValues.VEHICLE_DAMAGE]: true,
|
||||
[fmgPageValues.ESTIMATE]: false,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.VEHICLE_DAMAGE);
|
||||
});
|
||||
|
||||
test("user has YMMS and no vehicle questions > should return vin-lookup", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {},
|
||||
};
|
||||
|
||||
// Mock out the lazy load calls for all components.
|
||||
mockLazyLoadComponentReturnValues({
|
||||
[fmgPageValues.VEHICLE_MAKE]: true,
|
||||
[fmgPageValues.VEHICLE_MODEL]: true,
|
||||
[fmgPageValues.VEHICLE_STYLE]: true,
|
||||
[fmgPageValues.VEHICLE_DAMAGE]: true,
|
||||
[fmgPageValues.ESTIMATE]: true,
|
||||
[fmgPageValues.CAPABILITY_QUESTIONS]: false,
|
||||
[fmgPageValues.MOLDING_QUESTIONS]: false,
|
||||
[fmgPageValues.VEHICLE_PARTS]: false,
|
||||
[fmgPageValues.PART_QUESTIONS]: false,
|
||||
[fmgPageValues.VIN_LOOKUP]: true,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.VIN_LOOKUP);
|
||||
});
|
||||
|
||||
test("user has YMMS but no questions or carId > should return estimate", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {},
|
||||
};
|
||||
|
||||
// Mock out the lazy load calls for all components.
|
||||
mockLazyLoadComponentReturnValues({
|
||||
[fmgPageValues.VEHICLE_MAKE]: true,
|
||||
[fmgPageValues.VEHICLE_MODEL]: true,
|
||||
[fmgPageValues.VEHICLE_STYLE]: true,
|
||||
[fmgPageValues.VEHICLE_DAMAGE]: true,
|
||||
[fmgPageValues.ESTIMATE]: true,
|
||||
[fmgPageValues.CAPABILITY_QUESTIONS]: false,
|
||||
[fmgPageValues.MOLDING_QUESTIONS]: false,
|
||||
[fmgPageValues.VEHICLE_PARTS]: false,
|
||||
[fmgPageValues.PART_QUESTIONS]: false,
|
||||
[fmgPageValues.VIN_LOOKUP]: false,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.ESTIMATE);
|
||||
});
|
||||
|
||||
test("user has capability questions and molding questions > should return capability questions", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {},
|
||||
};
|
||||
|
||||
// Mock out the lazy load calls for all components.
|
||||
mockLazyLoadComponentReturnValues({
|
||||
[fmgPageValues.VEHICLE_MAKE]: true,
|
||||
[fmgPageValues.VEHICLE_MODEL]: true,
|
||||
[fmgPageValues.VEHICLE_STYLE]: true,
|
||||
[fmgPageValues.VEHICLE_DAMAGE]: true,
|
||||
[fmgPageValues.ESTIMATE]: true,
|
||||
[fmgPageValues.CAPABILITY_QUESTIONS]: true,
|
||||
[fmgPageValues.MOLDING_QUESTIONS]: true,
|
||||
[fmgPageValues.VEHICLE_PARTS]: false,
|
||||
[fmgPageValues.PART_QUESTIONS]: false,
|
||||
[fmgPageValues.VIN_LOOKUP]: false,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.CAPABILITY_QUESTIONS);
|
||||
});
|
||||
|
||||
test("user has molding questions and part questions > should return molding questions", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {},
|
||||
};
|
||||
|
||||
// Mock out the lazy load calls for all components.
|
||||
mockLazyLoadComponentReturnValues({
|
||||
[fmgPageValues.VEHICLE_MAKE]: true,
|
||||
[fmgPageValues.VEHICLE_MODEL]: true,
|
||||
[fmgPageValues.VEHICLE_STYLE]: true,
|
||||
[fmgPageValues.VEHICLE_DAMAGE]: true,
|
||||
[fmgPageValues.ESTIMATE]: true,
|
||||
[fmgPageValues.CAPABILITY_QUESTIONS]: false,
|
||||
[fmgPageValues.MOLDING_QUESTIONS]: true,
|
||||
[fmgPageValues.VEHICLE_PARTS]: false,
|
||||
[fmgPageValues.PART_QUESTIONS]: true,
|
||||
[fmgPageValues.VIN_LOOKUP]: false,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.MOLDING_QUESTIONS);
|
||||
});
|
||||
|
||||
test("user has vehicle parts questions > should return vehicle-parts", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {},
|
||||
};
|
||||
|
||||
// Mock out the lazy load calls for all components.
|
||||
mockLazyLoadComponentReturnValues({
|
||||
[fmgPageValues.VEHICLE_MAKE]: true,
|
||||
[fmgPageValues.VEHICLE_MODEL]: true,
|
||||
[fmgPageValues.VEHICLE_STYLE]: true,
|
||||
[fmgPageValues.VEHICLE_DAMAGE]: true,
|
||||
[fmgPageValues.ESTIMATE]: true,
|
||||
[fmgPageValues.CAPABILITY_QUESTIONS]: false,
|
||||
[fmgPageValues.MOLDING_QUESTIONS]: false,
|
||||
[fmgPageValues.VEHICLE_PARTS]: true,
|
||||
[fmgPageValues.PART_QUESTIONS]: true,
|
||||
[fmgPageValues.VIN_LOOKUP]: false,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.VEHICLE_PARTS);
|
||||
});
|
||||
|
||||
test("user has part questions > should return part-questions", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {},
|
||||
};
|
||||
|
||||
// Mock out the lazy load calls for all components.
|
||||
mockLazyLoadComponentReturnValues({
|
||||
[fmgPageValues.VEHICLE_MAKE]: true,
|
||||
[fmgPageValues.VEHICLE_MODEL]: true,
|
||||
[fmgPageValues.VEHICLE_STYLE]: true,
|
||||
[fmgPageValues.VEHICLE_DAMAGE]: true,
|
||||
[fmgPageValues.ESTIMATE]: true,
|
||||
[fmgPageValues.CAPABILITY_QUESTIONS]: false,
|
||||
[fmgPageValues.MOLDING_QUESTIONS]: false,
|
||||
[fmgPageValues.VEHICLE_PARTS]: false,
|
||||
[fmgPageValues.PART_QUESTIONS]: true,
|
||||
[fmgPageValues.VIN_LOOKUP]: false,
|
||||
});
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, false);
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(fmgPageValues.PART_QUESTIONS);
|
||||
});
|
||||
|
||||
test("existing order > should return heritage", async () => {
|
||||
// Arrange
|
||||
const toRoute = {
|
||||
query: {
|
||||
[queryStrings.START_TYPE]: "fmg",
|
||||
},
|
||||
};
|
||||
|
||||
// Act
|
||||
const result = await getPageToRouteExistingOrderTo(toRoute, true);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(fmgPageValues.HERITAGE);
|
||||
});
|
||||
});
|
||||
|
||||
describe("navigateToHeritageFunnel", () => {
|
||||
test("should save session", async () => {
|
||||
// Arrange
|
||||
const mockReferralNumber = "2";
|
||||
const mockCorrelationId = "55";
|
||||
const mockReferralDate = "2022";
|
||||
const mockAccountNumber = "167132";
|
||||
const mockSavedSessionId = "xxx-xxx-xxx";
|
||||
const mockCrmCustomerId = "xxx-xxx-xxx";
|
||||
|
||||
const mockOrderInfo = getMockOrderInfo(
|
||||
mockReferralNumber,
|
||||
mockCorrelationId,
|
||||
mockReferralDate,
|
||||
mockAccountNumber,
|
||||
mockSavedSessionId,
|
||||
mockCrmCustomerId
|
||||
);
|
||||
|
||||
const mockData = {
|
||||
actionList: [
|
||||
{
|
||||
actionName: storeActions.SAVE_SESSION,
|
||||
data: mockOrderInfo,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
setupMocksForJsFiles(mockData);
|
||||
const saveSessionFunction = jest.spyOn(orderHelper, "saveSession");
|
||||
router.navigateToExternalUrl = jest.fn();
|
||||
|
||||
// Act
|
||||
await navigateToHeritageFunnel();
|
||||
|
||||
// Assert
|
||||
expect(saveSessionFunction).toHaveBeenCalled();
|
||||
|
||||
// Should save before we navigate to heritage by default
|
||||
const saveSessionFunctionCallOrder = saveSessionFunction.mock.invocationCallOrder[0];
|
||||
const routerNavigateFunctionCallOrder =
|
||||
router.navigateToExternalUrl.mock.invocationCallOrder[0];
|
||||
expect(saveSessionFunctionCallOrder).toBeLessThan(routerNavigateFunctionCallOrder);
|
||||
saveSessionFunction.mockRestore();
|
||||
});
|
||||
|
||||
test("should go to heritage funnel", async () => {
|
||||
// Arrange
|
||||
const mockReferralNumber = "2";
|
||||
const mockCorrelationId = "55";
|
||||
const mockReferralDate = "2022";
|
||||
const mockAccountNumber = "167132";
|
||||
const mockSavedSessionId = "xxx-xxx-xxx";
|
||||
const mockCrmCustomerId = "xxx-xxx-xxx";
|
||||
|
||||
const mockOrderInfo = getMockOrderInfo(
|
||||
mockReferralNumber,
|
||||
mockCorrelationId,
|
||||
mockReferralDate,
|
||||
mockAccountNumber,
|
||||
mockSavedSessionId,
|
||||
mockCrmCustomerId
|
||||
);
|
||||
|
||||
const mockData = {
|
||||
actionList: [
|
||||
{
|
||||
actionName: storeActions.SAVE_SESSION,
|
||||
data: mockOrderInfo,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
setupMocksForJsFiles(mockData);
|
||||
|
||||
store.getters.order.referralCorrelationId = mockCorrelationId;
|
||||
|
||||
router.navigateToExternalUrl = jest.fn();
|
||||
|
||||
// Act
|
||||
await navigateToHeritageFunnel();
|
||||
|
||||
// Assert
|
||||
expect(router.navigateToExternalUrl).toHaveBeenCalled();
|
||||
expect(router.navigateToExternalUrl).toHaveBeenCalledWith(
|
||||
externalUrls.HERITAGE_FUNNEL,
|
||||
expect.objectContaining({
|
||||
corid: mockCorrelationId,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
test("should not save session, but should still navigate", async () => {
|
||||
// Arrange
|
||||
const mockReferralNumber = "2";
|
||||
const mockCorrelationId = "55";
|
||||
const mockReferralDate = "2022";
|
||||
|
||||
const mockOrderInfo = getMockOrderInfo(
|
||||
mockReferralNumber,
|
||||
mockCorrelationId,
|
||||
mockReferralDate
|
||||
);
|
||||
|
||||
const mockData = {
|
||||
actionList: [
|
||||
{
|
||||
actionName: storeActions.SAVE_SESSION,
|
||||
data: mockOrderInfo,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
setupMocksForJsFiles(mockData);
|
||||
const saveSessionFunction = jest.spyOn(orderHelper, "saveSession");
|
||||
router.navigateToExternalUrl = jest.fn();
|
||||
|
||||
// Act
|
||||
await navigateToHeritageFunnel(false);
|
||||
|
||||
// Assert
|
||||
expect(saveSessionFunction).not.toHaveBeenCalled();
|
||||
expect(router.navigateToExternalUrl).toHaveBeenCalled();
|
||||
saveSessionFunction.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* `arePagePrerequisitesValidObject` is an object where the keys are fmgPageValue names and the values are booleans that indicate
|
||||
* whether arePagePrerequisitesValid is true or false
|
||||
*/
|
||||
function mockLazyLoadComponentReturnValues(arePagePrerequisitesValidObject = {}) {
|
||||
lazyLoadComponent.mockImplementation((pageName) => {
|
||||
return async () => {
|
||||
return Promise.resolve({
|
||||
default: {
|
||||
methods: {
|
||||
arePagePrerequisitesValid: jest
|
||||
.fn()
|
||||
.mockReturnValueOnce(arePagePrerequisitesValidObject[pageName]),
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
113
src/helpers/heritage-integration/order-helper.js
Normal file
113
src/helpers/heritage-integration/order-helper.js
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
import { storeActions } from "@/constants/store-actions.js";
|
||||
import {
|
||||
getFunnelCookie,
|
||||
updateOrCreateFunnelCookie,
|
||||
deleteFunnelCookie,
|
||||
} from "@/helpers/heritage-integration/cookie-helper.js";
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
import store from "@/store";
|
||||
import { storeMutations } from "@/constants/store-mutations";
|
||||
|
||||
/*
|
||||
Will call API and hydrate state with data from API if present. If there is no order present
|
||||
method will return null. If the cookie dictates the state should be reset
|
||||
it will reset the state and go back to the start of the funnel.
|
||||
|
||||
*/
|
||||
export async function loadSessionIfPresent() {
|
||||
const funnelCookie = getFunnelCookie();
|
||||
|
||||
// Do nothing if there is no cookie, correlation id, or referral number.
|
||||
if (
|
||||
funnelCookie == null ||
|
||||
funnelCookie.ReferralCorrelationId == null ||
|
||||
!funnelCookie.ReferralNumber
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Reset state if cookie says to.
|
||||
if (funnelCookie.ShouldResetState) {
|
||||
baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);
|
||||
deleteFunnelCookie();
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load referral if there is a cookie, and it doesn't indicate it needs a state reset.
|
||||
return (
|
||||
await loadSession(
|
||||
funnelCookie.ReferralNumber,
|
||||
funnelCookie.ReferralDate,
|
||||
funnelCookie.ReferralCorrelationId,
|
||||
funnelCookie.ReferralParentAccountNumber
|
||||
)
|
||||
).data;
|
||||
}
|
||||
|
||||
/*
|
||||
Will call API to save existing order, or create new one depending where it's called from.
|
||||
This will also set Referral information in the store after saving, and then
|
||||
update the cookie.
|
||||
*/
|
||||
export async function saveSession() {
|
||||
var saveSessionPromise;
|
||||
if (store.getters.applicationUser.saveSessionPromise) {
|
||||
// queue newest request after current saveSessionPromise resolves
|
||||
saveSessionPromise = store.getters.applicationUser.saveSessionPromise.then(() => {
|
||||
// get a new saveSessionPromise
|
||||
return saveSessionHelper();
|
||||
});
|
||||
} else {
|
||||
// create an initial saveSessionPromise
|
||||
saveSessionPromise = saveSessionHelper();
|
||||
}
|
||||
store.commit(storeMutations.UPDATE_SAVE_SESSION_PROMISE, saveSessionPromise);
|
||||
// await here to allow for a caller to await and make the function synchronous
|
||||
await saveSessionPromise;
|
||||
}
|
||||
|
||||
// PRIVATE FUNCTIONS //
|
||||
|
||||
/*
|
||||
Calls API to load session given the referral number, referralDate, and referralCorrelationId
|
||||
and returns the response.
|
||||
*/
|
||||
async function loadSession(referralNumber, referralDate, referralCorrelationId, accountNumber) {
|
||||
// await the saveSessionPromise in the store to make sure we're loading up to date information
|
||||
await store.getters.applicationUser.saveSessionPromise;
|
||||
const response = await baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.LOAD_SESSION,
|
||||
{
|
||||
referralNumber: referralNumber.toString(),
|
||||
referralDate: referralDate,
|
||||
referralCorrelationId: referralCorrelationId,
|
||||
accountNumber: accountNumber?.toString(),
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/*
|
||||
Encapsulates asynchronous Save Session logic inside a promise to allow for Save Session queuing
|
||||
*/
|
||||
async function saveSessionHelper() {
|
||||
const savedSessionInfo = await baseMixin.methods.dispatchStoreAction(storeActions.SAVE_SESSION);
|
||||
// Update the store with information received from the saveSession response
|
||||
await baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE,
|
||||
{
|
||||
referralNumber: savedSessionInfo.data.referralNumber.toString(),
|
||||
referralCorrelationId: savedSessionInfo.data.referralCorrelationId,
|
||||
referralDate: savedSessionInfo.data.referralDate,
|
||||
accountNumber: savedSessionInfo.data.accountNumber.toString(),
|
||||
savedSessionId: savedSessionInfo.data.savedSessionId,
|
||||
crmCustomerId: savedSessionInfo.data.crmCustomerId.toString(),
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
// Update the cookie with the referral information when saved.
|
||||
updateOrCreateFunnelCookie();
|
||||
}
|
||||
236
src/helpers/heritage-integration/order-helper.spec.js
Normal file
236
src/helpers/heritage-integration/order-helper.spec.js
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
import * as cookieHelper from "@/helpers/heritage-integration/cookie-helper";
|
||||
import { loadSessionIfPresent, saveSession } from "@/helpers/heritage-integration/order-helper";
|
||||
import { cookieNames } from "@/constants/cookie-names";
|
||||
import {
|
||||
setupMocksForJsFiles,
|
||||
removeAllTestCookies,
|
||||
getMockOrderInfo,
|
||||
setupCookies,
|
||||
} from "@/helpers/unit-test-helper.js";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import router from "@/router";
|
||||
|
||||
describe("loadSessionIfPresent", () => {
|
||||
afterEach(() => {
|
||||
removeAllTestCookies();
|
||||
});
|
||||
|
||||
test("ShouldResetState == true => funnel cookie is deleted", () => {
|
||||
// Arrange
|
||||
cookieHelper.deleteFunnelCookie = jest.spyOn(cookieHelper, "deleteFunnelCookie");
|
||||
const testShouldResetState = true;
|
||||
|
||||
const testCookieValue = {
|
||||
ShouldResetState: testShouldResetState,
|
||||
ReferralCorrelationId: "xxx",
|
||||
ReferralNumber: "12345",
|
||||
};
|
||||
|
||||
document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}=${JSON.stringify(
|
||||
testCookieValue
|
||||
)}; path=/; ${cookieHelper.getCookieDomainValue()}`;
|
||||
|
||||
// Act
|
||||
loadSessionIfPresent();
|
||||
|
||||
// Assert
|
||||
expect(cookieHelper.deleteFunnelCookie).toHaveBeenCalled();
|
||||
expect(document.cookie).toBe("");
|
||||
});
|
||||
|
||||
test("ShouldResetState == true => reset store", () => {
|
||||
// Arrange
|
||||
cookieHelper.getFunnelCookie = jest
|
||||
.spyOn(cookieHelper, "getFunnelCookie")
|
||||
.mockReturnValueOnce({
|
||||
ShouldResetState: true,
|
||||
ReferralCorrelationId: "xxx-xxx-xxx",
|
||||
ReferralNumber: "12345",
|
||||
});
|
||||
|
||||
const mockData = {
|
||||
actionList: [
|
||||
{
|
||||
actionName: storeActions.RESET_STATE,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
var mocks = setupMocksForJsFiles(mockData);
|
||||
|
||||
// Act
|
||||
loadSessionIfPresent();
|
||||
|
||||
// Assert
|
||||
expect(cookieHelper.getFunnelCookie).toHaveBeenCalled();
|
||||
expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(
|
||||
storeActions.RESET_STATE
|
||||
);
|
||||
});
|
||||
|
||||
test("Funnel cookie is null => store is unchanged", () => {
|
||||
// Arrange
|
||||
cookieHelper.getFunnelCookie = jest
|
||||
.spyOn(cookieHelper, "getFunnelCookie")
|
||||
.mockReturnValueOnce(null);
|
||||
|
||||
const mockData = {
|
||||
actionList: [
|
||||
{
|
||||
actionName: storeActions.RESET_STATE,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
var mocks = setupMocksForJsFiles(mockData);
|
||||
|
||||
// Act
|
||||
loadSessionIfPresent();
|
||||
|
||||
// Assert
|
||||
expect(cookieHelper.getFunnelCookie).toHaveBeenCalled();
|
||||
expect(mocks.baseMixin.methods.dispatchStoreAction).not.toHaveBeenCalledWith(
|
||||
storeActions.RESET_STATE
|
||||
);
|
||||
});
|
||||
|
||||
test("Funnel cookie valid, should call loadSession", async () => {
|
||||
// Arrange
|
||||
cookieHelper.getFunnelCookie = jest
|
||||
.spyOn(cookieHelper, "getFunnelCookie")
|
||||
.mockReturnValueOnce({
|
||||
ShouldResetState: false,
|
||||
ReferralNumber: 123456,
|
||||
ReferralCorrelationId: "yyy-yyy-yyyy",
|
||||
ReferralDate: new Date(),
|
||||
});
|
||||
|
||||
const mockData = {
|
||||
actionList: [
|
||||
{
|
||||
actionName: storeActions.LOAD_SESSION,
|
||||
data: { ReferralNumber: 123456, vehicle: { year: 2010 } },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
var mocks = setupMocksForJsFiles(mockData);
|
||||
|
||||
// Act
|
||||
const result = await loadSessionIfPresent();
|
||||
|
||||
// Assert
|
||||
expect(cookieHelper.getFunnelCookie).toHaveBeenCalled();
|
||||
expect(mocks.baseMixin.methods.dispatchStoreAction).not.toHaveBeenCalledWith(
|
||||
storeActions.LOAD_SESSION
|
||||
);
|
||||
expect(result.ReferralNumber).toBe(123456);
|
||||
expect(result.vehicle.year).toBe(2010);
|
||||
});
|
||||
});
|
||||
|
||||
describe("saveSession", () => {
|
||||
afterEach(() => {
|
||||
removeAllTestCookies();
|
||||
});
|
||||
|
||||
test("saveSession => should set state order values", async () => {
|
||||
// Arrange
|
||||
const mockReferralNumber = "2";
|
||||
const mockCorrelationId = "55";
|
||||
const mockReferralDate = "2022";
|
||||
const mockAccountNumber = "167132";
|
||||
const mockSavedSessionId = "xxx-xxx-xxx";
|
||||
const mockCrmCustomerId = "xxx-xxx-xxx";
|
||||
|
||||
const mockOrderInfo = getMockOrderInfo(
|
||||
mockReferralNumber,
|
||||
mockCorrelationId,
|
||||
mockReferralDate,
|
||||
mockAccountNumber,
|
||||
mockSavedSessionId,
|
||||
mockCrmCustomerId
|
||||
);
|
||||
|
||||
const mockData = {
|
||||
actionList: [
|
||||
{
|
||||
actionName: storeActions.SAVE_SESSION,
|
||||
data: mockOrderInfo,
|
||||
},
|
||||
{
|
||||
actionName: storeActions.UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mocks = setupMocksForJsFiles(mockData);
|
||||
|
||||
// Act
|
||||
await saveSession();
|
||||
|
||||
// Assert
|
||||
expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(
|
||||
storeActions.SAVE_SESSION
|
||||
);
|
||||
expect(mocks.baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(
|
||||
storeActions.UPDATE_STORE_WITH_SAVE_SESSION_RESPONSE,
|
||||
{
|
||||
referralNumber: mockReferralNumber,
|
||||
referralDate: mockReferralDate,
|
||||
referralCorrelationId: mockCorrelationId,
|
||||
accountNumber: mockAccountNumber,
|
||||
savedSessionId: mockSavedSessionId,
|
||||
crmCustomerId: mockCrmCustomerId,
|
||||
},
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test("saveSession => should update DidHeritageFunnelUpdateLast cookie value to false", async () => {
|
||||
// Arrange
|
||||
const mockReferralNumber = 1566818;
|
||||
const mockReferralDate = "2022-03-15T10:56:24.597";
|
||||
const mockReferralCorrelationId = "404d2b04-f86e-45c3-b373-127b6217b060";
|
||||
const mockAccountNumber = "167132";
|
||||
const mockSavedSessionId = "xxx-xxx-xxx";
|
||||
const mockCrmCustomerId = "xxx-xxx-xxx";
|
||||
|
||||
const mockOrderInfo = getMockOrderInfo(
|
||||
mockReferralNumber,
|
||||
mockReferralCorrelationId,
|
||||
mockReferralDate,
|
||||
mockAccountNumber,
|
||||
mockSavedSessionId,
|
||||
mockCrmCustomerId
|
||||
);
|
||||
|
||||
const mockData = {
|
||||
actionList: [
|
||||
{
|
||||
actionName: storeActions.SAVE_SESSION,
|
||||
data: mockOrderInfo,
|
||||
},
|
||||
],
|
||||
router: router,
|
||||
};
|
||||
|
||||
setupMocksForJsFiles(mockData);
|
||||
|
||||
const testCookieValue = {
|
||||
ReferralNumber: mockReferralNumber,
|
||||
ReferralDate: mockReferralDate,
|
||||
ReferralCorrelationId: mockReferralCorrelationId,
|
||||
ShouldResetState: false,
|
||||
DidHeritageFunnelUpdateLast: true,
|
||||
};
|
||||
|
||||
setupCookies({ funnelCookieValue: JSON.stringify(testCookieValue) });
|
||||
|
||||
// Act
|
||||
await saveSession();
|
||||
|
||||
// Assert
|
||||
expect(cookieHelper.getFunnelCookie().DidHeritageFunnelUpdateLast).toEqual(false);
|
||||
});
|
||||
});
|
||||
47
src/helpers/heritage-integration/session-helper.js
Normal file
47
src/helpers/heritage-integration/session-helper.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import { applicationConfig } from "@/constants/application-config";
|
||||
import { getFunnelCookie } from "@/helpers/heritage-integration/cookie-helper.js";
|
||||
|
||||
/*
|
||||
Method to determine if our analytics session has timed out or not.
|
||||
Amount used for timeout is configurable in application-config.js
|
||||
*/
|
||||
export function isAnalyticsSessionStillActive() {
|
||||
if (getFunnelCookie() !== null) {
|
||||
const lastTouchedValue = getFunnelCookie().LastTouched;
|
||||
const timeoutAmount = applicationConfig.ANALYTICS_SESSION_TIMEOUT_MINUTES;
|
||||
const isMoreThanHalfHourAgo =
|
||||
(new Date() - new Date(lastTouchedValue)) / 60000 > timeoutAmount;
|
||||
|
||||
if (isMoreThanHalfHourAgo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Method to determine if the users 'saved' session is still active.
|
||||
When user state is created, there is a date that is saved into state
|
||||
this method checks against that date.
|
||||
|
||||
Note: That time for saved session timeout is configurable in application-config.js
|
||||
*/
|
||||
export function isSavedSessionStillActive() {
|
||||
if (getFunnelCookie() !== null) {
|
||||
const savedSessionTimeStamp = new Date(getFunnelCookie().SavedSessionTimeoutDate);
|
||||
const isSavedSessionTimedOut = new Date(new Date().toUTCString()) > savedSessionTimeStamp;
|
||||
|
||||
return !isSavedSessionTimedOut;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Function to calculate the date for the saved session timeout.
|
||||
*/
|
||||
|
||||
export function getDateForSavedSessionTimeout() {
|
||||
const currentDate = new Date(new Date().toUTCString());
|
||||
currentDate.setDate(currentDate.getDate() + applicationConfig.SAVED_SESSION_TIMEOUT_DAYS);
|
||||
return currentDate.toUTCString();
|
||||
}
|
||||
89
src/helpers/heritage-integration/session-helper.spec.js
Normal file
89
src/helpers/heritage-integration/session-helper.spec.js
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import * as cookieHelper from "@/helpers/heritage-integration/cookie-helper";
|
||||
import {
|
||||
isAnalyticsSessionStillActive,
|
||||
isSavedSessionStillActive,
|
||||
getDateForSavedSessionTimeout,
|
||||
} from "@/helpers/heritage-integration/session-helper";
|
||||
import { applicationConfig } from "@/constants/application-config";
|
||||
|
||||
describe("isAnalyticsSessionStillActive", () => {
|
||||
test("isAnalyticsSessionStillActive, should return true", () => {
|
||||
// Arrange
|
||||
const mockDate = new Date(new Date().toUTCString());
|
||||
mockDate.setDate(mockDate.getDate() + 1);
|
||||
|
||||
cookieHelper.getFunnelCookie = jest
|
||||
.spyOn(cookieHelper, "getFunnelCookie")
|
||||
.mockReturnValue({ LastTouched: mockDate });
|
||||
|
||||
// Act
|
||||
const result = isAnalyticsSessionStillActive();
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("isAnalyticsSessionStillActive, should return false", () => {
|
||||
// Arrange
|
||||
const mockDate = new Date(new Date().toUTCString());
|
||||
mockDate.setDate(mockDate.getDate() - 1);
|
||||
|
||||
cookieHelper.getFunnelCookie = jest
|
||||
.spyOn(cookieHelper, "getFunnelCookie")
|
||||
.mockReturnValue({ LastTouched: mockDate });
|
||||
|
||||
// Act
|
||||
const result = isAnalyticsSessionStillActive();
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isSavedSessionStillActive", () => {
|
||||
test("isSavedSessionStillActive, should return true", () => {
|
||||
// Arrange
|
||||
const mockDate = new Date(new Date().toUTCString());
|
||||
mockDate.setDate(mockDate.getDate() + 1);
|
||||
|
||||
cookieHelper.getFunnelCookie = jest
|
||||
.spyOn(cookieHelper, "getFunnelCookie")
|
||||
.mockReturnValue({ SavedSessionTimeoutDate: mockDate });
|
||||
|
||||
// Act
|
||||
const result = isSavedSessionStillActive();
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("isSavedSessionStillActive, should return false", () => {
|
||||
// Arrange
|
||||
const mockDate = new Date(new Date().toUTCString());
|
||||
mockDate.setDate(mockDate.getDate() - 1);
|
||||
|
||||
cookieHelper.getFunnelCookie = jest
|
||||
.spyOn(cookieHelper, "getFunnelCookie")
|
||||
.mockReturnValue({ SavedSessionTimeoutDate: mockDate });
|
||||
|
||||
// Act
|
||||
const result = isSavedSessionStillActive();
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getDateForSavedSessionTimeout", () => {
|
||||
test("getDateForSavedSessionTimeout, should equal application config setting", () => {
|
||||
// Arrange
|
||||
const currentDate = new Date(new Date().toUTCString());
|
||||
currentDate.setDate(currentDate.getDate() + applicationConfig.SAVED_SESSION_TIMEOUT_DAYS);
|
||||
|
||||
// Act
|
||||
const result = getDateForSavedSessionTimeout();
|
||||
|
||||
// Assert
|
||||
expect(result).toEqual(currentDate.toUTCString());
|
||||
});
|
||||
});
|
||||
144
src/helpers/unit-test-helper.js
Normal file
144
src/helpers/unit-test-helper.js
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
import { storeActions } from "@/constants/store-actions";
|
||||
import { storeMutations } from "@/constants/store-mutations.js";
|
||||
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios.js";
|
||||
import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
||||
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
||||
import { cookieNames } from "@/constants/cookie-names";
|
||||
import { Form } from "vee-validate";
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
import {
|
||||
getCookieDomainValue,
|
||||
setCookieProperties,
|
||||
} from "@/helpers/heritage-integration/cookie-helper";
|
||||
import {
|
||||
analyticsPageEvents,
|
||||
GaCategories,
|
||||
GaActions,
|
||||
GaLabels,
|
||||
GaEvents,
|
||||
ValueToLogTypes,
|
||||
} from "@/constants/analytics";
|
||||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { routerParams } from "@/router/router-constants/router-params";
|
||||
|
||||
// Common methods
|
||||
export function getMountOptions(mockData) {
|
||||
// Define our mocks to attached to the 'global' object for Vue/Jest.
|
||||
const mocks = {};
|
||||
|
||||
//this is mocking if you use the mixin directly(baseMixin.methods.dispatchStoreAction) vs this.dispatchStoreAction
|
||||
setupBaseMixinDispatchStoreAction(mockData);
|
||||
|
||||
mocks.pushEventToGA = jest.fn();
|
||||
mocks.pushPageViewToGA = jest.fn();
|
||||
mocks.logEvent = jest.fn();
|
||||
mocks.pushExperimentsToDataLayer = jest.fn();
|
||||
mocks.prependActionToMethod = jest.fn();
|
||||
mocks.dispatchStoreAction = jest.fn();
|
||||
mocks.dispatchStoreAction.mockImplementation((actionName) => {
|
||||
let actionFilterResult = mockData.actionList?.filter((x) => x.actionName == actionName);
|
||||
|
||||
if (actionFilterResult?.length === 1) {
|
||||
return Promise.resolve({
|
||||
data: actionFilterResult[0].data,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Mock const files
|
||||
mocks.storeActions = storeActions;
|
||||
mocks.storeMutations = storeMutations;
|
||||
mocks.navigationScenarios = navigationScenarios;
|
||||
mocks.vehicleCategories = vehicleCategories;
|
||||
mocks.fmgPageValues = fmgPageValues;
|
||||
mocks.analyticsPageEvents = analyticsPageEvents;
|
||||
mocks.GaCategories = GaCategories;
|
||||
mocks.GaActions = GaActions;
|
||||
mocks.GaLabels = GaLabels;
|
||||
mocks.GaEvents = GaEvents;
|
||||
mocks.ValueToLogTypes = ValueToLogTypes;
|
||||
mocks.queryStrings = queryStrings;
|
||||
mocks.routerParams = routerParams;
|
||||
|
||||
// Mock $store and $router when accessing this.$store/$router
|
||||
mocks.$store = mockData.store;
|
||||
mocks.$router = mockData.router;
|
||||
mocks.$route = mockData.route;
|
||||
mocks.$loadScript = mockData.loadScript;
|
||||
|
||||
const global = {
|
||||
mocks: mocks,
|
||||
mixins: mockData.mixins,
|
||||
stubs: { Form },
|
||||
};
|
||||
|
||||
return { global };
|
||||
}
|
||||
|
||||
export function setupMocksForJsFiles(mockData = {}) {
|
||||
setupBaseMixinDispatchStoreAction(mockData);
|
||||
|
||||
return { baseMixin };
|
||||
}
|
||||
|
||||
// Heritage integration common methods
|
||||
export const cookies = {
|
||||
[cookieNames.FUNNEL_SESSION_INFO]: `{"ReferralNumber":"1566818","ReferralDate":"2022-03-15T10:56:24.597","ReferralCorrelationId":"404d2b04-f86e-45c3-b373-127b6217b060","ShouldResetState":false,"DidHeritageFunnelUpdateLast":true}`,
|
||||
UNIQUE_SESSION_ID: "33756020-b58e-4ec7-b8b8-3f1576719c40",
|
||||
anotherCookie: "{}",
|
||||
someOtherCookie: "{}",
|
||||
dxdev: "did=21b9b94a-ec23-42c1-aaac-e2ae4e4dbffe",
|
||||
sid: "cba0c3d1-3c1b-4305-bb56-31aa50f58e27",
|
||||
skey: "12345",
|
||||
};
|
||||
|
||||
// Removes test cookies for testing cookie-helper and order-helper
|
||||
export function removeAllTestCookies() {
|
||||
Object.keys(cookies).forEach((key) => {
|
||||
document.cookie = `${key}=;Max-Age=0;`;
|
||||
document.cookie = `${key}=;Max-Age=0;path=/;${getCookieDomainValue()}`;
|
||||
});
|
||||
}
|
||||
|
||||
export function getMockOrderInfo(
|
||||
mockReferralNumber,
|
||||
mockCorrelationId,
|
||||
mockReferralDate,
|
||||
accountNumber = "0",
|
||||
savedSessionId,
|
||||
crmCustomerId
|
||||
) {
|
||||
return {
|
||||
referralNumber: mockReferralNumber,
|
||||
referralCorrelationId: mockCorrelationId,
|
||||
referralDate: mockReferralDate,
|
||||
accountNumber: accountNumber,
|
||||
savedSessionId: savedSessionId,
|
||||
crmCustomerId: crmCustomerId,
|
||||
};
|
||||
}
|
||||
|
||||
export function setupCookies({ funnelCookieValue = "", includeHeritageCookie = true }) {
|
||||
Object.keys(cookies).forEach((key) => {
|
||||
const cookieValue =
|
||||
key == cookieNames.FUNNEL_SESSION_INFO ? funnelCookieValue : cookies[key];
|
||||
if (includeHeritageCookie || key != cookieNames.FUNNEL_SESSION_INFO)
|
||||
setCookieProperties({ [key]: cookieValue }, { isSecure: false });
|
||||
});
|
||||
}
|
||||
|
||||
// Private methods
|
||||
function setupBaseMixinDispatchStoreAction(mockData) {
|
||||
if (mockData.actionList !== undefined) {
|
||||
baseMixin.methods.dispatchStoreAction = jest.fn();
|
||||
baseMixin.methods.dispatchStoreAction.mockImplementation((actionName) => {
|
||||
let actionFilterResult = mockData.actionList.filter((x) => x.actionName == actionName);
|
||||
|
||||
if (actionFilterResult.length > 0 && actionFilterResult.length === 1) {
|
||||
return Promise.resolve({
|
||||
data: actionFilterResult[0].data,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
212
src/mixins/analytics-mixin.js
Normal file
212
src/mixins/analytics-mixin.js
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
import { storeActions } from "@/constants/store-actions";
|
||||
import {
|
||||
setCookieProperties,
|
||||
getDeviceIdValue,
|
||||
getSessionIdValue,
|
||||
getSessionKeyValue,
|
||||
} from "@/helpers/heritage-integration/cookie-helper";
|
||||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { experimentSettings } from "@/constants/experiments";
|
||||
import {
|
||||
analyticsPageEvents,
|
||||
GaCategories,
|
||||
GaActions,
|
||||
GaLabels,
|
||||
GaEvents,
|
||||
ValueToLogTypes,
|
||||
} from "@/constants/analytics";
|
||||
import { cookieNames } from "@/constants/cookie-names";
|
||||
import store from "@/store";
|
||||
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
logPageView(pageEvent) {
|
||||
const currentPageName = getPageNameByQueryString();
|
||||
var payload = {
|
||||
userId: getDeviceIdValue(),
|
||||
sessionKey: getSessionKeyValue(),
|
||||
pageName: currentPageName,
|
||||
sessionId: getSessionIdValue(),
|
||||
action: "",
|
||||
event: pageEvent,
|
||||
shouldUseSessionId: false,
|
||||
experimentsForUser: store.getters.applicationUser.experiments,
|
||||
};
|
||||
|
||||
baseMixin.methods.dispatchStoreAction(storeActions.LOG_PAGE_VIEW, payload, false);
|
||||
},
|
||||
|
||||
logCustomEvent(category, action, label, value) {
|
||||
const currentPageName = getPageNameByQueryString();
|
||||
|
||||
var payload = {
|
||||
userId: getDeviceIdValue(),
|
||||
sessionKey: getSessionKeyValue(),
|
||||
pageName: currentPageName,
|
||||
sessionId: getSessionIdValue(),
|
||||
category: category,
|
||||
action: action,
|
||||
label: label,
|
||||
value: value,
|
||||
shouldUseSessionId: false,
|
||||
experimentsForUser: store.getters.applicationUser.experiments,
|
||||
};
|
||||
|
||||
baseMixin.methods.dispatchStoreAction(storeActions.LOG_CUSTOM_EVENT, payload, false);
|
||||
},
|
||||
|
||||
pushEventToGA(category, action, label, pushToLogApp = false, valueToLogType = null) {
|
||||
const currentPageName = getPageNameByQueryString();
|
||||
const labelToLog = getValueToLog(label, valueToLogType);
|
||||
|
||||
const eventToBePushed = {
|
||||
event: GaEvents.GENERIC_EVENT,
|
||||
category: category,
|
||||
action: action,
|
||||
label: labelToLog,
|
||||
value: undefined,
|
||||
path: `/fmg/?${queryStrings.FMG_PAGE}=${currentPageName}`,
|
||||
};
|
||||
|
||||
pushToDataLayerIfDefined(eventToBePushed);
|
||||
|
||||
if (pushToLogApp) {
|
||||
this.logCustomEvent(category, action, labelToLog, undefined);
|
||||
}
|
||||
},
|
||||
|
||||
pushPageViewToGA() {
|
||||
const currentPageName = getPageNameByQueryString();
|
||||
const pageViewEvent = {
|
||||
event: GaEvents.PAGE_VIEW_EVENT,
|
||||
pagePath: `/fmg/?${queryStrings.FMG_PAGE}=${currentPageName}`,
|
||||
pageTitle: currentPageName,
|
||||
};
|
||||
|
||||
pushToDataLayerIfDefined(pageViewEvent);
|
||||
|
||||
this.logPageView(analyticsPageEvents.ENTRY);
|
||||
},
|
||||
|
||||
pushExperimentsToDataLayer() {
|
||||
const experiments = store.getters.applicationUser.experiments;
|
||||
experiments?.forEach((exp) => {
|
||||
// Set Google Dimension Index based on experiment settings.
|
||||
let googleDimensionIndex = 99;
|
||||
|
||||
if (exp.settings[experimentSettings.GOOGLE_CUSTOM_DIMENSION_INDEX] !== undefined) {
|
||||
googleDimensionIndex =
|
||||
exp.settings[experimentSettings.GOOGLE_CUSTOM_DIMENSION_INDEX];
|
||||
}
|
||||
|
||||
// Create object with dimension index and value.
|
||||
const experimentWithDimension = {
|
||||
[`experimentId_${googleDimensionIndex}`]: exp.universeId,
|
||||
[`variationId_${googleDimensionIndex}`]: exp.variationId,
|
||||
[`experimentName_${googleDimensionIndex}`]: exp.universeName,
|
||||
[`variationName_${googleDimensionIndex}`]: exp.variationName,
|
||||
[`customDimension_${googleDimensionIndex}`]: `${exp.universeId}_${exp.variationId}_${exp.universeName}_${exp.variationName}`,
|
||||
};
|
||||
|
||||
// Push to the data layer with the Google Custom Dimension Index.
|
||||
pushToDataLayerIfDefined(experimentWithDimension);
|
||||
});
|
||||
},
|
||||
|
||||
prependActionToMethod(object, method, actionToPrepend) {
|
||||
const baseMethodName = method.name.startsWith("bound ")
|
||||
? method.name.substring(6)
|
||||
: method.name;
|
||||
const baseMethod = object[baseMethodName];
|
||||
object[baseMethodName] = function () {
|
||||
actionToPrepend.apply(this, arguments);
|
||||
return baseMethod.apply(object, arguments);
|
||||
};
|
||||
},
|
||||
|
||||
async initSession() {
|
||||
const sid = getSessionIdValue();
|
||||
const skey = getSessionKeyValue();
|
||||
var payload = {
|
||||
userId: getDeviceIdValue(),
|
||||
sessionId: sid,
|
||||
userAgent: navigator.userAgent,
|
||||
referrer: document.referrer,
|
||||
};
|
||||
|
||||
const response = await baseMixin.methods.dispatchStoreAction(
|
||||
storeActions.INITIALIZE_SESSION,
|
||||
payload,
|
||||
false
|
||||
);
|
||||
|
||||
if (response.data) {
|
||||
if (response.data.sessionKey && skey === 0) {
|
||||
setCookieProperties(
|
||||
{ [cookieNames.SESSION_KEY]: response.data.sessionKey },
|
||||
{
|
||||
useDefaultFunnelCookieAttributes: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
if (response.data.sessionId && sid === "00000000-0000-0000-0000-000000000000") {
|
||||
setCookieProperties(
|
||||
{ [cookieNames.SESSION_ID]: response.data.sessionId },
|
||||
{
|
||||
maxAge: 60 * 30, // 30 minutes
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
noSession() {
|
||||
return (
|
||||
getSessionKeyValue() === 0 ||
|
||||
getSessionIdValue() === "00000000-0000-0000-0000-000000000000"
|
||||
);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
analyticsPageEvents() {
|
||||
return analyticsPageEvents;
|
||||
},
|
||||
GaCategories() {
|
||||
return GaCategories;
|
||||
},
|
||||
GaActions() {
|
||||
return GaActions;
|
||||
},
|
||||
GaLabels() {
|
||||
return GaLabels;
|
||||
},
|
||||
ValueToLogTypes() {
|
||||
return ValueToLogTypes;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function pushToDataLayerIfDefined(data) {
|
||||
if (window.dataLayer !== undefined) {
|
||||
window.dataLayer.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
function getPageNameByQueryString() {
|
||||
const params = new URLSearchParams(location.search);
|
||||
|
||||
if (params.has(queryStrings.FMG_PAGE)) {
|
||||
return params.get(queryStrings.FMG_PAGE);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function getValueToLog(value, valueToLogType) {
|
||||
if (valueToLogType != null && valueToLogType === ValueToLogTypes.LAST_5) {
|
||||
return value.slice(-5);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
299
src/mixins/analytics-mixin.spec.js
Normal file
299
src/mixins/analytics-mixin.spec.js
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
import analyticsMixin from "@/mixins/analytics-mixin";
|
||||
import { setupMocksForJsFiles, setupCookies } from "@/helpers/unit-test-helper.js";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import {
|
||||
analyticsPageEvents,
|
||||
GaCategories,
|
||||
GaActions,
|
||||
GaLabels,
|
||||
GaEvents,
|
||||
ValueToLogTypes,
|
||||
} from "@/constants/analytics";
|
||||
import store from "@/store";
|
||||
|
||||
describe("analyticsMixin.js", () => {
|
||||
test("logPageView: calls dispatch with type and payload", () => {
|
||||
const type = "";
|
||||
const payload = {};
|
||||
|
||||
const mockData = {
|
||||
actionList: [
|
||||
{
|
||||
actionName: storeActions.LOG_PAGE_VIEW,
|
||||
},
|
||||
],
|
||||
};
|
||||
const mocks = setupMocksForJsFiles(mockData);
|
||||
|
||||
const testCookieValue = {
|
||||
sid: "10000000-0000-0000-0000-000000000001",
|
||||
};
|
||||
|
||||
setupCookies({ funnelCookieValue: JSON.stringify(testCookieValue) });
|
||||
|
||||
analyticsMixin.methods.logPageView(type, payload);
|
||||
|
||||
expect(mocks.baseMixin.methods.dispatchStoreAction).toBeCalled();
|
||||
});
|
||||
|
||||
test("logCustomEvent: calls dispatch with type and payload", () => {
|
||||
const mockData = {
|
||||
actionList: [
|
||||
{
|
||||
actionName: storeActions.LOG_CUSTOM_EVENT,
|
||||
},
|
||||
],
|
||||
};
|
||||
const mocks = setupMocksForJsFiles(mockData);
|
||||
|
||||
analyticsMixin.methods.logCustomEvent("someCat", "someAction", "someLabel", "someVal");
|
||||
|
||||
expect(mocks.baseMixin.methods.dispatchStoreAction).toBeCalled();
|
||||
});
|
||||
|
||||
test("pushEventToGA, should call dataLayer push and logCustomEvent too", () => {
|
||||
// Arrange
|
||||
window.dataLayer = [];
|
||||
const mockData = {
|
||||
actionList: [
|
||||
{
|
||||
actionName: storeActions.LOG_CUSTOM_EVENT,
|
||||
},
|
||||
],
|
||||
};
|
||||
const mocks = setupMocksForJsFiles(mockData);
|
||||
var mockDataLayer = [];
|
||||
mockDataLayer.push({
|
||||
event: "event",
|
||||
category: "category",
|
||||
action: "action",
|
||||
label: "label",
|
||||
value: undefined,
|
||||
path: "/fmg/?fmgPage=",
|
||||
});
|
||||
|
||||
// Act
|
||||
analyticsMixin.methods.pushEventToGA("category", "action", "label", true);
|
||||
|
||||
// Assert
|
||||
expect(mockDataLayer).toEqual(expect.arrayContaining(window.dataLayer));
|
||||
expect(mocks.baseMixin.methods.dispatchStoreAction).toBeCalled();
|
||||
});
|
||||
|
||||
test("pushEventToGA, should call dataLayer push and ValueToLogTypes.LAST_5 only logs last 5 of label", () => {
|
||||
// Arrange
|
||||
window.dataLayer = [];
|
||||
var expectedDataLayer = [];
|
||||
expectedDataLayer.push({
|
||||
event: "event",
|
||||
category: "category",
|
||||
action: "action",
|
||||
label: "33333",
|
||||
value: undefined,
|
||||
path: "/fmg/?fmgPage=",
|
||||
});
|
||||
|
||||
// Act
|
||||
analyticsMixin.methods.pushEventToGA(
|
||||
"category",
|
||||
"action",
|
||||
"1111122222333333",
|
||||
false,
|
||||
ValueToLogTypes.LAST_5
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(expectedDataLayer).toEqual(expect.arrayContaining(window.dataLayer));
|
||||
});
|
||||
|
||||
test("pushEventToGA, should call dataLayer push and ValueToLogTypes.LAST_5 logs only the last 3 characters for a 3 character string", () => {
|
||||
// Arrange
|
||||
window.dataLayer = [];
|
||||
var expectedDataLayer = [];
|
||||
expectedDataLayer.push({
|
||||
event: "event",
|
||||
category: "category",
|
||||
action: "action",
|
||||
label: "111",
|
||||
value: undefined,
|
||||
path: "/fmg/?fmgPage=",
|
||||
});
|
||||
|
||||
// Act
|
||||
analyticsMixin.methods.pushEventToGA(
|
||||
"category",
|
||||
"action",
|
||||
"111",
|
||||
false,
|
||||
ValueToLogTypes.LAST_5
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(expectedDataLayer).toEqual(expect.arrayContaining(window.dataLayer));
|
||||
});
|
||||
|
||||
test("Experiments, should push to dataLayer with default Google Custom Dimension Index", () => {
|
||||
// Arrange
|
||||
window.dataLayer = [];
|
||||
|
||||
const mockExperimentData = [
|
||||
{
|
||||
settings: {},
|
||||
variationName: "test",
|
||||
universeName: "testUniverse",
|
||||
},
|
||||
];
|
||||
|
||||
// Mock store
|
||||
jest.mock(
|
||||
"@/store",
|
||||
() => {
|
||||
return {};
|
||||
},
|
||||
{ virtual: true }
|
||||
);
|
||||
|
||||
store.getters = {
|
||||
applicationUser: {
|
||||
experiments: [
|
||||
{
|
||||
settings: {},
|
||||
variationName: "test",
|
||||
universeName: "testUniverse",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
// Act
|
||||
analyticsMixin.methods.pushExperimentsToDataLayer(mockExperimentData);
|
||||
|
||||
// Assert
|
||||
expect(window.dataLayer).toEqual([
|
||||
{
|
||||
experimentId_99: undefined,
|
||||
variationId_99: undefined,
|
||||
experimentName_99: "testUniverse",
|
||||
variationName_99: "test",
|
||||
customDimension_99: "undefined_undefined_testUniverse_test",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("Experiments, should push to dataLayer with custom Google Custom Dimension Index", () => {
|
||||
// Arrange
|
||||
window.dataLayer = [];
|
||||
|
||||
const mockExperimentData = [
|
||||
{
|
||||
settings: { "Google Custom Dimension Index": "5" },
|
||||
variationName: "test",
|
||||
universeName: "testUniverse",
|
||||
},
|
||||
];
|
||||
|
||||
// Mock store
|
||||
jest.mock(
|
||||
"@/store",
|
||||
() => {
|
||||
return {};
|
||||
},
|
||||
{ virtual: true }
|
||||
);
|
||||
|
||||
store.getters = {
|
||||
applicationUser: {
|
||||
experiments: [
|
||||
{
|
||||
settings: { "Google Custom Dimension Index": "5" },
|
||||
variationName: "test",
|
||||
universeName: "testUniverse",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
// Act
|
||||
analyticsMixin.methods.pushExperimentsToDataLayer(mockExperimentData);
|
||||
|
||||
// Assert
|
||||
expect(window.dataLayer).toEqual([
|
||||
{
|
||||
experimentId_5: undefined,
|
||||
variationId_5: undefined,
|
||||
experimentName_5: "testUniverse",
|
||||
variationName_5: "test",
|
||||
customDimension_5: "undefined_undefined_testUniverse_test",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("Obj is not null after action prepended", () => {
|
||||
//Arrange
|
||||
const obj = { baseMethodName: "testMethodName", data: "testData" };
|
||||
const method = { name: "testMethodName", data: "testData" };
|
||||
const action = "testAction";
|
||||
|
||||
//Act
|
||||
analyticsMixin.methods.prependActionToMethod(obj, method, action);
|
||||
|
||||
//Assert
|
||||
expect(obj != null);
|
||||
});
|
||||
test("Obj method name does not include bound", () => {
|
||||
//Arrange
|
||||
const obj = { baseMethodName: "testMethodName", data: "testData" };
|
||||
const method = { name: "testMethodName", data: "testData" };
|
||||
const action = "testAction";
|
||||
|
||||
//Act
|
||||
analyticsMixin.methods.prependActionToMethod(obj, method, action);
|
||||
|
||||
//Assert
|
||||
expect(method.name.startsWith("bound ")).toBe(false);
|
||||
});
|
||||
test("Prepended action does not include bound", () => {
|
||||
//Arrange
|
||||
const obj = { baseMethodName: "testMethodName", data: "testData" };
|
||||
const method = { name: "testMethodName", data: "testData" };
|
||||
const action = "testAction";
|
||||
|
||||
//Act
|
||||
analyticsMixin.methods.prependActionToMethod(obj, method, action);
|
||||
|
||||
//Assert
|
||||
expect(action.startsWith("bound ")).toBe(false);
|
||||
});
|
||||
|
||||
test("analyticsPageEvents returns constants analyticsPageEvents", () => {
|
||||
//Act
|
||||
const analyticsPE = analyticsMixin.computed.analyticsPageEvents();
|
||||
|
||||
//Assert
|
||||
expect(analyticsPE).toEqual(analyticsPageEvents);
|
||||
});
|
||||
|
||||
test("GaActions returns constants GaActions", () => {
|
||||
//Act
|
||||
const gaActions = analyticsMixin.computed.GaActions();
|
||||
|
||||
//Assert
|
||||
expect(gaActions).toEqual(GaActions);
|
||||
});
|
||||
|
||||
test("GaCategories returns constants GaCategories", () => {
|
||||
//Act
|
||||
const gaCategories = analyticsMixin.computed.GaCategories();
|
||||
|
||||
//Assert
|
||||
expect(gaCategories).toEqual(GaCategories);
|
||||
});
|
||||
|
||||
test("GaLabels returns constants GaLabels", () => {
|
||||
//Act
|
||||
const gaLabels = analyticsMixin.computed.GaLabels();
|
||||
|
||||
//Assert
|
||||
expect(gaLabels).toEqual(GaLabels);
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue