diff --git a/src/payment/checkout/Checkout.test.jsx b/src/payment/checkout/Checkout.test.jsx
index 1c485d945..123fe7122 100644
--- a/src/payment/checkout/Checkout.test.jsx
+++ b/src/payment/checkout/Checkout.test.jsx
@@ -11,11 +11,14 @@ import { submitPayment } from '../data/actions';
import '../__factories__/basket.factory';
import '../__factories__/userAccount.factory';
import { transformResults } from '../data/service';
+import { getPerformanceProperties } from '../performanceEventing';
jest.mock('@edx/frontend-platform/analytics', () => ({
sendTrackEvent: jest.fn(),
}));
+jest.useFakeTimers('modern');
+
configureI18n({
config: {
ENVIRONMENT: process.env.ENVIRONMENT,
@@ -106,19 +109,24 @@ describe('', () => {
// Apple Pay temporarily disabled per REV-927 - https://github.com/openedx/frontend-app-payment/pull/256
- // TODO: Disabling for now update once we can swap between stripe and cybersource
- // it('submits and tracks the payment form', () => {
- // const formSubmitButton = wrapper.find('form button[type="submit"]').hostNodes();
- // formSubmitButton.simulate('click');
-
- // expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.basket.payment_selected', {
- // type: 'click',
- // category: 'checkout',
- // paymentMethod: 'Credit Card',
- // checkoutType: 'client_side',
- // flexMicroformEnabled: true,
- // });
- // });
+ it('submits and tracks the payment form', () => {
+ expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.payment_mfe.payment_form_rendered', {
+ ...getPerformanceProperties(),
+ paymentProcessor: 'Cybersource',
+ });
+ const formSubmitButton = wrapper.find('form button[type="submit"]').hostNodes();
+ formSubmitButton.simulate('click');
+
+ expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.basket.payment_selected', {
+ type: 'click',
+ category: 'checkout',
+ paymentMethod: 'Credit Card',
+ checkoutType: 'client_side',
+ flexMicroformEnabled: true,
+ stripeEnabled: false,
+
+ });
+ });
it('fires an action when handling a cybersource submission', () => {
const formData = { name: 'test' };
diff --git a/src/payment/checkout/payment-form/CardHolderInformation.test.jsx b/src/payment/checkout/payment-form/CardHolderInformation.test.jsx
index d6d9f4a7f..8d2fa84e3 100644
--- a/src/payment/checkout/payment-form/CardHolderInformation.test.jsx
+++ b/src/payment/checkout/payment-form/CardHolderInformation.test.jsx
@@ -1,21 +1,20 @@
/* eslint-disable react/jsx-no-constructed-context-values */
-// import React from 'react';
-// import { Provider } from 'react-redux';
-// import { mount } from 'enzyme';
+import React from 'react';
+import { Provider } from 'react-redux';
+import { mount } from 'enzyme';
import {
- // IntlProvider,
+ IntlProvider,
configure as configureI18n,
} from '@edx/frontend-platform/i18n';
-// import { AppContext } from '@edx/frontend-platform/react';
-// import { Factory } from 'rosie';
-// import { createStore } from 'redux';
+import { AppContext } from '@edx/frontend-platform/react';
+import { Factory } from 'rosie';
+import { createStore } from 'redux';
-// import CardHolderInformation, { CardHolderInformationComponent } from './CardHolderInformation';
-// import PaymentForm from './PaymentForm';
-// import createRootReducer from '../../../data/reducers';
+import CardHolderInformation, { CardHolderInformationComponent } from './CardHolderInformation';
+import PaymentForm from './PaymentForm';
+import createRootReducer from '../../../data/reducers';
import '../../__factories__/userAccount.factory';
-// import { iteratee } from 'lodash';
jest.mock('@edx/frontend-platform/analytics', () => ({
sendTrackEvent: jest.fn(),
@@ -47,70 +46,66 @@ configureI18n({
},
});
-it('is using stripe', () => {
- expect(null).toEqual(null);
-});
-// TODO: Disabling for now update once we can swap between stripe and cybersource
-// describe('', () => {
-// let store;
+describe('', () => {
+ let store;
-// describe('handleSelectCountry', () => {
-// it('updates state with selected country', () => {
-// const authenticatedUser = Factory.build('userAccount');
+ describe('handleSelectCountry', () => {
+ it('updates state with selected country', () => {
+ const authenticatedUser = Factory.build('userAccount');
-// store = createStore(createRootReducer(), {});
-// const component = (
-//
-//
-//
-// {}} onSubmitButtonClick={() => {}}>
-//
-//
-//
-//
-//
-// );
-// const wrapper = mount(component);
-// const cardHolderInformation = wrapper
-// .find(CardHolderInformationComponent)
-// .first()
-// .instance();
-// const eventMock = jest.fn();
+ store = createStore(createRootReducer(), {});
+ const component = (
+
+
+
+ {}} onSubmitButtonClick={() => {}}>
+
+
+
+
+
+ );
+ const wrapper = mount(component);
+ const cardHolderInformation = wrapper
+ .find(CardHolderInformationComponent)
+ .first()
+ .instance();
+ const eventMock = jest.fn();
-// cardHolderInformation.handleSelectCountry(eventMock, 'US');
+ cardHolderInformation.handleSelectCountry(eventMock, 'US');
-// expect(cardHolderInformation.state).toEqual({ selectedCountry: 'US' });
-// });
-// });
-// describe('purchasedForOrganization field', () => {
-// it('renders for bulk purchase', () => {
-// const wrapper = mount((
-//
-//
-// {}}
-// onSubmitPayment={() => {}}
-// onSubmitButtonClick={() => {}}
-// />
-//
-//
-// ));
-// expect(wrapper.exists('#purchasedForOrganization')).toEqual(true);
-// });
-// it('does not render if not bulk purchase', () => {
-// const wrapper = mount((
-//
-//
-// {}}
-// onSubmitPayment={() => {}}
-// onSubmitButtonClick={() => {}}
-// />
-//
-//
-// ));
-// expect(wrapper.exists('#purchasedForOrganization')).toEqual(false);
-// });
-// });
-// });
+ expect(cardHolderInformation.state).toEqual({ selectedCountry: 'US' });
+ });
+ });
+ describe('purchasedForOrganization field', () => {
+ it('renders for bulk purchase', () => {
+ const wrapper = mount((
+
+
+ {}}
+ onSubmitPayment={() => {}}
+ onSubmitButtonClick={() => {}}
+ />
+
+
+ ));
+ expect(wrapper.exists('#purchasedForOrganization')).toEqual(true);
+ });
+ it('does not render if not bulk purchase', () => {
+ const wrapper = mount((
+
+
+ {}}
+ onSubmitPayment={() => {}}
+ onSubmitButtonClick={() => {}}
+ />
+
+
+ ));
+ expect(wrapper.exists('#purchasedForOrganization')).toEqual(false);
+ });
+ });
+});
diff --git a/src/payment/checkout/payment-form/PaymentForm.test.jsx b/src/payment/checkout/payment-form/PaymentForm.test.jsx
index cdee96b66..561474c2b 100644
--- a/src/payment/checkout/payment-form/PaymentForm.test.jsx
+++ b/src/payment/checkout/payment-form/PaymentForm.test.jsx
@@ -82,7 +82,7 @@ describe('', () => {
lastName: '',
address: '',
city: '',
- country: 'UK',
+ country: 'GB',
cardExpirationMonth: '',
cardExpirationYear: '',
optionalField: '',
diff --git a/src/payment/checkout/payment-form/StripePaymentForm.test.jsx b/src/payment/checkout/payment-form/StripePaymentForm.test.jsx
new file mode 100644
index 000000000..f2e830506
--- /dev/null
+++ b/src/payment/checkout/payment-form/StripePaymentForm.test.jsx
@@ -0,0 +1,276 @@
+/* eslint-disable react/jsx-no-constructed-context-values */
+import React from 'react';
+import * as reactRedux from 'react-redux';
+import { createStore } from 'redux';
+import { mount } from 'enzyme';
+import { IntlProvider, configure as configureI18n } from '@edx/frontend-platform/i18n';
+import { Factory } from 'rosie';
+import configureMockStore from 'redux-mock-store';
+
+import { Elements, PaymentElement } from '@stripe/react-stripe-js';
+import { AppContext } from '@edx/frontend-platform/react';
+import StripePaymentForm from './StripePaymentForm';
+import * as formValidators from './utils/form-validators';
+import createRootReducer from '../../../data/reducers';
+import '../../__factories__/userAccount.factory';
+import * as mocks from '../stripeMocks';
+import PlaceOrderButton from './PlaceOrderButton';
+
+jest.mock('@edx/frontend-platform/analytics', () => ({
+ sendTrackEvent: jest.fn(),
+}));
+
+jest.useFakeTimers('modern');
+
+const validateRequiredFieldsMock = jest.spyOn(formValidators, 'validateRequiredFields');
+
+const mockStore = configureMockStore();
+
+configureI18n({
+ config: {
+ ENVIRONMENT: process.env.ENVIRONMENT,
+ LANGUAGE_PREFERENCE_COOKIE_NAME: process.env.LANGUAGE_PREFERENCE_COOKIE_NAME,
+ },
+ loggingService: {
+ logError: jest.fn(),
+ logInfo: jest.fn(),
+ },
+ messages: {
+ uk: {},
+ th: {},
+ ru: {},
+ 'pt-br': {},
+ pl: {},
+ 'ko-kr': {},
+ id: {},
+ he: {},
+ ca: {},
+ 'zh-cn': {},
+ fr: {},
+ 'es-419': {},
+ ar: {},
+ },
+});
+
+const authenticatedUser = Factory.build('userAccount');
+
+describe('', () => {
+ let store;
+ let mockStripe;
+ let mockElements;
+ let submitButton;
+ let state;
+ let mockElement;
+ let paymentElement;
+
+ beforeEach(() => {
+ store = createStore(createRootReducer(), {});
+ mockStripe = mocks.mockStripe();
+ mockElement = mocks.mockElement();
+ mockElements = mocks.mockElements();
+ mockStripe.elements.mockReturnValue(mockElements);
+ mockElements.create.mockReturnValue(mockElement);
+
+ state = {
+ payment: {
+ basket: {
+ loading: true,
+ loaded: false,
+ submitting: false,
+ redirect: false,
+ isBasketProcessing: false,
+ products: [{ sku: '00000' }],
+ enableStripePaymentProcessor: true,
+ },
+ clientSecret: { isClientSecretProcessing: false, clientSecretId: '' },
+ },
+ feedback: { byId: {}, orderedIds: [] },
+ form: {},
+ };
+
+ const wrapper = mount((
+
+
+
+
+ {}}
+ onSubmitPayment={() => {}}
+ onSubmitButtonClick={() => {}}
+ />
+
+
+
+
+ ));
+ paymentElement = wrapper.find(PaymentElement);
+ submitButton = wrapper.find(PlaceOrderButton);
+ });
+
+ afterEach(() => {
+ jest.restoreAllMocks();
+ });
+
+ describe('load stripe', () => {
+ it('creates the payment element', () => {
+ expect(mockElements.create).toHaveBeenCalledWith('payment', null);
+ expect(paymentElement.length > 0).toBe(true);
+ });
+ });
+
+ describe('getRequiredFields', () => {
+ it('returns expected required fields', () => {
+ const testFormValues = [
+ {
+ firstName: '',
+ lastName: '',
+ address: '',
+ city: '',
+ country: 'GB',
+ postalCode: '',
+ optionalField: '',
+ },
+ {
+ firstName: '',
+ lastName: '',
+ address: '',
+ city: '',
+ country: 'US',
+ postalCode: '',
+ state: '',
+ optionalField: '',
+ },
+ {
+ firstName: '',
+ lastName: '',
+ address: '',
+ city: '',
+ country: 'IN',
+ state: '',
+ optionalField: '',
+ },
+ ];
+
+ testFormValues.forEach((formValues) => {
+ const requiredFields = formValidators.getRequiredFields(formValues, false, true);
+ const { optionalField, ...expectedRequiredFields } = formValues;
+ expect(requiredFields).toStrictEqual(expectedRequiredFields);
+ });
+ });
+
+ it('returns organization fields for a bulk order', () => {
+ const isBulkOrder = true;
+ mount((
+
+
+
+
+ {}}
+ onSubmitPayment={() => {}}
+ onSubmitButtonClick={() => {}}
+ />
+
+
+
+
+ ));
+
+ const formValues = {
+ firstName: '',
+ lastName: '',
+ address: '',
+ city: '',
+ country: 'UK',
+ cardNumber: '',
+ securityCode: '',
+ cardExpirationMonth: '',
+ cardExpirationYear: '',
+ optionalField: '',
+ organization: 'edx',
+ };
+
+ const requiredFields = formValidators.getRequiredFields(formValues, isBulkOrder);
+ expect(formValues.organization).toEqual(requiredFields.organization);
+ });
+ });
+ describe('onSubmit', () => {
+ it('throws expected errors', () => {
+ state = {
+ payment: {
+ basket: {
+ loading: true,
+ loaded: false,
+ submitting: false,
+ redirect: false,
+ isBasketProcessing: false,
+ products: [{ sku: '00000' }],
+ enableStripePaymentProcessor: true,
+ },
+ clientSecret: { isClientSecretProcessing: false, clientSecretId: '' },
+ },
+ feedback: { byId: {}, orderedIds: [] },
+ form: {
+ payment: {
+ submitErrors: { firstName: 'error' },
+ },
+ },
+ };
+ const submitStripePayment = jest.fn();
+ mount((
+
+
+
+
+ {}}
+ onSubmitPayment={() => submitStripePayment}
+ onSubmitButtonClick={() => {}}
+ shouldFocusFirstError
+ firstErrorId={null}
+ />
+
+
+
+
+ ));
+ const SubmissionError = jest.fn();
+ const testData = [
+ [
+ { firstName: 'This field is required' },
+ new SubmissionError({
+ firstName: 'This field is required',
+ }),
+ ],
+ [
+ {},
+ null,
+ ],
+ ];
+
+ testData.forEach((testCaseData) => {
+ validateRequiredFieldsMock.mockReturnValueOnce(testCaseData[0]);
+ if (testCaseData[1]) {
+ expect(() => submitButton.simulate('click'));
+ expect(submitStripePayment).not.toHaveBeenCalled();
+ } else {
+ expect(() => submitButton.simulate('click')).not.toThrow();
+ }
+ });
+ });
+ });
+
+ describe('validateRequiredFields', () => {
+ it('returns errors if values are empty', () => {
+ const values = {
+ firsName: 'Jane',
+ lastName: undefined,
+ };
+ const expectedErrors = {
+ lastName: 'payment.form.errors.required.field',
+ };
+ expect(formValidators.validateRequiredFields(values)).toEqual(expectedErrors);
+ });
+ });
+});
diff --git a/src/payment/checkout/stripeMocks.js b/src/payment/checkout/stripeMocks.js
new file mode 100644
index 000000000..90f0202f0
--- /dev/null
+++ b/src/payment/checkout/stripeMocks.js
@@ -0,0 +1,29 @@
+export const mockElement = () => ({
+ mount: jest.fn(),
+ destroy: jest.fn(),
+ on: jest.fn(),
+ update: jest.fn(),
+});
+
+export const mockElements = () => {
+ const elements = {};
+ return {
+ create: jest.fn((type) => {
+ elements[type] = mockElement();
+ return elements[type];
+ }),
+ getElement: jest.fn((type) => elements[type] || null),
+ };
+};
+
+export const mockStripe = () => ({
+ elements: jest.fn(() => mockElements()),
+ createToken: jest.fn(),
+ createSource: jest.fn(),
+ createPaymentMethod: jest.fn(),
+ confirmCardPayment: jest.fn(),
+ confirmCardSetup: jest.fn(),
+ paymentRequest: jest.fn(),
+ registerAppInfo: jest.fn(),
+ _registerWrapper: jest.fn(),
+});