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:
parent
e04e5d8033
commit
89a1ec7206
|
@ -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;
|
||||
|
|
|
@ -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 /> }
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
} );
|
||||
} );
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue