woocommerce/plugins/woocommerce-blocks/assets/js/data/cart/test/notify-quantity-changes.ts

182 lines
5.0 KiB
TypeScript
Raw Normal View History

Add notice on quantity change and update `wc/store/cart` to use thunks (https://github.com/woocommerce/woocommerce-blocks/pull/7938) * Add receiveCart thunk * Add mapCartResponseToCart helper * Add getItemsPendingQuantityUpdate selector * Update cart resolvers to be thunks * Remove RECEIVE_CART action and replace with SET_CART_DATA receiveCart will turn into a thunk. * Add notifyQuantityChanges functions * Remove receiveCart from action type definition, replace with setCartData * Move apiFetchWithHeaders out of controls This will just be a normal function since we'll be updating actions to thunks which will use this instead of a control. * Include thunks in actions file * Update receiveCart action to setCartData * Update applyCoupon action to a thunk * Update useStoreCartCoupons to get action from correct place * Update StoreCartCoupon types * Add types for Thunk and ThunkReturnType in mapped-types * Change applyCoupon to a thunk * Get applyCoupon, removeCoupon, receiveApplyingCoupon from useDispatch This is to separate the concerns of actions vs. selectors. Previously the actions were fetched during useSelect which is not a pattern we use anywhere else in the codebase. Since we updated the MapToDispatch type, we can now get correctly typed thunks from the data store. * Improve apiFetchWithHeaders typings * Convert removeCoupon from generator to thunk * Add applyCoupon and removeCoupon to CartAction type * Remove unused old-style type-def * Add receiveApplyingCoupon & receiveRemovingCoupon to StoreCartCoupon * Correct issues with StoreCartCoupon type These were not intended to reflect the actions in data store, rather the functions offered by the useStoreCartCoupons hook. * Update applyExtensionCartUpdate to a thunk * Update addItemToCart to thunk * Add ResolveSelectFromMap type that works with thunks * Add CartDispatchFromMap and CartResolveSelectFromMap types We can add this to all data stores to get them working with thunks properly. * Add docs and update generic name in ResolveSelectFromMap * Add correct types for thunk resolvers in cart data store * Update removeItemFromCart to thunk * Update apiFetchWithHeaders to use generic * Update selectShippingRate to thunk * Update resolver tests to test correct thunk functionality * Update updateCustomerData to thunk * Update reducer test to reflect new action name * Update comments on CartDispatchFromMap and CartResolveSelectFromMap * Add quantity_limits to preview cart * Make notices speak when shown * Remove copilot comment * Add isWithinQuantityLimits function This is because we shouldn't show a notice if the quantity limits change, but the item's quantity is still OK. * Add tests for notifyQuantityChanges * Show notice when multiple_of is updated * Update test to test for multiple_of changes * Remove empty export * Remove controls from cart data store Not needed anymore since the exported value from the shared-controls file was empty. * Export a control and async function for apiFetchWithHeaders This is required because async functions cannot be called from sync generators. * Use control version of apiFetchWithHeaders in the collections store * Improve comments and remove incorrect TypeScript * Update assets/js/data/cart/actions.ts Co-authored-by: Mike Jolley <mike.jolley@me.com> * Update ResolveSelectFromMap to include selectors too * Update TS in actions * Use finally to remove duplicate code * remove item pending delete/qty update after action runs in all cases This will also reset the state when the request to remove it/change quantity errors * Remove unnecessary type from param. Not needed because we have TS now. The description can stay though, it is useful. * Update snackbar wording to use active voice * Remove old WP version check * Set max quantity to high number instead of null This would only happen in a niche case, and would require several TS changes to fix, so it's better to set it as a number here. 9999 should be high enough, and is the default quantity limit set below in get_product_quantity_limit * Set code on woocommerce_rest_cart_invalid_key to 409 This is so the cart is returned in the response, so the client can update. * Fix typo in comment and add CartSelectFromMap * Remove unnecessary docblock * Add getItemsPendingDelete selector This is needed so we can show a notice for items that are unexpectedly removed from the cart. We need to know which ones are pending delete so we can skip showing the notice for them. * Add type for notifyQuantityChanges args and change args to object * Add notifyIfRemoved function This will check items that have been removed and show a notice for them. * Fix TS in receiveCart & pass itemsPendingDelete to notifyQuantiyChanges * Update wording on removal notice * Update types for notifyQuantityChanges args * Update tests to reflect new wording and args being an object * Check item is truth before running comparison of keys * Update tests for unexpectedly and expectedly removed items * Ignore print_r to satisfy phpcs * Update PHP tests to reflect correct response code when deleting items * Remove unnecessary controls and dispatch events directly from thunk Co-authored-by: Mike Jolley <mike.jolley@me.com>
2022-12-16 16:06:37 +00:00
/**
* External dependencies
*/
import { dispatch } from '@wordpress/data';
import { previewCart } from '@woocommerce/resource-previews';
import { camelCase, cloneDeep, mapKeys } from 'lodash';
import { Cart, CartResponse } from '@woocommerce/types';
Add notice on quantity change and update `wc/store/cart` to use thunks (https://github.com/woocommerce/woocommerce-blocks/pull/7938) * Add receiveCart thunk * Add mapCartResponseToCart helper * Add getItemsPendingQuantityUpdate selector * Update cart resolvers to be thunks * Remove RECEIVE_CART action and replace with SET_CART_DATA receiveCart will turn into a thunk. * Add notifyQuantityChanges functions * Remove receiveCart from action type definition, replace with setCartData * Move apiFetchWithHeaders out of controls This will just be a normal function since we'll be updating actions to thunks which will use this instead of a control. * Include thunks in actions file * Update receiveCart action to setCartData * Update applyCoupon action to a thunk * Update useStoreCartCoupons to get action from correct place * Update StoreCartCoupon types * Add types for Thunk and ThunkReturnType in mapped-types * Change applyCoupon to a thunk * Get applyCoupon, removeCoupon, receiveApplyingCoupon from useDispatch This is to separate the concerns of actions vs. selectors. Previously the actions were fetched during useSelect which is not a pattern we use anywhere else in the codebase. Since we updated the MapToDispatch type, we can now get correctly typed thunks from the data store. * Improve apiFetchWithHeaders typings * Convert removeCoupon from generator to thunk * Add applyCoupon and removeCoupon to CartAction type * Remove unused old-style type-def * Add receiveApplyingCoupon & receiveRemovingCoupon to StoreCartCoupon * Correct issues with StoreCartCoupon type These were not intended to reflect the actions in data store, rather the functions offered by the useStoreCartCoupons hook. * Update applyExtensionCartUpdate to a thunk * Update addItemToCart to thunk * Add ResolveSelectFromMap type that works with thunks * Add CartDispatchFromMap and CartResolveSelectFromMap types We can add this to all data stores to get them working with thunks properly. * Add docs and update generic name in ResolveSelectFromMap * Add correct types for thunk resolvers in cart data store * Update removeItemFromCart to thunk * Update apiFetchWithHeaders to use generic * Update selectShippingRate to thunk * Update resolver tests to test correct thunk functionality * Update updateCustomerData to thunk * Update reducer test to reflect new action name * Update comments on CartDispatchFromMap and CartResolveSelectFromMap * Add quantity_limits to preview cart * Make notices speak when shown * Remove copilot comment * Add isWithinQuantityLimits function This is because we shouldn't show a notice if the quantity limits change, but the item's quantity is still OK. * Add tests for notifyQuantityChanges * Show notice when multiple_of is updated * Update test to test for multiple_of changes * Remove empty export * Remove controls from cart data store Not needed anymore since the exported value from the shared-controls file was empty. * Export a control and async function for apiFetchWithHeaders This is required because async functions cannot be called from sync generators. * Use control version of apiFetchWithHeaders in the collections store * Improve comments and remove incorrect TypeScript * Update assets/js/data/cart/actions.ts Co-authored-by: Mike Jolley <mike.jolley@me.com> * Update ResolveSelectFromMap to include selectors too * Update TS in actions * Use finally to remove duplicate code * remove item pending delete/qty update after action runs in all cases This will also reset the state when the request to remove it/change quantity errors * Remove unnecessary type from param. Not needed because we have TS now. The description can stay though, it is useful. * Update snackbar wording to use active voice * Remove old WP version check * Set max quantity to high number instead of null This would only happen in a niche case, and would require several TS changes to fix, so it's better to set it as a number here. 9999 should be high enough, and is the default quantity limit set below in get_product_quantity_limit * Set code on woocommerce_rest_cart_invalid_key to 409 This is so the cart is returned in the response, so the client can update. * Fix typo in comment and add CartSelectFromMap * Remove unnecessary docblock * Add getItemsPendingDelete selector This is needed so we can show a notice for items that are unexpectedly removed from the cart. We need to know which ones are pending delete so we can skip showing the notice for them. * Add type for notifyQuantityChanges args and change args to object * Add notifyIfRemoved function This will check items that have been removed and show a notice for them. * Fix TS in receiveCart & pass itemsPendingDelete to notifyQuantiyChanges * Update wording on removal notice * Update types for notifyQuantityChanges args * Update tests to reflect new wording and args being an object * Check item is truth before running comparison of keys * Update tests for unexpectedly and expectedly removed items * Ignore print_r to satisfy phpcs * Update PHP tests to reflect correct response code when deleting items * Remove unnecessary controls and dispatch events directly from thunk Co-authored-by: Mike Jolley <mike.jolley@me.com>
2022-12-16 16:06:37 +00:00
/**
* Internal dependencies
*/
import { notifyQuantityChanges } from '../notify-quantity-changes';
jest.mock( '@wordpress/data' );
const mockedCreateInfoNotice = jest.fn();
dispatch.mockImplementation( ( store ) => {
if ( store === 'core/notices' ) {
return {
createInfoNotice: mockedCreateInfoNotice,
};
}
} );
/**
* Clones the preview cart and turns it into a `Cart`.
*/
const getFreshCarts = (): { oldCart: Cart; newCart: Cart } => {
const oldCart = mapKeys(
cloneDeep< CartResponse >( previewCart ),
( _, key ) => camelCase( key )
) as unknown as Cart;
const newCart = mapKeys(
cloneDeep< CartResponse >( previewCart ),
( _, key ) => camelCase( key )
) as unknown as Cart;
return { oldCart, newCart };
};
describe( 'notifyQuantityChanges', () => {
afterEach( () => {
jest.clearAllMocks();
} );
it( 'shows notices when the quantity limits of an item change', () => {
const { oldCart, newCart } = getFreshCarts();
newCart.items[ 0 ].quantity_limits.minimum = 50;
notifyQuantityChanges( {
oldCart,
newCart,
cartItemsPendingQuantity: [],
} );
expect( mockedCreateInfoNotice ).toHaveBeenLastCalledWith(
'The quantity of "Beanie" was increased to 50. This is the minimum required quantity.',
{
context: 'wc/cart',
speak: true,
type: 'snackbar',
id: '1-quantity-update',
}
);
newCart.items[ 0 ].quantity_limits.minimum = 1;
newCart.items[ 0 ].quantity_limits.maximum = 10;
// Quantity needs to be outside the limits for the notice to show.
newCart.items[ 0 ].quantity = 11;
notifyQuantityChanges( {
oldCart,
newCart,
cartItemsPendingQuantity: [],
} );
expect( mockedCreateInfoNotice ).toHaveBeenLastCalledWith(
'The quantity of "Beanie" was decreased to 10. This is the maximum allowed quantity.',
{
context: 'wc/cart',
speak: true,
type: 'snackbar',
id: '1-quantity-update',
}
);
newCart.items[ 0 ].quantity = 10;
oldCart.items[ 0 ].quantity = 10;
newCart.items[ 0 ].quantity_limits.multiple_of = 6;
notifyQuantityChanges( {
oldCart,
newCart,
cartItemsPendingQuantity: [],
} );
expect( mockedCreateInfoNotice ).toHaveBeenLastCalledWith(
'The quantity of "Beanie" was changed to 6. You must purchase this product in groups of 6.',
{
context: 'wc/cart',
speak: true,
type: 'snackbar',
id: '1-quantity-update',
}
);
} );
it( 'does not show notices if the quantity limit changes, and the quantity is within limits', () => {
const { oldCart, newCart } = getFreshCarts();
newCart.items[ 0 ].quantity = 5;
oldCart.items[ 0 ].quantity = 5;
newCart.items[ 0 ].quantity_limits.maximum = 10;
notifyQuantityChanges( {
oldCart,
newCart,
cartItemsPendingQuantity: [],
} );
expect( mockedCreateInfoNotice ).not.toHaveBeenCalled();
newCart.items[ 0 ].quantity_limits.minimum = 4;
notifyQuantityChanges( {
oldCart,
newCart,
cartItemsPendingQuantity: [],
} );
expect( mockedCreateInfoNotice ).not.toHaveBeenCalled();
} );
it( 'shows notices when the quantity of an item changes', () => {
const { oldCart, newCart } = getFreshCarts();
newCart.items[ 0 ].quantity = 50;
notifyQuantityChanges( {
oldCart,
newCart,
cartItemsPendingQuantity: [],
} );
expect( mockedCreateInfoNotice ).toHaveBeenLastCalledWith(
'The quantity of "Beanie" was changed to 50.',
{
context: 'wc/cart',
speak: true,
type: 'snackbar',
id: '1-quantity-update',
}
);
} );
it( 'does not show notices when the the item is the one being updated', () => {
const { oldCart, newCart } = getFreshCarts();
newCart.items[ 0 ].quantity = 5;
newCart.items[ 0 ].quantity_limits.maximum = 10;
notifyQuantityChanges( {
oldCart,
newCart,
cartItemsPendingQuantity: [ '1' ],
} );
expect( mockedCreateInfoNotice ).not.toHaveBeenCalled();
} );
it( 'does not show notices when a deleted item is the one being removed', () => {
const { oldCart, newCart } = getFreshCarts();
// Remove both items from the new cart.
delete newCart.items[ 0 ];
delete newCart.items[ 1 ];
notifyQuantityChanges( {
oldCart,
newCart,
// This means the user is only actively removing item with key '1'. The second item is "unexpected" so we
// expect exactly one notification to be shown.
cartItemsPendingDelete: [ '1' ],
} );
// Check it was called for item 2, but not item 1.
expect( mockedCreateInfoNotice ).toHaveBeenCalledTimes( 1 );
} );
it( 'shows a notice when an item is unexpectedly removed', () => {
const { oldCart, newCart } = getFreshCarts();
delete newCart.items[ 0 ];
notifyQuantityChanges( {
oldCart,
newCart,
} );
expect( mockedCreateInfoNotice ).toHaveBeenLastCalledWith(
'"Beanie" was removed from your cart.',
{
context: 'wc/cart',
speak: true,
type: 'snackbar',
id: '1-removed',
}
);
} );
} );