diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index ba2e359dc..e58f51862 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -78,4 +78,5 @@ stages:
appDeployVariables:
__VUE_APP_CONSUMER_API_GATEWAY__: $(__VUE_APP_CONSUMER_API_GATEWAY__)
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
+ __VUE_APP_HERITAGE_FUNNEL__: $(__VUE_APP_HERITAGE_FUNNEL__)
cfDistributionId: $(cfDistributionId)
\ No newline at end of file
diff --git a/src/.eslintrc.js b/src/.eslintrc.js
new file mode 100644
index 000000000..309a0e4fd
--- /dev/null
+++ b/src/.eslintrc.js
@@ -0,0 +1,6 @@
+module.exports = {
+ env: {
+ jest: true
+ },
+//...
+}
\ No newline at end of file
diff --git a/src/common-components/funnel-header/funnel-header.vue b/src/common-components/funnel-header/funnel-header.vue
index 382640219..3266b88a9 100644
--- a/src/common-components/funnel-header/funnel-header.vue
+++ b/src/common-components/funnel-header/funnel-header.vue
@@ -66,4 +66,7 @@ export default {
.logo-image {
max-width: 78px;
}
+.alert {
+ left: 0;
+}
diff --git a/src/common-components/vehicle-banner/vehicle-banner.spec.js b/src/common-components/vehicle-banner/vehicle-banner.spec.js
index a6dc13154..210653df2 100644
--- a/src/common-components/vehicle-banner/vehicle-banner.spec.js
+++ b/src/common-components/vehicle-banner/vehicle-banner.spec.js
@@ -93,7 +93,7 @@ function setupMocks({
categoryValue = "CAR"
}) {
//Mock store
- store.dispatch = jest.fn(() => dataFromStoreApi);
+ store.dispatch = jest.fn(() => {});
store.getters = { vehicle: { category: categoryValue, imageUrl: imageUrlValue } };
const mountOptions = getMountOptions({
store: {
diff --git a/src/constants/application-config.js b/src/constants/application-config.js
index 9c90aadbc..489776e66 100644
--- a/src/constants/application-config.js
+++ b/src/constants/application-config.js
@@ -1,6 +1,9 @@
+
const applicationConfig = {
CONSUMER_APIGATEWAY_URL: process.env.VUE_APP_CONSUMER_API_GATEWAY,
GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY,
+ ANALYTICS_SESSION_TIMEOUT_MINUTES: 30,
+ SAVED_SESSION_TIMEOUT_DAYS: 45
};
-export { applicationConfig };
+export { applicationConfig };
\ No newline at end of file
diff --git a/src/constants/cookie-names.js b/src/constants/cookie-names.js
new file mode 100644
index 000000000..2756a8eb7
--- /dev/null
+++ b/src/constants/cookie-names.js
@@ -0,0 +1,6 @@
+const cookieNames = {
+ FUNNEL_SESSION_INFO: "FunnelSessionInfo",
+};
+
+export { cookieNames };
+
\ No newline at end of file
diff --git a/src/constants/endpoints.js b/src/constants/endpoints.js
index 24e5120bd..03c03cf6c 100644
--- a/src/constants/endpoints.js
+++ b/src/constants/endpoints.js
@@ -47,6 +47,14 @@ const endpoints = {
url: "/parts/api/v1/parts/parts-or-questions",
method: "POST",
},
+ SaveOrder: {
+ url: "/order/api/v1/order/save",
+ method: "POST",
+ },
+ LoadOrder: {
+ url: "/order/api/v1/order/load",
+ method: "POST",
+ }
};
export { endpoints };
diff --git a/src/constants/query-strings.js b/src/constants/query-strings.js
new file mode 100644
index 000000000..6655f9378
--- /dev/null
+++ b/src/constants/query-strings.js
@@ -0,0 +1,7 @@
+const queryStrings = {
+ FMG_PAGE: 'fmgPage',
+ START_TYPE: 'start_type'
+ };
+
+ export { queryStrings };
+
\ No newline at end of file
diff --git a/src/constants/store-actions.js b/src/constants/store-actions.js
index e83b44096..c0e6893a4 100644
--- a/src/constants/store-actions.js
+++ b/src/constants/store-actions.js
@@ -12,12 +12,17 @@ const storeActions = {
LOOKUP_VEHICLE_BY_YMMS: "lookupVehicleByYmms",
LOOKUP_VEHICLE_BY_VIN: "lookupVehicleByVin",
GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions",
+ SAVE_ORDER: "saveOrder",
+ LOAD_ORDER: "loadOrder",
+ SET_REFERRAL_INFORMATION: "setReferralInformation",
+ SET_LOAD_ORDER_DATA: "setLoadOrderData",
// DEPENDENCY MUTATIONS
RESET_VEHICLE_STATE_AND_DEPENDENCIES: "resetVehicleAndDependencies",
RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies",
RESET_REGISTRATION_STATE_AND_DEPENDENCIES: "resetRegistrationAndDependencies",
RESET_PARTS_STATE_AND_DEPENDENCIES: "resetPartsAndDependencies",
+ RESET_STATE: "resetState",
};
export { storeActions };
diff --git a/src/constants/store-mutations.js b/src/constants/store-mutations.js
index c07e88bf2..8b59bacb0 100644
--- a/src/constants/store-mutations.js
+++ b/src/constants/store-mutations.js
@@ -15,6 +15,12 @@ const storeMutations = {
UPDATE_GLASS_TO_REPLACE: "updateGlassToReplace",
UPDATE_PARTS: "updateParts",
+ // ORDER MUTATIONS
+ UPDATE_REFERRAL_NUMBER: "updateReferralNumber",
+ UPDATE_REFERRAL_DATE: "updateReferralDate",
+ UPDATE_REFERRAL_CORRELATION_ID: "updateReferralCorrelationId",
+ UPDATE_PARENT_ACCT_NUMBER: "updateParentAcctNumber",
+
// EVENT BUS MUTATIONS
ADD_EVENT_TO_BUS: "addEventToBus",
REMOVE_EVENT_FROM_BUS: "removeEventFromBus",
@@ -24,9 +30,11 @@ const storeMutations = {
RESET_DAMAGE_STATE: "resetDamageState",
RESET_REGISTRATION_STATE: "resetRegistrationState",
RESET_PARTS_STATE: "resetPartsState",
+ RESET_STATE: "resetState",
// OTHER MUTATIONS
UPDATE_PAGE_DATA: "updatePageData",
+ SET_LOAD_FUNNEL_SESSION_INFO: "setLoadOrderInformation"
};
diff --git a/src/constants/widget-names.js b/src/constants/widget-names.js
deleted file mode 100644
index d9782f90b..000000000
--- a/src/constants/widget-names.js
+++ /dev/null
@@ -1,8 +0,0 @@
-const widgetNames = {
- FUNNEL_SUB_HEADER_WIDGET: "FunnelSubHeaderWidget",
- RADIO_QUESTION_WIDGET: "RadioQuestionWidget",
- VEHICLE_BANNER_WIDGET: "VehicleBannerWidget",
- FUNNEL_HEADER_WIDGET: "FunnelHeaderWidget",
-};
-
-export { widgetNames };
diff --git a/src/global-methods.js b/src/global-methods.js
index 788d4c2e4..f0675c02d 100644
--- a/src/global-methods.js
+++ b/src/global-methods.js
@@ -6,6 +6,7 @@ export default {
callHttpClient({ method, endpoint, payload }) {
return new Promise((resolve, reject) => {
const apiGatewayUrl = applicationConfig.CONSUMER_APIGATEWAY_URL;
+
const payloadAndAnalyticsData = Object.assign({}, payload, {
AppName: "FixMyGlass",
});
diff --git a/src/helpers/cms-content-helper.js b/src/helpers/cms-content-helper.js
index a20446ef8..3bd5319b5 100644
--- a/src/helpers/cms-content-helper.js
+++ b/src/helpers/cms-content-helper.js
@@ -1,5 +1,4 @@
import { storeActions } from "@/constants/store-actions.js";
-import { widgetNames } from "@/constants/widget-names.js";
import store from "@/store";
export function fetchCmsContentForPage(fmgPage) {
diff --git a/src/helpers/heritage-integration/cookie-helper.js b/src/helpers/heritage-integration/cookie-helper.js
new file mode 100644
index 000000000..cee400e0c
--- /dev/null
+++ b/src/helpers/heritage-integration/cookie-helper.js
@@ -0,0 +1,85 @@
+import { cookieNames } from "@/constants/cookie-names";
+import store from "@/store";
+
+/*
+ Will update the cookie if present, or create a new one if not.
+*/
+export function updateOrCreateFunnelCookie() {
+ // Create the cookie
+ document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}={}; path=/; ${getCookieDomainValue()};`;
+
+ // Set up cookie with all the props.
+ setFunnelCookieProperties({
+ LastTouched: new Date().toUTCString(),
+ SavedQuoteTimeoutDate: store.getters.applicationUser.savedSessionTimeout,
+ DidHeritageFunnelUpdateLast: false,
+ ShouldResetState: false,
+ ReferralNumber: store.getters.order.referralNumber,
+ ReferralDate: store.getters.order.referralDate,
+ ReferralCorrelationId: store.getters.order.referralCorrelationId,
+ });
+
+}
+
+/*
+ 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() {
+ console.log("hi there")
+ document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}=; Max-Age=0; path=/; ${getCookieDomainValue()}`;
+}
+
+/*
+ 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);
+
+ document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}=${cookieValueJson}; path=/; ${getCookieDomainValue()}`;
+ }
+ }
+}
+
+export function getCookieDomainValue() {
+ return location.hostname.includes("localhost") ? "" : `domain=${getDomainWithoutSubdomain()};`;
+}
+
+function getDomainWithoutSubdomain() {
+ let url = location.hostname;
+ if (url.includes("localhost")) {
+ return "localhost";
+ }
+
+ const urlParts = url.split('.');
+
+ return `.${urlParts
+ .slice(0)
+ .slice(-(urlParts.length === 4 ? 3 : 2))
+ .join('.')}`;
+}
diff --git a/src/helpers/heritage-integration/cookie-helper.spec.js b/src/helpers/heritage-integration/cookie-helper.spec.js
new file mode 100644
index 000000000..9d14389e2
--- /dev/null
+++ b/src/helpers/heritage-integration/cookie-helper.spec.js
@@ -0,0 +1,104 @@
+import {getFunnelCookie} 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
+ }
+
+ 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();
+ });
+ })
+ })
+
\ No newline at end of file
diff --git a/src/helpers/heritage-integration/navigation-helper.js b/src/helpers/heritage-integration/navigation-helper.js
new file mode 100644
index 000000000..12207acf4
--- /dev/null
+++ b/src/helpers/heritage-integration/navigation-helper.js
@@ -0,0 +1,72 @@
+import { queryStrings } from "@/constants/query-strings";
+import { externalUrls } from "@/router/router-constants/externalUrl-values";
+import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js";
+import { saveOrder } from "@/helpers/heritage-integration/order-helper.js";
+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 'heritage';
+ }
+ }
+
+
+ // 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 lazyLoadComponent('vehicle-make')()).default;
+ const vehicleModelComponent = (await lazyLoadComponent('vehicle-model')()).default;
+ const vehicleStyleComponent = (await lazyLoadComponent('vehicle-style')()).default;
+ const vehicleDamageComponent = (await lazyLoadComponent('vehicle-damage')()).default;
+
+
+ if (!vehicleMakeComponent.methods.arePagePrerequisitesValid()) {
+ return "vehicle-year";
+ } else if (!vehicleModelComponent.methods.arePagePrerequisitesValid()) {
+ return "vehicle-make";
+ } else if (!vehicleStyleComponent.methods.arePagePrerequisitesValid()) {
+ return "vehicle-model";
+ } else if (!vehicleDamageComponent.methods.arePagePrerequisitesValid()) {
+ return "vehicle-style";
+ } else if (store.getters.damage.isRepair == null || !store.getters.vehicle.carId) {
+ return 'vehicle-damage'
+ } else {
+ if (store.getters.vehicle.vin) {
+ return 'vehicle-damage';
+ //return "vin-lookup"; (uncomment)
+ } else {
+ return 'vehicle-damage';
+ //return "estimate" (uncomment)
+ }
+ }
+
+}
+
+/*
+ Used to navigate to the heritage funnel with the correct query string and url.
+*/
+
+export async function navigateToHeritageFunnel() {
+
+ // Create the order (or save existing order) when navigating to Heritage Funnel.
+ await saveOrder();
+
+ router.navigateToExternalUrl(
+ externalUrls.HERITAGE_FUNNEL,
+ {
+ corid: store.getters.order.referralCorrelationId,
+ src: "concept-funnel",
+ }
+ );
+}
\ No newline at end of file
diff --git a/src/helpers/heritage-integration/navigation-helper.spec.js b/src/helpers/heritage-integration/navigation-helper.spec.js
new file mode 100644
index 000000000..be1136e63
--- /dev/null
+++ b/src/helpers/heritage-integration/navigation-helper.spec.js
@@ -0,0 +1,372 @@
+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 store from "@/store";
+import router from "@/router";
+
+// Mock Lazy Load
+jest.mock("@/router/dynamic-routing/component-loader.js", () => ({
+ lazyLoadComponent: jest.fn(),
+}));
+
+describe("getPageToRouteExistingOrderTo", () => {
+
+ test("getPageToRouteExistingOrderTo, should return vehicle-year", async () => {
+ // Arrange
+ const toRoute = {
+ query: {}
+ };
+
+ // Mock out the lazy load calls for all components.
+ lazyLoadComponent
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(false)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(false)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(false)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(false)
+ }
+ }
+ }
+ });
+
+ // Act
+ const result = await getPageToRouteExistingOrderTo(toRoute, false);
+
+ //Assert
+ expect(result).toBe('vehicle-year');
+ });
+
+ test("getPageToRouteExistingOrderTo, should return vehicle-model", async () => {
+ // Arrange
+ const toRoute = {
+ query: {}
+ };
+
+ // Mock out the lazy load calls for all components.
+ lazyLoadComponent
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(false)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(false)
+ }
+ }
+ }
+ });
+
+ // Act
+ const result = await getPageToRouteExistingOrderTo(toRoute, false);
+
+ //Assert
+ expect(result).toBe('vehicle-model');
+ });
+
+ test("getPageToRouteExistingOrderTo, should return vehicle-damage", async () => {
+ // Arrange
+ const toRoute = {
+ query: {}
+ };
+
+ // Mock out the lazy load calls for all components.
+ lazyLoadComponent
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ });
+
+ store.getters.damage.isRepair = undefined;
+ store.getters.vehicle.carId = 'C00000';
+
+ // Act
+ const result = await getPageToRouteExistingOrderTo(toRoute, false);
+
+ //Assert
+ expect(result).toBe('vehicle-damage');
+ });
+
+ test("getPageToRouteExistingOrderTo, should return vin-lookup", async () => {
+ // Arrange
+ const toRoute = {
+ query: {}
+ };
+
+ // Mock out the lazy load calls for all components.
+ lazyLoadComponent
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ });
+
+ store.getters.damage.isRepair = true;
+ store.getters.vehicle.carId = 'C00000';
+ store.getters.vehicle.vin = "1FADP3F26DL212886"
+
+ // Act
+ const result = await getPageToRouteExistingOrderTo(toRoute, false);
+
+ //Assert
+ //expect(result).toBe('vin-lookup');
+ expect(result).toBe('vehicle-damage');
+ });
+
+ test("getPageToRouteExistingOrderTo, should return estimate", async () => {
+ // Arrange
+ const toRoute = {
+ query: {}
+ };
+
+ // Mock out the lazy load calls for all components.
+ lazyLoadComponent
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ })
+ .mockReturnValueOnce(() => {
+ return {
+ default: {
+ methods: {
+ arePagePrerequisitesValid: jest.fn().mockReturnValueOnce(true)
+ }
+ }
+ }
+ });
+
+ store.getters.damage.isRepair = true;
+ store.getters.vehicle.carId = 'C00000';
+ store.getters.vehicle.vin = null;
+ // Act
+ const result = await getPageToRouteExistingOrderTo(toRoute, false);
+
+ //Assert
+ //expect(result).toBe('estimate');
+ expect(result).toBe('vehicle-damage');
+ });
+
+ test("getPageToRouteExistingOrderTo, 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("heritage");
+ })
+});
+
+describe("navigateToHeritageFunnel", () => {
+ test("should save order", async () => {
+ // Arrange
+ const mockReferralNumber = 2;
+ const mockCorrelationId = "55";
+ const mockReferralDate = "2022";
+
+ const mockOrderInfo = getMockOrderInfo(mockReferralNumber, mockCorrelationId, mockReferralDate);
+
+ const mockData = {
+ actionList: [{
+ actionName: storeActions.SAVE_ORDER,
+ data: mockOrderInfo,
+ }]
+ }
+
+ setupMocksForJsFiles(mockData);
+ const saveOrderFunction = jest.spyOn(orderHelper, "saveOrder");
+ router.navigateToExternalUrl = jest.fn();
+
+ // Act
+ await navigateToHeritageFunnel();
+
+ // Assert
+ expect(saveOrderFunction).toHaveBeenCalled();
+
+ // Should alway save before we navigate to heritage
+ const saveOrderFunctionCallOrder = saveOrderFunction.mock.invocationCallOrder[0];
+ const routerNavigateFunctionCallOrder = router.navigateToExternalUrl.mock.invocationCallOrder[0];
+ expect(saveOrderFunctionCallOrder).toBeLessThan(routerNavigateFunctionCallOrder);
+ saveOrderFunction.mockRestore();
+ });
+
+ test("should go to heritage funnel", async () => {
+ // Arrange
+ const mockReferralNumber = 2;
+ const mockCorrelationId = "55";
+ const mockReferralDate = "2022";
+
+ const mockOrderInfo = getMockOrderInfo(mockReferralNumber, mockCorrelationId, mockReferralDate);
+
+ const mockData = {
+ actionList: [{
+ actionName: storeActions.SAVE_ORDER,
+ 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
+ })
+ );
+ });
+});
diff --git a/src/helpers/heritage-integration/order-helper.js b/src/helpers/heritage-integration/order-helper.js
new file mode 100644
index 000000000..8dddb5436
--- /dev/null
+++ b/src/helpers/heritage-integration/order-helper.js
@@ -0,0 +1,68 @@
+import { storeActions } from "@/constants/store-actions.js";
+import { getFunnelCookie, updateOrCreateFunnelCookie, deleteFunnelCookie } from "@/helpers/heritage-integration/cookie-helper.js";
+import baseMixin from "@/mixins/base-mixin";
+
+/*
+ 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 loadOrderIfPresent() {
+ const funnelCookie = getFunnelCookie();
+ console.log(funnelCookie)
+
+ // Do nothing if there is no cookie or no correlation id.
+ if (funnelCookie == null || funnelCookie.ReferralCorrelationId == null) {
+ return null;
+ }
+
+ // Reset state if cookie says to.
+ if (funnelCookie.ShouldResetState) {
+ baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.RESET_STATE);
+ deleteFunnelCookie();
+ console.log("sup")
+ return null;
+ }
+
+
+ // Load referral if there is a cookie, and it doesn't indicate it needs a state reset.
+ return (await loadOrder(funnelCookie.ReferralNumber, funnelCookie.ReferralDate, funnelCookie.ReferralCorrelationId)).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 saveOrder() {
+ const savedOrderInfo = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.SAVE_ORDER);
+
+ // Save the referral information back from the store.
+ await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.SET_REFERRAL_INFORMATION, {
+ referralNumber: savedOrderInfo.data.referralNumber,
+ referralCorrelationId: savedOrderInfo.data.referralCorrelationId,
+ referralDate: savedOrderInfo.data.referralDate,
+ }, false);
+
+ // Update the cookie with the referral information when saved.
+ updateOrCreateFunnelCookie();
+}
+
+
+// PRIVATE FUNCTIONS //
+
+/*
+ Calls API to load order given the referral number, referralDate, and referralCorrelationId
+ and returns the response.
+*/
+async function loadOrder(referralNumber, referralDate, referralCorrelationId) {
+ const response = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOAD_ORDER,
+ {
+ referralNumber: referralNumber.toString(),
+ referralDate: referralDate,
+ referralCorrelationId: referralCorrelationId
+ }, false);
+
+ return response;
+}
\ No newline at end of file
diff --git a/src/helpers/heritage-integration/order-helper.spec.js b/src/helpers/heritage-integration/order-helper.spec.js
new file mode 100644
index 000000000..cbe54ba25
--- /dev/null
+++ b/src/helpers/heritage-integration/order-helper.spec.js
@@ -0,0 +1,172 @@
+import * as cookieHelper from "@/helpers/heritage-integration/cookie-helper";
+import { loadOrderIfPresent, saveOrder } 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("loadOrderIfPresent", () => {
+
+ 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"
+ }
+
+ document.cookie = `${cookieNames.FUNNEL_SESSION_INFO}=${JSON.stringify(testCookieValue)}; path=/; ${cookieHelper.getCookieDomainValue()}`;
+
+ // Act
+ loadOrderIfPresent();
+
+ // 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" });
+
+ const mockData = {
+ actionList: [{
+ actionName: storeActions.RESET_STATE
+ }],
+ }
+
+ var mocks = setupMocksForJsFiles(mockData);
+
+ // Act
+ loadOrderIfPresent();
+
+ // Assert
+ expect(cookieHelper.getFunnelCookie).toHaveBeenCalled();
+ expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).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
+ loadOrderIfPresent();
+
+ // Assert
+ expect(cookieHelper.getFunnelCookie).toHaveBeenCalled();
+ expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).not.toHaveBeenCalledWith(storeActions.RESET_STATE);
+ });
+
+ test("Funnel cookie valid, should call loadOrder", 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_ORDER,
+ data: { ReferralNumber: 123456, vehicle: { year: 2010 } }
+ }],
+ }
+
+ var mocks = setupMocksForJsFiles(mockData);
+
+ // Act
+ const result = await loadOrderIfPresent();
+
+ // Assert
+ expect(cookieHelper.getFunnelCookie).toHaveBeenCalled();
+ expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).not.toHaveBeenCalledWith(storeActions.LOAD_ORDER);
+ expect(result.ReferralNumber).toBe(123456);
+ expect(result.vehicle.year).toBe(2010);
+ });
+});
+
+describe("saveOrder", () => {
+ afterEach(() => {
+ removeAllTestCookies();
+ });
+
+ test("saveOrder => should set state order values", async () => {
+ // Arrange
+ const mockReferralNumber = 2;
+ const mockCorrelationId = "55";
+ const mockReferralDate = "2022";
+
+ const mockOrderInfo = getMockOrderInfo(mockReferralNumber, mockCorrelationId, mockReferralDate);
+
+ const mockData = {
+ actionList: [
+ {
+ actionName: storeActions.SAVE_ORDER,
+ data: mockOrderInfo,
+ },
+ {
+ actionName: storeActions.SET_REFERRAL_INFORMATION
+ }
+ ]
+ }
+
+ const mocks = setupMocksForJsFiles(mockData);
+
+ // Act
+ await saveOrder();
+
+ // Assert
+ expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).toHaveBeenCalledWith(storeActions.SAVE_ORDER);
+ expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).toHaveBeenCalledWith(storeActions.SET_REFERRAL_INFORMATION, {
+ referralNumber: mockReferralNumber,
+ referralDate: mockReferralDate,
+ referralCorrelationId: mockCorrelationId
+ }, false);
+ });
+
+ test("saveOrder => should update DidHeritageFunnelUpdateLast cookie value to false", async () => {
+ // Arrange
+ const testReferralNumber = 1566818;
+ const testReferralDate = "2022-03-15T10:56:24.597";
+ const testReferralCorrelationId = "404d2b04-f86e-45c3-b373-127b6217b060";
+
+ const mockOrderInfo = getMockOrderInfo(testReferralNumber, testReferralCorrelationId, testReferralDate);
+
+ const mockData = {
+ actionList: [{
+ actionName: storeActions.SAVE_ORDER,
+ data: mockOrderInfo,
+ }],
+ router: router
+ };
+
+ setupMocksForJsFiles(mockData);
+
+ const testCookieValue = {
+ ReferralNumber: testReferralNumber,
+ ReferralDate: testReferralDate,
+ ReferralCorrelationId: testReferralCorrelationId,
+ ShouldResetState: false,
+ DidHeritageFunnelUpdateLast: true
+ }
+
+ setupCookies({ funnelCookieValue: JSON.stringify(testCookieValue) });
+
+ // Act
+ await saveOrder();
+
+ // Assert
+ expect(cookieHelper.getFunnelCookie().DidHeritageFunnelUpdateLast).toEqual(false);
+ });
+});
diff --git a/src/helpers/heritage-integration/session-helper.js b/src/helpers/heritage-integration/session-helper.js
new file mode 100644
index 000000000..ba816cdb1
--- /dev/null
+++ b/src/helpers/heritage-integration/session-helper.js
@@ -0,0 +1,50 @@
+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().SavedQuoteTimeoutDate);
+ const isSavedSessionTimedOut = (new Date(new Date().toUTCString()) > savedSessionTimeStamp);
+
+ if (isSavedSessionTimedOut) {
+ return false;
+ }
+
+ return true;
+ }
+}
+
+/*
+Function to get 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();
+}
\ No newline at end of file
diff --git a/src/helpers/heritage-integration/session-helper.spec.js b/src/helpers/heritage-integration/session-helper.spec.js
new file mode 100644
index 000000000..8a17743d1
--- /dev/null
+++ b/src/helpers/heritage-integration/session-helper.spec.js
@@ -0,0 +1,82 @@
+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({ SavedQuoteTimeoutDate: 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({ SavedQuoteTimeoutDate: 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());
+ });
+})
\ No newline at end of file
diff --git a/src/helpers/unit-test-helper.js b/src/helpers/unit-test-helper.js
index 1509a4b16..7b81cca00 100644
--- a/src/helpers/unit-test-helper.js
+++ b/src/helpers/unit-test-helper.js
@@ -3,27 +3,18 @@ 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 baseMixin from "@/mixins/base-mixin";
+import { getCookieDomainValue } from "@/helpers/heritage-integration/cookie-helper";
+// 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.dispatchNonBlockingStoreAction) vs this.dispatchNonBlockingStoreAction
- if (mockData.actionList !== undefined) {
- baseMixin.methods.dispatchNonBlockingStoreAction = jest.fn();
- baseMixin.methods.dispatchNonBlockingStoreAction.mockImplementation((actionName) => {
- let actionFilterResult = mockData.actionList.filter(
- (x) => x.actionName == actionName
- );
+ setupBaseMixinDispatchNonBlockingStoreAction(mockData);
- if (actionFilterResult.length > 0 && actionFilterResult.length === 1) {
- return Promise.resolve({
- data: actionFilterResult[0].data,
- });
- }
- });
- }
mocks.dispatchNonBlockingStoreAction = jest.fn();
mocks.dispatchNonBlockingStoreAction.mockImplementation((actionName) => {
let actionFilterResult = mockData.actionList.filter(
@@ -49,10 +40,64 @@ export function getMountOptions(mockData) {
mocks.$router = mockData.router;
mocks.$route = mockData.route;
-
const global = {
mocks: mocks,
};
return { global };
}
+
+export function setupMocksForJsFiles(mockData = {}) {
+ setupBaseMixinDispatchNonBlockingStoreAction(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": "{}"
+};
+
+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) {
+ return {
+ referralNumber: mockReferralNumber,
+ referralCorrelationId: mockCorrelationId,
+ referralDate: mockReferralDate
+ }
+}
+
+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)
+ document.cookie = `${key}=${cookieValue}; path=/; ${getCookieDomainValue()}`;
+ });
+}
+
+// Private methods
+function setupBaseMixinDispatchNonBlockingStoreAction(mockData) {
+ if (mockData.actionList !== undefined) {
+ baseMixin.methods.dispatchNonBlockingStoreAction = jest.fn();
+ baseMixin.methods.dispatchNonBlockingStoreAction.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,
+ });
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/layouts/address-poc/address-poc.vue b/src/layouts/address-poc/address-poc.vue
deleted file mode 100644
index 8bb42a659..000000000
--- a/src/layouts/address-poc/address-poc.vue
+++ /dev/null
@@ -1,233 +0,0 @@
-
- List Buttons
-
- List ButtonHorizontals
-
- List Cards
-
- Radio
-
-