// Components import payment from "@/layouts/payment/payment"; // Supporting Files import { shallowMount } from "@vue/test-utils"; import { getMountOptions } from "@/helpers/unit-test-helper"; import store from "@/store"; import { storeActions } from "@/constants/store-actions"; import { paymentMethods } from "@/constants/payment-method-constants"; jest.mock("@/helpers/pricing-helper.js", () => ({ getAmountDue: jest.fn(), getDisplayAmountDue: jest.fn(), })); // Constants 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, }, frontWipers: { name: "front wipers", partNumber: "SBB16", description: "SAFELITE BEAM BLADE 16", partType: "FRONT WIPER", price: 32.64, }, rearWipers: { name: "rear wipers", partNumber: "SBBR12A", description: "SAFELITE REAR BLADE 12A", partType: "REAR WIPER", price: 24.48, }, rainDefense: { name: "rain defense", partNumber: "RAIN DEFENSE", description: null, partType: "RAIN DEFENSE", price: 35.5, }, }; const pricedParts = { windshield: { ...parts.windshield, kitPrice: 0, laborAmount: 60, salesTax: 0, sellingPrice: 791.46, }, frontWipers: { ...parts.frontWipers, kitPrice: 0, laborAmount: 20, salesTax: 0, sellingPrice: 32.64, }, rearWipers: { ...parts.rearWipers, kitPrice: 10, laborAmount: 10, salesTax: 0, sellingPrice: 24.48, }, rainDefense: { ...parts.rainDefense, kitPrice: 20, laborAmount: 0, salesTax: 0, sellingPrice: 35.5, }, }; const taxedParts = { windshield: { ...pricedParts.windshield, salesTax: 63.86, subTotal: 100, }, frontWipers: { ...pricedParts.frontWipers, salesTax: 2.36, subTotal: 100, }, rearWipers: { ...pricedParts.rearWipers, salesTax: 6.0, subTotal: 100, }, rainDefense: { ...pricedParts.rainDefense, salesTax: 1.0, subTotal: 100, }, }; // Setup global mocks let mockCmsContent = {}; let mockStoreActionData = {}; async function mockDispatchStoreAction(actionName) { return mockStoreActionData[actionName]; } jest.mock("@/mixins/base-mixin.js", () => ({ methods: { dispatchStoreAction: jest.fn().mockImplementation(mockDispatchStoreAction), dispatchStoreActionWithLogging: jest.fn().mockImplementation(mockDispatchStoreAction), getAmountDue: jest.fn().mockImplementation(() => 5), getDisplayAmountDue: jest.fn().mockImplementation(() => "$5.00"), }, })); jest.mock("@/helpers/promotions-helper", () => ({ revalidatePromosAndValidateQueryStringPromo: async () => ({}), getVapsThatNeedToBeAddedToSatisfyPromos: () => [], })); jest.mock("@/helpers/cms-content-helper", () => ({ fetchCmsContentForPage: () => Promise.resolve("content"), })); describe("payment.vue", () => { beforeEach(() => { 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: "zip", zipCodeCtu: "zipCtu", appointmentType: "IN_SHOP", isVehicleProtected: true, provider: { providerNumber: 2, address: { streetAddress: "add3", city: "city2", state: "state2", zipCode: "zip2", zipCodeCtu: "zipCtu2", }, }, techNotes: "", }, customer: { firstName: "first", lastName: "last", emailAddress: "builddigitaltest@safelite.com", phoneNumber: "555-555-5555", isSmsOptIn: false, }, damage: { isRepair: false, numberOfChips: null, glassToReplace: [{ location: "windshield" }], }, lineItems: { glassParts: [parts.windshield], supportingItems: [], vaps: [parts.frontWipers], promos: [], }, 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", }, }, applicationUser: { loggingOption: false, }, policy: { currentDeductible: 1, }, get payment() { return this.order.payment; }, get vehicle() { return this.order.vehicle; }, }; mockCmsContent = {}; mockStoreActionData = { [storeActions.GET_SIGNATURE]: { token: "token", signature: "signature", startDate: "startDate", }, [storeActions.GET_WIPERS]: [parts.frontWipers, parts.rearWipers], [storeActions.GET_RAIN_DEFENSE]: parts.rainDefense, [storeActions.PRICE_ORDER_ITEMS_AND_SAVE_SERVER_DATA]: [ pricedParts.windshield, pricedParts.frontWipers, ], [storeActions.TAX_ORDER_ITEMS_AND_SAVE_SERVER_DATA]: [ taxedParts.windshield, taxedParts.frontWipers, ], }; }); afterEach(() => { jest.clearAllMocks(); }); describe("arePagePrerequisitesValid", () => { test("Returns true in nominal conditions", () => { // Arrange // Store defaults are valid const wrapper = setupMocks({}); // Act const result = wrapper.vm.arePagePrerequisitesValid(); // Assert expect(result).toBe(true); }); describe("Payment method cases", () => { test("Returns false if pia options are not valid", () => { // Arrange store.getters.order.payment.isPia = null; store.getters.order.payment.piaType = null; const wrapper = setupMocks({}); // Act const result = wrapper.vm.arePagePrerequisitesValid(); // Assert expect(result).toBe(false); }); test("Returns false if pay at time of service", () => { // Arrange store.getters.order.payment.isPia = false; store.getters.order.payment.piaType = null; const wrapper = setupMocks({}); // Act const result = wrapper.vm.arePagePrerequisitesValid(); // Assert expect(result).toBe(false); }); }); }); describe("beforeRouteEnter", () => { test("Properly initializes fields and kicks off hop", async () => { // Arrange const vmMock = { setCmsContent: jest.fn(), $refs: { cart: { cartItems: [], }, }, getPiaLineItems: jest.fn(), fetchSignatureInfo: jest.fn(), setIFrameListener: jest.fn(), $nextTick: (f) => { f(); }, }; const nextF = (f) => { f(vmMock); }; // Act await payment.beforeRouteEnter.call( vmMock, { query: { fmgPage: "payment" } }, undefined, nextF ); // Assert expect(vmMock.availableVaps).not.toBeUndefined(); expect(vmMock.lineItems).not.toBeUndefined(); expect(vmMock.setCmsContent).toBeCalled(); expect(vmMock.getPiaLineItems).toBeCalled(); expect(vmMock.fetchSignatureInfo).toBeCalled(); expect(vmMock.setIFrameListener).toBeCalled(); }); }); describe("payment type mapping", () => { describe("getPaymentType", () => { test("Maps AFTERPAY -> ap", () => { // Arrange store.getters.order.payment.piaType = paymentMethods.AFTERPAY; const wrapper = setupMocks({}); // Act const mapped = wrapper.vm.getPaymentType(); // Assert expect(mapped).toBe("ap"); }); test("Maps CREDIT_CARD -> cc", () => { // Arrange store.getters.order.payment.piaType = paymentMethods.CREDIT_CARD; const wrapper = setupMocks({}); // Act const mapped = wrapper.vm.getPaymentType(); // Assert expect(mapped).toBe("cc"); }); test("Maps PAYPAL -> pp", () => { // Arrange store.getters.order.payment.piaType = paymentMethods.PAYPAL; const wrapper = setupMocks({}); // Act const mapped = wrapper.vm.getPaymentType(); // Assert expect(mapped).toBe("pp"); }); test("Maps other values to self", () => { // Arrange store.getters.order.payment.piaType = "SomeOtherText"; const wrapper = setupMocks({}); // Act const mapped = wrapper.vm.getPaymentType(); // Assert expect(mapped).toBe("SomeOtherText"); }); }); describe("isPaypal", () => { test("Is responsive if initial data changes", async () => { // Arrange // note: begins with payment-type = afterpay const wrapper = setupMocks({}); // Act const preVal = wrapper.vm.isPaypal; wrapper.vm.paymentType = "pp"; await wrapper.vm.$nextTick(); const postVal = wrapper.vm.isPaypal; // Assert expect(preVal).toBe(false); expect(postVal).toBe(true); }); }); }); describe("getPiaLineItems", () => { test("Properly formatted output", () => { // Arrange const wrapper = setupMocks({}); const cartItems = [ taxedParts.windshield, taxedParts.frontWipers, taxedParts.rainDefense, ]; // Act wrapper.vm.getPiaLineItems(cartItems); const result = wrapper.vm.piaLineItems; // Assert expect(result).toMatch(/^[\w ]+\|[\d]+(.\d\d)?\|1(\|\|[\w ]+\|[\d]+(.\d\d)?\|1)*$/); }); test("Includes repair supply for non-glass orders", () => { // Arrange store.getters.order.lineItems.glassParts = null; const wrapper = setupMocks({}); const cartItems = [taxedParts.frontWipers, taxedParts.rainDefense]; // Act wrapper.vm.getPiaLineItems(cartItems); const result = wrapper.vm.piaLineItems; // Assert expect(result).toMatch(/Repair supplies/); }); test("Does not include promos", () => { // Arrange store.getters.order.lineItems.promos = [ { name: "PROMO", salesTax: 10, subTotal: 20, }, ]; const wrapper = setupMocks({}); const cartItems = [ taxedParts.windshield, taxedParts.frontWipers, taxedParts.rainDefense, ]; // Act wrapper.vm.getPiaLineItems(cartItems); const result = wrapper.vm.piaLineItems; // Assert expect(result).not.toMatch(/PROMO/); }); }); describe("hop form", () => { describe("fetchSignatureInfo", () => { test("Sets auth fields", async () => { // Arrange const signature = { token: "TOKEN", signature: "SIGNATURE", startDate: "STARTDATE", }; const wrapper = setupMocks({}); wrapper.vm.submitHopForm = jest.fn(); // Act await wrapper.vm.fetchSignatureInfo(signature); // Assert expect(wrapper.vm.authToken).toBe(signature.token); expect(wrapper.vm.authSignature).toBe(signature.signature); expect(wrapper.vm.authSignatureStart).toBe(signature.startDate); expect(wrapper.vm.submitHopForm).toBeCalled(); }); test("If paypal, show the loading modal", async () => { // Arrange const signature = { token: "TOKEN", signature: "SIGNATURE", startDate: "STARTDATE", }; store.getters.order.payment.piaType = "Paypal"; const wrapper = setupMocks({}); wrapper.vm.submitHopForm = jest.fn(); wrapper.vm.$refs.loadingModal.showModal = jest.fn(); // Act await wrapper.vm.fetchSignatureInfo(signature); // Assert expect(wrapper.vm.$refs.loadingModal.showModal).toBeCalled(); }); }); describe("submitHopForm", () => { test("Submits form", async () => { // Arrange const wrapper = setupMocks({}); wrapper.vm.$refs.hopForm.submit = jest.fn(); wrapper.vm.$refs.paymentFrame = null; // Act wrapper.vm.submitHopForm(); await wrapper.vm.$nextTick(); // Assert expect(wrapper.vm.$refs.hopForm.submit).toBeCalled(); }); }); describe("handleIFrameContentWindowMessage", () => { test("Navigates back if afterpay is closed", () => { // Arrange const event = { data: "afterpayClosed", }; const wrapper = setupMocks({}); wrapper.vm.backButtonAction = jest.fn(); // Act wrapper.vm.handleIFrameContentWindowMessage(event); // Assert expect(wrapper.vm.backButtonAction).toBeCalled(); }); test("Blocks UI interaction if credit card is submitted", () => { // Arrange const event = { data: "creditCardSubmit", }; const wrapper = setupMocks({}); // Act wrapper.vm.handleIFrameContentWindowMessage(event); // Assert expect(wrapper.vm.shouldBlockInteraction).toBe(true); }); }); }); describe("UI Blocking", () => { test("UI block appears when toggled", async () => { // Arrange const wrapper = setupMocks({}); // Act wrapper.vm.setUIBlock(true); await wrapper.vm.$nextTick(); const uiBlockElements = wrapper.findAll(".ui-block"); // Assert expect(uiBlockElements.length).toBeGreaterThan(0); }); test("Don't propogate clicks from UI block", async () => { // Arrange const wrapper = setupMocks({}); const outerDiv = wrapper.find(".container-fluid"); const clickFn = jest.fn(); outerDiv.element.addEventListener("click", clickFn); // Act wrapper.vm.setUIBlock(true); await wrapper.vm.$nextTick(); const uiBlockElement = wrapper.find(".ui-block"); await uiBlockElement.trigger("click"); // Assert expect(clickFn).not.toBeCalled(); }); }); }); function setupMocks({ customMountOptions }) { const mountOptions = getMountOptions({ ...customMountOptions, route: { name: "payment-method" }, router: { navigateWithSaving: jest.fn(), navigateWithoutSaving: jest.fn(), navigateWithPageData: jest.fn(), navigateAndForceTopLevelNavigation: jest.fn(), }, }); // set all mock stuff mountOptions.global.mocks["$store"] = store; mountOptions.mixins = [ { methods: { getCmsContent: jest.fn().mockImplementation((widgetName, fieldName) => { if (mockCmsContent[widgetName] && mockCmsContent[widgetName][fieldName]) return mockCmsContent[widgetName][fieldName]; }), setCmsContent: jest.fn(), }, }, ]; const wrapper = shallowMount(payment, mountOptions); // act on vm return wrapper; }