622 lines
18 KiB
JavaScript
622 lines
18 KiB
JavaScript
// 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,
|
|
},
|
|
rainRepel: {
|
|
name: "rain repel",
|
|
partNumber: "RAIN REPEL",
|
|
description: null,
|
|
partType: "RAIN REPEL",
|
|
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,
|
|
},
|
|
rainRepel: {
|
|
...parts.rainRepel,
|
|
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,
|
|
},
|
|
rainRepel: {
|
|
...pricedParts.rainRepel,
|
|
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_REPEL]: parts.rainRepel,
|
|
[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.rainRepel];
|
|
|
|
// 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.rainRepel];
|
|
|
|
// 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.rainRepel];
|
|
|
|
// 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");
|
|
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;
|
|
}
|