From c50e21a2a7f8e7483b29a684f9a5875d886b5fff Mon Sep 17 00:00:00 2001 From: FrankRua Date: Tue, 29 Mar 2022 09:13:19 -0400 Subject: [PATCH] refactor heritage helper --- src/helpers/heritage-integration-helper.js | 292 ----------- .../heritage-integration-helper.spec.js | 475 ------------------ .../heritage-integration/cookie-helper.js | 78 +++ .../cookie-helper.spec.js | 128 +++++ .../heritage-integration/navigation-helper.js | 77 +++ .../navigation-helper.spec.js | 310 ++++++++++++ .../heritage-integration/order-helper.js | 75 +++ .../heritage-integration/order-helper.spec.js | 187 +++++++ .../heritage-integration/session-helper.js | 52 ++ src/layouts/vehicle-damage/vehicle-damage.vue | 2 +- src/router/index.js | 21 +- src/store/index.js | 4 +- 12 files changed, 923 insertions(+), 778 deletions(-) delete mode 100644 src/helpers/heritage-integration-helper.js delete mode 100644 src/helpers/heritage-integration-helper.spec.js create mode 100644 src/helpers/heritage-integration/cookie-helper.js create mode 100644 src/helpers/heritage-integration/cookie-helper.spec.js create mode 100644 src/helpers/heritage-integration/navigation-helper.js create mode 100644 src/helpers/heritage-integration/navigation-helper.spec.js create mode 100644 src/helpers/heritage-integration/order-helper.js create mode 100644 src/helpers/heritage-integration/order-helper.spec.js create mode 100644 src/helpers/heritage-integration/session-helper.js diff --git a/src/helpers/heritage-integration-helper.js b/src/helpers/heritage-integration-helper.js deleted file mode 100644 index f4ae1c1a2..000000000 --- a/src/helpers/heritage-integration-helper.js +++ /dev/null @@ -1,292 +0,0 @@ -// import * as self from "./heritage-integration-helper"; -import { storeActions } from "@/constants/store-actions.js"; -import { cookieNames } from "@/constants/cookie-names"; -import { queryStrings } from "@/constants/query-strings"; -import { externalUrls } from "@/router/router-constants/externalUrl-values"; -import { applicationConfig } from "@/constants/application-config"; -import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; -import store from "@/store"; -import router from "@/router"; -import baseMixin from "../mixins/base-mixin"; - -/* - 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. -*/ -async function saveOrder() { - console.log("saving order..."); - 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. - updateOrCreateConceptCookie(); -} - -/* - 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. - -*/ -async function loadOrderIfPresent() { - console.log("attempting to load referral...."); - // const conceptCookie = self.getConceptCookie(); - const conceptCookie = exportFunctions.getConceptCookie(); - - console.log(conceptCookie) - - // Do nothing if there is no cookie or no correlation id. - if (conceptCookie === null || conceptCookie.ReferralCorrelationId === null) { - console.log("No referral found"); - return null; - } - - // Reset state if cookie says to. - if (conceptCookie.ShouldResetState) { - console.log("Resetting state..."); - baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.RESET_STATE); - deleteConceptCookie(); - return null; - } - - console.log("calling load order from loadOrderIfPresent()..."); - - // Load referral if there is a cookie, and it doesn't indicate it needs a state reset. - return (await loadOrder(conceptCookie.ReferralNumber, conceptCookie.ReferralDate, conceptCookie.ReferralCorrelationId)).data; -} - -/* - If the user has visited the concept 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. -*/ -async function getPageToRouteExistingOrderTo(toRoute = {}, existingHeritageOrder = false) { - - // If the user is coming in via the Safelite.Com CTA - if (toRoute.query[queryStrings.START_TYPE] === 'fmg') { - console.log("Start type is FMG... trying to figure out where to send them..."); - - // If they have an existing order, return 'heritage' for the page name. - if (existingHeritageOrder) { - console.log("Existing heritage order found, returning 'heritage' for page redirect..."); - 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 "vin-lookup"; - } else { - return "estimate" - } - } - -} - -/* - Will update the cookie if present, or create a new one if not. -*/ -function updateOrCreateConceptCookie() { - console.log("Updating cookie...", { - 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, - }); - - // Create the cookie - document.cookie = `${cookieNames.CONCEPT_SESSION_INFO}={}; path=/`; - - // Set up cookie with all the props. - setConceptCookieProperties({ - 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, - }); - -} - -/* - Method to determine if our analytics session has timed out or not. - Amount used for timeout is configurable in application-config.js -*/ -function isAnalyticsSessionStillActive() { - if (getConceptCookie() !== null) { - const lastTouchedValue = getConceptCookie().LastTouched; - const timeoutAmount = applicationConfig.ANALYTICS_SESSION_TIMEOUT; - 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 -*/ -function isSavedSessionStillActive() { - if (getConceptCookie() !== null) { - const savedSessionTimeStamp = new Date(getConceptCookie().SavedQuoteTimeoutDate); - const isSavedSessionTimedOut = (new Date(new Date().toUTCString()) > savedSessionTimeStamp); - - console.log("Saved Session Timed Out? --->", isSavedSessionTimedOut); - - if (isSavedSessionTimedOut) { - return false; - } - - return true; - } -} - -/* - Used to navigate to the heritage funnel with the correct query string and url. -*/ - -async function navigateToHeritageFunnel() { - - // Create the order (or save existing order) when navigating to Heritage Funnel. - // console.log(exports) - // await self.saveOrder(); - await exportFunctions.saveOrder(); - - router.navigateToExternalUrl( - externalUrls.HERITAGE_FUNNEL, - { - corid: store.getters.order.referralCorrelationId, - src: "concept-funnel", - // TODO CSR-28, remove this - cns: "all", - experiments: "RemoveServiceAreaPage=ServAreaRemoval_V7=ServAreaNoRemove_V7_TEST=true,ConceptFunnel=ConceptFunnel_V1=ConceptFunnel_TEST=true" - } - ); -} - -/* - Gets the current instance of the concept funnel cookie. - Returns null if cookie isn't valid JSON. -*/ -function getConceptCookie() { - const cookieJson = document.cookie - ?.split("; ") - ?.find(row => row.startsWith(`${cookieNames.CONCEPT_SESSION_INFO}=`)) - ?.split("=")[1]; - - try { - return JSON.parse(cookieJson); - } catch (error) { - return null; - } -} - -/* -Function to get the date for the saved session timeout. -*/ - -function getDateForSavedSessionTimeout() { - const currentDate = new Date(new Date().toUTCString()) - currentDate.setDate(currentDate.getDate() + applicationConfig.SAVED_SESSION_TIMEOUT) - return currentDate.toUTCString(); -} - -//-------------------------------------\\\ -// --------- PRIVATE FUNCTIONS --------- \\\ -//----------------------------------------\\\ - -/* - Calls API to load order given the referral number, referralDate, and referralCorrelationId - and returns the response. -*/ -async function loadOrder(referralNumber, referralDate, referralCorrelationId) { - console.log("loading order...", referralNumber, referralDate, referralCorrelationId); - const response = await baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.LOAD_ORDER, - { - referralNumber: referralNumber.toString(), - referralDate: referralDate, - referralCorrelationId: referralCorrelationId - }, false); - - return response; -} - -/* - Used to set properties on the concept funnel cookie. - Takes an object with properties to set. Will overwrite existing properties. -*/ -function setConceptCookieProperties(properties) { - if (typeof properties == "object") { - let cookie = getConceptCookie(); - - if (cookie !== null) { - Object.keys(properties).forEach(key => { - cookie[key] = properties[key]; - }); - - const cookieValueJson = JSON.stringify(cookie); - - document.cookie = `${cookieNames.CONCEPT_SESSION_INFO}=${cookieValueJson}; path=/`; - } - } -} - -/* - Removes concept cookie from browser. -*/ -function deleteConceptCookie() { - document.cookie = `${cookieNames.CONCEPT_SESSION_INFO}=; Max-Age=0; path=/; domain=${location.hostname}`; -} - -const exportFunctions = { - saveOrder, - loadOrderIfPresent, - getPageToRouteExistingOrderTo, - updateOrCreateConceptCookie, - isAnalyticsSessionStillActive, - isSavedSessionStillActive, - navigateToHeritageFunnel, - getConceptCookie, - getDateForSavedSessionTimeout -}; - -export default exportFunctions; \ No newline at end of file diff --git a/src/helpers/heritage-integration-helper.spec.js b/src/helpers/heritage-integration-helper.spec.js deleted file mode 100644 index 7d6de90c2..000000000 --- a/src/helpers/heritage-integration-helper.spec.js +++ /dev/null @@ -1,475 +0,0 @@ -import helper from "@/helpers/heritage-integration-helper"; -import { cookieNames } from "@/constants/cookie-names"; -import { setupMocksForJsFiles } from "@/helpers/unit-test-helper.js"; -import { storeActions } from "@/constants/store-actions"; -import store from "@/store"; -import router from "@/router"; -import baseMixin from "@/mixins/base-mixin"; -import { externalUrls } from "@/router/router-constants/externalUrl-values"; -import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; - -// Mock Lazy Load -jest.mock("@/router/dynamic-routing/component-loader.js", () => ({ - lazyLoadComponent: jest.fn(), -})); - -describe("loadOrderIfPresent", () => { - afterEach(() => { - removeAllTestCookies(); - }); - - test("ShouldResetState == true => concept cookie is deleted", () => { - // Arrange - const testShouldResetState = true; - - const testCookieValue = { - ShouldResetState: testShouldResetState - } - - document.cookie = `${cookieNames.ORDER_INFO}=${JSON.stringify(testCookieValue)}; path=/; domain=${location.hostname}`; - - // Act - helper.loadOrderIfPresent(); - - // Assert - expect(document.cookie).toBe(""); - }); - - test("ShouldResetState == true => reset store", () => { - // Arrange - // helper.getConceptCookie = jest.fn().mockReturnValueOnce({ ShouldResetState: true, DidHeritageFunnelUpdateLast: false }); - helper.getConceptCookie = jest.spyOn(helper, "getConceptCookie").mockReturnValueOnce({ ShouldResetState: true, ReferralCorrelationId: "xxx-xxx-xxx"}); - // store.dispatch = jest.spyOn(store, "dispatch"); - - const mockData = { - actionList: [{ - actionName: storeActions.RESET_STATE - }], - } - - var mocks = setupMocksForJsFiles(mockData); - - // Act - helper.loadOrderIfPresent(); - - // Assert - expect(helper.getConceptCookie).toHaveBeenCalled(); - expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).toHaveBeenCalledWith(storeActions.RESET_STATE); - - helper.getConceptCookie.mockRestore(); - // store.dispatch.mockRestore(); - }); - - test("Concept cookie is null => store is unchanged", () => { - // Arrange - helper.getConceptCookie = jest.spyOn(helper, "getConceptCookie").mockReturnValueOnce(null); - // store.dispatch = jest.spyOn(store, "dispatch"); - - const mockData = { - actionList: [{ - actionName: storeActions.RESET_STATE - }], - } - - var mocks = setupMocksForJsFiles(mockData); - - // Act - helper.loadOrderIfPresent(); - - // Assert - expect(helper.getConceptCookie).toHaveBeenCalled(); - expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).not.toHaveBeenCalledWith(storeActions.RESET_STATE); - - helper.getConceptCookie.mockRestore(); - }); -}); - -describe("saveOrder", () => { - afterEach(() => { - removeAllTestCookies(); - // jest.resetAllMocks(); - // jest.unmock("@/helpers/heritage-integration-helper"); - // // helper.getConceptCookie = jest.fn().mockImplementation(arg => arg) - }); - - 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 helper.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({ conceptCookieValue: JSON.stringify(testCookieValue) }); - - // Act - await helper.saveOrder(); - - console.log(document.cookie) - console.log(helper.getConceptCookie) - console.log(helper.getConceptCookie()) - - // Assert - expect(helper.getConceptCookie().DidHeritageFunnelUpdateLast).toEqual(false); - }); -}); - -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(helper, "saveOrder"); - router.navigateToExternalUrl = jest.fn(); - - // Act - await helper.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 helper.navigateToHeritageFunnel(); - - // Assert - expect(router.navigateToExternalUrl).toHaveBeenCalled(); - expect(router.navigateToExternalUrl).toHaveBeenCalledWith(externalUrls.HERITAGE_FUNNEL, - expect.objectContaining({ - corid: mockCorrelationId - }) - ); - }); -}); - -describe("cookies", () => { - afterEach(() => { - removeAllTestCookies(); - }) - - describe("getConceptCookie 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({ conceptCookieValue: JSON.stringify(testCookieValue) }); - - // Act - var result = helper.getConceptCookie(); - - // 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({ conceptCookieValue: JSON.stringify(testCookieValue) }); - - // Act - var result = helper.getConceptCookie(); - - // 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({ conceptCookieValue: testCookieValue }); - - // Act - var result = helper.getConceptCookie(); - - // Assert - expect(result).toEqual(null); - }); - - test("returns null when concept cookie doesn't exist", () => { - // Arrange - setupCookies({ includeHeritageCookie: false }); - - // Act - var result = helper.getConceptCookie(); - - // Assert - expect(result).toEqual(null); - }); - - test("gets correct cookie value", () => { - // Arrange - const testCookieValue = { test: "testValue" }; - - setupCookies({ conceptCookieValue: JSON.stringify(testCookieValue) }); - - // Act - const actualCookieValue = helper.getConceptCookie(); - - // Assert - expect(actualCookieValue).toEqual(testCookieValue); - }); - - test("getCookieValue: Gets null cookie value", () => { - // Arrange - setupCookies({ includeHeritageCookie: false }); - - // Act - const actualCookieValue = helper.getConceptCookie(); - - // Assert - expect(actualCookieValue).toBeNull(); - }); - }) -}) - -describe("getPageToRouteExistingOrderTo", () => { - 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 helper.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 = false; - store.getters.vehicle.carId = 'C00000'; - - // Act - const result = await helper.getPageToRouteExistingOrderTo(toRoute, false); - - //Assert - expect(result).toBe('vehicle-damage'); - }); - -}); - -// document.cookie is not just a string, needs to be added individually -const cookies = { - [cookieNames.CONCEPT_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": "{}" -}; - -function setupCookies({ conceptCookieValue = "", includeHeritageCookie = true }) { - Object.keys(cookies).forEach(key => { - const cookieValue = key == cookieNames.CONCEPT_SESSION_INFO ? conceptCookieValue : cookies[key]; - - if (includeHeritageCookie || key != cookieNames.CONCEPT_SESSION_INFO) - document.cookie = `${key}=${cookieValue}; path=/;`; - }); -} - -function removeAllTestCookies() { - Object.keys(cookies).forEach(key => { - document.cookie = `${key}=;Max-Age=0;`; - document.cookie = `${key}=;Max-Age=0;path=/`; - }); -} - -function getMockOrderInfo(mockReferralNumber, mockCorrelationId, mockReferralDate) { - return { - referralNumber: mockReferralNumber, - referralCorrelationId: mockCorrelationId, - referralDate: mockReferralDate - } -} diff --git a/src/helpers/heritage-integration/cookie-helper.js b/src/helpers/heritage-integration/cookie-helper.js new file mode 100644 index 000000000..9de5d5760 --- /dev/null +++ b/src/helpers/heritage-integration/cookie-helper.js @@ -0,0 +1,78 @@ +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 updateOrCreateConceptCookie() { + console.log("Updating cookie...", { + 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, + }); + + // Create the cookie + document.cookie = `${cookieNames.CONCEPT_SESSION_INFO}={}; path=/`; + + // Set up cookie with all the props. + setConceptCookieProperties({ + 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 concept funnel cookie. + Returns null if cookie isn't valid JSON. +*/ +export function getConceptCookie() { + const cookieJson = document.cookie + ?.split("; ") + ?.find(row => row.startsWith(`${cookieNames.CONCEPT_SESSION_INFO}=`)) + ?.split("=")[1]; + + try { + return JSON.parse(cookieJson); + } catch (error) { + return null; + } +} + +/* + Removes concept cookie from browser. +*/ +export function deleteConceptCookie() { + document.cookie = `${cookieNames.CONCEPT_SESSION_INFO}=; Max-Age=0; path=/; domain=${location.hostname}`; +} + +/* + Used to set properties on the concept funnel cookie. + Takes an object with properties to set. Will overwrite existing properties. +*/ +function setConceptCookieProperties(properties) { + if (typeof properties == "object") { + let cookie = getConceptCookie(); + + if (cookie !== null) { + Object.keys(properties).forEach(key => { + cookie[key] = properties[key]; + }); + + const cookieValueJson = JSON.stringify(cookie); + + document.cookie = `${cookieNames.CONCEPT_SESSION_INFO}=${cookieValueJson}; path=/`; + } + } +} + 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..73b3bd9bc --- /dev/null +++ b/src/helpers/heritage-integration/cookie-helper.spec.js @@ -0,0 +1,128 @@ +import {getConceptCookie} from "@/helpers/heritage-integration/cookie-helper.js"; +import { cookieNames } from "@/constants/cookie-names"; + +describe("cookies", () => { + + afterEach(() => { + removeAllTestCookies(); + }) + + describe("getConceptCookie 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({ conceptCookieValue: JSON.stringify(testCookieValue) }); + + // Act + var result = getConceptCookie(); + + // 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({ conceptCookieValue: JSON.stringify(testCookieValue) }); + + // Act + var result = getConceptCookie(); + + // 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({ conceptCookieValue: testCookieValue }); + + // Act + var result = getConceptCookie(); + + // Assert + expect(result).toEqual(null); + }); + + test("returns null when concept cookie doesn't exist", () => { + // Arrange + setupCookies({ includeHeritageCookie: false }); + + // Act + var result = getConceptCookie(); + + // Assert + expect(result).toEqual(null); + }); + + test("gets correct cookie value", () => { + // Arrange + const testCookieValue = { test: "testValue" }; + + setupCookies({ conceptCookieValue: JSON.stringify(testCookieValue) }); + + // Act + const actualCookieValue = getConceptCookie(); + + // Assert + expect(actualCookieValue).toEqual(testCookieValue); + }); + + test("getCookieValue: Gets null cookie value", () => { + // Arrange + setupCookies({ includeHeritageCookie: false }); + + // Act + const actualCookieValue = getConceptCookie(); + + // Assert + expect(actualCookieValue).toBeNull(); + }); + }) + }) + + // document.cookie is not just a string, needs to be added individually +const cookies = { + [cookieNames.CONCEPT_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": "{}" + }; + + function setupCookies({ conceptCookieValue = "", includeHeritageCookie = true }) { + Object.keys(cookies).forEach(key => { + const cookieValue = key == cookieNames.CONCEPT_SESSION_INFO ? conceptCookieValue : cookies[key]; + + if (includeHeritageCookie || key != cookieNames.CONCEPT_SESSION_INFO) + document.cookie = `${key}=${cookieValue}; path=/;`; + }); + } + + function removeAllTestCookies() { + Object.keys(cookies).forEach(key => { + document.cookie = `${key}=;Max-Age=0;`; + document.cookie = `${key}=;Max-Age=0;path=/`; + }); + } + \ 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..2195ce66c --- /dev/null +++ b/src/helpers/heritage-integration/navigation-helper.js @@ -0,0 +1,77 @@ +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 concept 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') { + console.log("Start type is FMG... trying to figure out where to send them..."); + + // If they have an existing order, return 'heritage' for the page name. + if (existingHeritageOrder) { + console.log("Existing heritage order found, returning 'heritage' for page redirect..."); + 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 "vin-lookup"; + } else { + return "estimate" + } + } + +} + +/* + 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. + // console.log(exports) + // await self.saveOrder(); + await saveOrder(); + + router.navigateToExternalUrl( + externalUrls.HERITAGE_FUNNEL, + { + corid: store.getters.order.referralCorrelationId, + src: "concept-funnel", + // TODO CSR-28, remove this + cns: "all", + experiments: "RemoveServiceAreaPage=ServAreaRemoval_V7=ServAreaNoRemove_V7_TEST=true,ConceptFunnel=ConceptFunnel_V1=ConceptFunnel_TEST=true" + } + ); +} \ 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..2c586a76f --- /dev/null +++ b/src/helpers/heritage-integration/navigation-helper.spec.js @@ -0,0 +1,310 @@ +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 } from "@/helpers/unit-test-helper.js"; +import { externalUrls } from "@/router/router-constants/externalUrl-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("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'); + }); + + 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'); + }); +}); + +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 + }) + ); + }); +}); + +function getMockOrderInfo(mockReferralNumber, mockCorrelationId, mockReferralDate) { + return { + referralNumber: mockReferralNumber, + referralCorrelationId: mockCorrelationId, + referralDate: mockReferralDate + } +} diff --git a/src/helpers/heritage-integration/order-helper.js b/src/helpers/heritage-integration/order-helper.js new file mode 100644 index 000000000..8d8fe01d4 --- /dev/null +++ b/src/helpers/heritage-integration/order-helper.js @@ -0,0 +1,75 @@ +import { storeActions } from "@/constants/store-actions.js"; +import { getConceptCookie, updateOrCreateConceptCookie, deleteConceptCookie } 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() { + console.log("attempting to load referral...."); + // const conceptCookie = self.getConceptCookie(); + const conceptCookie = getConceptCookie(); + + console.log(conceptCookie) + + // Do nothing if there is no cookie or no correlation id. + if (conceptCookie == null || conceptCookie.ReferralCorrelationId == null) { + console.log("No referral found"); + return null; + } + + // Reset state if cookie says to. + if (conceptCookie.ShouldResetState) { + console.log("Resetting state..."); + baseMixin.methods.dispatchNonBlockingStoreAction(storeActions.RESET_STATE); + deleteConceptCookie(); + return null; + } + + console.log("calling load order from loadOrderIfPresent()..."); + + // Load referral if there is a cookie, and it doesn't indicate it needs a state reset. + return (await loadOrder(conceptCookie.ReferralNumber, conceptCookie.ReferralDate, conceptCookie.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() { + console.log("saving order..."); + 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. + updateOrCreateConceptCookie(); +} + + +// PRIVATE FUNCTIONS // + +/* + Calls API to load order given the referral number, referralDate, and referralCorrelationId + and returns the response. +*/ +async function loadOrder(referralNumber, referralDate, referralCorrelationId) { + console.log("loading order...", 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..1fa223466 --- /dev/null +++ b/src/helpers/heritage-integration/order-helper.spec.js @@ -0,0 +1,187 @@ +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 } from "@/helpers/unit-test-helper.js"; +import { storeActions } from "@/constants/store-actions"; +import router from "@/router"; + +describe("loadOrderIfPresent", () => { + + afterEach(() => { + removeAllTestCookies(); + }); + + test("ShouldResetState == true => concept cookie is deleted", () => { + // Arrange + const testShouldResetState = true; + + const testCookieValue = { + ShouldResetState: testShouldResetState + } + + document.cookie = `${cookieNames.ORDER_INFO}=${JSON.stringify(testCookieValue)}; path=/; domain=${location.hostname}`; + + // Act + loadOrderIfPresent(); + + // Assert + expect(document.cookie).toBe(""); + }); + + test("ShouldResetState == true => reset store", () => { + // Arrange + cookieHelper.getConceptCookie = jest.spyOn(cookieHelper, "getConceptCookie").mockReturnValueOnce({ ShouldResetState: true, ReferralCorrelationId: "xxx-xxx-xxx" }); + + const mockData = { + actionList: [{ + actionName: storeActions.RESET_STATE + }], + } + + var mocks = setupMocksForJsFiles(mockData); + + // Act + loadOrderIfPresent(); + + // Assert + expect(cookieHelper.getConceptCookie).toHaveBeenCalled(); + expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).toHaveBeenCalledWith(storeActions.RESET_STATE); + + cookieHelper.getConceptCookie.mockRestore(); + // store.dispatch.mockRestore(); + }); + + test("Concept cookie is null => store is unchanged", () => { + // Arrange + cookieHelper.getConceptCookie = jest.spyOn(cookieHelper, "getConceptCookie").mockReturnValueOnce(null); + + const mockData = { + actionList: [{ + actionName: storeActions.RESET_STATE + }], + } + + var mocks = setupMocksForJsFiles(mockData); + + // Act + loadOrderIfPresent(); + + // Assert + expect(cookieHelper.getConceptCookie).toHaveBeenCalled(); + expect(mocks.baseMixin.methods.dispatchNonBlockingStoreAction).not.toHaveBeenCalledWith(storeActions.RESET_STATE); + + cookieHelper.getConceptCookie.mockRestore(); + }); +}); + +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({ conceptCookieValue: JSON.stringify(testCookieValue) }); + + // Act + await saveOrder(); + + console.log(document.cookie) + console.log(cookieHelper.getConceptCookie) + console.log(cookieHelper.getConceptCookie()) + + // Assert + expect(cookieHelper.getConceptCookie().DidHeritageFunnelUpdateLast).toEqual(false); + }); +}); + +// document.cookie is not just a string, needs to be added individually +const cookies = { + [cookieNames.CONCEPT_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": "{}" + }; + +function removeAllTestCookies() { + Object.keys(cookies).forEach(key => { + document.cookie = `${key}=;Max-Age=0;`; + document.cookie = `${key}=;Max-Age=0;path=/`; + }); +} + +function getMockOrderInfo(mockReferralNumber, mockCorrelationId, mockReferralDate) { + return { + referralNumber: mockReferralNumber, + referralCorrelationId: mockCorrelationId, + referralDate: mockReferralDate + } +} + +function setupCookies({ conceptCookieValue = "", includeHeritageCookie = true }) { + Object.keys(cookies).forEach(key => { + const cookieValue = key == cookieNames.CONCEPT_SESSION_INFO ? conceptCookieValue : cookies[key]; + + if (includeHeritageCookie || key != cookieNames.CONCEPT_SESSION_INFO) + document.cookie = `${key}=${cookieValue}; path=/;`; + }); + } \ No newline at end of file diff --git a/src/helpers/heritage-integration/session-helper.js b/src/helpers/heritage-integration/session-helper.js new file mode 100644 index 000000000..068c445eb --- /dev/null +++ b/src/helpers/heritage-integration/session-helper.js @@ -0,0 +1,52 @@ +import { applicationConfig } from "@/constants/application-config"; +import { getConceptCookie} 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 (getConceptCookie() !== null) { + const lastTouchedValue = getConceptCookie().LastTouched; + const timeoutAmount = applicationConfig.ANALYTICS_SESSION_TIMEOUT; + 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 (getConceptCookie() !== null) { + const savedSessionTimeStamp = new Date(getConceptCookie().SavedQuoteTimeoutDate); + const isSavedSessionTimedOut = (new Date(new Date().toUTCString()) > savedSessionTimeStamp); + + console.log("Saved Session Timed Out? --->", isSavedSessionTimedOut); + + 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) + return currentDate.toUTCString(); +} \ No newline at end of file diff --git a/src/layouts/vehicle-damage/vehicle-damage.vue b/src/layouts/vehicle-damage/vehicle-damage.vue index ae6ba756e..93a3c9680 100644 --- a/src/layouts/vehicle-damage/vehicle-damage.vue +++ b/src/layouts/vehicle-damage/vehicle-damage.vue @@ -74,7 +74,7 @@ import { required } from "@/helpers/validation-rules"; import { errorMessages } from "@/constants/error-messages"; import { damageLocationsCms } from "@/constants/damage-locations-cms.js"; import { damageLocationsSelected } from "@/constants/damage-locations-selected.js"; -import { navigateToHeritageFunnel } from "@/helpers/heritage-integration-helper"; +import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper"; import store from "@/store"; import baseMixin from "@/mixins/base-mixin"; diff --git a/src/router/index.js b/src/router/index.js index 51e010980..eef9d4b23 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -4,7 +4,12 @@ import { storeActions } from "@/constants/store-actions"; import { lazyLoadComponent } from "@/router/dynamic-routing/component-loader.js"; import { routingTable } from "@/router/router-constants/routing-table.js"; import { globalEvents, globalEventTypes } from "@/constants/events"; -import heritageIntegrationHelper from "@/helpers/heritage-integration-helper"; + +// Heritage integration +import { isSavedSessionStillActive } from "@/helpers/heritage-integration/session-helper"; +import { updateOrCreateConceptCookie,getConceptCookie } from "@/helpers/heritage-integration/cookie-helper"; +import { loadOrderIfPresent, saveOrder } from "@/helpers/heritage-integration/order-helper"; +import { getPageToRouteExistingOrderTo, navigateToHeritageFunnel} from "@/helpers/heritage-integration/navigation-helper"; import baseMixin from "@/mixins/base-mixin"; import eventBus from "@/helpers/event-bus/event-bus"; @@ -36,22 +41,22 @@ const routes = [ try { // If the saved session has timed out, clear the session, execute 404 logic. - if (!heritageIntegrationHelper.isSavedSessionStillActive()) { + if (!isSavedSessionStillActive()) { await GoToFunnelStartOn404(next); } // Process concept funnel cookie. - heritageIntegrationHelper.updateOrCreateConceptCookie(); + updateOrCreateConceptCookie(); // On entering the concept funnel "fresh", read cookie information, decide what to do next. if (from.redirectedFrom === undefined) { - const loadOrderResponse = await heritageIntegrationHelper.loadOrderIfPresent(); - const pageToRedirectTo = await heritageIntegrationHelper.getPageToRouteExistingOrderTo(to, loadOrderResponse); + const loadOrderResponse = await loadOrderIfPresent(); + const pageToRedirectTo = await getPageToRouteExistingOrderTo(to, loadOrderResponse); // If getPageToRouteExistingOrderTo determines that the return user needs to // go back to heritage funnel, send them there and stop our current navigation. if (pageToRedirectTo === 'heritage') { - await heritageIntegrationHelper.navigateToHeritageFunnel(); + await navigateToHeritageFunnel(); return next(false); } @@ -159,8 +164,8 @@ async function navigate(scenario, currentRoute, invalidateOnSave, optionalQuery baseMixin.methods.savePageDataToStore(destinationFmgPageValue, optionalPageData); // if cookie and referralNumber/Date exists - if (heritageIntegrationHelper.getConceptCookie()?.ReferralNumber && heritageIntegrationHelper.getConceptCookie()?.ReferralDate) { - await heritageIntegrationHelper.saveOrder(); + if (getConceptCookie()?.ReferralNumber && getConceptCookie()?.ReferralDate) { + await saveOrder(); } router.push({ diff --git a/src/store/index.js b/src/store/index.js index 5d2a0a422..26a0b31b6 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,7 +1,7 @@ import { createStore } from "vuex"; import { endpoints } from "@/constants/endpoints.js"; import { storeMutations } from "@/constants/store-mutations"; -import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration-helper"; +import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration/session-helper"; import createPersistedState from "vuex-persistedstate"; import globalMethods from "@/global-methods"; @@ -53,7 +53,7 @@ const getDefaultState = () => { applicationUser: { eventBus: [], pageData: {}, - // savedSessionTimeout: getDateForSavedSessionTimeout() TODO CSR-98 + savedSessionTimeout: getDateForSavedSessionTimeout() }, } };