// Components import payment from '@/layouts/payment-page/payment-page.vue'; // Supporting Files import { shallowMount } from '@vue/test-utils'; import { getMountOptions } from '@/helpers/unit-test-helper'; import { useMainStore } from '@/store'; import { paymentMethods, hopPaymentMethods } from '@/constants/payment-method-constants.js'; // 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 = {}; jest.mock('@/mixins/base-mixin.js', () => ({ methods: { getAmountDue: jest.fn().mockImplementation(() => 5), getDisplayAmountDue: jest.fn().mockImplementation(() => '$5.00') } })); jest.mock('@/helpers/cms-content-helper', () => ({ fetchCmsContentForPage: () => Promise.resolve('content') })); function setupMocks({ customMountOptions }) { const mountOptions = getMountOptions({ ...customMountOptions, route: { query: { issPage: 'page-name' }, params: {} } }); // set all mock stuff mountOptions.mixins = [ { methods: { getCmsContent: jest.fn().mockImplementation((widgetName, fieldName) => { if (mockCmsContent[widgetName] && mockCmsContent[widgetName][fieldName]) { return mockCmsContent[widgetName][fieldName]; } return undefined; }), setCmsContent: jest.fn() } } ]; const wrapper = shallowMount(payment, mountOptions); // act on vm return wrapper; } describe('payment-page.vue', () => { beforeEach(() => { useMainStore().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: '' }, contactInfo: { firstName: 'first', lastName: 'last', emailAddress: 'builddigitaltest@safelite.com', phoneNumber: '555-555-5555' }, 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 }, isPayInAdvance: true, payInAdvanceType: 'Afterpay', inactivePromos: [] }, schedule: { date: 'date', startTime: 'start', endTime: 'end', jobMinMinutes: '30', jobMaxMinutes: '45' } }; mockCmsContent = {}; }); 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 useMainStore().order.payment.isPayInAdvance = null; useMainStore().order.payment.payInAdvanceType = 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 useMainStore().order.payment.isPayInAdvance = false; useMainStore().order.payment.payInAdvanceType = 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: { issPage: 'payment-page' } }, undefined, nextF ); // Assert expect(vmMock.setCmsContent).toBeCalled(); expect(vmMock.fetchSignatureInfo).toBeCalled(); expect(vmMock.setIFrameListener).toBeCalled(); }); }); describe('payment type mapping', () => { describe('getPaymentType', () => { test('Maps AFTERPAY -> hopPaymentMethods.AFTERPAY', () => { // Arrange useMainStore().order.payment.payInAdvanceType = paymentMethods.AFTERPAY; const wrapper = setupMocks({}); // Act const mapped = wrapper.vm.getPaymentType(); // Assert expect(mapped).toBe(hopPaymentMethods.AFTERPAY); }); test('Maps CREDIT_CARD -> hopPaymentMethods.CREDIT_CARD', () => { // Arrange useMainStore().order.payment.payInAdvanceType = paymentMethods.CREDIT_CARD; const wrapper = setupMocks({}); // Act const mapped = wrapper.vm.getPaymentType(); // Assert expect(mapped).toBe(hopPaymentMethods.CREDIT_CARD); }); test('Maps PAYPAL -> hopPaymentMethods.PAYPAL', () => { // Arrange useMainStore().order.payment.payInAdvanceType = paymentMethods.PAYPAL; const wrapper = setupMocks({}); // Act const mapped = wrapper.vm.getPaymentType(); // Assert expect(mapped).toBe(hopPaymentMethods.PAYPAL); }); test('Maps other values to self', () => { // Arrange useMainStore().order.payment.payInAdvanceType = '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 = hopPaymentMethods.PAYPAL; await wrapper.vm.$nextTick(); const postVal = wrapper.vm.isPaypal; // Assert expect(preVal).toBe(false); expect(postVal).toBe(true); }); }); }); 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(); }); }); 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(); }); }); });