// 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'; import queryStrings from '@/constants/query-strings'; import CartDropdown from '@/iss-components/cart-dropdown/cart-dropdown.vue'; import { createTestingPinia } from '@pinia/testing'; import settleAllPromises from '@/helpers/layout-helper'; import { fetchCmsContentForPage } from '@/helpers/cms-content-helper'; import baseMixin from '@/mixins/base-mixin'; // 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 // Mock fetchCmsContentForPage jest.mock('@/helpers/cms-content-helper', () => ({ fetchCmsContentForPage: jest.fn(), doesCopyContainRouterLink: jest.fn() })); // Mock our module for promises. jest.mock('@/helpers/layout-helper.js', () => jest.fn()); function getMountedComponent(mainInitialState = {}, initialData = {}, methodToRunAfterInitializingStore = () => {}, queryString = {}) { const mountOptions = getMountOptions({ router: { navigate: jest.fn() }, route: { query: { issPage: 'page-name', ...queryString }, params: {} } }); const testingPinia = createTestingPinia({ initialState: { main: mainInitialState } }); useMainStore(testingPinia); methodToRunAfterInitializingStore(); mountOptions.global.plugins = [testingPinia]; mountOptions.data = () => (initialData); const apiResponses = { cmsContent: {} }; settleAllPromises.mockImplementation(() => apiResponses); fetchCmsContentForPage.mockImplementation(() => Promise.resolve()); const wrapper = shallowMount(payment, mountOptions); wrapper.vm.getCmsContent = jest.fn().mockImplementation(() => {}); wrapper.vm.setCmsContent = jest.fn(); wrapper.vm.$router.navigateWithSpinner = jest.fn(); wrapper.vm.navigateBack = baseMixin.methods.navigateBack; return { wrapper }; } const defaultOrder = { 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', servicePhone: '555-555-5555' }, damage: { isRepair: false, numberOfChips: null, glassToReplace: [{location: 'windshield'}] }, lineItems: { glassParts: [parts.windshield], supportingItems: [], vaps: [parts.frontWipers], promos: [] }, insuranceCoverage: { coverageStatus: null, coverageType: null, claimNumber: null }, payment: { isInsurance: false, isPayInAdvance: true, payInAdvanceType: 'Afterpay', inactivePromos: [] }, schedule: { date: 'date', startTime: 'start', endTime: 'end', jobMinMinutes: '30', jobMaxMinutes: '45' } } }; describe('payment-page.vue', () => { describe('arePagePrerequisitesValid', () => { test('Returns true in nominal conditions', () => { // Arrange // Store defaults are valid const { wrapper } = getMountedComponent(defaultOrder); // 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 const { wrapper } = getMountedComponent(defaultOrder); useMainStore().order.payment.isPayInAdvance = null; useMainStore().order.payment.payInAdvanceType = null; // Act const result = wrapper.vm.arePagePrerequisitesValid(); // Assert expect(result).toBe(false); }); test('Returns false if pay at time of service', () => { // Arrange const { wrapper } = getMountedComponent(defaultOrder); useMainStore().order.payment.isPayInAdvance = false; useMainStore().order.payment.payInAdvanceType = null; // Act const result = wrapper.vm.arePagePrerequisitesValid(); // Assert expect(result).toBe(false); }); }); }); describe('payment type mapping', () => { describe('getPaymentType', () => { test('Maps AFTERPAY -> hopPaymentMethods.AFTERPAY', () => { // Arrange const { wrapper } = getMountedComponent(defaultOrder); useMainStore().order.payment.payInAdvanceType = paymentMethods.AFTERPAY; // Act const mapped = wrapper.vm.getPaymentType(); // Assert expect(mapped).toBe(hopPaymentMethods.AFTERPAY); }); test('Maps CREDIT_CARD -> hopPaymentMethods.CREDIT_CARD', () => { // Arrange const { wrapper } = getMountedComponent(defaultOrder); useMainStore().order.payment.payInAdvanceType = paymentMethods.CREDIT_CARD; // Act const mapped = wrapper.vm.getPaymentType(); // Assert expect(mapped).toBe(hopPaymentMethods.CREDIT_CARD); }); test('Maps PAYPAL -> hopPaymentMethods.PAYPAL', () => { // Arrange const { wrapper } = getMountedComponent(defaultOrder); useMainStore().order.payment.payInAdvanceType = paymentMethods.PAYPAL; // Act const mapped = wrapper.vm.getPaymentType(); // Assert expect(mapped).toBe(hopPaymentMethods.PAYPAL); }); test('Maps other values to self', () => { // Arrange const { wrapper } = getMountedComponent(defaultOrder); useMainStore().order.payment.payInAdvanceType = 'SomeOtherText'; // Act const mapped = wrapper.vm.getPaymentType(); // Assert expect(mapped).toBe('SomeOtherText'); }); }); describe('cart-dropdown', () => { test('Show cart dropdown if credit card', async () => { const { wrapper } = getMountedComponent(defaultOrder); wrapper.vm.paymentType = hopPaymentMethods.CREDIT_CARD; // Act await wrapper.vm.$nextTick(); const cartTable = wrapper.findComponent(CartDropdown); // Assert expect(cartTable.exists()).toBeTruthy(); }); test('show cart dropdown if afterpay', async () => { const { wrapper } = getMountedComponent(defaultOrder); wrapper.vm.paymentType = hopPaymentMethods.AFTERPAY; // Act await wrapper.vm.$nextTick(); const cartTable = wrapper.findComponent(CartDropdown); // Assert expect(cartTable.exists()).toBeTruthy(); }); test('Hide cart dropdown if paypal', async () => { const { wrapper } = getMountedComponent(defaultOrder); wrapper.vm.paymentType = hopPaymentMethods.PAYPAL; // Act await wrapper.vm.$nextTick(); const cartTable = wrapper.findComponent(CartDropdown); // Assert expect(cartTable.exists()).toBeFalsy(); }); }); describe('isPaypal', () => { test('Is responsive if initial data changes', async () => { // Arrange // note: begins with payment-type = afterpay const { wrapper } = getMountedComponent(defaultOrder); // 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 } = getMountedComponent(defaultOrder); 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 } = getMountedComponent(defaultOrder); 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 } = getMountedComponent(defaultOrder); 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 } = getMountedComponent(defaultOrder); // 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 } = getMountedComponent(defaultOrder); // 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 } = getMountedComponent(defaultOrder); 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(); }); }); describe('Credit Card Error Alert', () => { test('show credit card error alert', () => { // Arrange const queryString = { [queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: paymentMethods.CREDIT_CARD }; const { wrapper } = getMountedComponent(defaultOrder, {}, () => {}, queryString); // Act // Assert const creditCardErrorAlert = wrapper.find('[name="payInAdvanceCreditCardErrorAlert"]'); expect(creditCardErrorAlert.exists()).toBeTruthy(); expect(creditCardErrorAlert.isVisible()).toBeTruthy(); }); }); describe('AfterPay Error Alert', () => { test('show after pay error alert', () => { // Arrange const queryString = { [queryStrings.DISPLAY_PAY_IN_ADVANCE_ALERT]: paymentMethods.AFTERPAY }; const { wrapper } = getMountedComponent(defaultOrder, {}, () => {}, queryString); // Act // Assert const afterPayErrorAlert = wrapper.find('[name="payInAdvanceAfterPayErrorAlert"]'); expect(afterPayErrorAlert.exists()).toBeTruthy(); expect(afterPayErrorAlert.isVisible()).toBeTruthy(); }); }); });