From 9ed5373ef7788b5ca6beb89db913780c06777a7e Mon Sep 17 00:00:00 2001 From: julianajlk Date: Wed, 3 Jul 2019 09:09:45 -0400 Subject: [PATCH] UI: Payment Page clean-up (#47) * fix: merge conflicts * fix: saga tests to reflect complete basket state * fix: delete comment * test: update saga test with blank voucher --- src/payment/BasketSummary.jsx | 3 +- src/payment/PaymentForm.jsx | 1 + src/payment/PaymentPage.test.jsx | 2 +- src/payment/ProductLineItem.jsx | 6 +- src/payment/ProductLineItems.jsx | 4 +- .../__mocks__/loadedBasket.mockStore.js | 2 +- .../loadedBasketWithNoTotals.mockStore.js | 2 +- .../__snapshots__/PaymentPage.test.jsx.snap | 4 +- src/payment/_style.scss | 4 +- src/payment/coupon/data/sagas.js | 15 ++-- src/payment/coupon/data/sagas.test.js | 83 +++++++++++++++++-- src/payment/coupon/data/service.js | 11 ++- src/payment/data/__mocks__/getBasket.json | 10 +-- src/payment/data/service.js | 24 +++--- 14 files changed, 121 insertions(+), 50 deletions(-) diff --git a/src/payment/BasketSummary.jsx b/src/payment/BasketSummary.jsx index 2ab7874cf..44dc97da6 100644 --- a/src/payment/BasketSummary.jsx +++ b/src/payment/BasketSummary.jsx @@ -32,7 +32,8 @@ function SummaryTable({ calculatedDiscount, totalExclDiscount }) { - {calculatedDiscount !== undefined ? ( + {calculatedDiscount !== undefined && calculatedDiscount !== null && + calculatedDiscount > 0 ? ( diff --git a/src/payment/PaymentPage.test.jsx b/src/payment/PaymentPage.test.jsx index 7190764cc..5f9dbd201 100644 --- a/src/payment/PaymentPage.test.jsx +++ b/src/payment/PaymentPage.test.jsx @@ -107,7 +107,7 @@ describe('', () => { }); const product = { - imageURL: 'https://prod-discovery.edx-cdn.org/media/course/image/21be6203-b140-422c-9233-a1dc278d7266-941abf27df4d.small.jpg', + imageUrl: 'https://prod-discovery.edx-cdn.org/media/course/image/21be6203-b140-422c-9233-a1dc278d7266-941abf27df4d.small.jpg', title: 'Introduction to Happiness', seatType: 'Verified', }; diff --git a/src/payment/ProductLineItem.jsx b/src/payment/ProductLineItem.jsx index 9d665921f..66f25fee2 100644 --- a/src/payment/ProductLineItem.jsx +++ b/src/payment/ProductLineItem.jsx @@ -28,14 +28,14 @@ class ProductLineItem extends React.PureComponent { render() { const { - imageURL, + imageUrl, title, seatType, } = this.props; return (
- {title} + {title}
{title}
@@ -47,7 +47,7 @@ class ProductLineItem extends React.PureComponent { } ProductLineItem.propTypes = { - imageURL: PropTypes.string.isRequired, + imageUrl: PropTypes.string.isRequired, title: PropTypes.string.isRequired, seatType: PropTypes.oneOf(['professional', 'no-id-professional', 'Verified', 'honor', 'audit']), }; diff --git a/src/payment/ProductLineItems.jsx b/src/payment/ProductLineItems.jsx index 5b9cf7c16..a8e039d50 100644 --- a/src/payment/ProductLineItems.jsx +++ b/src/payment/ProductLineItems.jsx @@ -13,7 +13,7 @@ function ProductLineItems({ products }) {

@@ -31,7 +31,7 @@ function ProductLineItems({ products }) { ProductLineItems.propTypes = { products: PropTypes.arrayOf(PropTypes.shape({ - imageURL: PropTypes.string, + imageUrl: PropTypes.string, title: PropTypes.string, seatType: PropTypes.oneOf(['professional', 'no-id-professional', 'Verified', 'honor', 'audit']), })), diff --git a/src/payment/__mocks__/loadedBasket.mockStore.js b/src/payment/__mocks__/loadedBasket.mockStore.js index b01fb0e2e..e2fb6d657 100644 --- a/src/payment/__mocks__/loadedBasket.mockStore.js +++ b/src/payment/__mocks__/loadedBasket.mockStore.js @@ -47,7 +47,7 @@ module.exports = { totalExclDiscount: 161, products: [ { - imageURL: + imageUrl: 'https://prod-discovery.edx-cdn.org/media/course/image/21be6203-b140-422c-9233-a1dc278d7266-941abf27df4d.small.jpg', title: 'Introduction to Happiness', seatType: 'Verified', diff --git a/src/payment/__mocks__/loadedBasketWithNoTotals.mockStore.js b/src/payment/__mocks__/loadedBasketWithNoTotals.mockStore.js index 0613b2848..fe7ef7ace 100644 --- a/src/payment/__mocks__/loadedBasketWithNoTotals.mockStore.js +++ b/src/payment/__mocks__/loadedBasketWithNoTotals.mockStore.js @@ -45,7 +45,7 @@ module.exports = { sdnCheck: true, products: [ { - imageURL: + imageUrl: 'https://prod-discovery.edx-cdn.org/media/course/image/21be6203-b140-422c-9233-a1dc278d7266-941abf27df4d.small.jpg', title: 'Introduction to Happiness', seatType: 'Verified', diff --git a/src/payment/__snapshots__/PaymentPage.test.jsx.snap b/src/payment/__snapshots__/PaymentPage.test.jsx.snap index a1137831f..0c26d4129 100644 --- a/src/payment/__snapshots__/PaymentPage.test.jsx.snap +++ b/src/payment/__snapshots__/PaymentPage.test.jsx.snap @@ -100,7 +100,7 @@ exports[` Renders correctly in various states should render the b className="section-heading" > - In your cart + In Your Cart

@@ -2124,7 +2124,7 @@ exports[` Renders correctly in various states should successfully className="section-heading" > - In your cart + In Your Cart

diff --git a/src/payment/_style.scss b/src/payment/_style.scss index 328f1b3d4..df734649b 100644 --- a/src/payment/_style.scss +++ b/src/payment/_style.scss @@ -8,7 +8,7 @@ @extend .mb-5; } .section-heading { - @extend .h6; + @extend .h5; } label { font-weight: normal; @@ -35,7 +35,7 @@ display: inline-block; padding: map-get($spacers, 2); margin-right: map-get($spacers, 2); - border: solid $border-width $border-color; + border: solid $border-width $border-color; border-radius: $border-radius; cursor: pointer; display: inline-flex; diff --git a/src/payment/coupon/data/sagas.js b/src/payment/coupon/data/sagas.js index 470a8d036..6d5f97c9c 100644 --- a/src/payment/coupon/data/sagas.js +++ b/src/payment/coupon/data/sagas.js @@ -9,6 +9,7 @@ import { addCouponFailure, removeCouponFailure, } from './actions'; +import { fetchBasketSuccess } from '../../data/actions'; import { deleteCoupon, postCoupon } from './service'; import { @@ -21,11 +22,15 @@ export function* handleAddCoupon(action) { yield put(addCouponBegin()); try { const result = yield call(postCoupon, action.payload.code); - const { id: voucherId, code, benefit } = result.voucher; - yield put(addCouponSuccess(voucherId, code, benefit)); - yield put(addMessage('payment.coupon.added', null, { - code, - }, INFO)); + yield put(fetchBasketSuccess(result)); + if (result.voucher === undefined) { + yield put(addCouponSuccess(null, null, null)); + } else { + yield put(addCouponSuccess(result.voucher.id, result.voucher.code, result.voucher.benefit)); + yield put(addMessage('payment.coupon.added', null, { + code: result.voucher.code, + }, INFO)); + } } catch (e) { yield put(addCouponFailure()); yield call(handleErrors, e); diff --git a/src/payment/coupon/data/sagas.test.js b/src/payment/coupon/data/sagas.test.js index b2bcac4bf..0e70b0e1e 100644 --- a/src/payment/coupon/data/sagas.test.js +++ b/src/payment/coupon/data/sagas.test.js @@ -12,6 +12,8 @@ import { removeCoupon, removeCouponFailure, } from './actions'; +import { fetchBasketSuccess } from '../../data/actions'; +import { transformResults } from '../../data/service'; import { PERCENTAGE_BENEFIT } from './constants'; import { addMessage, INFO, DANGER } from '../../../feedback'; @@ -34,6 +36,7 @@ describe('saga tests', () => { const responses = { successResponse: { data: { + show_voucher_form: true, voucher: { id: 12345, code: 'DEMO25', @@ -42,6 +45,31 @@ describe('saga tests', () => { value: 25, }, }, + total_excl_discount: 161, + order_total: 149, + calculated_discount: 12, + products: [ + { + image_url: 'https://prod-discovery.edx-cdn.org/media/course/image/21be6203-b140-422c-9233-a1dc278d7266-941abf27df4d.small.jpg', + title: 'Introduction to Happiness', + seat_type: 'Verified', + }, + ], + }, + }, + blankVoucherResponse: { + data: { + show_voucher_form: true, + total_excl_discount: 161, + order_total: 161, + calculated_discount: 0, + products: [ + { + image_url: 'https://prod-discovery.edx-cdn.org/media/course/image/21be6203-b140-422c-9233-a1dc278d7266-941abf27df4d.small.jpg', + title: 'Introduction to Happiness', + seat_type: 'Verified', + }, + ], }, }, errorResponse: { @@ -75,6 +103,7 @@ describe('saga tests', () => { expect(dispatched).toEqual([ addCouponBegin(), + fetchBasketSuccess(transformResults(responses.successResponse.data)), addCouponSuccess(12345, 'DEMO25', { type: PERCENTAGE_BENEFIT, value: 25, @@ -88,7 +117,37 @@ describe('saga tests', () => { INFO, ), ]); + expect(apiClientPost).toHaveBeenCalledWith( + 'http://localhost/bff/payment/v0/vouchers/', + { code: 'DEMO25' }, + { headers: { 'Content-Type': 'application/json' } }, + ); + }); + + it('should handle an empty vouchers', async () => { + const apiClientPost = jest.fn(() => + new Promise((resolve) => { + resolve(responses.blankVoucherResponse); + })); + + configureApiService(configuration, { + post: apiClientPost, + }); + + const dispatched = []; + await runSaga( + { + dispatch: action => dispatched.push(action), + }, + handleAddCoupon, + addCoupon('DEMO25'), + ).toPromise(); + expect(dispatched).toEqual([ + addCouponBegin(), + fetchBasketSuccess(transformResults(responses.blankVoucherResponse.data)), + addCouponSuccess(null, null, null), + ]); expect(apiClientPost).toHaveBeenCalledWith( 'http://localhost/bff/payment/v0/vouchers/', { code: 'DEMO25' }, @@ -172,9 +231,25 @@ describe('saga tests', () => { const responses = { successResponse: { data: { + show_voucher_form: true, + voucher: { + id: 12345, + code: 'DEMO25', + benefit: { + type: PERCENTAGE_BENEFIT, + value: 25, + }, + }, + total_excl_discount: 161, order_total: 149, calculated_discount: 12, - total_excl_discount: 161, + products: [ + { + image_url: 'https://prod-discovery.edx-cdn.org/media/course/image/21be6203-b140-422c-9233-a1dc278d7266-941abf27df4d.small.jpg', + title: 'Introduction to Happiness', + seat_type: 'Verified', + }, + ], }, }, errorResponse: { @@ -211,11 +286,7 @@ describe('saga tests', () => { expect(dispatched).toEqual([ removeCouponBegin(), - removeCouponSuccess({ - order_total: 149, - calculated_discount: 12, - total_excl_discount: 161, - }), + removeCouponSuccess(transformResults(responses.successResponse.data)), addMessage( 'payment.coupon.removed', null, diff --git a/src/payment/coupon/data/service.js b/src/payment/coupon/data/service.js index 40553cadb..e3dfdda52 100644 --- a/src/payment/coupon/data/service.js +++ b/src/payment/coupon/data/service.js @@ -1,6 +1,7 @@ import pick from 'lodash.pick'; import { handleRequestError, applyConfiguration } from '../../../common/serviceUtils'; +import { transformResults } from '../../data/service'; let config = { ECOMMERCE_BASE_URL: null, @@ -15,7 +16,7 @@ export function configureApiService(newConfig, newApiClient) { } export async function postCoupon(code) { - const response = await apiClient + const { data } = await apiClient .post( `${config.ECOMMERCE_BASE_URL}/bff/payment/v0/vouchers/`, { code }, @@ -24,14 +25,12 @@ export async function postCoupon(code) { }, ) .catch(handleRequestError); - - return response.data; + return transformResults(data); } export async function deleteCoupon(voucherId) { - const response = await apiClient + const { data } = await apiClient .delete(`${config.ECOMMERCE_BASE_URL}/bff/payment/v0/vouchers/${voucherId}`) .catch(handleRequestError); - - return response.data; + return transformResults(data); } diff --git a/src/payment/data/__mocks__/getBasket.json b/src/payment/data/__mocks__/getBasket.json index 2b1f4c7a8..93c951983 100644 --- a/src/payment/data/__mocks__/getBasket.json +++ b/src/payment/data/__mocks__/getBasket.json @@ -1,13 +1,5 @@ { "show_voucher_form": true, - "payment_providers": [ - { - "type": "cybersource" - }, - { - "type": "paypal" - } - ], "order_total": 149, "calculated_discount": 12, "total_excl_discount": 161, @@ -15,7 +7,7 @@ "products": [ { "image_url": "https://prod-discovery.edx-cdn.org/media/course/image/21be6203-b140-422c-9233-a1dc278d7266-941abf27df4d.small.jpg", - "name": "Introduction to Happiness", + "title": "Introduction to Happiness", "seat_type": "Verified" } ], diff --git a/src/payment/data/service.js b/src/payment/data/service.js index 0e9a36898..16759ee4a 100644 --- a/src/payment/data/service.js +++ b/src/payment/data/service.js @@ -3,6 +3,7 @@ import pick from 'lodash.pick'; import { configureApiService as configureCouponApiService } from '../coupon'; import { applyConfiguration, handleRequestError } from '../../common/serviceUtils'; + let config = { ACCOUNTS_API_BASE_URL: null, ECOMMERCE_BASE_URL: null, @@ -21,27 +22,28 @@ export function configureApiService(newConfig, newApiClient) { configureCouponApiService(config, apiClient); } -export async function getBasket() { - const { data } = await apiClient - .get(`${config.ECOMMERCE_BASE_URL}/bff/payment/v0/payment/`) - .catch(handleRequestError); - +export function transformResults(data) { const transformedResults = { - basketId: data.basket_id, showVoucherForm: data.show_voucher_form, - paymentProviders: data.payment_providers, orderTotal: Number.parseInt(data.order_total, 10), - calculatedDiscount: Number.parseInt(data.calculated_discount, 10), + calculatedDiscount: data.calculated_discount !== null ? + Number.parseInt(data.calculated_discount, 10) : null, totalExclDiscount: Number.parseInt(data.total_excl_discount, 10), - products: data.products.map(({ image_url: imageURL, title, seat_type: seatType }) => ({ - imageURL, title, seatType, + products: data.products.map(({ image_url: imageUrl, title, seat_type: seatType }) => ({ + imageUrl, title, seatType, })), voucher: data.voucher, }; - return transformedResults; } +export async function getBasket() { + const { data } = await apiClient + .get(`${config.ECOMMERCE_BASE_URL}/bff/payment/v0/payment/`) + .catch(handleRequestError); + return transformResults(data); +} + export async function sdnCheck(firstName, lastName, city, country) { const { data } = await apiClient.post( `${config.ECOMMERCE_BASE_URL}/api/v2/sdn/search/`,