Ensure new payment methods are only displayed when no saved payment method is selected (https://github.com/woocommerce/woocommerce-blocks/pull/3247)

* Ensure new payment methods are only displayed when no saved payment method is selected

* Simplify logic

* Add tests

* Fix wrong props definition in JSDoc

* Use default parameter instead of default prop for functional component (Label)

* Remove usePaymentMethods mock

* Remove NoPaymentMethods mock

* Fix tests
This commit is contained in:
Albert Juhé Lluveras 2020-10-12 14:43:52 +02:00 committed by GitHub
parent e04e5d8033
commit 89a1ec7206
5 changed files with 151 additions and 38 deletions

View File

@ -11,16 +11,16 @@ import classNames from 'classnames';
* specified via props.
*
* @param {Object} props Incoming props for the component.
* @param {string} props.label Label content.
* @param {string} props.screenReaderLabel Content for screen readers.
* @param {string} props.wrapperElement What element is used to wrap the label.
* @param {Object} props.wrapperProps Props passed into wrapper element.
* @param {string} [props.label] Label content.
* @param {string} [props.screenReaderLabel] Content for screen readers.
* @param {string} [props.wrapperElement] What element is used to wrap the label.
* @param {Object} [props.wrapperProps] Props passed into wrapper element.
*/
const Label = ( {
label,
screenReaderLabel,
wrapperElement,
wrapperProps,
wrapperProps = {},
} ) => {
let Wrapper;
@ -64,8 +64,4 @@ Label.propTypes = {
wrapperProps: PropTypes.object,
};
Label.defaultProps = {
wrapperProps: {},
};
export default Label;

View File

@ -2,7 +2,7 @@
* External dependencies
*/
import { usePaymentMethods } from '@woocommerce/base-hooks';
import { usePaymentMethodDataContext } from '@woocommerce/base-context';
import { useCallback, useState } from '@wordpress/element';
/**
* Internal dependencies
@ -17,23 +17,25 @@ import SavedPaymentMethodOptions from './saved-payment-method-options';
* @return {*} The rendered component.
*/
const PaymentMethods = () => {
const {
customerPaymentMethods = {},
paymentMethodData,
} = usePaymentMethodDataContext();
const { isInitialized, paymentMethods } = usePaymentMethods();
const [ showNewPaymentMethods, setShowNewPaymentMethods ] = useState(
true
);
const onChange = useCallback(
( token ) => {
setShowNewPaymentMethods( token === '0' );
},
[ setShowNewPaymentMethods ]
);
if ( isInitialized && Object.keys( paymentMethods ).length === 0 ) {
return <NoPaymentMethods />;
}
return Object.keys( customerPaymentMethods ).length > 0 &&
paymentMethodData.isSavedToken ? (
<SavedPaymentMethodOptions />
) : (
return (
<>
<SavedPaymentMethodOptions />
<PaymentMethodOptions />
<SavedPaymentMethodOptions onChange={ onChange } />
{ showNewPaymentMethods && <PaymentMethodOptions /> }
</>
);
};

View File

@ -9,6 +9,7 @@ import {
} from '@woocommerce/base-context';
import RadioControl from '@woocommerce/base-components/radio-control';
import { getPaymentMethods } from '@woocommerce/blocks-registry';
import PropTypes from 'prop-types';
/**
* @typedef {import('@woocommerce/type-defs/contexts').CustomerPaymentMethod} CustomerPaymentMethod
@ -87,7 +88,7 @@ const getDefaultPaymentMethodOptions = (
};
};
const SavedPaymentMethodOptions = () => {
const SavedPaymentMethodOptions = ( { onChange } ) => {
const { isEditor } = useEditorContext();
const {
setPaymentStatus,
@ -102,6 +103,18 @@ const SavedPaymentMethodOptions = () => {
* @property {Array} current The current options on the type.
*/
const currentOptions = useRef( [] );
const updateToken = useCallback(
( token ) => {
if ( token === '0' ) {
setPaymentStatus().started();
}
setSelectedToken( token );
onChange( token );
},
[ onChange, setSelectedToken, setPaymentStatus ]
);
useEffect( () => {
const types = Object.keys( customerPaymentMethods );
const options = types
@ -126,7 +139,7 @@ const SavedPaymentMethodOptions = () => {
setPaymentStatus
);
if ( paymentMethod.is_default && selectedToken === '' ) {
setSelectedToken( paymentMethod.tokenId + '' );
updateToken( paymentMethod.tokenId + '' );
option.onChange( paymentMethod.tokenId );
}
return option;
@ -136,27 +149,13 @@ const SavedPaymentMethodOptions = () => {
currentOptions.current = options;
}, [
customerPaymentMethods,
updateToken,
selectedToken,
setActivePaymentMethod,
setPaymentStatus,
standardMethods,
] );
const updateToken = useCallback(
( token ) => {
if ( token === '0' ) {
setPaymentStatus().started();
}
setSelectedToken( token );
},
[ setSelectedToken, setPaymentStatus ]
);
useEffect( () => {
if ( selectedToken && currentOptions.current.length > 0 ) {
updateToken( selectedToken );
}
}, [ selectedToken, updateToken ] );
// In the editor, show `Use a new payment method` option as selected.
const selectedOption = isEditor ? '0' : selectedToken + '';
const newPaymentMethodOption = {
@ -174,4 +173,8 @@ const SavedPaymentMethodOptions = () => {
) : null;
};
SavedPaymentMethodOptions.propTypes = {
onChange: PropTypes.func.isRequired,
};
export default SavedPaymentMethodOptions;

View File

@ -0,0 +1,112 @@
/**
* External dependencies
*/
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import {
registerPaymentMethod,
__experimentalDeRegisterPaymentMethod,
} from '@woocommerce/blocks-registry';
import { PaymentMethodDataProvider } from '@woocommerce/base-context';
/**
* Internal dependencies
*/
import PaymentMethods from '../payment-methods';
jest.mock( '../payment-method-options', () => () => (
<span>Payment method options</span>
) );
jest.mock( '../saved-payment-method-options', () => ( { onChange } ) => (
<>
<span>Saved payment method options</span>
<button onClick={ () => onChange( '1' ) }>Select saved</button>
<button onClick={ () => onChange( '0' ) }>Select not saved</button>
</>
) );
const registerMockPaymentMethods = () => {
[ 'cheque' ].forEach( ( name ) => {
registerPaymentMethod(
( Config ) =>
new Config( {
name,
label: name,
content: <div>A payment method</div>,
edit: <div>A payment method</div>,
icons: null,
canMakePayment: () => true,
ariaLabel: name,
} )
);
} );
};
const resetMockPaymentMethods = () => {
[ 'cheque' ].forEach( ( name ) => {
__experimentalDeRegisterPaymentMethod( name );
} );
};
describe( 'PaymentMethods', () => {
test( 'should show no payment methods component when there are no payment methods', async () => {
render(
<PaymentMethodDataProvider>
<PaymentMethods />
</PaymentMethodDataProvider>
);
await waitFor( () => {
const noPaymentMethods = screen.queryByText(
/no payment methods available/
);
expect( noPaymentMethods ).not.toBeNull();
} );
} );
test( 'should hide/show PaymentMethodOptions when a saved payment method is checked/unchecked', async () => {
registerMockPaymentMethods();
render(
<PaymentMethodDataProvider>
<PaymentMethods />
</PaymentMethodDataProvider>
);
await waitFor( () => {
const savedPaymentMethodOptions = screen.queryByText(
/Saved payment method options/
);
const paymentMethodOptions = screen.queryByText(
/Payment method options/
);
expect( savedPaymentMethodOptions ).not.toBeNull();
expect( paymentMethodOptions ).not.toBeNull();
} );
fireEvent.click( screen.getByText( 'Select saved' ) );
await waitFor( () => {
const savedPaymentMethodOptions = screen.queryByText(
/Saved payment method options/
);
const paymentMethodOptions = screen.queryByText(
/Payment method options/
);
expect( savedPaymentMethodOptions ).not.toBeNull();
expect( paymentMethodOptions ).toBeNull();
} );
fireEvent.click( screen.getByText( 'Select not saved' ) );
await waitFor( () => {
const savedPaymentMethodOptions = screen.queryByText(
/Saved payment method options/
);
const paymentMethodOptions = screen.queryByText(
/Payment method options/
);
expect( savedPaymentMethodOptions ).not.toBeNull();
expect( paymentMethodOptions ).not.toBeNull();
} );
resetMockPaymentMethods();
} );
} );

View File

@ -192,7 +192,7 @@ describe( 'Testing Payment Method Data Context Provider', () => {
return (
<>
<CheckoutExpressPayment />
<SavedPaymentMethodOptions />
<SavedPaymentMethodOptions onChange={ () => void null } />
{ 'Active Payment Method: ' + activePaymentMethod }
{ paymentMethodData[ 'wc-stripe-payment-token' ] && (
<span>Stripe token</span>