Merge pull request #1646 from Safelite/unit-test/payment

Unit test/payment
This commit is contained in:
chloeherdsafelite 2023-12-19 13:37:57 -05:00 committed by GitHub
commit c438164f56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 874 additions and 0 deletions

View file

@ -0,0 +1,133 @@
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import paymentSwitchButton from "@/layouts/payment/payment-switch/payment-switch-button/payment-switch-button";
const testConstants = {
images: {
A: "imageA",
B: "imageB",
NONE: null,
},
names: {
A: "nameA",
B: "nameB",
},
content: {
withInline: "Button text with {custom:inlineImage,Alt} inline.",
noInline: "Button text with no inline",
},
};
let cmsContent;
describe("Payment Switch Button", () => {
beforeEach(() => {
cmsContent = {};
});
describe("Inline images", () => {
test("Renders image if inline-image token is present", () => {
// Arrange
const props = {
buttonText: testConstants.content.withInline,
buttonImage: testConstants.images.A,
buttonImageId: testConstants.names.A,
};
const wrapper = setupMocks({
propsData: props,
});
// Act
const img = wrapper.findAll("img");
// Assert
expect(img.length).toBe(1);
});
test("Renders no image if inline-image token is not present", () => {
// Arrange
const props = {
buttonText: testConstants.content.noInline,
buttonImage: testConstants.images.A,
buttonImageId: testConstants.names.A,
};
const wrapper = setupMocks({
propsData: props,
});
// Act
const img = wrapper.findAll("img");
// Assert
expect(img.length).toBe(0);
});
test("Renders no image, but instead alt text, when token is present but no image is", () => {
// Arrange
const props = {
buttonText: testConstants.content.withInline,
buttonImage: testConstants.images.NONE,
buttonImageId: testConstants.names.A,
};
const wrapper = setupMocks({
propsData: props,
});
// Act
const img = wrapper.findAll("img");
// Assert
expect(img.length).toBe(0);
expect(wrapper.text()).toContain("Alt");
});
});
test("Emits click event when clicked", async () => {
// Arrange
const props = {
buttonText: testConstants.content.noInline,
buttonImage: testConstants.images.NONE,
buttonImageId: testConstants.images.NONE,
};
const wrapper = setupMocks({
propsData: props,
});
// Act
await wrapper.trigger("click");
// Assert
expect(wrapper.emitted("click-event")).toBeTruthy();
});
});
function setupMocks(customMountOptions = {}) {
customMountOptions.route = {
query: {
fmgPage: "page-name",
},
};
const mountOptions = getMountOptions(customMountOptions);
const mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return cmsContent?.[widgetName]?.[cmsFieldName] ?? "";
}),
pushEventToGA: jest.fn(),
},
};
mountOptions.global.mixins = [mockMixin];
const wrapper = shallowMount(paymentSwitchButton, mountOptions);
wrapper.vm.setCmsContent = jest.fn();
return wrapper;
}

View file

@ -0,0 +1,128 @@
// component
import paymentSwitch from "@/layouts/payment/payment-switch/payment-switch";
// supporting files
import { shallowMount } from "@vue/test-utils";
import { getMountOptions } from "@/helpers/unit-test-helper";
import paymentSwitchButton from "@/layouts/payment/payment-switch/payment-switch-button/payment-switch-button";
import store from "@/store";
let mockCmsContent = {};
describe("payment-switch", () => {
beforeEach(() => {
store.getters = {};
mockCmsContent = {
PaymentSwitchWidget: {
Answers: [
{
Name: "ap",
Text: "Afterpay Text",
SubText: "Afterpay SubText",
ImageId: "Afterpay Id",
AnswerImageUrl: "Afterpay Image",
SubWidgetName: "Afterpay SubWidget",
},
{
Name: "pp",
Text: "Paypal Text",
SubText: "Paypal SubText",
ImageId: "Paypal Id",
AnswerImageUrl: "Paypal Image",
SubWidgetName: "Paypal SubWidget",
},
{
Name: "cc",
Text: "Credit Card Text",
SubText: "Credit Card SubText",
ImageId: "Credit Card Id",
AnswerImageUrl: "Credit Card Image",
SubWidgetName: "Credit Card SubWidget",
},
],
},
};
});
describe("Always renders two components", () => {
const paymentTypes = ["ap", "cc", "pp"];
paymentTypes.forEach((type) => {
test(`Type Chosen: ${type}`, () => {
// Arrange
const wrapper = setupMocks({
propsData: {
cmsWidgetName: "PaymentSwitchWidget",
paymentType: type,
},
});
// Act
const buttons = wrapper.findAllComponents(paymentSwitchButton);
const separators = wrapper.findAll(".switch-button-separator");
// Assert
expect(buttons.length).toBe(2);
expect(separators.length).toBe(1);
});
});
});
describe("Correctly maps cms content to dictionary", () => {
test("Maps array to dict", () => {
// Arrange
const wrapper = setupMocks({
propsData: {
cmsWidgetName: "PaymentSwitchWidget",
paymentType: "ap",
},
});
// Act
const mappedVals = wrapper.vm.paymentMethodsInfo;
// Assert
expect(mappedVals.ap).toBeDefined();
expect(mappedVals.ap.buttonText).toContain("Afterpay");
expect(mappedVals.ap.imageId).toContain("Afterpay");
expect(mappedVals.ap.answerImageUrl).toContain("Afterpay");
expect(mappedVals.pp).toBeDefined();
expect(mappedVals.pp.buttonText).toContain("Paypal");
expect(mappedVals.pp.imageId).toContain("Paypal");
expect(mappedVals.pp.answerImageUrl).toContain("Paypal");
expect(mappedVals.cc).toBeDefined();
expect(mappedVals.cc.buttonText).toContain("Credit Card");
expect(mappedVals.cc.imageId).toContain("Credit Card");
expect(mappedVals.cc.answerImageUrl).toContain("Credit Card");
});
});
});
function setupMocks(customMountOptions) {
const mountOptions = getMountOptions({
...customMountOptions,
});
// 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(paymentSwitch, mountOptions);
// act on vm
return wrapper;
}

View file

@ -0,0 +1,613 @@
// 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 baseMixin from "@/mixins/base-mixin";
import { storeActions } from "@/constants/store-actions";
import { paymentMethods } from "@/constants/payment-method-constants";
// 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",
},
},
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("handleIFrameContentWindwMessage", () => {
test("Navigates back if afterpay is closed", () => {
// Arrange
const event = {
data: "afterpayClosed",
};
const wrapper = setupMocks({});
wrapper.vm.backButtonAction = jest.fn();
// Act
wrapper.vm.handleIFrameContentWindwMessage(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.handleIFrameContentWindwMessage(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,
});
// 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;
}