import analyticsMixin from "@/mixins/analytics-mixin"; import { setupMocksForJsFiles, setupCookies, setupCrypto, removeAllTestCookies, } from "@/helpers/unit-test-helper.js"; import { storeActions } from "@/constants/store-actions"; import { analyticsPageEvents, GaCategories, GaActions, GaLabels, GaEvents, ValueToLogTypes, } from "@/constants/analytics"; import store from "@/store"; import { getDeviceIdValue, getSessionIdValue, getSessionKeyValue, getUserIdValue, } from "@/helpers/heritage-integration/cookie-helper"; const parts = { windshield: { name: "windshield", canSafeliteRecalibrate: true, childParts: [], color: "Green Tint", description: "solar, soundproofing, lane keep assist, lane departure warning system, w/adaptive cruise control", id: "db22fd44-10dd-456f-979b-ff88cf68cca6", partNumber: "FW04896GTYN", partType: "WINDSHIELD", recalibrationType: "STATIC", requiresCapabilityQuestions: false, requiresRecalibration: true, salesTax: 63.86, sellingPrice: 791.46, kitPrice: 0, laborAmount: 0, }, frontWipers: { name: "front wipers", partNumber: "SBB16", description: "SAFELITE BEAM BLADE 16", partType: "FRONT WIPER", sellingPrice: 32.64, kitPrice: 0, laborAmount: 0, salesTax: 1, }, rearWipers: { name: "rear wipers", partNumber: "SBBR12A", description: "SAFELITE REAR BLADE 12A", partType: "REAR WIPER", sellingPrice: 24.48, kitPrice: 0, laborAmount: 0, salesTax: 2, }, rainDefense: { name: "rain defense", partNumber: "RAIN DEFENSE", description: null, partType: "RAIN DEFENSE", sellingPrice: 35.5, kitPrice: 0, laborAmount: 0, salesTax: 0, }, }; describe("analyticsMixin.js", () => { beforeEach(() => { removeAllTestCookies(); setupCrypto("7e4727f3-9a3d-4c59-9cb3-6f4121b5ea94"); }); test("logPageView: calls dispatch with type and payload", async () => { const type = ""; const payload = {}; const mockData = { actionList: [ { actionName: storeActions.LOG_PAGE_VIEW, }, { actionName: storeActions.INITIALIZE_SESSION, }, ], }; const mocks = setupMocksForJsFiles(mockData); const testCookieValue = { sid: "10000000-0000-0000-0000-000000000001", }; setupCookies({ funnelCookieValue: JSON.stringify(testCookieValue) }); await analyticsMixin.methods.logPageView(type, payload); expect(mocks.baseMixin.methods.dispatchStoreAction).toBeCalled(); }); test("logCustomEvent: calls dispatch with type and payload", async () => { const mockData = { actionList: [ { actionName: storeActions.LOG_CUSTOM_EVENT, }, { actionName: storeActions.INITIALIZE_SESSION, }, ], }; const mocks = setupMocksForJsFiles(mockData); await analyticsMixin.methods.logCustomEvent( "someCat", "someAction", "someLabel", "someVal" ); expect(mocks.baseMixin.methods.dispatchStoreAction).toBeCalled(); }); test("pushEventToGA, should call dataLayer push and logCustomEvent too", async () => { // Arrange window.dataLayer = []; const mockData = { actionList: [ { actionName: storeActions.LOG_CUSTOM_EVENT, }, { actionName: storeActions.INITIALIZE_SESSION, }, ], }; const mocks = setupMocksForJsFiles(mockData); var mockDataLayer = []; mockDataLayer.push({ event: "event", category: "category", action: "action", label: "label", value: undefined, path: "/fmg/?fmgPage=", }); // Act await analyticsMixin.methods.pushEventToGA("category", "action", "label", true); // Assert expect(mockDataLayer).toEqual(expect.arrayContaining(window.dataLayer)); expect(mocks.baseMixin.methods.dispatchStoreAction).toBeCalled(); }); test("pushEventToGA, should call dataLayer push and ValueToLogTypes.LAST_5 only logs last 5 of label", async () => { // Arrange window.dataLayer = []; var expectedDataLayer = []; expectedDataLayer.push({ event: "event", category: "category", action: "action", label: "33333", value: undefined, path: "/fmg/?fmgPage=", }); const mockData = { actionList: [ { actionName: storeActions.LOG_CUSTOM_EVENT, }, { actionName: storeActions.INITIALIZE_SESSION, }, ], }; const mocks = setupMocksForJsFiles(mockData); // Act await analyticsMixin.methods.pushEventToGA( "category", "action", "1111122222333333", false, ValueToLogTypes.LAST_5 ); // Assert expect(expectedDataLayer).toEqual(expect.arrayContaining(window.dataLayer)); }); test("pushEventToGA, should call dataLayer push and ValueToLogTypes.LAST_5 logs only the last 3 characters for a 3 character string", async () => { // Arrange window.dataLayer = []; var expectedDataLayer = []; expectedDataLayer.push({ event: "event", category: "category", action: "action", label: "111", value: undefined, path: "/fmg/?fmgPage=", }); const mockData = { actionList: [ { actionName: storeActions.LOG_CUSTOM_EVENT, }, { actionName: storeActions.INITIALIZE_SESSION, }, ], }; const mocks = setupMocksForJsFiles(mockData); // Act await analyticsMixin.methods.pushEventToGA( "category", "action", "111", false, ValueToLogTypes.LAST_5 ); // Assert expect(expectedDataLayer).toEqual(expect.arrayContaining(window.dataLayer)); }); test("Experiments, should push to dataLayer with default Google Custom Dimension Index", () => { // Arrange window.dataLayer = []; const mockExperimentData = [ { settings: {}, variationName: "test", universeName: "testUniverse", }, ]; // Mock store jest.mock( "@/store", () => { return {}; }, { virtual: true } ); store.getters = { applicationUser: { experiments: [ { settings: {}, variationName: "test", universeName: "testUniverse", }, ], }, }; // Act analyticsMixin.methods.pushExperimentsToDataLayer(mockExperimentData); // Assert expect(window.dataLayer).toEqual([ { experimentId_99: undefined, variationId_99: undefined, experimentName_99: "testUniverse", variationName_99: "test", customDimension_99: "undefined_undefined_testUniverse_test", }, ]); }); test("Experiments, should push to dataLayer with custom Google Custom Dimension Index", () => { // Arrange window.dataLayer = []; const mockExperimentData = [ { settings: { "Google Custom Dimension Index": "5" }, variationName: "test", universeName: "testUniverse", }, ]; // Mock store jest.mock( "@/store", () => { return {}; }, { virtual: true } ); store.getters = { applicationUser: { experiments: [ { settings: { "Google Custom Dimension Index": "5" }, variationName: "test", universeName: "testUniverse", }, ], }, }; // Act analyticsMixin.methods.pushExperimentsToDataLayer(mockExperimentData); // Assert expect(window.dataLayer).toEqual([ { experimentId_5: undefined, variationId_5: undefined, experimentName_5: "testUniverse", variationName_5: "test", customDimension_5: "undefined_undefined_testUniverse_test", }, ]); }); describe("pushOrderToDataLayer", () => { beforeEach(() => { store.getters.hasSubmittedOrder = false; store.getters.order = { vehicle: { year: "2020", make: "acura", model: "mdx", style: "4-door sedan", carId: "dummyCarId", category: "dummyCategory", vin: "dummyVin", }, serviceLocation: { address: "add1", address2: "add2", city: "city", state: "state", zipCode: "11111", zipCodeCtu: "11110", appointmentType: "IN_SHOP", isVehicleProtected: true, provider: { providerNumber: 2, address: { streetAddress: "add3", city: "city2", state: "state2", zipCode: "22222", zipCodeCtu: "22220", }, }, techNotes: "", }, customer: { firstName: "first", lastName: "last", emailAddress: "builddigitaltest@safelite.com", phoneNumber: "555-555-5555", isSmsOptIn: false, }, damage: { isRepair: false, numberOfChips: null, glassToReplace: [{ glassName: "single", glassLocation: "windshield" }], }, lineItems: { glassParts: [parts.windshield], supportingItems: [], vaps: [parts.frontWipers], promos: [ { promoCode: "promoTEST", kitPrice: 0, sellingPrice: 0, laborAmount: 0, salesTax: 0, }, { promoCode: "promoTEST2", kitPrice: 0, sellingPrice: 0, laborAmount: 0, salesTax: 0, }, ], }, payment: { isInsurance: false, insuranceCoverage: { isVerified: null, coverageStatus: null, coverageVerificationType: null, }, isPia: true, piaType: "Afterpay", inactivePromos: [], }, schedule: { date: "date", startTime: "start", endTime: "end", jobMinMinutes: "30", jobMaxMinutes: "45", }, workOrderNumber: "01820-111111", workOrderId: "222222222222", }; store.getters.isRecalibrationOnOrder = true; store.getters.isRecalibrationOnSubmittedOrder = false; }); test("Pushes to data layer if nominal", () => { // Arrange const mockDataLayerFn = jest.fn(); window.dataLayer = { push: mockDataLayerFn, }; // Act analyticsMixin.methods.pushOrderToDataLayer(); // Assert expect(mockDataLayerFn).toHaveBeenCalled(); }); test("Pushes populated data to data layer", () => { // Arrange window.dataLayer = []; // Act analyticsMixin.methods.pushOrderToDataLayer(); const result = window.dataLayer[0]; // Assert console.log(result); const allFieldsPopulated = Object.keys(result).every( (key) => result[key] === false || !!result[key] ); expect(allFieldsPopulated).toBe(true); }); test("Pushes correct data when submitted order is present", () => { // Arrange window.dataLayer = []; store.getters.hasSubmittedOrder = true; store.getters.submittedOrder = store.getters.order; store.getters.order = { vehicle: { year: null, make: null, model: null, style: null, carId: null, category: null, vin: null, }, serviceLocation: { address: null, address2: null, city: null, state: null, zipCode: null, zipCodeCtu: null, appointmentType: null, isVehicleProtected: null, provider: { providerNumber: null, address: { streetAddress: null, city: null, state: null, zipCode: null, zipCodeCtu: null, }, }, techNotes: null, }, customer: { firstName: null, lastName: null, emailAddress: null, phoneNumber: null, isSmsOptIn: null, }, damage: { isRepair: null, numberOfChips: null, glassToReplace: null, }, lineItems: { glassParts: null, supportingItems: null, vaps: null, promos: null, }, payment: { isInsurance: null, insuranceCoverage: { isVerified: null, coverageStatus: null, coverageVerificationType: null, }, isPia: null, piaType: null, inactivePromos: null, }, schedule: { date: null, startTime: null, endTime: null, jobMinMinutes: null, jobMaxMinutes: null, }, workOrderNumber: null, workOrderId: null, }; // Act analyticsMixin.methods.pushOrderToDataLayer(); const result = window.dataLayer[0]; // Assert console.log(result); const allFieldsPopulated = Object.keys(result).every( (key) => result[key] === false || !!result[key] ); expect(allFieldsPopulated).toBe(true); }); test("Pushes default values when data is missing", () => { // Arrange window.dataLayer = []; store.getters.order = { vehicle: { year: null, make: null, model: null, style: null, carId: null, category: null, vin: null, }, serviceLocation: { address: null, address2: null, city: null, state: null, zipCode: null, zipCodeCtu: null, appointmentType: null, isVehicleProtected: null, provider: { providerNumber: null, address: { streetAddress: null, city: null, state: null, zipCode: null, zipCodeCtu: null, }, }, techNotes: null, }, customer: { firstName: null, lastName: null, emailAddress: null, phoneNumber: null, isSmsOptIn: null, }, damage: { isRepair: null, numberOfChips: null, glassToReplace: null, }, lineItems: { glassParts: null, supportingItems: null, vaps: null, promos: null, }, payment: { isInsurance: null, insuranceCoverage: { isVerified: null, coverageStatus: null, coverageVerificationType: null, }, isPia: null, piaType: null, inactivePromos: null, }, schedule: { date: null, startTime: null, endTime: null, jobMinMinutes: null, jobMaxMinutes: null, }, workOrderNumber: null, workOrderId: null, }; // Act analyticsMixin.methods.pushOrderToDataLayer(); const result = window.dataLayer[0]; // Assert console.log(result); const allFieldsPopulatedOrDefault = Object.keys(result).every( (key) => result[key] === false || !!result[key] || result[key] === "" ); expect(allFieldsPopulatedOrDefault).toBe(true); }); test("Glass and Promo strings correctly formatted", () => { // Arrange window.dataLayer = []; // Act analyticsMixin.methods.pushOrderToDataLayer(); // Assert const glassString = window.dataLayer[0].glassToReplace; const promoString = window.dataLayer[0].promoCodes; expect(glassString).toMatch(/(\w+\/\w+)?(,\w+\/\w+)*/); expect(promoString).toMatch(/(\w+)?(,\w+)*/); }); }); test("Obj is not null after action prepended", () => { //Arrange const obj = { baseMethodName: "testMethodName", data: "testData" }; const method = { name: "testMethodName", data: "testData" }; const action = "testAction"; //Act analyticsMixin.methods.prependActionToMethod(obj, method, action); //Assert expect(obj != null); }); test("Obj method name does not include bound", () => { //Arrange const obj = { baseMethodName: "testMethodName", data: "testData" }; const method = { name: "testMethodName", data: "testData" }; const action = "testAction"; //Act analyticsMixin.methods.prependActionToMethod(obj, method, action); //Assert expect(method.name.startsWith("bound ")).toBe(false); }); test("Prepended action does not include bound", () => { //Arrange const obj = { baseMethodName: "testMethodName", data: "testData" }; const method = { name: "testMethodName", data: "testData" }; const action = "testAction"; //Act analyticsMixin.methods.prependActionToMethod(obj, method, action); //Assert expect(action.startsWith("bound ")).toBe(false); }); test("analyticsPageEvents returns constants analyticsPageEvents", () => { //Act const analyticsPE = analyticsMixin.computed.analyticsPageEvents(); //Assert expect(analyticsPE).toEqual(analyticsPageEvents); }); test("GaActions returns constants GaActions", () => { //Act const gaActions = analyticsMixin.computed.GaActions(); //Assert expect(gaActions).toEqual(GaActions); }); test("GaCategories returns constants GaCategories", () => { //Act const gaCategories = analyticsMixin.computed.GaCategories(); //Assert expect(gaCategories).toEqual(GaCategories); }); test("GaLabels returns constants GaLabels", () => { //Act const gaLabels = analyticsMixin.computed.GaLabels(); //Assert expect(gaLabels).toEqual(GaLabels); }); describe("initSession", () => { test("Generates random values for userId and deviceId if not present", async () => { // Arrange const mockData = { actionList: [ { actionName: storeActions.LOG_CUSTOM_EVENT, }, { actionName: storeActions.INITIALIZE_SESSION, data: { sessionKey: "12345", sessionId: "4f1eba4a-4dd5-4144-9a6b-1363c1e5e54f", }, }, ], }; const mocks = setupMocksForJsFiles(mockData); // Act await analyticsMixin.methods.initSession(); const userId = getUserIdValue(); const deviceId = getDeviceIdValue(); // Assert expect(userId).not.toBe("00000000-0000-0000-0000-000000000000"); expect(deviceId).not.toBe("00000000-0000-0000-0000-000000000000"); }); test("Pulls sessionId and sessionKey from api if not set", async () => { // Arrange const mockData = { actionList: [ { actionName: storeActions.LOG_CUSTOM_EVENT, }, { actionName: storeActions.INITIALIZE_SESSION, data: { sessionKey: "54321", sessionId: "4f1eba4a-4dd5-4144-9a6b-1363c1e5e54f", }, }, ], }; const mocks = setupMocksForJsFiles(mockData); // Act await analyticsMixin.methods.initSession(); const sessionId = getSessionIdValue(); const sessionKey = getSessionKeyValue(); // Assert expect(sessionId).toBe("4f1eba4a-4dd5-4144-9a6b-1363c1e5e54f"); expect(sessionKey).toBe("54321"); }); test("Does not overwrite values that are already set", async () => { // Arrange const mockData = { actionList: [ { actionName: storeActions.LOG_CUSTOM_EVENT, }, { actionName: storeActions.INITIALIZE_SESSION, data: { sessionKey: "54321", sessionId: "4f1eba4a-4dd5-4144-9a6b-1363c1e5e54f", }, }, ], }; const mocks = setupMocksForJsFiles(mockData); setupCookies({}); // Act await analyticsMixin.methods.initSession(); const userId = getUserIdValue(); const deviceId = getDeviceIdValue(); const sessionId = getSessionIdValue(); const sessionKey = getSessionKeyValue(); // Assert expect(userId).toBe("11aec5e8-92ba-4dc9-a8b6-179a916d8d7a"); expect(deviceId).toBe("21b9b94a-ec23-42c1-aaac-e2ae4e4dbffe"); expect(sessionId).toBe("cba0c3d1-3c1b-4305-bb56-31aa50f58e27"); expect(sessionKey).toBe("12345"); }); test("Calls dispatchStoreAction", async () => { // Arrange const mockData = { actionList: [ { actionName: storeActions.LOG_CUSTOM_EVENT, }, { actionName: storeActions.INITIALIZE_SESSION, data: { sessionKey: "12345", sessionId: "4f1eba4a-4dd5-4144-9a6b-1363c1e5e54f", }, }, ], }; const mocks = setupMocksForJsFiles(mockData); // Act await analyticsMixin.methods.initSession(); // Assert expect(mocks.baseMixin.methods.dispatchStoreAction).toBeCalled(); }); }); });