Refactor some of the shipping hooks/context usage (https://github.com/woocommerce/woocommerce-blocks/pull/2146)

* Counting helpers for shipping rates and packages

* Use new helpers

* Make shipping calculator use shipping context directly

* Totals should use current address

* Avoid useShippingRates

* Return rates and other items from useShippingRatse in useStoreCart instead

* Update tests

* Merge conflict

* Merge conflict
This commit is contained in:
Mike Jolley 2020-04-08 12:20:41 +01:00 committed by GitHub
parent 9a4fc8e2cc
commit d73d9ca12e
9 changed files with 65 additions and 221 deletions

View File

@ -2,6 +2,7 @@
* External dependencies
*/
import PropTypes from 'prop-types';
import { useShippingDataContext } from '@woocommerce/base-context';
/**
* Internal dependencies
@ -9,13 +10,18 @@ import PropTypes from 'prop-types';
import ShippingCalculatorAddress from './address';
import './style.scss';
const ShippingCalculator = ( { onUpdate, address, addressFields } ) => {
const ShippingCalculator = ( {
onUpdate = () => {},
addressFields = [ 'country', 'state', 'city', 'postcode' ],
} ) => {
const { shippingAddress, setShippingAddress } = useShippingDataContext();
return (
<div className="wc-block-cart__shipping-calculator">
<ShippingCalculatorAddress
address={ address }
address={ shippingAddress }
addressFields={ addressFields }
onUpdate={ ( newAddress ) => {
setShippingAddress( newAddress );
onUpdate( newAddress );
} }
/>
@ -24,9 +30,8 @@ const ShippingCalculator = ( { onUpdate, address, addressFields } ) => {
};
ShippingCalculator.propTypes = {
onUpdate: PropTypes.func.isRequired,
address: PropTypes.object.isRequired,
addressFields: PropTypes.array.isRequired,
onUpdate: PropTypes.func,
addressFields: PropTypes.array,
};
export default ShippingCalculator;

View File

@ -9,7 +9,7 @@ import {
} from '@woocommerce/base-components/cart-checkout';
import PropTypes from 'prop-types';
import { useState } from '@wordpress/element';
import { useShippingRates } from '@woocommerce/base-hooks';
import { useStoreCart } from '@woocommerce/base-hooks';
/**
* Internal dependencies
@ -32,14 +32,12 @@ const TotalsShippingItem = ( {
const [ isShippingCalculatorOpen, setIsShippingCalculatorOpen ] = useState(
false
);
const defaultAddressFields = [ 'country', 'state', 'city', 'postcode' ];
const {
shippingRates,
shippingAddress,
shippingRatesLoading,
hasShippingAddress,
setShippingAddress,
} = useShippingRates();
shippingAddress,
} = useStoreCart();
const totalShippingValue = DISPLAY_CART_PRICES_INCLUDING_TAX
? parseInt( values.total_shipping, 10 ) +
parseInt( values.total_shipping_tax, 10 )
@ -80,12 +78,9 @@ const TotalsShippingItem = ( {
<>
{ showCalculator && isShippingCalculatorOpen && (
<ShippingCalculator
onUpdate={ ( newAddress ) => {
setShippingAddress( newAddress );
onUpdate={ () => {
setIsShippingCalculatorOpen( false );
} }
address={ shippingAddress }
addressFields={ defaultAddressFields }
/>
) }
</>
@ -119,12 +114,9 @@ const TotalsShippingItem = ( {
) }
{ showCalculator && isShippingCalculatorOpen && (
<ShippingCalculator
onUpdate={ ( newAddress ) => {
setShippingAddress( newAddress );
onUpdate={ () => {
setIsShippingCalculatorOpen( false );
} }
address={ shippingAddress }
addressFields={ defaultAddressFields }
/>
) }
</>

View File

@ -11,7 +11,6 @@ import {
} from '@wordpress/element';
import {
useShippingAddress,
useShippingRates,
useStoreCart,
useSelectShippingRate,
} from '@woocommerce/base-hooks';
@ -65,7 +64,11 @@ export const useShippingDataContext = () => {
*/
export const ShippingDataProvider = ( { children } ) => {
const { dispatchActions } = useCheckoutContext();
const { cartNeedsShipping: needsShipping } = useStoreCart();
const {
cartNeedsShipping: needsShipping,
shippingRates,
shippingRatesLoading,
} = useStoreCart();
const [ shippingErrorStatus, dispatchErrorStatus ] = useReducer(
errorStatusReducer,
NONE
@ -73,7 +76,6 @@ export const ShippingDataProvider = ( { children } ) => {
const [ observers, subscriber ] = useReducer( emitReducer, {} );
const { shippingAddress, setShippingAddress } = useShippingAddress();
const currentObservers = useRef( observers );
const { shippingRates, shippingRatesLoading } = useShippingRates();
const {
selectShippingRate: setSelectedRates,
selectedShippingRates: selectedRates,

View File

@ -34,13 +34,15 @@ describe( 'useStoreCart', () => {
cartIsLoading: false,
cartItemErrors: [],
cartErrors: [],
shippingRates: previewCart.shipping_rates,
shippingAddress: {
country: '',
state: '',
city: '',
postcode: '',
},
shippingRates: previewCart.shipping_rates,
shippingRatesLoading: false,
hasShippingAddress: false,
};
const mockCartItems = [ { key: '1', id: 1, name: 'Lorem Ipsum' } ];
@ -67,11 +69,13 @@ describe( 'useStoreCart', () => {
cartItemsCount: 1,
cartItemsWeight: 10,
cartNeedsShipping: true,
shippingAddress: mockShippingAddress,
shippingRates: [],
cartTotals: mockCartTotals,
cartIsLoading: mockCartIsLoading,
cartErrors: mockCartErrors,
shippingAddress: mockShippingAddress,
shippingRates: [],
shippingRatesLoading: false,
hasShippingAddress: false,
};
const getWrappedComponents = ( Component ) => (
@ -94,6 +98,7 @@ describe( 'useStoreCart', () => {
hasFinishedResolution: jest
.fn()
.mockReturnValue( ! mockCartIsLoading ),
areShippingRatesLoading: jest.fn().mockReturnValue( false ),
},
};
registry.registerStore( storeKey, {

View File

@ -33,6 +33,8 @@ export const defaultCartData = {
country: '',
},
shippingRates: [],
shippingRatesLoading: false,
hasShippingAddress: false,
};
/**
@ -68,13 +70,15 @@ export const useStoreCart = ( options = { shouldSelect: true } ) => {
cartTotals: previewCart.totals,
cartIsLoading: false,
cartErrors: [],
shippingRates: previewCart.shipping_rates,
shippingAddress: {
country: '',
state: '',
city: '',
postcode: '',
},
shippingRates: previewCart.shipping_rates,
shippingRatesLoading: false,
hasShippingAddress: false,
};
}
@ -85,10 +89,10 @@ export const useStoreCart = ( options = { shouldSelect: true } ) => {
const cartIsLoading = ! store.hasFinishedResolution(
'getCartData'
);
const shippingRatesLoading = store.areShippingRatesLoading();
return {
cartCoupons: cartData.coupons,
shippingRates: cartData.shippingRates,
shippingAddress: cartData.shippingAddress,
cartItems: cartData.items,
cartItemsCount: cartData.itemsCount,
cartItemsWeight: cartData.itemsWeight,
@ -97,6 +101,10 @@ export const useStoreCart = ( options = { shouldSelect: true } ) => {
cartTotals,
cartIsLoading,
cartErrors,
shippingAddress: cartData.shippingAddress,
shippingRates: cartData.shippingRates,
shippingRatesLoading,
hasShippingAddress: !! cartData.shippingAddress.country,
};
},
[ shouldSelect ]

View File

@ -1,3 +1,2 @@
export * from './use-select-shipping-rate';
export * from './use-shipping-rates';
export * from './use-shipping-address';

View File

@ -1,40 +0,0 @@
/**
* External dependencies
*/
import { useSelect } from '@wordpress/data';
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
/**
* Internal dependencies
*/
import { useStoreCart } from '../cart/use-store-cart';
import { useShippingAddress } from '../shipping/use-shipping-address';
/**
* This is a custom hook that is wired up to the `wc/store/cart/shipping-rates` route.
* Given a a set of default fields keys, this will handle shipping form state and load
* new rates when certain fields change.
*
* @return {Object} This hook will return an object with three properties:
* - {Boolean} shippingRatesLoading A boolean indicating whether the shipping
* rates are still loading or not.
* - {Function} setShippingAddress An function that optimistically
* update shipping address and dispatches async rate fetching.
* - {Object} shippingAddress An object containing shipping address.
* - {Object} shippingAddress True when address data exists.
*/
export const useShippingRates = () => {
const { cartErrors, shippingRates } = useStoreCart();
const { shippingAddress, setShippingAddress } = useShippingAddress();
const shippingRatesLoading = useSelect(
( select ) => select( storeKey ).areShippingRatesLoading(),
[]
);
return {
shippingRates,
shippingAddress,
setShippingAddress,
shippingRatesLoading,
shippingRatesErrors: cartErrors,
hasShippingAddress: !! shippingAddress.country,
};
};

View File

@ -1,132 +0,0 @@
/**
* External dependencies
*/
import TestRenderer, { act } from 'react-test-renderer';
import { createRegistry, RegistryProvider } from '@wordpress/data';
import { CART_STORE_KEY as storeKey } from '@woocommerce/block-data';
/**
* Internal dependencies
*/
import { useShippingRates } from '../shipping';
jest.mock( '@woocommerce/block-data', () => ( {
__esModule: true,
CART_STORE_KEY: 'test/store',
} ) );
describe( 'useShippingRates', () => {
let registry, mocks, renderer;
const getProps = ( testRenderer ) => {
const {
shippingRates,
shippingAddress,
setShippingAddress,
shippingRatesLoading,
} = testRenderer.root.findByType( 'div' ).props;
return {
shippingRates,
shippingAddress,
setShippingAddress,
shippingRatesLoading,
};
};
const getWrappedComponents = ( Component ) => (
<RegistryProvider value={ registry }>
<Component />
</RegistryProvider>
);
const getTestComponent = () => () => {
const items = useShippingRates();
return <div { ...items } />;
};
const mockCartData = {
coupons: [],
items: [ { foo: 'bar' } ],
itemsCount: 123,
itemsWeight: 123,
needsShipping: false,
shippingAddress: {
country: '',
state: '',
city: '',
postcode: '',
},
shippingRates: [
{
shippingRates: [ { foo: 'bar' } ],
destination: {
country: '',
state: '',
city: '',
postcode: '',
},
},
],
};
const setUpMocks = () => {
mocks = {
selectors: {
getCartData: jest.fn().mockReturnValue( mockCartData ),
getCartErrors: jest.fn().mockReturnValue( false ),
getCartTotals: jest.fn().mockReturnValue( 123 ),
areShippingRatesLoading: jest.fn().mockReturnValue( false ),
hasFinishedResolution: jest.fn().mockReturnValue( true ),
},
};
registry.registerStore( storeKey, {
reducer: () => ( {} ),
selectors: mocks.selectors,
} );
};
beforeEach( () => {
registry = createRegistry();
mocks = {};
renderer = null;
setUpMocks();
} );
it( 'should return expected address provided by the store', () => {
const TestComponent = getTestComponent();
act( () => {
renderer = TestRenderer.create(
getWrappedComponents( TestComponent )
);
} );
const { shippingAddress } = getProps( renderer );
expect( shippingAddress ).toStrictEqual( mockCartData.shippingAddress );
// rerender
act( () => {
renderer.update( getWrappedComponents( TestComponent ) );
} );
// re-render should result in same shippingAddress object.
const { shippingAddress: newShippingAddress } = getProps( renderer );
expect( newShippingAddress ).toStrictEqual( shippingAddress );
renderer.unmount();
} );
it( 'should return expected shipping rates provided by the store', () => {
const TestComponent = getTestComponent();
act( () => {
renderer = TestRenderer.create(
getWrappedComponents( TestComponent )
);
} );
const { shippingRates } = getProps( renderer );
expect( shippingRates ).toStrictEqual( mockCartData.shippingRates );
// rerender
act( () => {
renderer.update( getWrappedComponents( TestComponent ) );
} );
// re-render should result in same shippingAddress object.
const { shippingRates: newShippingRates } = getProps( renderer );
expect( newShippingRates ).toStrictEqual( shippingRates );
renderer.unmount();
} );
} );

View File

@ -6,27 +6,32 @@
/**
* @typedef {Object} StoreCart
*
* @property {Array} cartCoupons An array of coupons applied
* to the cart.
* @property {Array} shippingRates array of selected shipping
* rates
* @property {CartShippingAddress} shippingAddress Shipping address for the
* cart.
* @property {Array} cartItems An array of items in the
* cart.
* @property {number} cartItemsCount The number of items in the
* cart.
* @property {number} cartItemsWeight The weight of all items in
* the cart.
* @property {boolean} cartNeedsShipping True when the cart will
* require shipping.
* @property {Array} cartItemErrors Item validation errors.
* @property {Object} cartTotals Cart and line total
* amounts.
* @property {boolean} cartIsLoading True when cart data is
* being loaded.
* @property {Array} cartErrors An array of errors thrown
* by the cart.
* @property {Array} cartCoupons An array of coupons applied
* to the cart.
* @property {Array} cartItems An array of items in the
* cart.
* @property {number} cartItemsCount The number of items in the
* cart.
* @property {number} cartItemsWeight The weight of all items in
* the cart.
* @property {boolean} cartNeedsShipping True when the cart will
* require shipping.
* @property {Array} cartItemErrors Item validation errors.
* @property {Object} cartTotals Cart and line total
* amounts.
* @property {boolean} cartIsLoading True when cart data is
* being loaded.
* @property {Array} cartErrors An array of errors thrown
* by the cart.
* @property {CartShippingAddress} shippingAddress Shipping address for the
* cart.
* @property {Array} shippingRates array of selected shipping
* rates.
* @property {boolean} shippingRatesLoading Whether or not the
* shipping rates are
* being loaded.
* @property {boolean} hasShippingAddress Whether or not the cart
* has a shipping address yet.
*/
/**