diff --git a/assets/js/base/context/providers/cart-checkout/checkout-state/constants.ts b/assets/js/base/context/providers/cart-checkout/checkout-state/constants.ts index 79b91ad2b4d..d674c35f2b0 100644 --- a/assets/js/base/context/providers/cart-checkout/checkout-state/constants.ts +++ b/assets/js/base/context/providers/cart-checkout/checkout-state/constants.ts @@ -26,15 +26,15 @@ export enum STATUS { AFTER_PROCESSING = 'after_processing', } -const preloadedApiRequests = getSetting( 'preloadedApiRequests', {} ) as Record< +const preloadedCheckoutData = getSetting( 'checkoutData', {} ) as Record< string, - { body: Record< string, unknown > } + unknown >; const checkoutData = { order_id: 0, customer_id: 0, - ...( preloadedApiRequests[ '/wc/store/checkout' ]?.body || {} ), + ...( preloadedCheckoutData || {} ), }; export const DEFAULT_CHECKOUT_STATE_DATA: CheckoutStateContextType = { diff --git a/assets/js/blocks/active-filters/frontend.js b/assets/js/blocks/active-filters/frontend.js index 6a1d2705ae5..7b7e0b629f6 100644 --- a/assets/js/blocks/active-filters/frontend.js +++ b/assets/js/blocks/active-filters/frontend.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import { withRestApiHydration } from '@woocommerce/block-hocs'; import { renderFrontend } from '@woocommerce/base-utils'; /** @@ -21,6 +20,6 @@ const getProps = ( el ) => { renderFrontend( { selector: '.wp-block-woocommerce-active-filters', - Block: withRestApiHydration( Block ), + Block, getProps, } ); diff --git a/assets/js/blocks/attribute-filter/frontend.js b/assets/js/blocks/attribute-filter/frontend.js index 5eca0cc8b90..72478cbce28 100644 --- a/assets/js/blocks/attribute-filter/frontend.js +++ b/assets/js/blocks/attribute-filter/frontend.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import { withRestApiHydration } from '@woocommerce/block-hocs'; import { renderFrontend } from '@woocommerce/base-utils'; /** @@ -25,6 +24,6 @@ const getProps = ( el ) => { renderFrontend( { selector: '.wp-block-woocommerce-attribute-filter', - Block: withRestApiHydration( Block ), + Block, getProps, } ); diff --git a/assets/js/blocks/cart-checkout/cart/frontend.js b/assets/js/blocks/cart-checkout/cart/frontend.js index 5c203a92653..d81248b99ef 100644 --- a/assets/js/blocks/cart-checkout/cart/frontend.js +++ b/assets/js/blocks/cart-checkout/cart/frontend.js @@ -1,10 +1,6 @@ /** * External dependencies */ -import { - withStoreCartApiHydration, - withRestApiHydration, -} from '@woocommerce/block-hocs'; import { getValidBlockAttributes } from '@woocommerce/base-utils'; import { Children, cloneElement, isValidElement } from '@wordpress/element'; import { useStoreCart } from '@woocommerce/base-context'; @@ -45,7 +41,7 @@ const Wrapper = ( { children } ) => { }; renderParentBlock( { - Block: withStoreCartApiHydration( withRestApiHydration( Block ) ), + Block, blockName, selector: '.wp-block-woocommerce-cart', getProps, diff --git a/assets/js/blocks/cart-checkout/checkout/checkout-order-error/index.js b/assets/js/blocks/cart-checkout/checkout/checkout-order-error/index.js index 30f34ed20eb..ccb2c293c97 100644 --- a/assets/js/blocks/cart-checkout/checkout/checkout-order-error/index.js +++ b/assets/js/blocks/cart-checkout/checkout/checkout-order-error/index.js @@ -27,6 +27,8 @@ const cartItemErrorCodes = [ GENERIC_CART_ITEM_ERROR, ]; +const preloadedCheckoutData = getSetting( 'checkoutData', {} ); + /** * When an order was not created for the checkout, for example, when an item * was out of stock, this component will be shown instead of the checkout form. @@ -35,11 +37,10 @@ const cartItemErrorCodes = [ * checkout block. */ const CheckoutOrderError = () => { - const preloadedApiRequests = getSetting( 'preloadedApiRequests', {} ); const checkoutData = { code: '', message: '', - ...( preloadedApiRequests[ '/wc/store/checkout' ]?.body || {} ), + ...( preloadedCheckoutData || {} ), }; const errorData = { diff --git a/assets/js/blocks/cart-checkout/checkout/frontend.tsx b/assets/js/blocks/cart-checkout/checkout/frontend.tsx index 261da2a8ea4..a99959a04a5 100644 --- a/assets/js/blocks/cart-checkout/checkout/frontend.tsx +++ b/assets/js/blocks/cart-checkout/checkout/frontend.tsx @@ -9,10 +9,6 @@ import { useValidation, } from '@woocommerce/base-context/hooks'; import { getRegisteredBlockComponents } from '@woocommerce/blocks-registry'; -import { - withStoreCartApiHydration, - withRestApiHydration, -} from '@woocommerce/block-hocs'; import { renderParentBlock } from '@woocommerce/atomic-utils'; /** @@ -57,7 +53,7 @@ const Wrapper = ( { }; renderParentBlock( { - Block: withStoreCartApiHydration( withRestApiHydration( Block ) ), + Block, blockName, selector: '.wp-block-woocommerce-checkout', getProps, diff --git a/assets/js/blocks/cart-checkout/mini-cart/component-frontend.tsx b/assets/js/blocks/cart-checkout/mini-cart/component-frontend.tsx index 3172ae3b1ef..13742be7d03 100644 --- a/assets/js/blocks/cart-checkout/mini-cart/component-frontend.tsx +++ b/assets/js/blocks/cart-checkout/mini-cart/component-frontend.tsx @@ -6,7 +6,6 @@ import { renderFrontend } from '@woocommerce/base-utils'; /** * Internal dependencies */ -import withMiniCartConditionalHydration from './with-mini-cart-conditional-hydration'; import MiniCartBlock from './block'; import './style.scss'; @@ -28,7 +27,7 @@ const renderMiniCartFrontend = () => { renderFrontend( { selector: '.wc-block-mini-cart', - Block: withMiniCartConditionalHydration( MiniCartBlock ), + Block: MiniCartBlock, getProps: ( el: HTMLElement ) => ( { isDataOutdated: el.dataset.isDataOutdated, isInitiallyOpen: el.dataset.isInitiallyOpen === 'true', diff --git a/assets/js/blocks/cart-checkout/mini-cart/with-mini-cart-conditional-hydration.tsx b/assets/js/blocks/cart-checkout/mini-cart/with-mini-cart-conditional-hydration.tsx deleted file mode 100644 index b04e2053cda..00000000000 --- a/assets/js/blocks/cart-checkout/mini-cart/with-mini-cart-conditional-hydration.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/** - * External dependencies - */ -import { - withStoreCartApiHydration, - withRestApiHydration, -} from '@woocommerce/block-hocs'; - -interface MiniCartBlockInterface { - // Signals whether the cart data is outdated. That happens when - // opening the mini cart after adding a product to the cart. - isDataOutdated?: boolean; - // Signals whether it should be open when the React component is loaded. For - // example, when adding a product to the cart, the Mini Cart should open - // when loaded, but when removing a product from the cart, it shouldn't. - isInitiallyOpen?: boolean; -} - -// Custom HOC to conditionally hydrate API data depending on the isDataOutdated -// prop. -export default ( - OriginalComponent: ( component: MiniCartBlockInterface ) => JSX.Element -) => { - return ( { - isDataOutdated, - ...props - }: MiniCartBlockInterface ): JSX.Element => { - const Component = isDataOutdated - ? OriginalComponent - : withStoreCartApiHydration( - withRestApiHydration( OriginalComponent ) - ); - return ; - }; -}; diff --git a/assets/js/blocks/price-filter/frontend.js b/assets/js/blocks/price-filter/frontend.js index 390942c9fbf..20f33610604 100644 --- a/assets/js/blocks/price-filter/frontend.js +++ b/assets/js/blocks/price-filter/frontend.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import { withRestApiHydration } from '@woocommerce/block-hocs'; import { renderFrontend } from '@woocommerce/base-utils'; /** @@ -20,6 +19,6 @@ const getProps = ( el ) => { renderFrontend( { selector: '.wp-block-woocommerce-price-filter', - Block: withRestApiHydration( Block ), + Block, getProps, } ); diff --git a/assets/js/blocks/products/all-products/frontend.js b/assets/js/blocks/products/all-products/frontend.js index 9042d82df05..4d704bd3e34 100644 --- a/assets/js/blocks/products/all-products/frontend.js +++ b/assets/js/blocks/products/all-products/frontend.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import { withRestApiHydration } from '@woocommerce/block-hocs'; import { StoreNoticesProvider } from '@woocommerce/base-context'; import { renderFrontend } from '@woocommerce/base-utils'; @@ -29,6 +28,6 @@ const getProps = ( el ) => ( { renderFrontend( { selector: '.wp-block-woocommerce-all-products', - Block: withRestApiHydration( AllProductsFrontend ), + Block: AllProductsFrontend, getProps, } ); diff --git a/assets/js/blocks/stock-filter/frontend.js b/assets/js/blocks/stock-filter/frontend.js index 8c041d1d301..f00aebf2172 100644 --- a/assets/js/blocks/stock-filter/frontend.js +++ b/assets/js/blocks/stock-filter/frontend.js @@ -1,7 +1,6 @@ /** * External dependencies */ -import { withRestApiHydration } from '@woocommerce/block-hocs'; import { renderFrontend } from '@woocommerce/base-utils'; /** @@ -22,6 +21,6 @@ const getProps = ( el ) => { renderFrontend( { selector: '.wp-block-woocommerce-stock-filter', - Block: withRestApiHydration( Block ), + Block, getProps, } ); diff --git a/assets/js/data/cart/constants.ts b/assets/js/data/cart/constants.ts index ca1245ccac2..109bd72f36a 100644 --- a/assets/js/data/cart/constants.ts +++ b/assets/js/data/cart/constants.ts @@ -14,4 +14,3 @@ export const CART_API_ERROR = { status: 500, }, }; -export const LAST_CART_UPDATE_TIMESTAMP_KEY = 'wc-blocks_cart_update_timestamp'; diff --git a/assets/js/hocs/index.js b/assets/js/hocs/index.js index 68b83af8065..cdbd3fb8627 100644 --- a/assets/js/hocs/index.js +++ b/assets/js/hocs/index.js @@ -5,5 +5,3 @@ export { default as withProduct } from './with-product'; export { default as withProductVariations } from './with-product-variations'; export { default as withSearchedProducts } from './with-searched-products'; export { default as withTransformSingleSelectToMultipleSelect } from './with-transform-single-select-to-multiple-select'; -export { default as withStoreCartApiHydration } from './with-store-cart-api-hydration'; -export { default as withRestApiHydration } from './with-rest-api-hydration'; diff --git a/assets/js/hocs/with-rest-api-hydration.js b/assets/js/hocs/with-rest-api-hydration.js deleted file mode 100644 index e12ea7551a4..00000000000 --- a/assets/js/hocs/with-rest-api-hydration.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * External dependencies - */ -import { useRef } from '@wordpress/element'; -import { SCHEMA_STORE_KEY } from '@woocommerce/block-data'; -import { useSelect } from '@wordpress/data'; -import { blocksConfig } from '@woocommerce/block-settings'; - -/** - * Hydrate Rest API data from settings to reduce the number of API requests needed. - */ -const useRestApiHydration = () => { - const restApiRoutes = useRef( blocksConfig.restApiRoutes || {} ); - - useSelect( ( select, registry ) => { - if ( ! restApiRoutes.current ) { - return; - } - - const { isResolving, hasFinishedResolution } = select( - SCHEMA_STORE_KEY - ); - const { - receiveRoutes, - startResolution, - finishResolution, - } = registry.dispatch( SCHEMA_STORE_KEY ); - - Object.keys( restApiRoutes.current ).forEach( ( namespace ) => { - const routes = restApiRoutes.current[ namespace ]; - if ( - ! isResolving( 'getRoutes', [ namespace ] ) && - ! hasFinishedResolution( 'getRoutes', [ namespace ] ) - ) { - startResolution( 'getRoutes', [ namespace ] ); - receiveRoutes( routes, [ namespace ] ); - finishResolution( 'getRoutes', [ namespace ] ); - } - } ); - }, [] ); -}; - -/** - * HOC that calls the useRestApiHydration hook. - * - * @param {Function} OriginalComponent Component being wrapped. - */ -const withRestApiHydration = ( OriginalComponent ) => { - return ( props ) => { - useRestApiHydration(); - return ; - }; -}; - -export default withRestApiHydration; diff --git a/assets/js/hocs/with-store-cart-api-hydration.js b/assets/js/hocs/with-store-cart-api-hydration.js deleted file mode 100644 index a618aaa99fc..00000000000 --- a/assets/js/hocs/with-store-cart-api-hydration.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * External dependencies - */ -import { useRef } from '@wordpress/element'; -import { getSetting } from '@woocommerce/settings'; -import { CART_STORE_KEY } from '@woocommerce/block-data'; -import { useSelect, useDispatch } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { LAST_CART_UPDATE_TIMESTAMP_KEY } from '../data/cart/constants'; - -/** - * Hydrate Cart API data. - * - * Makes cart data available without an API request to wc/store/cart/. - */ -const useStoreCartApiHydration = () => { - const preloadedApiRequests = useRef( - getSetting( 'preloadedApiRequests', {} ) - ); - const { setIsCartDataStale } = useDispatch( CART_STORE_KEY ); - - useSelect( ( select, registry ) => { - const cartData = preloadedApiRequests.current[ '/wc/store/cart' ]?.body; - - if ( ! cartData ) { - return; - } - - const { isResolving, hasFinishedResolution, isCartDataStale } = select( - CART_STORE_KEY - ); - - /** - * This should only execute once. When the code further down the file executes - * then the condition directly below this comment should never evaluate to true - * on subsequent executions. Because of this localStorage.getItem won't be - * called multiple times. - */ - if ( - ! isCartDataStale() && - ! isResolving( 'getCartData' ) && - ! hasFinishedResolution( 'getCartData', [] ) - ) { - const lastCartUpdateRaw = window.localStorage.getItem( - LAST_CART_UPDATE_TIMESTAMP_KEY - ); - - if ( lastCartUpdateRaw ) { - const lastCartUpdate = parseFloat( lastCartUpdateRaw ); - const cartGeneratedTimestamp = parseFloat( - cartData.generated_timestamp - ); - - const needsUpdateFromAPI = - ! isNaN( cartGeneratedTimestamp ) && - ! isNaN( lastCartUpdate ) && - lastCartUpdate > cartGeneratedTimestamp; - - if ( needsUpdateFromAPI ) { - setIsCartDataStale(); - } - } - } - const { - receiveCart, - receiveError, - startResolution, - finishResolution, - } = registry.dispatch( CART_STORE_KEY ); - - if ( - ! isCartDataStale() && - ! isResolving( 'getCartData', [] ) && - ! hasFinishedResolution( 'getCartData', [] ) - ) { - startResolution( 'getCartData', [] ); - if ( cartData?.code?.includes( 'error' ) ) { - receiveError( cartData ); - } else { - receiveCart( cartData ); - } - finishResolution( 'getCartData', [] ); - } - }, [] ); -}; - -/** - * HOC that calls the useStoreCartApiHydration hook. - * - * @param {Function} OriginalComponent Component being wrapped. - */ -const withStoreCartApiHydration = ( OriginalComponent ) => { - return ( props ) => { - useStoreCartApiHydration(); - return ; - }; -}; - -export default withStoreCartApiHydration; diff --git a/assets/js/middleware/cart-update.ts b/assets/js/middleware/cart-update.ts deleted file mode 100644 index a95fe4593de..00000000000 --- a/assets/js/middleware/cart-update.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * External dependencies - */ -import apiFetch, { APIFetchOptions } from '@wordpress/api-fetch'; - -/** - * Internal dependencies - */ -import { LAST_CART_UPDATE_TIMESTAMP_KEY } from '../data/cart/constants'; - -/** - * Checks if this request is a POST to the wc/store/cart endpoint. - */ -const isCartUpdatePostRequest = ( options: APIFetchOptions ) => { - const url = options.url || options.path || ''; - const method = options.method || 'GET'; - - // Return false if there is no endpoint provided, or the request is not a POST request. - if ( ! url || method !== 'POST' ) { - return false; - } - - const cartRegExp = /wc\/store\/cart\//; - const batchRegExp = /wc\/store\/batch/; - - const isCart = cartRegExp.exec( url ) !== null; - const isBatch = batchRegExp.exec( url ) !== null; - - if ( isCart ) { - return true; - } - - if ( isBatch ) { - const requests = options?.data?.requests || []; - - return requests.some( ( request: { path: string } ) => { - const requestUrl = request.path || ''; - - return cartRegExp.exec( requestUrl ) !== null; - } ); - } - - return false; -}; - -/** - * Middleware which saves the time that the cart was last modified in - * the browser's Local Storage - * - * @param {Object} options Fetch options. - * @param {Function} next The next middleware or fetchHandler to call. - * - * @return {*} The evaluated result of the remaining middleware chain. - */ -const cartUpdateMiddleware = ( - options: APIFetchOptions, - /* eslint-disable @typescript-eslint/no-explicit-any */ - next: ( arg0: APIFetchOptions, arg1: any ) => any -) => { - if ( isCartUpdatePostRequest( options ) ) { - window.localStorage.setItem( - LAST_CART_UPDATE_TIMESTAMP_KEY, - ( Date.now() / 1000 ).toString() - ); - } - return next( options, next ); -}; - -apiFetch.use( cartUpdateMiddleware ); diff --git a/assets/js/middleware/index.js b/assets/js/middleware/index.js index 30a4a834bc7..f5d970a90d3 100644 --- a/assets/js/middleware/index.js +++ b/assets/js/middleware/index.js @@ -2,4 +2,3 @@ * Internal dependencies */ import './store-api-nonce'; -import './cart-update'; diff --git a/assets/js/previews/cart.ts b/assets/js/previews/cart.ts index eea13340da9..ad933766153 100644 --- a/assets/js/previews/cart.ts +++ b/assets/js/previews/cart.ts @@ -231,6 +231,5 @@ export const previewCart: CartResponse = { }, errors: [], payment_requirements: [ 'products' ], - generated_timestamp: Date.now(), extensions: {}, }; diff --git a/src/Assets/AssetDataRegistry.php b/src/Assets/AssetDataRegistry.php index 8addb202ec8..cf622971873 100644 --- a/src/Assets/AssetDataRegistry.php +++ b/src/Assets/AssetDataRegistry.php @@ -23,6 +23,13 @@ class AssetDataRegistry { */ private $data = []; + /** + * Contains preloaded API data. + * + * @var array + */ + private $preloaded_api_requests = []; + /** * Lazy data is an array of closures that will be invoked just before * asset data is generated for the enqueued script. @@ -292,11 +299,8 @@ public function add( $key, $data, $check_key_exists = false ) { * @param string $path REST API path to preload. */ public function hydrate_api_request( $path ) { - if ( ! isset( $this->data['preloadedApiRequests'] ) ) { - $this->data['preloadedApiRequests'] = []; - } - if ( ! isset( $this->data['preloadedApiRequests'][ $path ] ) ) { - $this->data['preloadedApiRequests'] = rest_preload_api_request( $this->data['preloadedApiRequests'], $path ); + if ( ! isset( $this->preloaded_api_requests[ $path ] ) ) { + $this->preloaded_api_requests = rest_preload_api_request( $this->preloaded_api_requests, $path ); } } @@ -322,7 +326,7 @@ public function register_data_script() { $this->api->register_script( $this->handle, 'build/wc-settings.js', - [], + [ 'wp-api-fetch' ], true ); } @@ -339,12 +343,16 @@ public function enqueue_asset_data() { if ( wp_script_is( $this->handle, 'enqueued' ) ) { $this->initialize_core_data(); $this->execute_lazy_data(); - $data = rawurlencode( wp_json_encode( $this->data ) ); + + $data = rawurlencode( wp_json_encode( $this->data ) ); + $preloaded_api_requests = rawurlencode( wp_json_encode( $this->preloaded_api_requests ) ); + wp_add_inline_script( $this->handle, - "var wcSettings = wcSettings || JSON.parse( decodeURIComponent( '" - . esc_js( $data ) - . "' ) );", + " + var wcSettings = wcSettings || JSON.parse( decodeURIComponent( '" . esc_js( $data ) . "' ) ); + wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( JSON.parse( decodeURIComponent( '" . esc_js( $preloaded_api_requests ) . "' ) ) ) ) + ", 'before' ); } diff --git a/src/BlockTypes/Checkout.php b/src/BlockTypes/Checkout.php index 296f4d003f0..843d91f67f8 100644 --- a/src/BlockTypes/Checkout.php +++ b/src/BlockTypes/Checkout.php @@ -332,13 +332,16 @@ protected function hydrate_customer_payment_methods() { * Hydrate the checkout block with data from the API. */ protected function hydrate_from_api() { + $this->asset_data_registry->hydrate_api_request( '/wc/store/cart' ); + // Print existing notices now, otherwise they are caught by the Cart // Controller and converted to exceptions. wc_print_notices(); - add_filter( 'woocommerce_store_api_disable_nonce_check', '__return_true' ); - $this->asset_data_registry->hydrate_api_request( '/wc/store/cart' ); - $this->asset_data_registry->hydrate_api_request( '/wc/store/checkout' ); + + $rest_preload_api_requests = rest_preload_api_request( [], '/wc/store/checkout' ); + $this->asset_data_registry->add( 'checkoutData', $rest_preload_api_requests['/wc/store/checkout']['body'] ?? [] ); + remove_filter( 'woocommerce_store_api_disable_nonce_check', '__return_true' ); } diff --git a/src/StoreApi/Schemas/CartSchema.php b/src/StoreApi/Schemas/CartSchema.php index f6093725679..ab62baa89ca 100644 --- a/src/StoreApi/Schemas/CartSchema.php +++ b/src/StoreApi/Schemas/CartSchema.php @@ -316,12 +316,6 @@ public function get_properties() { 'context' => [ 'view', 'edit' ], 'readonly' => true, ], - 'generated_timestamp' => [ - 'description' => __( 'The time at which this cart data was prepared', 'woo-gutenberg-products-block' ), - 'type' => 'number', - 'context' => [ 'view', 'edit' ], - 'readonly' => true, - ], self::EXTENDING_KEY => $this->get_extended_schema( self::IDENTIFIER ), ]; } @@ -377,7 +371,6 @@ public function get_item_response( $cart ) { ), 'errors' => $cart_errors, 'payment_requirements' => $this->extend->get_payment_requirements(), - 'generated_timestamp' => time(), self::EXTENDING_KEY => $this->get_extended_data( self::IDENTIFIER ), ]; } diff --git a/src/StoreApi/docs/cart.md b/src/StoreApi/docs/cart.md index 2c68e3bac71..4cd6c0885a9 100644 --- a/src/StoreApi/docs/cart.md +++ b/src/StoreApi/docs/cart.md @@ -1,6 +1,6 @@ # Cart API -The cart API returns the current state of the cart for the current session or logged in user. +The cart API returns the current state of the cart for the current session or logged in user. All POST endpoints require [Nonce Tokens](nonce-tokens.md) and return the updated state of the full cart once complete. @@ -24,235 +24,234 @@ All endpoints under `/cart` (listed in this doc) return responses in the same fo ```json { - "coupons": [ - { - "code": "discount20", - "totals": { - "currency_code": "GBP", - "currency_symbol": "£", - "currency_minor_unit": 2, - "currency_decimal_separator": ".", - "currency_thousand_separator": ",", - "currency_prefix": "£", - "currency_suffix": "", - "total_discount": "421", - "total_discount_tax": "0" - } - } - ], - "shipping_rates": [ - { - "package_id": 0, - "name": "Shipping", - "destination": { - "address_1": "550 Central Park West", - "address_2": "Corner Penthouse Spook Central", - "city": "New York", - "state": "NY", - "postcode": "10023", - "country": "US" - }, - "items": [ - { - "key": "9bf31c7ff062936a96d3c8bd1f8f2ff3", - "name": "Beanie", - "quantity": 1 - }, - { - "key": "e369853df766fa44e1ed0ff613f563bd", - "name": "WordPress Pennant", - "quantity": 1 - } - ], - "shipping_rates": [ - { - "rate_id": "flat_rate:4", - "name": "Flat rate", - "description": "", - "delivery_time": "", - "price": "500", - "instance_id": 4, - "method_id": "flat_rate", - "meta_data": [ - { - "key": "Items", - "value": "Beanie × 1, WordPress Pennant × 1" - } - ], - "selected": true, - "currency_code": "GBP", - "currency_symbol": "£", - "currency_minor_unit": 2, - "currency_decimal_separator": ".", - "currency_thousand_separator": ",", - "currency_prefix": "£", - "currency_suffix": "" - } - ] - } - ], - "shipping_address": { - "first_name": "Peter", - "last_name": "Venkman", - "company": "", - "address_1": "550 Central Park West", - "address_2": "Corner Penthouse Spook Central", - "city": "New York", - "state": "NY", - "postcode": "10023", - "country": "US" - }, - "items": [ - { - "key": "9bf31c7ff062936a96d3c8bd1f8f2ff3", - "id": 15, - "quantity": 1, - "quantity_limit": 99, - "name": "Beanie", - "summary": "

This is a simple product.<\/p>", - "short_description": "

This is a simple product.<\/p>", - "description": "

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.<\/p>", - "sku": "woo-beanie", - "low_stock_remaining": null, - "backorders_allowed": false, - "show_backorder_badge": false, - "sold_individually": false, - "permalink": "https:\/\/local.wordpress.test\/product\/beanie\/", - "images": [ - { - "id": 44, - "src": "https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/beanie-2.jpg", - "thumbnail": "https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/beanie-2-324x324.jpg", - "srcset": "https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/beanie-2.jpg 801w, https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/beanie-2-324x324.jpg 324w, https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/beanie-2-100x100.jpg 100w, https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/beanie-2-416x416.jpg 416w, https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/beanie-2-300x300.jpg 300w, https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/beanie-2-150x150.jpg 150w, https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/beanie-2-768x768.jpg 768w", - "sizes": "(max-width: 801px) 100vw, 801px", - "name": "beanie-2.jpg", - "alt": "" - } - ], - "variation": [], - "prices": { - "currency_code": "GBP", - "currency_symbol": "£", - "currency_minor_unit": 2, - "currency_decimal_separator": ".", - "currency_thousand_separator": ",", - "currency_prefix": "£", - "currency_suffix": "", - "price": "1000", - "regular_price": "2000", - "sale_price": "1000", - "price_range": null, - "raw_prices": { - "precision": 6, - "price": "10000000", - "regular_price": "20000000", - "sale_price": "10000000" - } - }, - "totals": { - "currency_code": "GBP", - "currency_symbol": "£", - "currency_minor_unit": 2, - "currency_decimal_separator": ".", - "currency_thousand_separator": ",", - "currency_prefix": "£", - "currency_suffix": "", - "line_subtotal": "1000", - "line_subtotal_tax": "0", - "line_total": "800", - "line_total_tax": "0" - } - }, - { - "key": "e369853df766fa44e1ed0ff613f563bd", - "id": 34, - "quantity": 1, - "quantity_limit": 99, - "name": "WordPress Pennant", - "summary": "

This is an external product.<\/p>", - "short_description": "

This is an external product.<\/p>", - "description": "

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.<\/p>", - "sku": "wp-pennant", - "low_stock_remaining": null, - "backorders_allowed": false, - "show_backorder_badge": false, - "sold_individually": false, - "permalink": "https:\/\/local.wordpress.test\/product\/wordpress-pennant\/", - "images": [ - { - "id": 57, - "src": "https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/pennant-1.jpg", - "thumbnail": "https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/pennant-1-324x324.jpg", - "srcset": "https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/pennant-1.jpg 800w, https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/pennant-1-324x324.jpg 324w, https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/pennant-1-100x100.jpg 100w, https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/pennant-1-416x416.jpg 416w, https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/pennant-1-300x300.jpg 300w, https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/pennant-1-150x150.jpg 150w, https:\/\/local.wordpress.test\/wp-content\/uploads\/2020\/03\/pennant-1-768x768.jpg 768w", - "sizes": "(max-width: 800px) 100vw, 800px", - "name": "pennant-1.jpg", - "alt": "" - } - ], - "variation": [], - "prices": { - "currency_code": "GBP", - "currency_symbol": "£", - "currency_minor_unit": 2, - "currency_decimal_separator": ".", - "currency_thousand_separator": ",", - "currency_prefix": "£", - "currency_suffix": "", - "price": "1105", - "regular_price": "1105", - "sale_price": "1105", - "price_range": null, - "raw_prices": { - "precision": 6, - "price": "11050000", - "regular_price": "11050000", - "sale_price": "11050000" - } - }, - "totals": { - "currency_code": "GBP", - "currency_symbol": "£", - "currency_minor_unit": 2, - "currency_decimal_separator": ".", - "currency_thousand_separator": ",", - "currency_prefix": "£", - "currency_suffix": "", - "line_subtotal": "1105", - "line_subtotal_tax": "0", - "line_total": "884", - "line_total_tax": "0" - } - } - ], - "items_count": 2, - "items_weight": 0, - "needs_payment": true, - "needs_shipping": true, - "has_calculated_shipping": true, - "totals": { - "currency_code": "GBP", - "currency_symbol": "£", - "currency_minor_unit": 2, - "currency_decimal_separator": ".", - "currency_thousand_separator": ",", - "currency_prefix": "£", - "currency_suffix": "", - "total_items": "2105", - "total_items_tax": "0", - "total_fees": "0", - "total_fees_tax": "0", - "total_discount": "421", - "total_discount_tax": "0", - "total_shipping": "500", - "total_shipping_tax": "0", - "total_price": "2184", - "total_tax": "0", - "tax_lines": [] - }, - "errors": [], - "payment_requirements": [ "products" ], - "generated_timestamp": "1628668379361", - "extensions": {}, + "coupons": [ + { + "code": "discount20", + "totals": { + "currency_code": "GBP", + "currency_symbol": "£", + "currency_minor_unit": 2, + "currency_decimal_separator": ".", + "currency_thousand_separator": ",", + "currency_prefix": "£", + "currency_suffix": "", + "total_discount": "421", + "total_discount_tax": "0" + } + } + ], + "shipping_rates": [ + { + "package_id": 0, + "name": "Shipping", + "destination": { + "address_1": "550 Central Park West", + "address_2": "Corner Penthouse Spook Central", + "city": "New York", + "state": "NY", + "postcode": "10023", + "country": "US" + }, + "items": [ + { + "key": "9bf31c7ff062936a96d3c8bd1f8f2ff3", + "name": "Beanie", + "quantity": 1 + }, + { + "key": "e369853df766fa44e1ed0ff613f563bd", + "name": "WordPress Pennant", + "quantity": 1 + } + ], + "shipping_rates": [ + { + "rate_id": "flat_rate:4", + "name": "Flat rate", + "description": "", + "delivery_time": "", + "price": "500", + "instance_id": 4, + "method_id": "flat_rate", + "meta_data": [ + { + "key": "Items", + "value": "Beanie × 1, WordPress Pennant × 1" + } + ], + "selected": true, + "currency_code": "GBP", + "currency_symbol": "£", + "currency_minor_unit": 2, + "currency_decimal_separator": ".", + "currency_thousand_separator": ",", + "currency_prefix": "£", + "currency_suffix": "" + } + ] + } + ], + "shipping_address": { + "first_name": "Peter", + "last_name": "Venkman", + "company": "", + "address_1": "550 Central Park West", + "address_2": "Corner Penthouse Spook Central", + "city": "New York", + "state": "NY", + "postcode": "10023", + "country": "US" + }, + "items": [ + { + "key": "9bf31c7ff062936a96d3c8bd1f8f2ff3", + "id": 15, + "quantity": 1, + "quantity_limit": 99, + "name": "Beanie", + "summary": "

This is a simple product.

", + "short_description": "

This is a simple product.

", + "description": "

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

", + "sku": "woo-beanie", + "low_stock_remaining": null, + "backorders_allowed": false, + "show_backorder_badge": false, + "sold_individually": false, + "permalink": "https://local.wordpress.test/product/beanie/", + "images": [ + { + "id": 44, + "src": "https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2.jpg", + "thumbnail": "https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-324x324.jpg", + "srcset": "https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2.jpg 801w, https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-324x324.jpg 324w, https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-100x100.jpg 100w, https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-416x416.jpg 416w, https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-300x300.jpg 300w, https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-150x150.jpg 150w, https://local.wordpress.test/wp-content/uploads/2020/03/beanie-2-768x768.jpg 768w", + "sizes": "(max-width: 801px) 100vw, 801px", + "name": "beanie-2.jpg", + "alt": "" + } + ], + "variation": [], + "prices": { + "currency_code": "GBP", + "currency_symbol": "£", + "currency_minor_unit": 2, + "currency_decimal_separator": ".", + "currency_thousand_separator": ",", + "currency_prefix": "£", + "currency_suffix": "", + "price": "1000", + "regular_price": "2000", + "sale_price": "1000", + "price_range": null, + "raw_prices": { + "precision": 6, + "price": "10000000", + "regular_price": "20000000", + "sale_price": "10000000" + } + }, + "totals": { + "currency_code": "GBP", + "currency_symbol": "£", + "currency_minor_unit": 2, + "currency_decimal_separator": ".", + "currency_thousand_separator": ",", + "currency_prefix": "£", + "currency_suffix": "", + "line_subtotal": "1000", + "line_subtotal_tax": "0", + "line_total": "800", + "line_total_tax": "0" + } + }, + { + "key": "e369853df766fa44e1ed0ff613f563bd", + "id": 34, + "quantity": 1, + "quantity_limit": 99, + "name": "WordPress Pennant", + "summary": "

This is an external product.

", + "short_description": "

This is an external product.

", + "description": "

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.

", + "sku": "wp-pennant", + "low_stock_remaining": null, + "backorders_allowed": false, + "show_backorder_badge": false, + "sold_individually": false, + "permalink": "https://local.wordpress.test/product/wordpress-pennant/", + "images": [ + { + "id": 57, + "src": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1.jpg", + "thumbnail": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-324x324.jpg", + "srcset": "https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1.jpg 800w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-324x324.jpg 324w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-100x100.jpg 100w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-416x416.jpg 416w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-300x300.jpg 300w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-150x150.jpg 150w, https://local.wordpress.test/wp-content/uploads/2020/03/pennant-1-768x768.jpg 768w", + "sizes": "(max-width: 800px) 100vw, 800px", + "name": "pennant-1.jpg", + "alt": "" + } + ], + "variation": [], + "prices": { + "currency_code": "GBP", + "currency_symbol": "£", + "currency_minor_unit": 2, + "currency_decimal_separator": ".", + "currency_thousand_separator": ",", + "currency_prefix": "£", + "currency_suffix": "", + "price": "1105", + "regular_price": "1105", + "sale_price": "1105", + "price_range": null, + "raw_prices": { + "precision": 6, + "price": "11050000", + "regular_price": "11050000", + "sale_price": "11050000" + } + }, + "totals": { + "currency_code": "GBP", + "currency_symbol": "£", + "currency_minor_unit": 2, + "currency_decimal_separator": ".", + "currency_thousand_separator": ",", + "currency_prefix": "£", + "currency_suffix": "", + "line_subtotal": "1105", + "line_subtotal_tax": "0", + "line_total": "884", + "line_total_tax": "0" + } + } + ], + "items_count": 2, + "items_weight": 0, + "needs_payment": true, + "needs_shipping": true, + "has_calculated_shipping": true, + "totals": { + "currency_code": "GBP", + "currency_symbol": "£", + "currency_minor_unit": 2, + "currency_decimal_separator": ".", + "currency_thousand_separator": ",", + "currency_prefix": "£", + "currency_suffix": "", + "total_items": "2105", + "total_items_tax": "0", + "total_fees": "0", + "total_fees_tax": "0", + "total_discount": "421", + "total_discount_tax": "0", + "total_shipping": "500", + "total_shipping_tax": "0", + "total_price": "2184", + "total_tax": "0", + "tax_lines": [] + }, + "errors": [], + "payment_requirements": [ "products" ], + "extensions": {} } ``` @@ -262,11 +261,11 @@ If a cart action cannot be performed, an error response will be returned. This w ```json { - "code": "woocommerce_rest_cart_invalid_product", - "message": "This product cannot be added to the cart.", - "data": { - "status": 400 - } + "code": "woocommerce_rest_cart_invalid_product", + "message": "This product cannot be added to the cart.", + "data": { + "status": 400 + } } ``` @@ -301,7 +300,7 @@ Returns the full cart object response (see [Cart Response](#cart-response)). ## Add Item -Add an item to the cart and return the full cart response, or an error. +Add an item to the cart and return the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](nonce-tokens.md) is provided. @@ -323,7 +322,7 @@ Returns the full [Cart Response](#cart-response) on success, or an [Error Respon ## Remove Item -Remove an item from the cart and return the full cart response, or an error. +Remove an item from the cart and return the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](nonce-tokens.md) is provided. @@ -343,7 +342,7 @@ Returns the full [Cart Response](#cart-response) on success, or an [Error Respon ## Update Item -Update an item in the cart and return the full cart response, or an error. +Update an item in the cart and return the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](nonce-tokens.md) is provided. @@ -364,7 +363,7 @@ Returns the full [Cart Response](#cart-response) on success, or an [Error Respon ## Apply Coupon -Apply a coupon to the cart and return the full cart response, or an error. +Apply a coupon to the cart and return the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](nonce-tokens.md) is provided. @@ -384,7 +383,7 @@ Returns the full [Cart Response](#cart-response) on success, or an [Error Respon ## Remove Coupon -Remove a coupon from the cart and return the full cart response, or an error. +Remove a coupon from the cart and return the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](nonce-tokens.md) is provided. @@ -404,7 +403,7 @@ Returns the full [Cart Response](#cart-response) on success, or an [Error Respon ## Update Customer -Update customer data and return the full cart response, or an error. +Update customer data and return the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](nonce-tokens.md) is provided. @@ -439,7 +438,7 @@ Returns the full [Cart Response](#cart-response) on success, or an [Error Respon ## Select Shipping Rate -Selects an available shipping rate for a package, then returns the full cart response, or an error. +Selects an available shipping rate for a package, then returns the full cart response, or an error. This endpoint will return an error unless a valid [Nonce Token](nonce-tokens.md) is provided. @@ -447,14 +446,13 @@ This endpoint will return an error unless a valid [Nonce Token](nonce-tokens.md) POST /cart/select-shipping-rate ``` -| Attribute | Type | Required | Description | -| :----------- | :------ | :------: | :---------------------------------------------- | -| `package_id` | integer|string | yes | The ID of the shipping package within the cart. | -| `rate_id` | string | yes | The chosen rate ID for the package. | +| Attribute | Type | Required | Description | +| :----------- | :------ | :------: | :---------------------------------- | +| `package_id` | integer | string | yes | The ID of the shipping package within the cart. | +| `rate_id` | string | yes | The chosen rate ID for the package. | ```http curl --header "X-WC-Store-API-Nonce: 12345" --request POST /cart/select-shipping-rate?package_id=1&rate_id=flat_rate:1 ``` Returns the full [Cart Response](#cart-response) on success, or an [Error Response](#error-response) on failure. - diff --git a/tests/e2e/specs/frontend/cart.test.js b/tests/e2e/specs/frontend/cart.test.js deleted file mode 100644 index b4cc0e5ab7a..00000000000 --- a/tests/e2e/specs/frontend/cart.test.js +++ /dev/null @@ -1,195 +0,0 @@ -/** - * External dependencies - */ -import { switchUserToAdmin } from '@wordpress/e2e-test-utils'; -import { shopper } from '@woocommerce/e2e-utils'; - -/** - * Internal dependencies - */ -import { - getBlockPagePermalink, - getNormalPagePermalink, - visitPostOfType, - scrollTo, -} from '../../../utils'; - -const block = { - name: 'Cart', - slug: 'woocommerce/cart', - class: '.wc-block-cart', -}; - -if ( process.env.WOOCOMMERCE_BLOCKS_PHASE < 2 ) - // eslint-disable-next-line jest/no-focused-tests - test.only( `skipping ${ block.name } tests`, () => {} ); - -describe( `${ block.name } Block (frontend)`, () => { - let cartBlockPermalink; - let productPermalink; - - beforeAll( async () => { - await page.evaluate( () => window.localStorage.clear() ); - await page.evaluate( () => { - localStorage.setItem( - 'wc-blocks_dismissed_compatibility_notices', - '["checkout","cart"]' - ); - } ); - await switchUserToAdmin(); - cartBlockPermalink = await getBlockPagePermalink( - `${ block.name } Block` - ); - await visitPostOfType( 'Woo Single #1', 'product' ); - productPermalink = await getNormalPagePermalink(); - await page.goto( productPermalink ); - await shopper.addToCart(); - } ); - afterAll( async () => { - // empty cart from shortcode page - await shopper.goToCart(); - await shopper.removeFromCart( 'Woo Single #1' ); - await page.evaluate( () => { - localStorage.removeItem( - 'wc-blocks_dismissed_compatibility_notices' - ); - } ); - } ); - - it( 'Adds a timestamp to localstorage when the cart is updated', async () => { - await jest.setTimeout( 60000 ); - await page.goto( cartBlockPermalink ); - await page.waitForFunction( () => { - const wcCartStore = wp.data.select( 'wc/store/cart' ); - return ( - ! wcCartStore.isResolving( 'getCartData' ) && - wcCartStore.hasFinishedResolution( 'getCartData', [] ) - ); - } ); - await page.click( - '.wc-block-cart__main .wc-block-components-quantity-selector__button--plus' - ); - await page.waitForFunction( () => { - const timeStamp = window.localStorage.getItem( - 'wc-blocks_cart_update_timestamp' - ); - return typeof timeStamp === 'string' && timeStamp; - } ); - const timestamp = await page.evaluate( () => { - return window.localStorage.getItem( - 'wc-blocks_cart_update_timestamp' - ); - } ); - expect( timestamp ).not.toBeNull(); - } ); - - it( 'Shows the freshest cart data when using browser navigation buttons', async () => { - await page.goto( cartBlockPermalink ); - - await page - .waitForFunction( () => { - const wcCartStore = wp.data.select( 'wc/store/cart' ); - return ( - ! wcCartStore.isResolving( 'getCartData' ) && - wcCartStore.hasFinishedResolution( 'getCartData', [] ) - ); - } ) - .catch( ( err ) => { - throw new Error( - 'Waiting for the wc/store/cart to not be resolving and to have finished resolution failed. This probably means the block did not load correctly.' - ); - } ); - await page.click( - '.wc-block-cart__main .wc-block-components-quantity-selector__button--plus' - ); - - await page.waitForResponse( - ( response ) => - ( response.url().includes( '/wc/store/cart/update-item' ) && - response.status() === 200 ) || - ( response.url().includes( '/wc/store/batch' ) && - response.status() === 207 ) - ); - - const selectedValue = parseInt( - await page.$eval( - '.wc-block-cart__main .wc-block-components-quantity-selector__input', - ( el ) => el.value - ) - ); - - // This is to ensure we've clicked the right cart button. - expect( selectedValue ).toBeGreaterThan( 1 ); - - await scrollTo( '.wc-block-cart__submit-button' ); - await Promise.all( [ - page.waitForNavigation(), - page.click( '.wc-block-cart__submit-button' ), - ] ); - - // go to checkout page - // note: shortcode checkout on CI / block on local env - await page.waitForSelector( 'h1', { text: 'Checkout' } ); - - // navigate back to cart block page - await page.goBack( { waitUntil: 'networkidle0' } ); - - await page - .waitForFunction( - () => { - const timeStamp = window.localStorage.getItem( - 'wc-blocks_cart_update_timestamp' - ); - return typeof timeStamp === 'string' && timeStamp; - }, - { timeout: 5000 } - ) - .catch( ( err ) => { - throw new Error( - 'Could not get the wc-blocks_cart_update_timestamp item from local storage. It must not have been set by the cartUpdateMiddleware.' - ); - } ); - - const timestamp = await page.evaluate( () => { - return window.localStorage.getItem( - 'wc-blocks_cart_update_timestamp' - ); - } ); - expect( timestamp ).not.toBeNull(); - - // We need this to check if the block is done loading. - await page - .waitForFunction( () => { - const wcCartStore = wp.data.select( 'wc/store/cart' ); - return ( - ! wcCartStore.isResolving( 'getCartData' ) && - wcCartStore.hasFinishedResolution( 'getCartData', [] ) - ); - } ) - .catch( ( err ) => { - throw new Error( - 'Waiting for the wc/store/cart to not be resolving and to have finished resolution failed. This probably means the block did not load correctly.' - ); - } ); - - // Then we check to ensure the stale cart action has been emitted, so it'll fetch the cart from the API. - await page - .waitForFunction( () => { - const wcCartStore = wp.data.select( 'wc/store/cart' ); - return wcCartStore.isCartDataStale() === true; - } ) - .catch( ( err ) => { - throw new Error( - 'isCartDataStale on the wc/store/cart store is not true. The cart contents were not correctly marked as stale.' - ); - } ); - - const value = parseInt( - await page.$eval( - '.wc-block-components-quantity-selector__input', - ( el ) => el.value - ) - ); - expect( value ).toBe( selectedValue ); - } ); -} );