From 04f3f4f2bdddae3babf0bab092f58488d109bedd Mon Sep 17 00:00:00 2001 From: Niels Lange Date: Tue, 15 Nov 2022 06:16:02 +0700 Subject: [PATCH 01/27] Convert product-elements/stock-indicator to TypeScript (#7567) * Convert product-elements/stock-indicator to TypeScript * bot: update checkstyle.xml * Add interface for blockAttributes * bot: update checkstyle.xml Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../stock-indicator/attributes.js | 8 --- .../stock-indicator/attributes.ts | 15 ++++++ .../stock-indicator/{block.js => block.tsx} | 54 ++++++++----------- .../{constants.js => constants.tsx} | 6 +-- .../stock-indicator/{edit.js => edit.tsx} | 23 ++++---- .../stock-indicator/{index.js => index.ts} | 5 +- .../{supports.js => supports.ts} | 0 .../product-elements/stock-indicator/types.ts | 3 ++ checkstyle.xml | 5 -- 9 files changed, 59 insertions(+), 60 deletions(-) delete mode 100644 assets/js/atomic/blocks/product-elements/stock-indicator/attributes.js create mode 100644 assets/js/atomic/blocks/product-elements/stock-indicator/attributes.ts rename assets/js/atomic/blocks/product-elements/stock-indicator/{block.js => block.tsx} (83%) rename assets/js/atomic/blocks/product-elements/stock-indicator/{constants.js => constants.tsx} (72%) rename assets/js/atomic/blocks/product-elements/stock-indicator/{edit.js => edit.tsx} (56%) rename assets/js/atomic/blocks/product-elements/stock-indicator/{index.js => index.ts} (86%) rename assets/js/atomic/blocks/product-elements/stock-indicator/{supports.js => supports.ts} (100%) create mode 100644 assets/js/atomic/blocks/product-elements/stock-indicator/types.ts diff --git a/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.js b/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.js deleted file mode 100644 index 5bac8820c40..00000000000 --- a/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.js +++ /dev/null @@ -1,8 +0,0 @@ -export const blockAttributes = { - productId: { - type: 'number', - default: 0, - }, -}; - -export default blockAttributes; diff --git a/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.ts b/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.ts new file mode 100644 index 00000000000..b2cae177672 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/stock-indicator/attributes.ts @@ -0,0 +1,15 @@ +interface BlockAttributes { + productId: { + type: string; + default: number; + }; +} + +export const blockAttributes: BlockAttributes = { + productId: { + type: 'number', + default: 0, + }, +}; + +export default blockAttributes; diff --git a/assets/js/atomic/blocks/product-elements/stock-indicator/block.js b/assets/js/atomic/blocks/product-elements/stock-indicator/block.tsx similarity index 83% rename from assets/js/atomic/blocks/product-elements/stock-indicator/block.js rename to assets/js/atomic/blocks/product-elements/stock-indicator/block.tsx index 90b487dd7c1..d30c2b7037d 100644 --- a/assets/js/atomic/blocks/product-elements/stock-indicator/block.js +++ b/assets/js/atomic/blocks/product-elements/stock-indicator/block.tsx @@ -2,7 +2,6 @@ * External dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import PropTypes from 'prop-types'; import classnames from 'classnames'; import { useInnerBlockLayoutContext, @@ -10,20 +9,35 @@ import { } from '@woocommerce/shared-context'; import { useColorProps, useTypographyProps } from '@woocommerce/base-hooks'; import { withProductDataContext } from '@woocommerce/shared-hocs'; +import type { HTMLAttributes } from 'react'; /** * Internal dependencies */ import './style.scss'; +import type { BlockAttributes } from './types'; -/** - * Product Stock Indicator Block Component. - * - * @param {Object} props Incoming props. - * @param {string} [props.className] CSS Class name for the component. - * @return {*} The component. - */ -const Block = ( props ) => { +const lowStockText = ( lowStock: string ): string => { + return sprintf( + /* translators: %d stock amount (number of items in stock for product) */ + __( '%d left in stock', 'woo-gutenberg-products-block' ), + lowStock + ); +}; + +const stockText = ( inStock: boolean, isBackordered: boolean ): string => { + if ( isBackordered ) { + return __( 'Available on backorder', 'woo-gutenberg-products-block' ); + } + + return inStock + ? __( 'In Stock', 'woo-gutenberg-products-block' ) + : __( 'Out of Stock', 'woo-gutenberg-products-block' ); +}; + +type Props = BlockAttributes & HTMLAttributes< HTMLDivElement >; + +export const Block = ( props: Props ): JSX.Element | null => { const { className } = props; const { parentClassName } = useInnerBlockLayoutContext(); const { product } = useProductDataContext(); @@ -66,26 +80,4 @@ const Block = ( props ) => { ); }; -const lowStockText = ( lowStock ) => { - return sprintf( - /* translators: %d stock amount (number of items in stock for product) */ - __( '%d left in stock', 'woo-gutenberg-products-block' ), - lowStock - ); -}; - -const stockText = ( inStock, isBackordered ) => { - if ( isBackordered ) { - return __( 'Available on backorder', 'woo-gutenberg-products-block' ); - } - - return inStock - ? __( 'In Stock', 'woo-gutenberg-products-block' ) - : __( 'Out of Stock', 'woo-gutenberg-products-block' ); -}; - -Block.propTypes = { - className: PropTypes.string, -}; - export default withProductDataContext( Block ); diff --git a/assets/js/atomic/blocks/product-elements/stock-indicator/constants.js b/assets/js/atomic/blocks/product-elements/stock-indicator/constants.tsx similarity index 72% rename from assets/js/atomic/blocks/product-elements/stock-indicator/constants.js rename to assets/js/atomic/blocks/product-elements/stock-indicator/constants.tsx index 32ee7520185..01d220daa3e 100644 --- a/assets/js/atomic/blocks/product-elements/stock-indicator/constants.js +++ b/assets/js/atomic/blocks/product-elements/stock-indicator/constants.tsx @@ -4,14 +4,14 @@ import { __ } from '@wordpress/i18n'; import { box, Icon } from '@wordpress/icons'; -export const BLOCK_TITLE = __( +export const BLOCK_TITLE: string = __( 'Product Stock Indicator', 'woo-gutenberg-products-block' ); -export const BLOCK_ICON = ( +export const BLOCK_ICON: JSX.Element = ( ); -export const BLOCK_DESCRIPTION = __( +export const BLOCK_DESCRIPTION: string = __( 'Display product stock status.', 'woo-gutenberg-products-block' ); diff --git a/assets/js/atomic/blocks/product-elements/stock-indicator/edit.js b/assets/js/atomic/blocks/product-elements/stock-indicator/edit.tsx similarity index 56% rename from assets/js/atomic/blocks/product-elements/stock-indicator/edit.js rename to assets/js/atomic/blocks/product-elements/stock-indicator/edit.tsx index a29336ad661..a27e2d88b2e 100644 --- a/assets/js/atomic/blocks/product-elements/stock-indicator/edit.js +++ b/assets/js/atomic/blocks/product-elements/stock-indicator/edit.tsx @@ -1,7 +1,6 @@ /** * External dependencies */ -import { __ } from '@wordpress/i18n'; import EditProductLink from '@woocommerce/editor-components/edit-product-link'; import { useBlockProps } from '@wordpress/block-editor'; @@ -10,9 +9,18 @@ import { useBlockProps } from '@wordpress/block-editor'; */ import Block from './block'; import withProductSelector from '../shared/with-product-selector'; -import { BLOCK_TITLE, BLOCK_ICON } from './constants'; +import { + BLOCK_TITLE as label, + BLOCK_ICON as icon, + BLOCK_DESCRIPTION as description, +} from './constants'; +import type { BlockAttributes } from './types'; -const Edit = ( { attributes } ) => { +interface Props { + attributes: BlockAttributes; +} + +const Edit = ( { attributes }: Props ): JSX.Element => { const blockProps = useBlockProps(); return (
@@ -22,11 +30,4 @@ const Edit = ( { attributes } ) => { ); }; -export default withProductSelector( { - icon: BLOCK_ICON, - label: BLOCK_TITLE, - description: __( - 'Choose a product to display its stock.', - 'woo-gutenberg-products-block' - ), -} )( Edit ); +export default withProductSelector( { icon, label, description } )( Edit ); diff --git a/assets/js/atomic/blocks/product-elements/stock-indicator/index.js b/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts similarity index 86% rename from assets/js/atomic/blocks/product-elements/stock-indicator/index.js rename to assets/js/atomic/blocks/product-elements/stock-indicator/index.ts index 439ebb8d131..213ea732545 100644 --- a/assets/js/atomic/blocks/product-elements/stock-indicator/index.js +++ b/assets/js/atomic/blocks/product-elements/stock-indicator/index.ts @@ -2,6 +2,7 @@ * External dependencies */ import { registerExperimentalBlockType } from '@woocommerce/block-settings'; +import type { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies @@ -18,7 +19,8 @@ import { BLOCK_DESCRIPTION as description, } from './constants'; -const blockConfig = { +const blockConfig: BlockConfiguration = { + ...sharedConfig, apiVersion: 2, title, description, @@ -30,6 +32,5 @@ const blockConfig = { }; registerExperimentalBlockType( 'woocommerce/product-stock-indicator', { - ...sharedConfig, ...blockConfig, } ); diff --git a/assets/js/atomic/blocks/product-elements/stock-indicator/supports.js b/assets/js/atomic/blocks/product-elements/stock-indicator/supports.ts similarity index 100% rename from assets/js/atomic/blocks/product-elements/stock-indicator/supports.js rename to assets/js/atomic/blocks/product-elements/stock-indicator/supports.ts diff --git a/assets/js/atomic/blocks/product-elements/stock-indicator/types.ts b/assets/js/atomic/blocks/product-elements/stock-indicator/types.ts new file mode 100644 index 00000000000..e64df6f3d4f --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/stock-indicator/types.ts @@ -0,0 +1,3 @@ +export interface BlockAttributes { + productId: number; +} diff --git a/checkstyle.xml b/checkstyle.xml index 4ffb65aab7f..46b3e0a8e7c 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -1172,11 +1172,6 @@ - - - - - From 74dd439d3d7efa8c1614ddf20c5ac0c3f67b5458 Mon Sep 17 00:00:00 2001 From: Alex Florisca Date: Tue, 15 Nov 2022 12:27:39 +0000 Subject: [PATCH 02/27] Refactor the payment status (#7666) * Add actions and selectors for new paymemt status * Remove set_complete from reducer * Replace all usages of getCurrentStatus with specific sepectors * Replace all `setCurrentStatus` with individual actions * Update docs * Removed currentStatus * bot: update checkstyle.xml * Update docs/third-party-developers/extensibility/data-store/payment.md Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com> * Address Thomas feedback * Add link to deprecated message Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Thomas Roberts <5656702+opr@users.noreply.github.com> --- .../use-payment-method-interface.ts | 16 +- .../cart-checkout/checkout-processor.js | 18 +- .../cart-checkout/payment-events/index.tsx | 51 +++--- .../express-payment-methods.js | 19 ++- assets/js/data/payment/action-types.ts | 7 +- assets/js/data/payment/actions.ts | 36 ++-- assets/js/data/payment/constants.ts | 1 - assets/js/data/payment/default-state.ts | 21 +-- assets/js/data/payment/reducers.ts | 61 ++++--- assets/js/data/payment/selectors.ts | 51 +++++- .../payment/set-default-payment-method.ts | 4 +- .../test/set-default-payment-method.ts | 7 +- assets/js/data/payment/thunks.ts | 16 +- assets/js/data/payment/types.ts | 17 -- .../type-defs/payment-method-interface.ts | 12 +- checkstyle.xml | 51 +++--- .../checkout/checkout-flow-and-events.md | 28 ++-- .../payment-method-integration.md | 43 +++-- .../extensibility/data-store/payment.md | 154 +++++++++++++++--- 19 files changed, 392 insertions(+), 221 deletions(-) diff --git a/assets/js/base/context/hooks/payment-methods/use-payment-method-interface.ts b/assets/js/base/context/hooks/payment-methods/use-payment-method-interface.ts index 3a546dd5132..21a2ebf7536 100644 --- a/assets/js/base/context/hooks/payment-methods/use-payment-method-interface.ts +++ b/assets/js/base/context/hooks/payment-methods/use-payment-method-interface.ts @@ -50,12 +50,22 @@ export const usePaymentMethodInterface = (): PaymentMethodInterface => { isCalculating: store.isCalculating(), }; } ); - const { currentStatus, activePaymentMethod, shouldSavePayment } = useSelect( + const { paymentStatus, activePaymentMethod, shouldSavePayment } = useSelect( ( select ) => { const store = select( PAYMENT_STORE_KEY ); return { - currentStatus: store.getCurrentStatus(), + // The paymentStatus is exposed to third parties via the payment method interface so the API must not be changed + paymentStatus: { + isPristine: store.isPaymentPristine(), + isStarted: store.isPaymentStarted(), + isProcessing: store.isPaymentProcessing(), + isFinished: store.isPaymentFinished(), + hasError: store.hasPaymentError(), + hasFailed: store.isPaymentFailed(), + isSuccessful: store.isPaymentSuccess(), + isDoingExpressPayment: store.isExpressPaymentMethodActive(), + }, activePaymentMethod: store.getActivePaymentMethod(), shouldSavePayment: store.getShouldSavePaymentMethod(), }; @@ -168,7 +178,7 @@ export const usePaymentMethodInterface = (): PaymentMethodInterface => { onShippingRateSuccess, }, onSubmit, - paymentStatus: currentStatus, + paymentStatus, setExpressPaymentError: deprecatedSetExpressPaymentError, shippingData: { isSelectingRate, diff --git a/assets/js/base/context/providers/cart-checkout/checkout-processor.js b/assets/js/base/context/providers/cart-checkout/checkout-processor.js index 20c54003b45..19abb8dee56 100644 --- a/assets/js/base/context/providers/cart-checkout/checkout-processor.js +++ b/assets/js/base/context/providers/cart-checkout/checkout-processor.js @@ -76,7 +76,8 @@ const CheckoutProcessor = () => { activePaymentMethod, paymentMethodData, isExpressPaymentMethodActive, - currentPaymentStatus, + hasPaymentError, + isPaymentSuccess, shouldSavePayment, } = useSelect( ( select ) => { const store = select( PAYMENT_STORE_KEY ); @@ -85,7 +86,8 @@ const CheckoutProcessor = () => { activePaymentMethod: store.getActivePaymentMethod(), paymentMethodData: store.getPaymentMethodData(), isExpressPaymentMethodActive: store.isExpressPaymentMethodActive(), - currentPaymentStatus: store.getCurrentStatus(), + hasPaymentError: store.hasPaymentError(), + isPaymentSuccess: store.isPaymentSuccess(), shouldSavePayment: store.getShouldSavePaymentMethod(), }; }, [] ); @@ -107,13 +109,13 @@ const CheckoutProcessor = () => { const checkoutWillHaveError = ( hasValidationErrors() && ! isExpressPaymentMethodActive ) || - currentPaymentStatus.hasError || + hasPaymentError || shippingErrorStatus.hasError; const paidAndWithoutErrors = ! checkoutHasError && ! checkoutWillHaveError && - ( currentPaymentStatus.isSuccessful || ! cartNeedsPayment ) && + ( isPaymentSuccess || ! cartNeedsPayment ) && checkoutIsProcessing; // Determine if checkout has an error. @@ -145,7 +147,7 @@ const CheckoutProcessor = () => { if ( hasValidationErrors() ) { return false; } - if ( currentPaymentStatus.hasError ) { + if ( hasPaymentError ) { return { errorMessage: __( 'There was a problem with your payment option.', @@ -163,11 +165,7 @@ const CheckoutProcessor = () => { } return true; - }, [ - hasValidationErrors, - currentPaymentStatus.hasError, - shippingErrorStatus.hasError, - ] ); + }, [ hasValidationErrors, hasPaymentError, shippingErrorStatus.hasError ] ); // Validate the checkout using the CHECKOUT_VALIDATION_BEFORE_PROCESSING event useEffect( () => { diff --git a/assets/js/base/context/providers/cart-checkout/payment-events/index.tsx b/assets/js/base/context/providers/cart-checkout/payment-events/index.tsx index ecc54f75662..085514f2855 100644 --- a/assets/js/base/context/providers/cart-checkout/payment-events/index.tsx +++ b/assets/js/base/context/providers/cart-checkout/payment-events/index.tsx @@ -62,13 +62,16 @@ export const PaymentEventsProvider = ( { isCalculating: store.isCalculating(), }; } ); - const { currentStatus } = useSelect( ( select ) => { - const store = select( PAYMENT_STORE_KEY ); + const { isPaymentSuccess, isPaymentFinished, isPaymentProcessing } = + useSelect( ( select ) => { + const store = select( PAYMENT_STORE_KEY ); - return { - currentStatus: store.getCurrentStatus(), - }; - } ); + return { + isPaymentSuccess: store.isPaymentSuccess(), + isPaymentFinished: store.isPaymentFinished(), + isPaymentProcessing: store.isPaymentProcessing(), + }; + } ); const { createErrorNotice, removeNotice } = useDispatch( 'core/notices' ); const { setValidationErrors } = useDispatch( VALIDATION_STORE_KEY ); @@ -82,7 +85,8 @@ export const PaymentEventsProvider = ( { }, [ observers ] ); const { - __internalSetPaymentStatus, + __internalSetPaymentProcessing, + __internalSetPaymentPristine, __internalSetPaymentMethodData, __internalEmitPaymentProcessingEvent, } = useDispatch( PAYMENT_STORE_KEY ); @@ -94,39 +98,31 @@ export const PaymentEventsProvider = ( { checkoutIsProcessing && ! checkoutHasError && ! checkoutIsCalculating && - ! currentStatus.isFinished + ! isPaymentFinished ) { - __internalSetPaymentStatus( { isProcessing: true } ); + __internalSetPaymentProcessing(); } }, [ checkoutIsProcessing, checkoutHasError, checkoutIsCalculating, - currentStatus.isFinished, - __internalSetPaymentStatus, + isPaymentFinished, + __internalSetPaymentProcessing, ] ); // When checkout is returned to idle, set payment status to pristine but only if payment status is already not finished. useEffect( () => { - if ( checkoutIsIdle && ! currentStatus.isSuccessful ) { - __internalSetPaymentStatus( { isPristine: true } ); + if ( checkoutIsIdle && ! isPaymentSuccess ) { + __internalSetPaymentPristine(); } - }, [ - checkoutIsIdle, - currentStatus.isSuccessful, - __internalSetPaymentStatus, - ] ); + }, [ checkoutIsIdle, isPaymentSuccess, __internalSetPaymentPristine ] ); // if checkout has an error sync payment status back to pristine. useEffect( () => { - if ( checkoutHasError && currentStatus.isSuccessful ) { - __internalSetPaymentStatus( { isPristine: true } ); + if ( checkoutHasError && isPaymentSuccess ) { + __internalSetPaymentPristine(); } - }, [ - checkoutHasError, - currentStatus.isSuccessful, - __internalSetPaymentStatus, - ] ); + }, [ checkoutHasError, isPaymentSuccess, __internalSetPaymentPristine ] ); // Emit the payment processing event useEffect( () => { @@ -134,16 +130,15 @@ export const PaymentEventsProvider = ( { // observer that returns a response that !== true. However, this still // allows for other observers that return true for continuing through // to the next observer (or bailing if there's a problem). - if ( currentStatus.isProcessing ) { + if ( isPaymentProcessing ) { __internalEmitPaymentProcessingEvent( currentObservers.current, setValidationErrors ); } }, [ - currentStatus.isProcessing, + isPaymentProcessing, setValidationErrors, - __internalSetPaymentStatus, removeNotice, createErrorNotice, setBillingAddress, diff --git a/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.js b/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.js index bf885fa9f94..c8250967220 100644 --- a/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.js +++ b/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment-methods.js @@ -36,7 +36,10 @@ const ExpressPaymentMethods = () => { ); const { __internalSetActivePaymentMethod, - __internalSetPaymentStatus, + __internalSetPaymentStarted, + __internalSetPaymentPristine, + __internalSetPaymentError, + __internalSetPaymentMethodData, __internalSetExpressPaymentError, } = useDispatch( PAYMENT_STORE_KEY ); const { paymentMethods } = useExpressPaymentMethods(); @@ -55,14 +58,14 @@ const ExpressPaymentMethods = () => { ( paymentMethodId ) => () => { previousActivePaymentMethod.current = activePaymentMethod; previousPaymentMethodData.current = paymentMethodData; - __internalSetPaymentStatus( { isStarted: true } ); + __internalSetPaymentStarted(); __internalSetActivePaymentMethod( paymentMethodId ); }, [ activePaymentMethod, paymentMethodData, __internalSetActivePaymentMethod, - __internalSetPaymentStatus, + __internalSetPaymentStarted, ] ); @@ -72,12 +75,12 @@ const ExpressPaymentMethods = () => { * This restores the active method and returns the state to pristine. */ const onExpressPaymentClose = useCallback( () => { - __internalSetPaymentStatus( { isPristine: true } ); + __internalSetPaymentPristine(); __internalSetActivePaymentMethod( previousActivePaymentMethod.current, previousPaymentMethodData.current ); - }, [ __internalSetActivePaymentMethod, __internalSetPaymentStatus ] ); + }, [ __internalSetActivePaymentMethod, __internalSetPaymentPristine ] ); /** * onExpressPaymentError should be triggered when the express payment process errors. @@ -86,7 +89,8 @@ const ExpressPaymentMethods = () => { */ const onExpressPaymentError = useCallback( ( errorMessage ) => { - __internalSetPaymentStatus( { hasError: true }, errorMessage ); + __internalSetPaymentError(); + __internalSetPaymentMethodData( errorMessage ); __internalSetExpressPaymentError( errorMessage ); __internalSetActivePaymentMethod( previousActivePaymentMethod.current, @@ -95,7 +99,8 @@ const ExpressPaymentMethods = () => { }, [ __internalSetActivePaymentMethod, - __internalSetPaymentStatus, + __internalSetPaymentError, + __internalSetPaymentMethodData, __internalSetExpressPaymentError, ] ); diff --git a/assets/js/data/payment/action-types.ts b/assets/js/data/payment/action-types.ts index 8d490b53e46..74f8043187e 100644 --- a/assets/js/data/payment/action-types.ts +++ b/assets/js/data/payment/action-types.ts @@ -1,4 +1,10 @@ export enum ACTION_TYPES { + SET_PAYMENT_PRISTINE = 'SET_PAYMENT_PRISTINE', + SET_PAYMENT_STARTED = 'SET_PAYMENT_STARTED', + SET_PAYMENT_PROCESSING = 'SET_PAYMENT_PROCESSING', + SET_PAYMENT_FAILED = 'SET_PAYMENT_FAILED', + SET_PAYMENT_ERROR = 'SET_PAYMENT_ERROR', + SET_PAYMENT_SUCCESS = 'SET_PAYMENT_SUCCESS', SET_PAYMENT_METHODS_INITIALIZED = 'SET_PAYMENT_METHODS_INITIALIZED', SET_EXPRESS_PAYMENT_METHODS_INITIALIZED = 'SET_EXPRESS_PAYMENT_METHODS_INITIALIZED', SET_ACTIVE_PAYMENT_METHOD = 'SET_ACTIVE_PAYMENT_METHOD', @@ -7,7 +13,6 @@ export enum ACTION_TYPES { SET_AVAILABLE_EXPRESS_PAYMENT_METHODS = 'SET_AVAILABLE_EXPRESS_PAYMENT_METHODS', REMOVE_AVAILABLE_PAYMENT_METHOD = 'REMOVE_AVAILABLE_PAYMENT_METHOD', REMOVE_AVAILABLE_EXPRESS_PAYMENT_METHOD = 'REMOVE_AVAILABLE_EXPRESS_PAYMENT_METHOD', - SET_PAYMENT_STATUS = 'SET_PAYMENT_STATUS', INITIALIZE_PAYMENT_METHODS = 'INITIALIZE_PAYMENT_METHODS', SET_PAYMENT_METHOD_DATA = 'SET_PAYMENT_METHOD_DATA', } diff --git a/assets/js/data/payment/actions.ts b/assets/js/data/payment/actions.ts index bcfbb27a717..2dc481bcc12 100644 --- a/assets/js/data/payment/actions.ts +++ b/assets/js/data/payment/actions.ts @@ -12,24 +12,32 @@ import { import { ACTION_TYPES } from './action-types'; import { checkPaymentMethodsCanPay } from './check-payment-methods'; import { setDefaultPaymentMethod } from './set-default-payment-method'; -import { PaymentStatus } from './types'; // `Thunks are functions that can be dispatched, similar to actions creators export * from './thunks'; -/** - * Set the status of the payment - * - * @param status An object that holds properties representing different status values - * @param paymentMethodData A config object for the payment method being used - */ -export const __internalSetPaymentStatus = ( - status: PaymentStatus, - paymentMethodData?: Record< string, unknown > -) => ( { - type: ACTION_TYPES.SET_PAYMENT_STATUS, - status, - paymentMethodData, +export const __internalSetPaymentPristine = () => ( { + type: ACTION_TYPES.SET_PAYMENT_PRISTINE, +} ); + +export const __internalSetPaymentStarted = () => ( { + type: ACTION_TYPES.SET_PAYMENT_STARTED, +} ); + +export const __internalSetPaymentProcessing = () => ( { + type: ACTION_TYPES.SET_PAYMENT_PROCESSING, +} ); + +export const __internalSetPaymentFailed = () => ( { + type: ACTION_TYPES.SET_PAYMENT_FAILED, +} ); + +export const __internalSetPaymentError = () => ( { + type: ACTION_TYPES.SET_PAYMENT_ERROR, +} ); + +export const __internalSetPaymentSuccess = () => ( { + type: ACTION_TYPES.SET_PAYMENT_SUCCESS, } ); /** diff --git a/assets/js/data/payment/constants.ts b/assets/js/data/payment/constants.ts index 1f27e87653d..f77e56d5aee 100644 --- a/assets/js/data/payment/constants.ts +++ b/assets/js/data/payment/constants.ts @@ -7,5 +7,4 @@ export enum STATUS { ERROR = 'has_error', FAILED = 'failed', SUCCESS = 'success', - COMPLETE = 'complete', } diff --git a/assets/js/data/payment/default-state.ts b/assets/js/data/payment/default-state.ts index a9c911b0ffd..0ce03d4afc5 100644 --- a/assets/js/data/payment/default-state.ts +++ b/assets/js/data/payment/default-state.ts @@ -12,17 +12,10 @@ import { * Internal dependencies */ import { SavedPaymentMethod } from './types'; +import { STATUS as PAYMENT_STATUS } from './constants'; export interface PaymentMethodDataState { - currentStatus: { - isPristine: boolean; - isStarted: boolean; - isProcessing: boolean; - isFinished: boolean; - hasError: boolean; - hasFailed: boolean; - isSuccessful: boolean; - }; + status: string; activePaymentMethod: string; activeSavedToken: string; // Avilable payment methods are payment methods which have been validated and can make payment @@ -37,15 +30,7 @@ export interface PaymentMethodDataState { shouldSavePaymentMethod: boolean; } export const defaultPaymentMethodDataState: PaymentMethodDataState = { - currentStatus: { - isPristine: true, - isStarted: false, - isProcessing: false, - isFinished: false, - hasError: false, - hasFailed: false, - isSuccessful: false, - }, + status: PAYMENT_STATUS.PRISTINE, activePaymentMethod: '', activeSavedToken: '', availablePaymentMethods: {}, diff --git a/assets/js/data/payment/reducers.ts b/assets/js/data/payment/reducers.ts index ebcf26e0789..758c39ffa02 100644 --- a/assets/js/data/payment/reducers.ts +++ b/assets/js/data/payment/reducers.ts @@ -12,6 +12,7 @@ import { PaymentMethodDataState, } from './default-state'; import { ACTION_TYPES } from './action-types'; +import { STATUS } from './constants'; const reducer: Reducer< PaymentMethodDataState > = ( state = defaultPaymentMethodDataState, @@ -19,37 +20,59 @@ const reducer: Reducer< PaymentMethodDataState > = ( ) => { let newState = state; switch ( action.type ) { - case ACTION_TYPES.SET_SHOULD_SAVE_PAYMENT_METHOD: + case ACTION_TYPES.SET_PAYMENT_PRISTINE: newState = { ...state, - shouldSavePaymentMethod: action.shouldSavePaymentMethod, + status: STATUS.PRISTINE, }; break; - case ACTION_TYPES.SET_PAYMENT_METHOD_DATA: + case ACTION_TYPES.SET_PAYMENT_STARTED: newState = { ...state, - paymentMethodData: action.paymentMethodData, + status: STATUS.STARTED, }; break; - case ACTION_TYPES.SET_PAYMENT_STATUS: + case ACTION_TYPES.SET_PAYMENT_PROCESSING: newState = { ...state, - currentStatus: { - // When the status is changed to pristine, we need to reset the currentStatus properties - // to their default initial values - ...( action.status?.isPristine === true - ? defaultPaymentMethodDataState.currentStatus - : state.currentStatus ), - ...action.status, - isFinished: - action.status.hasError || - action.status.hasFailed || - action.status.isSuccessful, - }, - paymentMethodData: - action.paymentMethodData || state.paymentMethodData, + status: STATUS.PROCESSING, + }; + break; + + case ACTION_TYPES.SET_PAYMENT_FAILED: + newState = { + ...state, + status: STATUS.FAILED, + }; + break; + + case ACTION_TYPES.SET_PAYMENT_ERROR: + newState = { + ...state, + status: STATUS.ERROR, + }; + break; + + case ACTION_TYPES.SET_PAYMENT_SUCCESS: + newState = { + ...state, + status: STATUS.SUCCESS, + }; + break; + + case ACTION_TYPES.SET_SHOULD_SAVE_PAYMENT_METHOD: + newState = { + ...state, + shouldSavePaymentMethod: action.shouldSavePaymentMethod, + }; + break; + + case ACTION_TYPES.SET_PAYMENT_METHOD_DATA: + newState = { + ...state, + paymentMethodData: action.paymentMethodData, }; break; diff --git a/assets/js/data/payment/selectors.ts b/assets/js/data/payment/selectors.ts index 2f36a8b714d..9e82a506392 100644 --- a/assets/js/data/payment/selectors.ts +++ b/assets/js/data/payment/selectors.ts @@ -2,12 +2,40 @@ * External dependencies */ import { objectHasProp } from '@woocommerce/types'; +import deprecated from '@wordpress/deprecated'; /** * Internal dependencies */ import { PaymentMethodDataState } from './default-state'; import { filterActiveSavedPaymentMethods } from './utils'; +import { STATUS as PAYMENT_STATUS } from './constants'; + +export const isPaymentPristine = ( state: PaymentMethodDataState ) => + state.status === PAYMENT_STATUS.PRISTINE; + +export const isPaymentStarted = ( state: PaymentMethodDataState ) => + state.status === PAYMENT_STATUS.STARTED; + +export const isPaymentProcessing = ( state: PaymentMethodDataState ) => + state.status === PAYMENT_STATUS.PROCESSING; + +export const isPaymentSuccess = ( state: PaymentMethodDataState ) => + state.status === PAYMENT_STATUS.SUCCESS; + +export const hasPaymentError = ( state: PaymentMethodDataState ) => + state.status === PAYMENT_STATUS.ERROR; + +export const isPaymentFailed = ( state: PaymentMethodDataState ) => + state.status === PAYMENT_STATUS.FAILED; + +export const isPaymentFinished = ( state: PaymentMethodDataState ) => { + return ( + state.status === PAYMENT_STATUS.SUCCESS || + state.status === PAYMENT_STATUS.ERROR || + state.status === PAYMENT_STATUS.FAILED + ); +}; export const isExpressPaymentMethodActive = ( state: PaymentMethodDataState @@ -73,8 +101,29 @@ export const expressPaymentMethodsInitialized = ( return state.expressPaymentMethodsInitialized; }; +/** + * @deprecated - use these selectors instead: isPaymentPristine, isPaymentStarted, isPaymentProcessing, + * isPaymentFinished, hasPaymentError, isPaymentSuccess, isPaymentFailed + */ export const getCurrentStatus = ( state: PaymentMethodDataState ) => { - return state.currentStatus; + deprecated( 'getCurrentStatus', { + since: '8.9.0', + alternative: + 'isPaymentPristine, isPaymentStarted, isPaymentProcessing, isPaymentFinished, hasPaymentError, isPaymentSuccess, isPaymentFailed', + plugin: 'WooCommerce Blocks', + link: 'https://github.com/woocommerce/woocommerce-blocks/pull/7666', + } ); + + return { + isPristine: isPaymentPristine( state ), + isStarted: isPaymentStarted( state ), + isProcessing: isPaymentProcessing( state ), + isFinished: isPaymentFinished( state ), + hasError: hasPaymentError( state ), + hasFailed: isPaymentFailed( state ), + isSuccessful: isPaymentSuccess( state ), + isDoingExpressPayment: isExpressPaymentMethodActive( state ), + }; }; export const getShouldSavePaymentMethod = ( state: PaymentMethodDataState ) => { diff --git a/assets/js/data/payment/set-default-payment-method.ts b/assets/js/data/payment/set-default-payment-method.ts index 5406d2617eb..ff1474dbae0 100644 --- a/assets/js/data/payment/set-default-payment-method.ts +++ b/assets/js/data/payment/set-default-payment-method.ts @@ -60,9 +60,7 @@ export const setDefaultPaymentMethod = async ( return; } - dispatch( PAYMENT_STORE_KEY ).__internalSetPaymentStatus( { - isPristine: true, - } ); + dispatch( PAYMENT_STORE_KEY ).__internalSetPaymentPristine(); dispatch( PAYMENT_STORE_KEY ).__internalSetActivePaymentMethod( paymentMethodKeys[ 0 ] diff --git a/assets/js/data/payment/test/set-default-payment-method.ts b/assets/js/data/payment/test/set-default-payment-method.ts index 3c04df0f01b..afd8a047676 100644 --- a/assets/js/data/payment/test/set-default-payment-method.ts +++ b/assets/js/data/payment/test/set-default-payment-method.ts @@ -125,7 +125,12 @@ describe( 'setDefaultPaymentMethod', () => { ...originalStore, __internalSetActivePaymentMethod: setActivePaymentMethodMock, - __internalSetPaymentStatus: () => void 0, + __internalSetPaymentError: () => void 0, + __internalSetPaymentFailed: () => void 0, + __internalSetPaymentSuccess: () => void 0, + __internalSetPaymentPristine: () => void 0, + __internalSetPaymentStarted: () => void 0, + __internalSetPaymentProcessing: () => void 0, }; } return originalStore; diff --git a/assets/js/data/payment/thunks.ts b/assets/js/data/payment/thunks.ts index e54e2b7e9d1..65516525b4d 100644 --- a/assets/js/data/payment/thunks.ts +++ b/assets/js/data/payment/thunks.ts @@ -85,9 +85,7 @@ export const __internalEmitPaymentProcessingEvent: emitProcessingEventType = ( ); } dispatch.__internalSetPaymentMethodData( paymentMethodData ); - dispatch.__internalSetPaymentStatus( { - isSuccessful: true, - } ); + dispatch.__internalSetPaymentSuccess(); } else if ( errorResponse && isFailResponse( errorResponse ) ) { if ( errorResponse.message && errorResponse.message.length ) { createErrorNotice( errorResponse.message, { @@ -105,10 +103,8 @@ export const __internalEmitPaymentProcessingEvent: emitProcessingEventType = ( if ( billingAddress ) { setBillingAddress( billingAddress ); } - dispatch.__internalSetPaymentStatus( - { hasFailed: true }, - paymentMethodData - ); + dispatch.__internalSetPaymentFailed(); + dispatch.__internalSetPaymentMethodData( paymentMethodData ); } else if ( errorResponse ) { if ( errorResponse.message && errorResponse.message.length ) { createErrorNotice( errorResponse.message, { @@ -120,14 +116,12 @@ export const __internalEmitPaymentProcessingEvent: emitProcessingEventType = ( } ); } - dispatch.__internalSetPaymentStatus( { hasError: true } ); + dispatch.__internalSetPaymentError(); setValidationErrors( errorResponse?.validationErrors ); } else { // otherwise there are no payment methods doing anything so // just consider success - dispatch.__internalSetPaymentStatus( { - isSuccessful: true, - } ); + dispatch.__internalSetPaymentSuccess(); } } ); }; diff --git a/assets/js/data/payment/types.ts b/assets/js/data/payment/types.ts index 6bc852b7fe4..3d6e3581501 100644 --- a/assets/js/data/payment/types.ts +++ b/assets/js/data/payment/types.ts @@ -65,23 +65,6 @@ export interface PaymentStatusDispatchers { ) => void; } -export type PaymentMethodCurrentStatusType = { - // If true then the payment method state in checkout is pristine. - isPristine: boolean; - // If true then the payment method has been initialized and has started. - isStarted: boolean; - // If true then the payment method is processing payment. - isProcessing: boolean; - // If true then the payment method is in a finished state (which may mean it's status is either error, failed, or success). - isFinished: boolean; - // If true then the payment method is in an error state. - hasError: boolean; - // If true then the payment method has failed (usually indicates a problem with the payment method used, not logic error). - hasFailed: boolean; - // If true then the payment method has completed it's processing successfully. - isSuccessful: boolean; -}; - export type PaymentMethodsDispatcherType = ( paymentMethods: PlainPaymentMethods ) => undefined | void; diff --git a/assets/js/types/type-defs/payment-method-interface.ts b/assets/js/types/type-defs/payment-method-interface.ts index 05def974573..84dd7ef4c49 100644 --- a/assets/js/types/type-defs/payment-method-interface.ts +++ b/assets/js/types/type-defs/payment-method-interface.ts @@ -17,7 +17,6 @@ import type { responseTypes, noticeContexts, } from '../../base/context/event-emit'; -import type { PaymentMethodCurrentStatusType } from '../../base/context/providers/cart-checkout/payment-events/types'; import type { CartResponseShippingAddress, CartResponseCouponItem, @@ -165,7 +164,16 @@ export type PaymentMethodInterface = { // Used to trigger checkout processing. onSubmit: () => void; // Various payment status helpers. - paymentStatus: PaymentMethodCurrentStatusType; + paymentStatus: { + isPristine: boolean; + isStarted: boolean; + isProcessing: boolean; + isFinished: boolean; + hasError: boolean; + hasFailed: boolean; + isSuccessful: boolean; + isDoingExpressPayment: boolean; + }; // Deprecated. For setting an error (error message string) for express payment methods. Does not change payment status. setExpressPaymentError: ( errorMessage?: string ) => void; // Various data related to shipping. diff --git a/checkstyle.xml b/checkstyle.xml index 46b3e0a8e7c..3fab73e7b78 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -680,24 +680,24 @@ - - - - - - - - - - + + + + + + + + + + - - - - - - + + + + + + - - - - - - + + - - + - - + - diff --git a/docs/internal-developers/block-client-apis/checkout/checkout-flow-and-events.md b/docs/internal-developers/block-client-apis/checkout/checkout-flow-and-events.md index 0473527c0bd..ad0556e6176 100644 --- a/docs/internal-developers/block-client-apis/checkout/checkout-flow-and-events.md +++ b/docs/internal-developers/block-client-apis/checkout/checkout-flow-and-events.md @@ -102,17 +102,23 @@ The status is exposed on the `currentErrorStatus` object provided by the `useShi ### Payment Method Data Store Status -The status of the payment lives in the payment data store. It can be retrieved with the `getCurrentStatus` selector, liek so: +The status of the payment lives in the payment data store. You can query the status with the following selectors: ```jsx -import { useSelect } from '@wordpress/data'; +import { select } from '@wordpress/data'; import { PAYMENT_STORE_KEY } from '@woocommerce/blocks-data'; const MyComponent = ( props ) => { - const currentStatus = useSelect( ( select ) => - select( PAYMENT_STORE_KEY ).getCurrentStatus() - ); - // do something with status + const isPaymentPristine = select( PAYMENT_STORE_KEY ).isPaymentPristine(); + const isPaymentStarted = select( PAYMENT_STORE_KEY ).isPaymentStarted(); + const isPaymentProcessing = + select( PAYMENT_STORE_KEY ).isPaymentProcessing(); + const isPaymentSuccess = select( PAYMENT_STORE_KEY ).isPaymentSuccess(); + const isPaymentFailed = select( PAYMENT_STORE_KEY ).isPaymentFailed(); + const hasPaymentError = select( PAYMENT_STORE_KEY ).hasPaymentError(); + const hasPaymentFinished = select( PAYMENT_STORE_KEY ).hasPaymentFinished(); + + // do something with the boolean values }; ``` @@ -127,16 +133,6 @@ The possible _internal_ statuses that may be set are: - `FAILED`: This status is set after an observer hooked into the payment processing event returns a fail response. This in turn will end up causing the checkout `hasError` flag to be set to true. - `ERROR`: This status is set after an observer hooked into the payment processing event returns an error response. This in turn will end up causing the checkout `hasError` flag to be set to true. -The `currentStatus` object has the following properties: - -- `isPristine`: This is true when the current payment status is `PRISTINE`. -- `isStarted`: This is true when the current payment status is `STARTED`. -- `isProcessing`: This is true when the current payment status is `PROCESSING`. -- `isFinished`: This is true when the current payment status is one of `ERROR`, `FAILED`, or`SUCCESS`. -- `hasError`: This is true when the current payment status is `ERROR`. -- `hasFailed`: This is true when the current payment status is `FAILED`. -- `isSuccessful`: This is true when the current payment status is `SUCCESS` - ### Emitting Events Another tricky thing for extensibility, is providing opinionated, yet flexible interfaces for extensions to act and react to specific events in the flow. For stability, it's important that the core checkout flow _controls_ all communication to and from the server specific to checkout/order processing and leave extension specific requirements for the extension to handle. This allows for extensions to predictably interact with the checkout data and flow as needed without impacting other extensions hooking into it. diff --git a/docs/third-party-developers/extensibility/checkout-payment-methods/payment-method-integration.md b/docs/third-party-developers/extensibility/checkout-payment-methods/payment-method-integration.md index 572da21e3bf..2dc5a056633 100644 --- a/docs/third-party-developers/extensibility/checkout-payment-methods/payment-method-integration.md +++ b/docs/third-party-developers/extensibility/checkout-payment-methods/payment-method-integration.md @@ -5,15 +5,15 @@ The checkout block has an API interface for payment methods to integrate that co ## Table of Contents - [Client Side integration](#client-side-integration) - - [Express payment methods - `registerExpressPaymentMethod( options )`](#express-payment-methods---registerexpresspaymentmethod-options-) - - [`name` (required)](#name-required) - - [`content` (required)](#content-required) - - [`edit` (required)](#edit-required) - - [`canMakePayment` (required):](#canmakepayment-required) - - [`paymentMethodId`](#paymentmethodid) - - [`supports:features`](#supportsfeatures) - - [Payment Methods - `registerPaymentMethod( options )`](#payment-methods---registerpaymentmethod-options-) - - [Props Fed to Payment Method Nodes](#props-fed-to-payment-method-nodes) + - [Express payment methods - `registerExpressPaymentMethod( options )`](#express-payment-methods---registerexpresspaymentmethod-options-) + - [`name` (required)](#name-required) + - [`content` (required)](#content-required) + - [`edit` (required)](#edit-required) + - [`canMakePayment` (required):](#canmakepayment-required) + - [`paymentMethodId`](#paymentmethodid) + - [`supports:features`](#supportsfeatures) + - [Payment Methods - `registerPaymentMethod( options )`](#payment-methods---registerpaymentmethod-options-) + - [Props Fed to Payment Method Nodes](#props-fed-to-payment-method-nodes) - [Server Side Integration](#server-side-integration) - [Processing Payment](#processing-payment) - [Registering Assets](#registering-assets) @@ -151,23 +151,31 @@ The options you feed the configuration instance are the same as those for expres A big part of the payment method integration is the interface that is exposed for payment methods to use via props when the node provided is cloned and rendered on block mount. While all the props are listed below, you can find more details about what the props reference, their types etc via the [typedefs described in this file](../../../../assets/js/types/type-defs/payment-method-interface.ts). -| Property | Type | Description | Values | -| ------ | ------ | ------ | ------ | -| `activePaymentMethod` | String | The slug of the current active payment method in the checkout. | -| `billing` | Object | Contains everything related to billing. | `billingData`, `cartTotal`, `currency`, `cartTotalItems`, `displayPricesIncludingTax`, `appliedCoupons`, `customerId` | -| `cartData` | Object | Data exposed from the cart including items, fees, and any registered extension data. Note that this data should be treated as immutable (should not be modified/mutated) or it will result in errors in your application. | `cartItems`, `cartFees`, `extensions` | `checkoutStatus` | Object | The current checkout status exposed as various boolean state. | `isCalculating`, `isComplete`, `isIdle`, `isProcessing` | +| Property | Type | Description | Values | +| ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------ | ------------------------------------------------------------- | ------------------------------------------------------- | +| `activePaymentMethod` | String | The slug of the current active payment method in the checkout. | +| `billing` | Object | Contains everything related to billing. | `billingData`, `cartTotal`, `currency`, `cartTotalItems`, `displayPricesIncludingTax`, `appliedCoupons`, `customerId` | +| `cartData` | Object | Data exposed from the cart including items, fees, and any registered extension data. Note that this data should be treated as immutable (should not be modified/mutated) or it will result in errors in your application. | `cartItems`, `cartFees`, `extensions` | `checkoutStatus` | Object | The current checkout status exposed as various boolean state. | `isCalculating`, `isComplete`, `isIdle`, `isProcessing` | | `components` | Object | It exposes React components that can be implemented by your payment method for various common interface elements used by payment methods. |
  • `ValidationInputError`: a container for holding validation errors which typically you'll include after any inputs
  • [`PaymentMethodLabel`](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/e089ae17043fa525e8397d605f0f470959f2ae95/assets/js/payment-method-extensions/payment-methods/paypal/index.js#L37-L40): use this component for the payment method label, including an optional icon
  • `PaymentMethodIcons`: a React component used for displaying payment method icons
  • `LoadingMask`: a wrapper component that handles displaying a loading state when the isLoading prop is true. Exposes the [LoadingMask component](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/c9074a4941919987dbad16a80f358b960336a09d/assets/js/base/components/loading-mask/index.js)
| | `emitResponse` | Object | Contains some constants that can be helpful when using the event emitter. Read the _[Emitting Events](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/e267cd96a4329a4eeef816b2ef627e113ebb72a5/docs/extensibility/checkout-flow-and-events.md#emitting-events)_ section for more details. |
  • `noticeContexts`: This is an object containing properties referencing areas where notices can be targeted in the checkout. The object has the following properties:
    • `PAYMENTS`: This is a reference to the notice area in the payment methods step.
    • `EXPRESS_PAYMENTS`: This is a reference to the notice area in the express payment methods step.
  • `responseTypes`: This is an object containing properties referencing the various response types that can be returned by observers for some event emitters. It makes it easier for autocompleting the types and avoiding typos due to human error. The types are `SUCCESS`, `FAIL`, `ERROR`. The values for these types also correspond to the [payment status types](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/34e17c3622637dbe8b02fac47b5c9b9ebf9e3596/src/Payments/PaymentResult.php#L21) from the [checkout endpoint response from the server](https://github.com/woocommerce/woocommerce-gutenberg-products-block/blob/34e17c3622637dbe8b02fac47b5c9b9ebf9e3596/src/RestApi/StoreApi/Schemas/CheckoutSchema.php#L103-L113).
| | `eventRegistration` | object | Contains all the checkout event emitter registration functions. These are functions the payment method can register observers on to interact with various points in the checkout flow (see [this doc](./checkout-flow-and-events.md) for more info). | `onCheckoutValidationBeforeProcessing`, `onCheckoutAfterProcessingWithSuccess`, `onCheckoutAfterProcessingWithError`, `onPaymentProcessing`, `onShippingRateSuccess`, `onShippingRateFail`, `onShippingRateSelectSuccess`, `onShippingRateSelectFail` | -| `onClick` | Function | **Provided to express payment methods** that should be triggered when the payment method button is clicked (which will signal to checkout the payment method has taken over payment processing) | - | | -| `onClose` | Function | **Provided to express payment methods** that should be triggered when the express payment method modal closes and control is returned to checkout. | - | | +| `onClick` | Function | **Provided to express payment methods** that should be triggered when the payment method button is clicked (which will signal to checkout the payment method has taken over payment processing) | - | | +| `onClose` | Function | **Provided to express payment methods** that should be triggered when the express payment method modal closes and control is returned to checkout. | - | | | `onSubmit` | Function | Submits the checkout and begins processing | - | -| `paymentStatus` | Object | Various payment status helpers. Note, your payment method does not have to handle setting this status client side. Checkout will handle this via the responses your payment method gives from observers registered to [checkout event emitters](./checkout-flow-and-events.md). | `isPristine`, `isStarted`, `isProcessing`, `isFinished`, `hasError`, `hasFailed`, `isSuccessful` | +| `paymentStatus` | Object | Various payment status helpers. Note, your payment method does not have to handle setting this status client side. Checkout will handle this via the responses your payment method gives from observers registered to [checkout event emitters](./checkout-flow-and-events.md). | `isPristine`, `isStarted`, `isProcessing`, `isFinished`, `hasError`, `hasFailed`, `isSuccessful` (see below for explanation) | | `setExpressPaymentError` | Function | Receives a string and allows express payment methods to set an error notice for the express payment area on demand. This can be necessary because some express payment method processing might happen outside of checkout events. | - | | `shippingData` | Object | Contains all shipping related data (outside of the shipping status). | `shippingRates`, `shippingRatesLoading`, `selectedRates`, `setSelectedRates`, `isSelectingRate`, `shippingAddress`, `setShippingAddress`, and `needsShipping` | | `shippingStatus` | Object | Various shipping status helpers. |
  • `shippingErrorStatus`: an object with various error statuses that might exist for shipping
  • `shippingErrorTypes`: an object containing all the possible types for shipping error status
| | `shouldSavePayment` | Boolean | Indicates whether or not the shopper has selected to save their payment method details (for payment methods that support saved payments). True if selected, false otherwise. Defaults to false. | - | +- `isPristine`: This is true when the current payment status is `PRISTINE`. +- `isStarted`: This is true when the current payment status is `STARTED`. +- `isProcessing`: This is true when the current payment status is `PROCESSING`. +- `isFinished`: This is true when the current payment status is one of `ERROR`, `FAILED`, or`SUCCESS`. +- `hasError`: This is true when the current payment status is `ERROR`. +- `hasFailed`: This is true when the current payment status is `FAILED`. +- `isSuccessful`: This is true when the current payment status is `SUCCESS` + Any registered `savedTokenComponent` node will also receive a `token` prop which includes the id for the selected saved token in case your payment method needs to use it for some internal logic. However, keep in mind, this is just the id representing this token in the database (and the value of the radio input the shopper checked), not the actual customer payment token (since processing using that usually happens on the server for security). ## Server Side Integration @@ -246,4 +254,3 @@ As an example, you can see how the Stripe extension adds it's integration in thi 🐞 Found a mistake, or have a suggestion? [Leave feedback about this document here.](https://github.com/woocommerce/woocommerce-blocks/issues/new?assignees=&labels=type%3A+documentation&template=--doc-feedback.md&title=Feedback%20on%20./docs/third-party-developers/extensibility/checkout-payment-methods/payment-method-integration.md) - diff --git a/docs/third-party-developers/extensibility/data-store/payment.md b/docs/third-party-developers/extensibility/data-store/payment.md index 3a5310faf9d..5c5815bd622 100644 --- a/docs/third-party-developers/extensibility/data-store/payment.md +++ b/docs/third-party-developers/extensibility/data-store/payment.md @@ -27,6 +27,136 @@ with. We do not encourage extensions to dispatch actions onto this data store ye ## Selectors +### isPaymentPristine + +Queries if the status is `pristine` + +#### _Returns_ + +`boolean`: True if the payment status is `pristine`, false otherwise. + +#### _Example_ + +```js +const store = select( 'wc/store/payment' ); +const isPaymentPristine = store.isPaymentPristine(); +``` + +### isPaymentStarted + +Queries if the status is `started`. + +#### _Returns_ + +`boolean`: True if the payment status is `started`, false otherwise. + +#### _Example_ + +```js +const store = select( 'wc/store/payment' ); +const isPaymentStarted = store.isPaymentStarted(); +``` + +### isPaymentProcessing + +Queries if the status is `processing`. + +#### _Returns_ + +`boolean`: True if the payment status is `processing`, false otherwise. + +#### _Example_ + +```js +const store = select( 'wc/store/payment' ); +const isPaymentProcessing = store.isPaymentProcessing(); +``` + +### isPaymentSuccess + +Queries if the status is `success`. + +#### _Returns_ + +`boolean`: True if the payment status is `success`, false otherwise. + +#### _Example_ + +```js +const store = select( 'wc/store/payment' ); +const isPaymentSuccess = store.isPaymentSuccess(); +``` + +### isPaymentFailed + +Queries if the status is `failed`. + +#### _Returns_ + +`boolean`: True if the payment status is `failed`, false otherwise. + +#### _Example_ + +```js +const store = select( 'wc/store/payment' ); +const isPaymentFailed = store.isPaymentFailed(); +``` + +### hasPaymentError + +Queries if the status is `error`. + +#### _Returns_ + +`boolean`: True if the payment status is `error`, false otherwise. + +#### _Example_ + +```js +const store = select( 'wc/store/payment' ); +const hasPaymentError = store.hasPaymentError(); +``` + +### isPaymentFinished + +Checks wether the payment has finished processing. This includes failed payments, payments with errors or successful payments. + +#### _Returns_ + +`boolean`: True if the payment status is `success`, `failed` or `error`, false otherwise. + +#### _Example_ + +```js +const store = select( 'wc/store/payment' ); +const isPaymentFinished = store.isPaymentFinished(); +``` + +### getCurrentStatus (deprecated) + +Returns an object with booleans representing the payment status. +_**This selector is deprecated and will be removed in a future release. Please use the selectors above**_ + +#### _Returns_ + +`object` - The current payment status. This will be an object with the following keys, the values are all booleans: + +- `isPristine` - True if the payment process has not started, does not have an error and has not finished. This is true + initially. +- `isStarted` - True if the payment process has started. +- `isProcessing` - True if the payment is processing. +- `hasError` - True if the payment process has resulted in an error. +- `hasFailed` - True if the payment process has failed. +- `isSuccessful` - True if the payment process is successful. +- `isDoingExpressPayment` - True if an express payment method is active, false otherwise + +#### Example + +```js +const store = select( 'wc/store/payment' ); +const currentStatus = store.getCurrentStatus(); +``` + ### isExpressPaymentMethodActive Returns whether an express payment method is active, this will be true when the express payment method is open and @@ -244,29 +374,6 @@ const store = select( 'wc/store/payment' ); const shouldSavePaymentMethod = store.getShouldSavePaymentMethod(); ``` -### getCurrentStatus - -Returns the current payment status. - -#### _Returns_ - -`object` - The current payment status. This will be an object with the following keys, the values are all booleans: - -- `isPristine` - True if the payment process has not started, does not have an error and has not finished. This is true - initially. -- `isStarted` - True if the payment process has started. -- `isProcessing` - True if the payment is processing. -- `hasError` - True if the payment process has resulted in an error. -- `hasFailed` - True if the payment process has failed. -- `isSuccessful` - True if the payment process is successful. - -#### Example - -```js -const store = select( 'wc/store/payment' ); -const currentStatus = store.getCurrentStatus(); -``` - ### paymentMethodsInitialized Returns whether the payment methods have been initialized. @@ -307,4 +414,3 @@ const expressPaymentMethodsInitialized = 🐞 Found a mistake, or have a suggestion? [Leave feedback about this document here.](https://github.com/woocommerce/woocommerce-blocks/issues/new?assignees=&labels=type%3A+documentation&template=--doc-feedback.md&title=Feedback%20on%20./docs/third-party-developers/extensibility/data-store/payment.md) - From a7f4d0e69b310625ce46cfb0283ce102b69dfa4d Mon Sep 17 00:00:00 2001 From: Alexandre Lara Date: Tue, 15 Nov 2022 18:08:44 -0300 Subject: [PATCH 03/27] Make loading placeholder colors match the current font color (#7658) * Make loading placeholder colors match the current font color Currently, the loading placeholder effect has a default gray color. However, since users can modify their themes with the Site Editor and choose a different set of colors for their websites, it would be interesting to make those placeholders match the color palette. In this commit, the idea was to modify the `placeholder` mixin to replace the transparent font color with the current color and also modify the background-color and the linear-gradient to match the current font color. Furthermore, transparency was added to the middle color of the linear-gradient so we can keep the loading animation close to what it currently is. * Add opacity to placeholder mixin Before our changes, when the font color was dark, we had a lighter placeholder background color. After the changes the color is currently darker than before so the idea for this commit is to change the opacity of the placeholder in a way that the current color blends with the background color set for the theme. * Change placeholder mixin opacity After testing different combinations of colors, we decided to change the opacity to 0.15 so when the font color is darker the placeholder will have a lighter background color. * bot: update checkstyle.xml Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- assets/css/abstracts/_mixins.scss | 7 ++++--- assets/js/base/components/filter-placeholder/style.scss | 5 ----- checkstyle.xml | 3 --- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/assets/css/abstracts/_mixins.scss b/assets/css/abstracts/_mixins.scss index 47f66c23bce..535b14b221f 100644 --- a/assets/css/abstracts/_mixins.scss +++ b/assets/css/abstracts/_mixins.scss @@ -32,8 +32,8 @@ $fontSizes: ( @mixin placeholder($include-border-radius: true) { outline: 0 !important; border: 0 !important; - background-color: #ebebeb !important; - color: transparent !important; + background-color: currentColor !important; + color: currentColor !important; width: 100%; @if $include-border-radius == true { border-radius: 0.25rem; @@ -46,6 +46,7 @@ $fontSizes: ( pointer-events: none; box-shadow: none; z-index: 1; /* Necessary for overflow: hidden to work correctly in Safari */ + opacity: 0.15; // Forces direct descendants to keep layout but lose visibility. > * { @@ -61,7 +62,7 @@ $fontSizes: ( top: 0; height: 100%; background-repeat: no-repeat; - background-image: linear-gradient(90deg, #ebebeb, #f5f5f5, #ebebeb); + background-image: linear-gradient(90deg, currentColor, #f5f5f54d, currentColor); transform: translateX(-100%); animation: loading__animation 1.5s ease-in-out infinite; } diff --git a/assets/js/base/components/filter-placeholder/style.scss b/assets/js/base/components/filter-placeholder/style.scss index 3d92bb02b96..7c461e79a46 100644 --- a/assets/js/base/components/filter-placeholder/style.scss +++ b/assets/js/base/components/filter-placeholder/style.scss @@ -1,6 +1,5 @@ .wc-block-filter-title-placeholder { @include placeholder(); - background-color: $gray-400 !important; border-radius: em(26px); box-shadow: none; min-width: 80px; @@ -8,10 +7,6 @@ margin-bottom: $gap-small; max-width: max-content !important; - &::after { - background-image: linear-gradient(90deg, $gray-400, $gray-200, $gray-400); - } - .wc-block-stock-filter__title, .wc-block-price-filter__title, .wc-block-active-filters__title, diff --git a/checkstyle.xml b/checkstyle.xml index 3fab73e7b78..c4f195da84e 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -1419,9 +1419,6 @@ Type 'Readonly<{}>' is not assignable to type 'Record<string, unknown> & { className: string; }'. Property 'className' is missing in type 'Readonly<{}>' but required in type '{ className: string; }'." source="TS2322" />
- - - From 1e4d336306800ac9ed6adae0d17ff7289179cc6d Mon Sep 17 00:00:00 2001 From: Tung Du Date: Wed, 16 Nov 2022 08:52:51 +0700 Subject: [PATCH 04/27] E2E: Fixing failed tests with Gutenberg (#7554) Co-authored-by: Lucio Giannotta --- package-lock.json | 26 ++++++------ package.json | 2 +- .../site-editing-templates.test.js.snap | 10 ++--- tests/e2e/specs/backend/product-query.test.ts | 14 ++++++- .../e2e/specs/shopper/active-filters.test.ts | 3 +- .../shopper/cart-checkout/account.test.js | 2 +- .../shopper/cart-checkout/checkout.test.js | 16 ++------ .../filter-products-by-attribute.test.ts | 9 +++-- .../shopper/filter-products-by-price.test.ts | 9 +++-- .../shopper/filter-products-by-rating.test.ts | 7 +++- .../shopper/filter-products-by-stock.test.ts | 9 +++-- tests/e2e/specs/shopper/mini-cart.test.js | 17 +++++++- tests/e2e/utils.js | 33 +++++---------- tests/utils/constants.js | 1 + tests/utils/get-toggle-id-by-label.ts | 40 +++++++++++++++++++ tests/utils/index.js | 3 ++ .../insert-block-using-quick-inserter.ts | 25 ++++++++++++ tests/utils/insert-block-using-slash.ts | 16 ++++++++ tests/utils/insert-inner-block.ts | 22 ++-------- tests/utils/selectors.ts | 1 + 20 files changed, 176 insertions(+), 89 deletions(-) create mode 100644 tests/utils/get-toggle-id-by-label.ts create mode 100644 tests/utils/insert-block-using-quick-inserter.ts create mode 100644 tests/utils/insert-block-using-slash.ts diff --git a/package-lock.json b/package-lock.json index 5e6bca5047a..ed32c3b03d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -94,7 +94,7 @@ "@wordpress/data-controls": "2.2.7", "@wordpress/dependency-extraction-webpack-plugin": "3.2.1", "@wordpress/dom": "3.16.0", - "@wordpress/e2e-test-utils": "8.3.0", + "@wordpress/e2e-test-utils": "^8.4.0", "@wordpress/e2e-tests": "4.6.0", "@wordpress/element": "4.0.4", "@wordpress/env": "^4.9.0", @@ -13358,15 +13358,15 @@ } }, "node_modules/@wordpress/e2e-test-utils": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-8.3.0.tgz", - "integrity": "sha512-4clSCcbyTwIXSL1NLyNjN0RWXfzbWcAbTSq8RQ3DeKc2wU4m2VorAZ/qXxrUFQMH3/zsHuoC4hxMlcXuL3kriw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-8.4.0.tgz", + "integrity": "sha512-FGOQz2JfZD0mAxMqqGSpK+L15LmIf613EJr4RRPLI1Ppbi1LZHIQPzV7jArDgYTOo0FYwfOYBRxn4CjbwL3Vgw==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.16.0", - "@wordpress/keycodes": "^3.19.0", - "@wordpress/url": "^3.20.0", + "@wordpress/api-fetch": "^6.17.0", + "@wordpress/keycodes": "^3.20.0", + "@wordpress/url": "^3.21.0", "change-case": "^4.1.2", "form-data": "^4.0.0", "node-fetch": "^2.6.0" @@ -59073,15 +59073,15 @@ } }, "@wordpress/e2e-test-utils": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-8.3.0.tgz", - "integrity": "sha512-4clSCcbyTwIXSL1NLyNjN0RWXfzbWcAbTSq8RQ3DeKc2wU4m2VorAZ/qXxrUFQMH3/zsHuoC4hxMlcXuL3kriw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-8.4.0.tgz", + "integrity": "sha512-FGOQz2JfZD0mAxMqqGSpK+L15LmIf613EJr4RRPLI1Ppbi1LZHIQPzV7jArDgYTOo0FYwfOYBRxn4CjbwL3Vgw==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/api-fetch": "^6.16.0", - "@wordpress/keycodes": "^3.19.0", - "@wordpress/url": "^3.20.0", + "@wordpress/api-fetch": "^6.17.0", + "@wordpress/keycodes": "^3.20.0", + "@wordpress/url": "^3.21.0", "change-case": "^4.1.2", "form-data": "^4.0.0", "node-fetch": "^2.6.0" diff --git a/package.json b/package.json index 4bc9e4d3325..b5fc852880c 100644 --- a/package.json +++ b/package.json @@ -145,7 +145,7 @@ "@wordpress/data-controls": "2.2.7", "@wordpress/dependency-extraction-webpack-plugin": "3.2.1", "@wordpress/dom": "3.16.0", - "@wordpress/e2e-test-utils": "8.3.0", + "@wordpress/e2e-test-utils": "8.4.0", "@wordpress/e2e-tests": "4.6.0", "@wordpress/element": "4.0.4", "@wordpress/env": "^4.9.0", diff --git a/tests/e2e/specs/backend/__snapshots__/site-editing-templates.test.js.snap b/tests/e2e/specs/backend/__snapshots__/site-editing-templates.test.js.snap index ac1587262cb..13f68464c66 100644 --- a/tests/e2e/specs/backend/__snapshots__/site-editing-templates.test.js.snap +++ b/tests/e2e/specs/backend/__snapshots__/site-editing-templates.test.js.snap @@ -3,7 +3,7 @@ exports[`Store Editing Templates Product Catalog block template should contain the "WooCommerce Product Grid Block" classic template 1`] = ` " - +
@@ -13,7 +13,7 @@ exports[`Store Editing Templates Product Catalog block template should contain t exports[`Store Editing Templates Product Search Results block template should contain the "WooCommerce Product Grid Block" classic template 1`] = ` " - +
@@ -23,7 +23,7 @@ exports[`Store Editing Templates Product Search Results block template should co exports[`Store Editing Templates Product by Category block template should contain the "WooCommerce Product Taxonomy Block" classic template 1`] = ` " - +
@@ -33,7 +33,7 @@ exports[`Store Editing Templates Product by Category block template should conta exports[`Store Editing Templates Products by Tag block template should contain the "WooCommerce Product Taxonomy Block" classic template 1`] = ` " - +
@@ -43,7 +43,7 @@ exports[`Store Editing Templates Products by Tag block template should contain t exports[`Store Editing Templates Single Product block template should contain the "WooCommerce Single Product Block" classic template 1`] = ` " - +
diff --git a/tests/e2e/specs/backend/product-query.test.ts b/tests/e2e/specs/backend/product-query.test.ts index 7dc77922b4c..58d3c6fe336 100644 --- a/tests/e2e/specs/backend/product-query.test.ts +++ b/tests/e2e/specs/backend/product-query.test.ts @@ -44,7 +44,12 @@ describeOrSkip( GUTENBERG_EDITOR_CONTEXT === 'gutenberg' )( await expect( page ).toRenderBlock( block ); } ); - it( 'Editor preview shows only on sale products after enabling `Show only products on sale`', async () => { + /** + * We changed the “Show only products on sale” from a top-level toggle + * setting to a product filter, but tests for them haven't been updated + * yet. We will fix these tests in a follow-up PR. + */ + it.skip( 'Editor preview shows only on sale products after enabling `Show only products on sale`', async () => { await visitBlockPage( `${ block.name } Block` ); const canvasEl = canvas(); await openDocumentSettingsSidebar(); @@ -84,7 +89,12 @@ describeOrSkip( GUTENBERG_EDITOR_CONTEXT === 'gutenberg' )( expect( products ).toHaveLength( 1 ); } ); - it( 'Does not have on sale toggle', async () => { + /** + * We changed the “Show only products on sale” from a top-level toggle + * setting to a product filter, but tests for them haven't been updated + * yet. We will fix these tests in a follow-up PR. + */ + it.skip( 'Does not have on sale toggle', async () => { await openDocumentSettingsSidebar(); await openListView(); await page.click( diff --git a/tests/e2e/specs/shopper/active-filters.test.ts b/tests/e2e/specs/shopper/active-filters.test.ts index 173d09ad6ab..3b9ee05f609 100644 --- a/tests/e2e/specs/shopper/active-filters.test.ts +++ b/tests/e2e/specs/shopper/active-filters.test.ts @@ -11,6 +11,7 @@ import { } from '@wordpress/e2e-test-utils'; import { SHOP_PAGE } from '@woocommerce/e2e-utils'; import { Frame, Page } from 'puppeteer'; +import { insertBlockUsingSlash } from '@woocommerce/blocks-test-utils'; /** * Internal dependencies @@ -97,7 +98,7 @@ describe( 'Shopper → Active Filters Block', () => { } ); await insertBlocks(); - await insertBlock( 'All Products' ); + await insertBlockUsingSlash( 'All Products' ); await configurateFilterProductsByAttributeBlock( page ); await publishPost(); diff --git a/tests/e2e/specs/shopper/cart-checkout/account.test.js b/tests/e2e/specs/shopper/cart-checkout/account.test.js index 29bf00dfb53..56ab92afe1f 100644 --- a/tests/e2e/specs/shopper/cart-checkout/account.test.js +++ b/tests/e2e/specs/shopper/cart-checkout/account.test.js @@ -46,7 +46,7 @@ describe( 'Shopper → Checkout → Account', () => { await selectBlockByName( 'woocommerce/checkout-contact-information-block' ); - await openBlockEditorSettings( { isFSEEditor: false } ); + await openBlockEditorSettings(); //Enable shoppers to sign up at checkout option. // eslint-disable-next-line jest/no-standalone-expect await expect( page ).toClick( 'label', { diff --git a/tests/e2e/specs/shopper/cart-checkout/checkout.test.js b/tests/e2e/specs/shopper/cart-checkout/checkout.test.js index 9231bd9a673..652460a6efb 100644 --- a/tests/e2e/specs/shopper/cart-checkout/checkout.test.js +++ b/tests/e2e/specs/shopper/cart-checkout/checkout.test.js @@ -8,11 +8,11 @@ import { unsetCheckbox, withRestApi, } from '@woocommerce/e2e-utils'; - import { visitBlockPage, selectBlockByName, saveOrPublish, + getToggleIdByLabel, } from '@woocommerce/blocks-test-utils'; /** @@ -36,7 +36,6 @@ if ( process.env.WOOCOMMERCE_BLOCKS_PHASE < 2 ) { test.only( 'Skipping Cart & Checkout tests', () => {} ); } -let companyCheckboxId = null; let coupon; describe( 'Shopper → Checkout', () => { @@ -75,16 +74,7 @@ describe( 'Shopper → Checkout', () => { 'woocommerce/checkout-shipping-address-block' ); - // This checkbox ID is unstable, so, we're getting its value from "for" attribute of the label - const [ companyCheckboxLabel ] = await page.$x( - `//label[contains(text(), "Company") and contains(@class, "components-toggle-control__label")]` - ); - companyCheckboxId = await page.evaluate( - ( label ) => `#${ label.getAttribute( 'for' ) }`, - companyCheckboxLabel - ); - - await setCheckbox( companyCheckboxId ); + await setCheckbox( await getToggleIdByLabel( 'Company' ) ); await saveOrPublish(); await shopper.block.emptyCart(); } ); @@ -96,7 +86,7 @@ describe( 'Shopper → Checkout', () => { await selectBlockByName( 'woocommerce/checkout-shipping-address-block' ); - await unsetCheckbox( companyCheckboxId ); + await unsetCheckbox( await getToggleIdByLabel( 'Company' ) ); await saveOrPublish(); await merchant.logout(); await reactivateCompatibilityNotice(); diff --git a/tests/e2e/specs/shopper/filter-products-by-attribute.test.ts b/tests/e2e/specs/shopper/filter-products-by-attribute.test.ts index 886227a91ff..7a5a618e6f3 100644 --- a/tests/e2e/specs/shopper/filter-products-by-attribute.test.ts +++ b/tests/e2e/specs/shopper/filter-products-by-attribute.test.ts @@ -9,7 +9,10 @@ import { switchUserToAdmin, publishPost, } from '@wordpress/e2e-test-utils'; -import { selectBlockByName } from '@woocommerce/blocks-test-utils'; +import { + selectBlockByName, + insertBlockUsingSlash, +} from '@woocommerce/blocks-test-utils'; /** * Internal dependencies @@ -65,7 +68,7 @@ describe( `${ block.name } Block`, () => { title: block.name, } ); - await insertBlock( 'All Products' ); + await insertBlockUsingSlash( 'All Products' ); await insertBlock( block.name ); const canvasEl = canvas(); @@ -185,7 +188,7 @@ describe( `${ block.name } Block`, () => { } ); await selectBlockByName( block.slug ); - await openBlockEditorSettings( { isFSEEditor: true } ); + await openBlockEditorSettings(); const [ filterButtonToggle ] = await page.$x( block.selectors.editor.filterButtonToggle ); diff --git a/tests/e2e/specs/shopper/filter-products-by-price.test.ts b/tests/e2e/specs/shopper/filter-products-by-price.test.ts index a377262b4fc..b6cee917e7a 100644 --- a/tests/e2e/specs/shopper/filter-products-by-price.test.ts +++ b/tests/e2e/specs/shopper/filter-products-by-price.test.ts @@ -8,7 +8,10 @@ import { switchUserToAdmin, publishPost, } from '@wordpress/e2e-test-utils'; -import { selectBlockByName } from '@woocommerce/blocks-test-utils'; +import { + selectBlockByName, + insertBlockUsingSlash, +} from '@woocommerce/blocks-test-utils'; /** * Internal dependencies @@ -70,7 +73,7 @@ describe( `${ block.name } Block`, () => { } ); await insertBlock( block.name ); - await insertBlock( 'All Products' ); + await insertBlockUsingSlash( 'All Products' ); await insertBlock( 'Active Product Filters' ); await publishPost(); @@ -182,7 +185,7 @@ describe( `${ block.name } Block`, () => { } ); await selectBlockByName( block.slug ); - await openBlockEditorSettings( { isFSEEditor: true } ); + await openBlockEditorSettings(); await page.waitForXPath( block.selectors.editor.filterButtonToggle ); diff --git a/tests/e2e/specs/shopper/filter-products-by-rating.test.ts b/tests/e2e/specs/shopper/filter-products-by-rating.test.ts index c1db540f4c4..a58b81c30be 100644 --- a/tests/e2e/specs/shopper/filter-products-by-rating.test.ts +++ b/tests/e2e/specs/shopper/filter-products-by-rating.test.ts @@ -8,7 +8,10 @@ import { switchUserToAdmin, publishPost, } from '@wordpress/e2e-test-utils'; -import { selectBlockByName } from '@woocommerce/blocks-test-utils'; +import { + selectBlockByName, + insertBlockUsingSlash, +} from '@woocommerce/blocks-test-utils'; /** * Internal dependencies @@ -62,7 +65,7 @@ describe( `${ block.name } Block`, () => { } ); await insertBlock( block.name ); - await insertBlock( 'All Products' ); + await insertBlockUsingSlash( 'All Products' ); await publishPost(); link = await page.evaluate( () => diff --git a/tests/e2e/specs/shopper/filter-products-by-stock.test.ts b/tests/e2e/specs/shopper/filter-products-by-stock.test.ts index 1303714b473..6df7aea7b7b 100644 --- a/tests/e2e/specs/shopper/filter-products-by-stock.test.ts +++ b/tests/e2e/specs/shopper/filter-products-by-stock.test.ts @@ -8,7 +8,10 @@ import { switchUserToAdmin, publishPost, } from '@wordpress/e2e-test-utils'; -import { selectBlockByName } from '@woocommerce/blocks-test-utils'; +import { + selectBlockByName, + insertBlockUsingSlash, +} from '@woocommerce/blocks-test-utils'; /** * Internal dependencies @@ -61,7 +64,7 @@ describe( `${ block.name } Block`, () => { } ); await insertBlock( block.name ); - await insertBlock( 'All Products' ); + await insertBlockUsingSlash( 'All Products' ); await publishPost(); link = await page.evaluate( () => @@ -160,7 +163,7 @@ describe( `${ block.name } Block`, () => { await waitForCanvas(); await selectBlockByName( block.slug ); - await openBlockEditorSettings( { isFSEEditor: true } ); + await openBlockEditorSettings(); await page.waitForXPath( block.selectors.editor.filterButtonToggle ); diff --git a/tests/e2e/specs/shopper/mini-cart.test.js b/tests/e2e/specs/shopper/mini-cart.test.js index a9da6453b84..3e67c749ccf 100644 --- a/tests/e2e/specs/shopper/mini-cart.test.js +++ b/tests/e2e/specs/shopper/mini-cart.test.js @@ -24,6 +24,10 @@ const block = { productPrice: '.wc-block-grid__product:nth-child(2) .wc-block-grid__product-price', addToCartButton: 'button.add_to_cart_button', + checkoutOrderSummary: { + content: '.wc-block-components-order-summary__content', + toggle: '.wc-block-components-order-summary button[aria-expanded="false"]', + }, }, }, }; @@ -554,7 +558,18 @@ describe( 'Shopper → Mini Cart', () => { await expect( page ).toMatchElement( 'h1', { text: 'Checkout' } ); - await expect( page ).toMatch( productTitle ); + const orderSummaryToggle = await page.$( + selectors.frontend.checkoutOrderSummary.toggle + ); + + if ( orderSummaryToggle ) { + await orderSummaryToggle.click(); + } + + await expect( page ).toMatchElement( + selectors.frontend.checkoutOrderSummary.content, + { text: productTitle } + ); } ); } ); diff --git a/tests/e2e/utils.js b/tests/e2e/utils.js index 75a5d05a412..93a6b3906bf 100644 --- a/tests/e2e/utils.js +++ b/tests/e2e/utils.js @@ -409,24 +409,20 @@ export const createCoupon = async ( coupon ) => { }; /** - * Open the block editor settings menu. + * Open the block editor settings menu if it hasn't opened. * - * @param {Object} [root0] - * @param {boolean} [root0.isFSEEditor] Amount to be applied. Defaults to 5. + * @todo Replace openBlockEditorSettings with ensureSidebarOpened when WordPress/gutenberg#45480 is released. See https://github.com/WordPress/gutenberg/pull/45480. */ +export const openBlockEditorSettings = async () => { + const toggleSidebarButton = await page.$( + '.edit-post-header__settings [aria-label="Settings"][aria-expanded="false"],' + + '.edit-site-header__actions [aria-label="Settings"][aria-expanded="false"],' + + '.edit-widgets-header__actions [aria-label="Settings"][aria-expanded="false"],' + + '.edit-site-header-edit-mode__actions [aria-label="Settings"][aria-expanded="false"]' + ); -export const openBlockEditorSettings = async ( { isFSEEditor = false } ) => { - const buttonSelector = isFSEEditor - ? '.edit-site-header__actions button[aria-label="Settings"]' - : '.edit-post-header__settings button[aria-label="Settings"]'; - - const isPressed = `${ buttonSelector }.is-pressed`; - - const isSideBarAlreadyOpened = await page.$( isPressed ); - - if ( isSideBarAlreadyOpened === null ) { - // @ts-ignore - await page.$eval( buttonSelector, ( el ) => el.click() ); + if ( toggleSidebarButton ) { + await toggleSidebarButton.click(); } }; @@ -449,10 +445,3 @@ export const waitForAllProductsBlockLoaded = async () => { */ export const describeOrSkip = ( condition ) => condition ? describe : describe.skip; - -/** - * Execute or skip the test base on the provided condition. - * - * @param {boolean} condition Condition to execute test. - */ -export const itOrSkip = ( condition ) => ( condition ? it : it.skip ); diff --git a/tests/utils/constants.js b/tests/utils/constants.js index dfd5395edec..4f78adb3f38 100644 --- a/tests/utils/constants.js +++ b/tests/utils/constants.js @@ -17,3 +17,4 @@ export const BILLING_DETAILS = config.get( 'addresses.customer.billing' ); export const PERFORMANCE_REPORT_FILENAME = 'reports/e2e-performance.json'; export const SHIPPING_DETAILS = config.get( 'addresses.customer.shipping' ); export const BASE_URL = config.get( 'url' ); +export const DEFAULT_TIMEOUT = 30000; diff --git a/tests/utils/get-toggle-id-by-label.ts b/tests/utils/get-toggle-id-by-label.ts new file mode 100644 index 00000000000..51195446905 --- /dev/null +++ b/tests/utils/get-toggle-id-by-label.ts @@ -0,0 +1,40 @@ +/** + * Internal dependencies + */ +import { DEFAULT_TIMEOUT } from './constants'; + +/** + * Get the ID of the setting toogle so test can manipulate the toggle using + * unsetCheckbox and setCheckbox utilities. + * + * We're using 'adaptive timeout' here as the id of the toggle is changed on + * every render, so we wait a bit for the toggle to finish rerendering, then + * check if the node still attached to the document before returning its + * ID. If the node is detached, it means that the toggle is rendered, then + * we retry by calling this function again with increased retry argument. We + * will retry until the timeout is reached. + */ +export const getToggleIdByLabel = async ( + label: string, + retry = 0 +): Promise< string > => { + const delay = 1000; + const labelElement = await page.waitForXPath( + `//label[contains(text(), "${ label }") and contains(@class, "components-toggle-control__label")]`, + { visible: true } + ); + const checkboxId = await page.evaluate( + ( toggleLabel ) => `#${ toggleLabel.getAttribute( 'for' ) }`, + labelElement + ); + // Wait a bit for toggle to finish rerendering. + await page.waitForTimeout( delay ); + const checkbox = await page.$( checkboxId ); + if ( ! checkbox ) { + if ( retry * delay < DEFAULT_TIMEOUT ) { + return await getToggleIdByLabel( label, retry + 1 ); + } + throw new Error( `Can't find toggle with label ${ label }` ); + } + return checkboxId; +}; diff --git a/tests/utils/index.js b/tests/utils/index.js index 058477cd54c..b30ce1b84fd 100644 --- a/tests/utils/index.js +++ b/tests/utils/index.js @@ -18,3 +18,6 @@ export * from './taxes'; export * from './constants'; export { insertInnerBlock } from './insert-inner-block'; export { getFixtureProductsData } from './get-fixture-products-data'; +export { getToggleIdByLabel } from './get-toggle-id-by-label'; +export { insertBlockUsingQuickInserter } from './insert-block-using-quick-inserter'; +export { insertBlockUsingSlash } from './insert-block-using-slash'; diff --git a/tests/utils/insert-block-using-quick-inserter.ts b/tests/utils/insert-block-using-quick-inserter.ts new file mode 100644 index 00000000000..90582970b6d --- /dev/null +++ b/tests/utils/insert-block-using-quick-inserter.ts @@ -0,0 +1,25 @@ +/** + * External dependencies + */ +import { canvas } from '@wordpress/e2e-test-utils'; + +/** + * Internal dependencies + */ +import SELECTORS from './selectors'; + +export const insertBlockUsingQuickInserter = async ( blockTitle: string ) => { + const blockInserterButton = await canvas().waitForSelector( + SELECTORS.quickInserter.button + ); + await blockInserterButton.click(); + const blockInsertInput = await page.waitForSelector( + SELECTORS.quickInserter.searchInput + ); + await blockInsertInput.focus(); + await page.keyboard.type( blockTitle ); + const insertButton = await page.waitForXPath( + `//button//span[contains(text(), '${ blockTitle }')]` + ); + await insertButton.click(); +}; diff --git a/tests/utils/insert-block-using-slash.ts b/tests/utils/insert-block-using-slash.ts new file mode 100644 index 00000000000..11ce7b95fa7 --- /dev/null +++ b/tests/utils/insert-block-using-slash.ts @@ -0,0 +1,16 @@ +/** + * External dependencies + */ +import { canvas, insertBlock } from '@wordpress/e2e-test-utils'; + +/** + * Internal dependencies + */ +import SELECTORS from './selectors'; + +export const insertBlockUsingSlash = async ( blockTitle: string ) => { + await insertBlock( 'Paragraph' ); + await canvas().keyboard.type( `/${ blockTitle }` ); + await canvas().waitForSelector( SELECTORS.popover ); + await canvas().keyboard.press( 'Enter' ); +}; diff --git a/tests/utils/insert-inner-block.ts b/tests/utils/insert-inner-block.ts index 3751a514d67..2eef11347a0 100644 --- a/tests/utils/insert-inner-block.ts +++ b/tests/utils/insert-inner-block.ts @@ -1,13 +1,8 @@ -/** - * External dependencies - */ -import { canvas } from '@wordpress/e2e-test-utils'; - /** * Internal dependencies */ import { selectBlockByName } from '.'; -import SELECTORS from './selectors'; +import { insertBlockUsingQuickInserter } from './insert-block-using-quick-inserter'; /** * Inserts an inner block into the currently selected block. If a parent block @@ -26,17 +21,6 @@ export const insertInnerBlock = async ( if ( parentBlockName ) { await selectBlockByName( parentBlockName ); } - const blockInserterButton = await canvas().waitForSelector( - SELECTORS.quickInserter.button - ); - await blockInserterButton.click(); - const blockInsertInput = await page.waitForSelector( - SELECTORS.quickInserter.searchInput - ); - await blockInsertInput.focus(); - await page.keyboard.type( blockTitle ); - const insertButton = await page.waitForXPath( - `//button//span[contains(text(), '${ blockTitle }')]` - ); - await insertButton.click(); + + await insertBlockUsingQuickInserter( blockTitle ); }; diff --git a/tests/utils/selectors.ts b/tests/utils/selectors.ts index cdc48dfa0fd..41ff0aa6902 100644 --- a/tests/utils/selectors.ts +++ b/tests/utils/selectors.ts @@ -4,4 +4,5 @@ export default { searchInput: '.block-editor-inserter__quick-inserter.has-search.has-expand .components-search-control__input', }, + popover: '.components-popover.components-autocomplete__popover', }; From 6dccf6f6703b893df525de2217f822fe1cb4ef7e Mon Sep 17 00:00:00 2001 From: kmanijak Date: Wed, 16 Nov 2022 10:25:40 +0100 Subject: [PATCH 05/27] Fix: skewed placeholder of product image - issue#7553 (#7651) * Fix typo in HTML markup of ProductImage block placeholder Typo caused rendering of incorrect HTML attributes: width and height of Product Image placeholder that were anyway ignored by the browser * Remove inline styles (width and height) from ImagePlaceholder in ProductElements > Image block Inline height took precedence over the inherited styles which made the placeholder image skewed (in loading and loaded state). Removal of those styles allows the ImagePlaceholder to adapt the height to the available space keeping the ratio or inherit the styles from the parent * Remove inline styles (width and height) from placeholder image in ProductImage.php block Inline styles applied to the placeholder image of ProductImage block were overriden by other styles with higher specificity, which made them redundant. Additionally, corresponding styles were removed from the placeholder image from Editor code as they caused UI glitches. Additional proof that it's safe to remove them is in the first commit in this branch, the purpose of which was to fix those styles as there was a typo which corrupted them and eventually inline width and height were ignored by the browser and not applied to the element * Add test to check placeholder image renders without width and height inline attributes This is to prevent adding inline width and height attributes so the sizing of the placeholder image is controlled by the inherited styles or element styles, in the same way as a regular product image * Fix TypeScript errors in the block test file of Product Image * Add generic alt text to placeholder image Alt text added in this commit is a generic text, not description of the actual image. That's because the image itself is set externally through the settings and may change over time * Revert adding placeholder image alt text and add comments to make the empty alt explicit After a Github discussion: https://github.com/woocommerce/woocommerce-blocks/pull/7651\#discussion_r1019560494 it was decided the placeholder should NOT have alt text as it serves the purpose of decorative image. According to the guidelines decorative images should not have alt text: https://www.w3.org/WAI/tutorials/images/decorative/. Comments were added also to other places where it happens * bot: update checkstyle.xml Co-authored-by: Karol Manijak Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../blocks/product-elements/image/block.js | 7 +-- .../product-elements/image/test/block.test.js | 46 ++++++++++++++++--- .../block-error-boundary/block-error.tsx | 3 ++ .../reviews/review-list-item/index.js | 3 ++ checkstyle.xml | 33 +++++-------- src/BlockTypes/ProductImage.php | 5 +- 6 files changed, 65 insertions(+), 32 deletions(-) diff --git a/assets/js/atomic/blocks/product-elements/image/block.js b/assets/js/atomic/blocks/product-elements/image/block.js index bc3e1cafab5..a94b9606e1c 100644 --- a/assets/js/atomic/blocks/product-elements/image/block.js +++ b/assets/js/atomic/blocks/product-elements/image/block.js @@ -129,9 +129,10 @@ export const Block = ( props ) => { }; const ImagePlaceholder = () => { - return ( - - ); + // The alt text is left empty on purpose, as it's considered a decorative image. + // More can be found here: https://www.w3.org/WAI/tutorials/images/decorative/. + // Github discussion for a context: https://github.com/woocommerce/woocommerce-blocks/pull/7651#discussion_r1019560494. + return ; }; const Image = ( { image, loaded, showFullSize, fallbackAlt } ) => { diff --git a/assets/js/atomic/blocks/product-elements/image/test/block.test.js b/assets/js/atomic/blocks/product-elements/image/test/block.test.js index 5e989a161ad..d9ae60071f2 100644 --- a/assets/js/atomic/blocks/product-elements/image/test/block.test.js +++ b/assets/js/atomic/blocks/product-elements/image/test/block.test.js @@ -62,7 +62,10 @@ describe( 'Product Image Block', () => { describe( 'with product link', () => { test( 'should render an anchor with the product image', () => { const component = render( - + ); @@ -79,14 +82,17 @@ describe( 'Product Image Block', () => { ); const anchor = productImage.closest( 'a' ); - expect( anchor.getAttribute( 'href' ) ).toBe( + expect( anchor?.getAttribute( 'href' ) ).toBe( productWithImages.permalink ); } ); test( 'should render an anchor with the placeholder image', () => { const component = render( - + ); @@ -97,10 +103,10 @@ describe( 'Product Image Block', () => { ); const anchor = placeholderImage.closest( 'a' ); - expect( anchor.getAttribute( 'href' ) ).toBe( + expect( anchor?.getAttribute( 'href' ) ).toBe( productWithoutImages.permalink ); - expect( anchor.getAttribute( 'aria-label' ) ).toBe( + expect( anchor?.getAttribute( 'aria-label' ) ).toBe( `Link to ${ productWithoutImages.name }` ); } ); @@ -109,7 +115,10 @@ describe( 'Product Image Block', () => { describe( 'without product link', () => { test( 'should render the product image without an anchor wrapper', () => { const component = render( - + ); @@ -129,7 +138,10 @@ describe( 'Product Image Block', () => { test( 'should render the placeholder image without an anchor wrapper', () => { const component = render( - + ); @@ -143,4 +155,24 @@ describe( 'Product Image Block', () => { expect( anchor ).toBe( null ); } ); } ); + + describe( 'without image', () => { + test( 'should render the placeholder with no inline width or height attributes', () => { + const component = render( + + + + ); + + const placeholderImage = component.getByAltText( '' ); + expect( placeholderImage.getAttribute( 'src' ) ).toBe( + 'placeholder.jpg' + ); + expect( placeholderImage.getAttribute( 'width' ) ).toBe( null ); + expect( placeholderImage.getAttribute( 'height' ) ).toBe( null ); + } ); + } ); } ); diff --git a/assets/js/base/components/block-error-boundary/block-error.tsx b/assets/js/base/components/block-error-boundary/block-error.tsx index e09f18fd4c9..b3924f541e9 100644 --- a/assets/js/base/components/block-error-boundary/block-error.tsx +++ b/assets/js/base/components/block-error-boundary/block-error.tsx @@ -23,6 +23,9 @@ const BlockError = ( { return showErrorBlock ? (
{ imageUrl && ( + // The alt text is left empty on purpose, as it's considered a decorative image. + // More can be found here: https://www.w3.org/WAI/tutorials/images/decorative/. + // Github discussion for a context: https://github.com/woocommerce/woocommerce-blocks/pull/7651#discussion_r1019560494. ) : ( + // The alt text is left empty on purpose, as it's considered a decorative image. + // More can be found here: https://www.w3.org/WAI/tutorials/images/decorative/. + // Github discussion for a context: https://github.com/woocommerce/woocommerce-blocks/pull/7651#discussion_r1019560494. - - - - + + + + - - - - - - - - - @@ -1726,14 +1717,14 @@ - - - - - - - - + + + + + + + + diff --git a/src/BlockTypes/ProductImage.php b/src/BlockTypes/ProductImage.php index aead33de4ad..591b883e369 100644 --- a/src/BlockTypes/ProductImage.php +++ b/src/BlockTypes/ProductImage.php @@ -147,7 +147,10 @@ private function render_image( $product ) { $image_info = wp_get_attachment_image_src( get_post_thumbnail_id( $product->get_id() ), 'woocommerce_thumbnail' ); if ( ! isset( $image_info[0] ) ) { - return sprintf( '', wc_placeholder_img_src( 'woocommerce_thumbnail' ) ); + // The alt text is left empty on purpose, as it's considered a decorative image. + // More can be found here: https://www.w3.org/WAI/tutorials/images/decorative/. + // Github discussion for a context: https://github.com/woocommerce/woocommerce-blocks/pull/7651#discussion_r1019560494. + return sprintf( '', wc_placeholder_img_src( 'woocommerce_thumbnail' ) ); } return sprintf( From e2b559d96431c67f79e3ea9e1ce31daea9a0b231 Mon Sep 17 00:00:00 2001 From: Alex Florisca Date: Wed, 16 Nov 2022 09:38:48 +0000 Subject: [PATCH 06/27] Move payment utils and delete orderPaymentMethods (#7679) * Moved all payment utils functions in a utils folder * Delete as it's not being used * bot: update checkstyle.xml * bot: update checkstyle.xml * Fix TS error * bot: update checkstyle.xml Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- assets/js/data/payment/actions.ts | 4 +-- assets/js/data/payment/selectors.ts | 2 +- assets/js/data/payment/test/actions.ts | 12 +++------ .../test/set-default-payment-method.ts | 2 +- assets/js/data/payment/test/utils.js | 25 ----------------- .../{ => utils}/check-payment-methods.ts | 18 ++++++------- .../filter-active-saved-payment-methods.ts} | 27 +------------------ .../{ => utils}/set-default-payment-method.ts | 2 +- checkstyle.xml | 11 +++----- 9 files changed, 22 insertions(+), 81 deletions(-) delete mode 100644 assets/js/data/payment/test/utils.js rename assets/js/data/payment/{ => utils}/check-payment-methods.ts (93%) rename assets/js/data/payment/{utils.ts => utils/filter-active-saved-payment-methods.ts} (52%) rename assets/js/data/payment/{ => utils}/set-default-payment-method.ts (96%) diff --git a/assets/js/data/payment/actions.ts b/assets/js/data/payment/actions.ts index 2dc481bcc12..675730fad26 100644 --- a/assets/js/data/payment/actions.ts +++ b/assets/js/data/payment/actions.ts @@ -10,8 +10,8 @@ import { * Internal dependencies */ import { ACTION_TYPES } from './action-types'; -import { checkPaymentMethodsCanPay } from './check-payment-methods'; -import { setDefaultPaymentMethod } from './set-default-payment-method'; +import { checkPaymentMethodsCanPay } from './utils/check-payment-methods'; +import { setDefaultPaymentMethod } from './utils/set-default-payment-method'; // `Thunks are functions that can be dispatched, similar to actions creators export * from './thunks'; diff --git a/assets/js/data/payment/selectors.ts b/assets/js/data/payment/selectors.ts index 9e82a506392..75418d7bb98 100644 --- a/assets/js/data/payment/selectors.ts +++ b/assets/js/data/payment/selectors.ts @@ -8,7 +8,7 @@ import deprecated from '@wordpress/deprecated'; * Internal dependencies */ import { PaymentMethodDataState } from './default-state'; -import { filterActiveSavedPaymentMethods } from './utils'; +import { filterActiveSavedPaymentMethods } from './utils/filter-active-saved-payment-methods'; import { STATUS as PAYMENT_STATUS } from './constants'; export const isPaymentPristine = ( state: PaymentMethodDataState ) => diff --git a/assets/js/data/payment/test/actions.ts b/assets/js/data/payment/test/actions.ts index f994bb9cf69..c76350461b2 100644 --- a/assets/js/data/payment/test/actions.ts +++ b/assets/js/data/payment/test/actions.ts @@ -1,13 +1,13 @@ /** * Internal dependencies */ -import * as setDefaultPaymentMethodFunctions from '../set-default-payment-method'; +import { setDefaultPaymentMethod as setDefaultPaymentMethodOriginal } from '../utils/set-default-payment-method'; import { PAYMENT_STORE_KEY } from '..'; import { PlainPaymentMethods } from '../../../types'; const originalDispatch = jest.requireActual( '@wordpress/data' ).dispatch; -jest.mock( '../set-default-payment-method', () => ( { +jest.mock( '../utils/set-default-payment-method', () => ( { setDefaultPaymentMethod: jest.fn(), } ) ); @@ -28,9 +28,7 @@ describe( 'payment data store actions', () => { Object.keys( paymentMethods )[ 0 ] ); actions.__internalSetAvailablePaymentMethods( paymentMethods ); - expect( - setDefaultPaymentMethodFunctions.setDefaultPaymentMethod - ).not.toBeCalled(); + expect( setDefaultPaymentMethodOriginal ).not.toBeCalled(); } ); it( 'Resets the default gateway if the current method is no longer available', () => { @@ -41,9 +39,7 @@ describe( 'payment data store actions', () => { actions.__internalSetAvailablePaymentMethods( [ paymentMethods[ Object.keys( paymentMethods )[ 0 ] ], ] ); - expect( - setDefaultPaymentMethodFunctions.setDefaultPaymentMethod - ).toBeCalled(); + expect( setDefaultPaymentMethodOriginal ).toBeCalled(); } ); } ); } ); diff --git a/assets/js/data/payment/test/set-default-payment-method.ts b/assets/js/data/payment/test/set-default-payment-method.ts index afd8a047676..c0ca7ae989c 100644 --- a/assets/js/data/payment/test/set-default-payment-method.ts +++ b/assets/js/data/payment/test/set-default-payment-method.ts @@ -7,7 +7,7 @@ import * as wpDataFunctions from '@wordpress/data'; /** * Internal dependencies */ -import { setDefaultPaymentMethod } from '../set-default-payment-method'; +import { setDefaultPaymentMethod } from '../utils/set-default-payment-method'; import { PlainPaymentMethods } from '../../../types'; import { PAYMENT_STORE_KEY } from '..'; diff --git a/assets/js/data/payment/test/utils.js b/assets/js/data/payment/test/utils.js deleted file mode 100644 index 2dbe67a4445..00000000000 --- a/assets/js/data/payment/test/utils.js +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Internal dependencies - */ -import { orderPaymentMethods } from '../utils'; - -describe( 'orderPaymentMethods', () => { - it( 'orders methods correctly', () => { - const order = [ 'cheque', 'cod', 'bacs', 'stripe' ]; - const methods = [ 'cod', 'bacs', 'stripe', 'cheque' ]; - const orderedMethods = orderPaymentMethods( order, methods ); - expect( orderedMethods ).toStrictEqual( order ); - } ); - it( 'orders methods correctly and appends missing ones', () => { - const order = [ 'cheque', 'cod', 'bacs', 'stripe' ]; - const methods = [ 'cod', 'paypal', 'bacs', 'stripe', 'cheque' ]; - const orderedMethods = orderPaymentMethods( order, methods ); - expect( orderedMethods ).toStrictEqual( [ - 'cheque', - 'cod', - 'bacs', - 'stripe', - 'paypal', - ] ); - } ); -} ); diff --git a/assets/js/data/payment/check-payment-methods.ts b/assets/js/data/payment/utils/check-payment-methods.ts similarity index 93% rename from assets/js/data/payment/check-payment-methods.ts rename to assets/js/data/payment/utils/check-payment-methods.ts index a92ff91e43c..b85734e04e0 100644 --- a/assets/js/data/payment/check-payment-methods.ts +++ b/assets/js/data/payment/utils/check-payment-methods.ts @@ -23,18 +23,18 @@ import { previewCart } from '@woocommerce/resource-previews'; /** * Internal dependencies */ -import { STORE_KEY as CART_STORE_KEY } from '../cart/constants'; -import { STORE_KEY as PAYMENT_STORE_KEY } from './constants'; -import { noticeContexts } from '../../base/context/event-emit'; +import { STORE_KEY as CART_STORE_KEY } from '../../cart/constants'; +import { STORE_KEY as PAYMENT_STORE_KEY } from '../constants'; +import { noticeContexts } from '../../../base/context/event-emit'; import { EMPTY_CART_ERRORS, EMPTY_CART_ITEM_ERRORS, EMPTY_EXTENSIONS, -} from '../../data/constants'; +} from '../../constants'; import { defaultBillingAddress, defaultShippingAddress, -} from '../../base/context/providers/cart-checkout/customer/constants'; +} from '../../../base/context/providers/cart-checkout/customer/constants'; export const checkPaymentMethodsCanPay = async ( express = false ) => { const isEditor = !! select( 'core/editor' ); @@ -129,11 +129,8 @@ export const checkPaymentMethodsCanPay = async ( express = false ) => { shippingRates: previewCart.shipping_rates, isLoadingRates: false, cartHasCalculatedShipping: previewCart.has_calculated_shipping, - paymentRequirements: previewCart.paymentRequirements, - receiveCart: - typeof previewCart?.receiveCart === 'function' - ? previewCart.receiveCart - : () => undefined, + paymentRequirements: previewCart.payment_requirements, + receiveCart: () => undefined, }; canPayArgument = { cart: cartForCanPayArgument, @@ -149,6 +146,7 @@ export const checkPaymentMethodsCanPay = async ( express = false ) => { }; } + // Order payment methods let paymentMethodsOrder; if ( express ) { paymentMethodsOrder = Object.keys( paymentMethods ); diff --git a/assets/js/data/payment/utils.ts b/assets/js/data/payment/utils/filter-active-saved-payment-methods.ts similarity index 52% rename from assets/js/data/payment/utils.ts rename to assets/js/data/payment/utils/filter-active-saved-payment-methods.ts index f95e83eabda..0c12b957f58 100644 --- a/assets/js/data/payment/utils.ts +++ b/assets/js/data/payment/utils/filter-active-saved-payment-methods.ts @@ -6,7 +6,7 @@ import { getPaymentMethods } from '@woocommerce/blocks-registry'; /** * Internal dependencies */ -import type { SavedPaymentMethods } from './types'; +import type { SavedPaymentMethods } from '../types'; /** * Gets the payment methods saved for the current user after filtering out disabled ones. @@ -47,28 +47,3 @@ export const filterActiveSavedPaymentMethods = ( } ); return activeSavedPaymentMethods; }; - -/** - * Given the order of methods from WooCommerce -> Payments, this method takes that order and sorts the list of available - * payment methods to match it. This is required to ensure the payment methods show up in the correct order in the - * Checkout - * - * @param order The order of payment methods from WooCommerce -> Settings -> Payments. - * @param methods The list of payment method names to add to the state as available. - * - * @return string[] The list of available methods in their correct order. - */ -export const orderPaymentMethods = ( order: string[], methods: string[] ) => { - const orderedMethods: string[] = []; - order.forEach( ( paymentMethodName ) => { - if ( methods.includes( paymentMethodName ) ) { - orderedMethods.push( paymentMethodName ); - } - } ); - // Now find any methods in `methods` that were not added to `orderedMethods` and append them to `orderedMethods` - methods - .filter( ( methodName ) => ! orderedMethods.includes( methodName ) ) - .forEach( ( methodName ) => orderedMethods.push( methodName ) ); - - return orderedMethods; -}; diff --git a/assets/js/data/payment/set-default-payment-method.ts b/assets/js/data/payment/utils/set-default-payment-method.ts similarity index 96% rename from assets/js/data/payment/set-default-payment-method.ts rename to assets/js/data/payment/utils/set-default-payment-method.ts index ff1474dbae0..bcf3d654d02 100644 --- a/assets/js/data/payment/set-default-payment-method.ts +++ b/assets/js/data/payment/utils/set-default-payment-method.ts @@ -7,7 +7,7 @@ import { PlainPaymentMethods } from '@woocommerce/type-defs/payments'; /** * Internal dependencies */ -import { STORE_KEY as PAYMENT_STORE_KEY } from './constants'; +import { STORE_KEY as PAYMENT_STORE_KEY } from '../constants'; export const setDefaultPaymentMethod = async ( paymentMethods: PlainPaymentMethods diff --git a/checkstyle.xml b/checkstyle.xml index 781e588603d..31a1cc1085c 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -666,15 +666,12 @@ - + - - - - - + - + From b7e090a0b10ff725a52bfe469643f5304830db8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alba=20Rinc=C3=B3n?= Date: Wed, 16 Nov 2022 14:37:41 +0100 Subject: [PATCH 07/27] Create the `Products by Attribute` template (#7660) * Create the `Products by Attribute` template * bot: update checkstyle.xml * Fix typo * Rename template to `taxonomy-product_attribute` * Rename test template file * bot: update checkstyle.xml * Fix test enabling archives for shade attribute Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- assets/js/blocks/classic-template/README.md | 2 +- .../js/blocks/classic-template/constants.ts | 7 ++ .../js/blocks/classic-template/test/utils.ts | 4 + src/BlockTemplatesController.php | 9 +- src/BlockTypes/ClassicTemplate.php | 3 +- src/Templates/ProductAttributeTemplate.php | 6 +- src/Utils/BlockTemplateUtils.php | 11 +- .../templates/taxonomy-product_attribute.html | 5 + tests/e2e/fixtures/fixture-data.js | 5 +- .../backend/site-editing-templates.test.js | 102 ++++++++++++++++++ .../taxonomy-product_attribute.html | 8 ++ 11 files changed, 149 insertions(+), 13 deletions(-) create mode 100644 templates/templates/taxonomy-product_attribute.html create mode 100644 tests/mocks/theme-with-woo-templates/block-templates/taxonomy-product_attribute.html diff --git a/assets/js/blocks/classic-template/README.md b/assets/js/blocks/classic-template/README.md index 71b1887cc69..e17e1a7525d 100644 --- a/assets/js/blocks/classic-template/README.md +++ b/assets/js/blocks/classic-template/README.md @@ -18,7 +18,7 @@ This block does not have any customizable options available, so any style or cus ### Props - `attributes` - - `template`: `single-product` | `archive-product` | `taxonomy-product_cat` | `taxonomy-product_tag` + - `template`: `single-product` | `archive-product` | `taxonomy-product_cat` | `taxonomy-product_tag` | `taxonomy-product_attribute` - `align`: `wide` | `full` ```html diff --git a/assets/js/blocks/classic-template/constants.ts b/assets/js/blocks/classic-template/constants.ts index dfeb37b3308..2e1252cf575 100644 --- a/assets/js/blocks/classic-template/constants.ts +++ b/assets/js/blocks/classic-template/constants.ts @@ -38,6 +38,13 @@ export const TEMPLATES: TemplateDetails = { ), placeholder: 'archive-product', }, + 'taxonomy-product_attribute': { + title: __( + 'WooCommerce Product Attribute Block', + 'woo-gutenberg-products-block' + ), + placeholder: 'archive-product', + }, 'product-search-results': { title: __( 'WooCommerce Product Search Results Block', diff --git a/assets/js/blocks/classic-template/test/utils.ts b/assets/js/blocks/classic-template/test/utils.ts index 8f413c21ab0..7f836ba9421 100644 --- a/assets/js/blocks/classic-template/test/utils.ts +++ b/assets/js/blocks/classic-template/test/utils.ts @@ -20,6 +20,10 @@ const TEMPLATES = { title: 'Product Taxonomy Title', placeholder: 'Product Taxonomy Placeholder', }, + 'taxonomy-product_attribute': { + title: 'Product Attribute Title', + placeholder: 'Product Attribute Placeholder', + }, }; describe( 'getTemplateDetailsBySlug', function () { diff --git a/src/BlockTemplatesController.php b/src/BlockTemplatesController.php index 5626068b3b8..a63278b7edc 100644 --- a/src/BlockTemplatesController.php +++ b/src/BlockTemplatesController.php @@ -2,6 +2,7 @@ namespace Automattic\WooCommerce\Blocks; use Automattic\WooCommerce\Blocks\Domain\Package; +use Automattic\WooCommerce\Blocks\Templates\ProductAttributeTemplate; use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils; /** @@ -110,7 +111,7 @@ public function get_block_file_template( $template, $id, $template_type ) { list( $template_id, $template_slug ) = $template_name_parts; - // If the theme has an archive-product.html template, but not a taxonomy-product_cat/tag.html template let's use the themes archive-product.html template. + // If the theme has an archive-product.html template, but not a taxonomy-product_cat/tag/attribute.html template let's use the themes archive-product.html template. if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback( $template_slug ) ) { $template_path = BlockTemplateUtils::get_theme_template_path( 'archive-product' ); $template_object = BlockTemplateUtils::create_new_block_template_object( $template_path, $template_type, $template_slug, true ); @@ -330,7 +331,7 @@ function ( $template ) use ( $template_slug ) { continue; } - // If the theme has an archive-product.html template, but not a taxonomy-product_cat.html template let's use the themes archive-product.html template. + // If the theme has an archive-product.html template, but not a taxonomy-product_cat/tag/attribute.html template let's use the themes archive-product.html template. if ( BlockTemplateUtils::template_is_eligible_for_product_archive_fallback( $template_slug ) ) { $template_file = BlockTemplateUtils::get_theme_template_path( 'archive-product' ); $templates[] = BlockTemplateUtils::create_new_block_template_object( $template_file, $template_type, $template_slug, true ); @@ -439,8 +440,8 @@ public function render_block_template() { } if ( isset( $queried_object->taxonomy ) && taxonomy_is_product_attribute( $queried_object->taxonomy ) && - ! BlockTemplateUtils::theme_has_template( 'archive-product' ) && - $this->block_template_is_available( 'archive-product' ) + ! BlockTemplateUtils::theme_has_template( ProductAttributeTemplate::SLUG ) && + $this->block_template_is_available( ProductAttributeTemplate::SLUG ) ) { add_filter( 'woocommerce_has_block_template', '__return_true', 10, 0 ); } diff --git a/src/BlockTypes/ClassicTemplate.php b/src/BlockTypes/ClassicTemplate.php index edb0903cb00..616ea349d9b 100644 --- a/src/BlockTypes/ClassicTemplate.php +++ b/src/BlockTypes/ClassicTemplate.php @@ -1,6 +1,7 @@ render_single_product(); diff --git a/src/Templates/ProductAttributeTemplate.php b/src/Templates/ProductAttributeTemplate.php index 566d0de89ab..35494fd93b2 100644 --- a/src/Templates/ProductAttributeTemplate.php +++ b/src/Templates/ProductAttributeTemplate.php @@ -8,7 +8,7 @@ * @internal */ class ProductAttributeTemplate { - const SLUG = 'archive-product'; + const SLUG = 'taxonomy-product_attribute'; /** * Constructor. @@ -25,14 +25,14 @@ protected function init() { } /** - * Render the Archive Product Template for product attributes. + * Renders the Product by Attribute template for product attributes taxonomy pages. * * @param array $templates Templates that match the product attributes taxonomy. */ public function update_taxonomy_template_hierarchy( $templates ) { $queried_object = get_queried_object(); if ( taxonomy_is_product_attribute( $queried_object->taxonomy ) && wc_current_theme_is_fse_theme() ) { - array_unshift( $templates, self::SLUG ); + array_splice( $templates, count( $templates ) - 1, 0, self::SLUG ); } return $templates; diff --git a/src/Utils/BlockTemplateUtils.php b/src/Utils/BlockTemplateUtils.php index 0655fd27f76..b298a7d7a06 100644 --- a/src/Utils/BlockTemplateUtils.php +++ b/src/Utils/BlockTemplateUtils.php @@ -1,6 +1,7 @@ _x( 'Products by Tag', 'Template name', 'woo-gutenberg-products-block' ), 'description' => __( 'Displays products filtered by a tag.', 'woo-gutenberg-products-block' ), ), + ProductAttributeTemplate::SLUG => array( + 'title' => _x( 'Products by Attribute', 'Template name', 'woo-gutenberg-products-block' ), + 'description' => __( 'Displays products filtered by an attribute.', 'woo-gutenberg-products-block' ), + ), ProductSearchResultsTemplate::SLUG => array( 'title' => _x( 'Product Search Results', 'Template name', 'woo-gutenberg-products-block' ), 'description' => __( 'Displays search results for your store.', 'woo-gutenberg-products-block' ), @@ -449,14 +454,14 @@ public static function get_block_template( $id, $template_type ) { /** * Checks if we can fallback to the `archive-product` template for a given slug * - * `taxonomy-product_cat` and `taxonomy-product_tag` templates can generally use the - * `archive-product` as a fallback if there are no specific overrides. + * `taxonomy-product_cat`, `taxonomy-product_tag`, `taxonomy-attribute` templates can + * generally use the `archive-product` as a fallback if there are no specific overrides. * * @param string $template_slug Slug to check for fallbacks. * @return boolean */ public static function template_is_eligible_for_product_archive_fallback( $template_slug ) { - $eligible_for_fallbacks = array( 'taxonomy-product_cat', 'taxonomy-product_tag' ); + $eligible_for_fallbacks = array( 'taxonomy-product_cat', 'taxonomy-product_tag', ProductAttributeTemplate::SLUG ); return in_array( $template_slug, $eligible_for_fallbacks, true ) && ! self::theme_has_template( $template_slug ) diff --git a/templates/templates/taxonomy-product_attribute.html b/templates/templates/taxonomy-product_attribute.html new file mode 100644 index 00000000000..7f3f66cd92d --- /dev/null +++ b/templates/templates/taxonomy-product_attribute.html @@ -0,0 +1,5 @@ + + +
+ + diff --git a/tests/e2e/fixtures/fixture-data.js b/tests/e2e/fixtures/fixture-data.js index 2fb4a4145ba..97194702bb5 100644 --- a/tests/e2e/fixtures/fixture-data.js +++ b/tests/e2e/fixtures/fixture-data.js @@ -26,7 +26,10 @@ const Attributes = () => [ ], }, { - attribute: { name: 'Shade' }, + attribute: { + name: 'Shade', + has_archives: true, + }, terms: [ { name: 'Red', diff --git a/tests/e2e/specs/backend/site-editing-templates.test.js b/tests/e2e/specs/backend/site-editing-templates.test.js index e0e4c15e042..948b0c41c35 100644 --- a/tests/e2e/specs/backend/site-editing-templates.test.js +++ b/tests/e2e/specs/backend/site-editing-templates.test.js @@ -93,6 +93,14 @@ const BLOCK_DATA = { }, name: 'woocommerce/legacy-template', }, + 'taxonomy-product_attribute': { + attributes: { + placeholder: 'archive-product', + template: 'taxonomy-product_attribute', + title: 'WooCommerce Product Attribute Block', + }, + name: 'woocommerce/legacy-template', + }, 'product-search-results': { attributes: { title: 'WooCommerce Product Search Results Block', @@ -516,6 +524,100 @@ describe( 'Store Editing Templates', () => { } ); } ); + describe( 'Products by Attribute template', () => { + it( 'default template from WooCommerce Blocks is available on an FSE theme', async () => { + const EXPECTED_TEMPLATE = defaultTemplateProps( + 'Products by Attribute' + ); + + await goToTemplatesList(); + + const templates = await getAllTemplates(); + + try { + expect( templates ).toContainEqual( EXPECTED_TEMPLATE ); + } catch ( ok ) { + // Depending on the speed of the execution and whether Chrome is headless or not + // the id might be parsed or not + + expect( templates ).toContainEqual( { + ...EXPECTED_TEMPLATE, + addedBy: WOOCOMMERCE_PARSED_ID, + } ); + } + } ); + + runOnlyWhenGutenbergIsDisabled( () => + it( 'should contain the "WooCommerce Product Taxonomy Block" classic template', async () => { + await goToTemplateEditor( { + postId: 'woocommerce/woocommerce//taxonomy-product_attribute', + } ); + + const [ classicBlock ] = await filterCurrentBlocks( + ( block ) => + block.name === + BLOCK_DATA[ 'taxonomy-product_attribute' ].name + ); + + expect( classicBlock.attributes.template ).toBe( + BLOCK_DATA[ 'taxonomy-product_attribute' ].attributes + .template + ); + expect( await getCurrentSiteEditorContent() ).toMatchSnapshot(); + } ) + ); + + it( 'should show the action menu if the template has been customized by the user', async () => { + const EXPECTED_TEMPLATE = { + ...defaultTemplateProps( 'Products by Attribute' ), + hasActions: true, + }; + + await visitTemplateAndAddCustomParagraph( + 'taxonomy-product_attribute' + ); + + await goToTemplatesList( { waitFor: 'actions' } ); + + const templates = await getAllTemplates(); + + try { + expect( templates ).toContainEqual( EXPECTED_TEMPLATE ); + } catch ( ok ) { + // Depending on the speed of the execution and whether Chrome is headless or not + // the id might be parsed or not + + expect( templates ).toContainEqual( { + ...EXPECTED_TEMPLATE, + addedBy: WOOCOMMERCE_PARSED_ID, + } ); + } + } ); + + it( 'should preserve and correctly show the user customization on the back-end', async () => { + await goToTemplateEditor( { + postId: 'woocommerce/woocommerce//taxonomy-product_attribute', + } ); + + await expect( canvas() ).toMatchElement( + SELECTORS.blocks.paragraph, + { + text: CUSTOMIZED_STRING, + timeout: DEFAULT_TIMEOUT, + } + ); + } ); + + it( 'should show the user customization on the front-end', async () => { + await page.goto( new URL( '/shade/red', BASE_URL ) ); + + await expect( page ).toMatchElement( 'p', { + text: CUSTOMIZED_STRING, + timeout: DEFAULT_TIMEOUT, + } ); + } ); + } ); + describe( 'Product Search Results block template', () => { it( 'default template from WooCommerce Blocks is available on an FSE theme', async () => { const EXPECTED_TEMPLATE = defaultTemplateProps( diff --git a/tests/mocks/theme-with-woo-templates/block-templates/taxonomy-product_attribute.html b/tests/mocks/theme-with-woo-templates/block-templates/taxonomy-product_attribute.html new file mode 100644 index 00000000000..7c0cb10777d --- /dev/null +++ b/tests/mocks/theme-with-woo-templates/block-templates/taxonomy-product_attribute.html @@ -0,0 +1,8 @@ + + +

Template loaded from theme

+
+ +
+ + From 1690e79fff7ad19d3316dab570d9e0621a7b38bd Mon Sep 17 00:00:00 2001 From: Thomas Roberts <5656702+opr@users.noreply.github.com> Date: Thu, 17 Nov 2022 13:33:58 +0000 Subject: [PATCH 08/27] Move `StoreNoticesContainer` to `@woocommerce/blocks-checkout` package and add tests (#7558) * Move StoreNoticesContainer to checkout package & convert to TS * Update @types/wordpres__notices version * Export StoreNoticesContainer from checkout package * Remove PropTypes from StoreNoticesContainer * Remove store-notices/index file * Update import of StoreNoticesContainer * Remove store notices export * Add docblock to StoreNoticesContainer component * Add tests for StoreNoticesContainer * bot: update checkstyle.xml * Fix typo and incorrect component name * Update import for StoreNoticesContainer * bot: update checkstyle.xml Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- assets/js/base/context/providers/index.js | 1 - .../context/providers/store-notices/index.js | 1 - .../express-payment/cart-express-payment.js | 6 +- .../checkout-express-payment.js | 7 +- .../payment-method-error-boundary.js | 2 +- assets/js/blocks/cart/block.js | 10 +- assets/js/blocks/checkout/block.tsx | 7 +- .../checkout-payment-block/frontend.tsx | 2 +- .../js/blocks/products/all-products/block.js | 2 +- assets/js/blocks/single-product/block.js | 2 +- checkstyle.xml | 57 +- .../block-client-apis/notices.md | 6 +- package-lock.json | 869 +++++++----------- package.json | 2 +- packages/checkout/components/index.js | 1 + .../store-notices-container/index.tsx | 34 +- .../store-notices-container}/style.scss | 0 .../store-notices-container/test/index.tsx | 104 +++ 18 files changed, 494 insertions(+), 619 deletions(-) delete mode 100644 assets/js/base/context/providers/store-notices/index.js rename assets/js/base/context/providers/store-notices/components/store-notices-container.js => packages/checkout/components/store-notices-container/index.tsx (72%) rename {assets/js/base/context/providers/store-notices/components => packages/checkout/components/store-notices-container}/style.scss (100%) create mode 100644 packages/checkout/components/store-notices-container/test/index.tsx diff --git a/assets/js/base/context/providers/index.js b/assets/js/base/context/providers/index.js index 88dacc9223e..73e64c7c2cc 100644 --- a/assets/js/base/context/providers/index.js +++ b/assets/js/base/context/providers/index.js @@ -1,7 +1,6 @@ export * from './editor-context'; export * from './add-to-cart-form'; export * from './cart-checkout'; -export * from './store-notices'; export * from './store-snackbar-notices'; export * from './container-width-context'; export * from './editor-context'; diff --git a/assets/js/base/context/providers/store-notices/index.js b/assets/js/base/context/providers/store-notices/index.js deleted file mode 100644 index 5279195fedb..00000000000 --- a/assets/js/base/context/providers/store-notices/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from './components/store-notices-container'; diff --git a/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment/cart-express-payment.js b/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment/cart-express-payment.js index f7014937157..19bd9b914ed 100644 --- a/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment/cart-express-payment.js +++ b/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment/cart-express-payment.js @@ -3,10 +3,8 @@ */ import { __ } from '@wordpress/i18n'; import { useExpressPaymentMethods } from '@woocommerce/base-context/hooks'; -import { - StoreNoticesContainer, - noticeContexts, -} from '@woocommerce/base-context'; +import { noticeContexts } from '@woocommerce/base-context'; +import { StoreNoticesContainer } from '@woocommerce/blocks-checkout'; import LoadingMask from '@woocommerce/base-components/loading-mask'; import { useSelect } from '@wordpress/data'; import { CHECKOUT_STORE_KEY, PAYMENT_STORE_KEY } from '@woocommerce/block-data'; diff --git a/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment/checkout-express-payment.js b/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment/checkout-express-payment.js index fe6a2da9c48..957e90fdc98 100644 --- a/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment/checkout-express-payment.js +++ b/assets/js/blocks/cart-checkout-shared/payment-methods/express-payment/checkout-express-payment.js @@ -2,11 +2,8 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { - StoreNoticesContainer, - useEditorContext, - noticeContexts, -} from '@woocommerce/base-context'; +import { useEditorContext, noticeContexts } from '@woocommerce/base-context'; +import { StoreNoticesContainer } from '@woocommerce/blocks-checkout'; import Title from '@woocommerce/base-components/title'; import LoadingMask from '@woocommerce/base-components/loading-mask'; import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings'; diff --git a/assets/js/blocks/cart-checkout-shared/payment-methods/payment-method-error-boundary.js b/assets/js/blocks/cart-checkout-shared/payment-methods/payment-method-error-boundary.js index aef36cc8a55..ea2904e7f49 100644 --- a/assets/js/blocks/cart-checkout-shared/payment-methods/payment-method-error-boundary.js +++ b/assets/js/blocks/cart-checkout-shared/payment-methods/payment-method-error-boundary.js @@ -5,7 +5,7 @@ import { __ } from '@wordpress/i18n'; import { Component } from 'react'; import PropTypes from 'prop-types'; import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings'; -import { StoreNoticesContainer } from '@woocommerce/base-context'; +import { StoreNoticesContainer } from '@woocommerce/blocks-checkout'; import { noticeContexts } from '@woocommerce/base-context/hooks'; class PaymentMethodErrorBoundary extends Component { diff --git a/assets/js/blocks/cart/block.js b/assets/js/blocks/cart/block.js index acb264e9d0d..42459b213a4 100644 --- a/assets/js/blocks/cart/block.js +++ b/assets/js/blocks/cart/block.js @@ -5,16 +5,16 @@ import { __ } from '@wordpress/i18n'; import { useStoreCart } from '@woocommerce/base-context/hooks'; import { useEffect } from '@wordpress/element'; import LoadingMask from '@woocommerce/base-components/loading-mask'; -import { - StoreNoticesContainer, - SnackbarNoticesContainer, -} from '@woocommerce/base-context'; +import { SnackbarNoticesContainer } from '@woocommerce/base-context'; import { CURRENT_USER_IS_ADMIN } from '@woocommerce/settings'; import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary'; import { translateJQueryEventToNative } from '@woocommerce/base-utils'; import withScrollToTop from '@woocommerce/base-hocs/with-scroll-to-top'; import { CartProvider } from '@woocommerce/base-context/providers'; -import { SlotFillProvider } from '@woocommerce/blocks-checkout'; +import { + SlotFillProvider, + StoreNoticesContainer, +} from '@woocommerce/blocks-checkout'; /** * Internal dependencies diff --git a/assets/js/blocks/checkout/block.tsx b/assets/js/blocks/checkout/block.tsx index be4e1be5de8..1e0bca0df2b 100644 --- a/assets/js/blocks/checkout/block.tsx +++ b/assets/js/blocks/checkout/block.tsx @@ -9,11 +9,14 @@ import { CheckoutProvider, SnackbarNoticesContainer, } from '@woocommerce/base-context'; -import { StoreNoticesContainer } from '@woocommerce/base-context/providers'; + import BlockErrorBoundary from '@woocommerce/base-components/block-error-boundary'; import { SidebarLayout } from '@woocommerce/base-components/sidebar-layout'; import { CURRENT_USER_IS_ADMIN, getSetting } from '@woocommerce/settings'; -import { SlotFillProvider } from '@woocommerce/blocks-checkout'; +import { + SlotFillProvider, + StoreNoticesContainer, +} from '@woocommerce/blocks-checkout'; import withScrollToTop from '@woocommerce/base-hocs/with-scroll-to-top'; import { useDispatch, useSelect } from '@wordpress/data'; import { diff --git a/assets/js/blocks/checkout/inner-blocks/checkout-payment-block/frontend.tsx b/assets/js/blocks/checkout/inner-blocks/checkout-payment-block/frontend.tsx index 5a8413bcb6b..1da7d62da9e 100644 --- a/assets/js/blocks/checkout/inner-blocks/checkout-payment-block/frontend.tsx +++ b/assets/js/blocks/checkout/inner-blocks/checkout-payment-block/frontend.tsx @@ -7,13 +7,13 @@ import { withFilteredAttributes } from '@woocommerce/shared-hocs'; import { FormStep } from '@woocommerce/base-components/cart-checkout'; import { useSelect } from '@wordpress/data'; import { CHECKOUT_STORE_KEY } from '@woocommerce/block-data'; +import { StoreNoticesContainer } from '@woocommerce/blocks-checkout'; /** * Internal dependencies */ import Block from './block'; import attributes from './attributes'; -import { StoreNoticesContainer } from '../../../../base/context/providers'; import { noticeContexts } from '../../../../base/context/event-emit'; const FrontendBlock = ( { diff --git a/assets/js/blocks/products/all-products/block.js b/assets/js/blocks/products/all-products/block.js index 6122b1324e5..26e92ca63a9 100644 --- a/assets/js/blocks/products/all-products/block.js +++ b/assets/js/blocks/products/all-products/block.js @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; import { ProductListContainer } from '@woocommerce/base-components/product-list'; import { InnerBlockLayoutContextProvider } from '@woocommerce/shared-context'; import { gridBlockPreview } from '@woocommerce/resource-previews'; -import { StoreNoticesContainer } from '@woocommerce/base-context'; +import { StoreNoticesContainer } from '@woocommerce/blocks-checkout'; /** * The All Products Block. diff --git a/assets/js/blocks/single-product/block.js b/assets/js/blocks/single-product/block.js index b07e05ae621..ea5b59f4cfc 100644 --- a/assets/js/blocks/single-product/block.js +++ b/assets/js/blocks/single-product/block.js @@ -7,7 +7,7 @@ import { InnerBlockLayoutContextProvider, ProductDataContextProvider, } from '@woocommerce/shared-context'; -import { StoreNoticesContainer } from '@woocommerce/base-context'; +import { StoreNoticesContainer } from '@woocommerce/blocks-checkout'; import { useStoreEvents } from '@woocommerce/base-context/hooks'; /** diff --git a/checkstyle.xml b/checkstyle.xml index 31a1cc1085c..1794c23708b 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -356,6 +356,16 @@
+ + + + + + + + + + @@ -635,9 +645,6 @@ - - - @@ -721,6 +728,10 @@ Type 'Object' is not assignable to type 'AnyAction | Generator<any, any, unknown>'. The 'Object' type is assignable to very few other types. Did you mean to use the 'any' type instead?" source="TS2322" /> + + + @@ -773,16 +784,6 @@ - - - - - - - - - - @@ -1064,18 +1065,10 @@
- - - - - - - - + @@ -2235,6 +2228,8 @@ + - - + - @@ -4190,6 +4185,18 @@ + + + + + + + + + + + + diff --git a/docs/internal-developers/block-client-apis/notices.md b/docs/internal-developers/block-client-apis/notices.md index 253dca4af41..382f6d84e44 100644 --- a/docs/internal-developers/block-client-apis/notices.md +++ b/docs/internal-developers/block-client-apis/notices.md @@ -9,7 +9,7 @@ ## Notices in WooCommerce Blocks -WooCommerce Blocks uses the [`@wordpress/notices`](https://github.com/WordPress/gutenberg/blob/d9eb36d80e05b4e45b1ad8462c8bace4e9cf1f6f/docs/reference-guides/data/data-core-notices.md) package to display notices in the frontend. For more information on the actiona and selectors available on this data store, please review [the `@wordpress/notices` documentation](https://github.com/WordPress/gutenberg/blob/d9eb36d80e05b4e45b1ad8462c8bace4e9cf1f6f/docs/reference-guides/data/data-core-notices.md) +WooCommerce Blocks uses the [`@wordpress/notices`](https://github.com/WordPress/gutenberg/blob/d9eb36d80e05b4e45b1ad8462c8bace4e9cf1f6f/docs/reference-guides/data/data-core-notices.md) package to display notices in the frontend. For more information on the actions and selectors available on this data store, please review [the `@wordpress/notices` documentation](https://github.com/WordPress/gutenberg/blob/d9eb36d80e05b4e45b1ad8462c8bace4e9cf1f6f/docs/reference-guides/data/data-core-notices.md) ### `StoreNoticesContainer` @@ -17,10 +17,10 @@ To display notices of a certain context, use the `StoreNoticesContainer` and pas The below example will show all notices with type `default` that are in the `wc/cart` context. If no context prop is passed, then the `default` context will be used. -On the Cart Block, a `StoreNoticesContainer` is already rendered with the `wc/cart` context, and on the Checkout Block, a `StoreNoticesContainer` is already rendered with the `wc/checkout` context. To display errors from other contexts, you can use the `StoreNoticesProvider` component with context passed as a prop. +On the Cart Block, a `StoreNoticesContainer` is already rendered with the `wc/cart` context, and on the Checkout Block, a `StoreNoticesContainer` is already rendered with the `wc/checkout` context. To display errors from other contexts, you can use the `StoreNoticesContainer` component with context passed as a prop. ```jsx -import { StoreNoticesContainer } from '@woocommerce/base-components/store-notices-container'; +import { StoreNoticesContainer } from '@woocommerce/blocks-checkout'; const PaymentErrors = () => { return ; diff --git a/package-lock.json b/package-lock.json index ed32c3b03d0..bf6c6b0b471 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,7 +76,7 @@ "@types/wordpress__data": "^6.0.1", "@types/wordpress__data-controls": "2.2.0", "@types/wordpress__editor": "^11.0.0", - "@types/wordpress__notices": "^3.3.0", + "@types/wordpress__notices": "^3.5.0", "@typescript-eslint/eslint-plugin": "5.30.5", "@typescript-eslint/parser": "5.35.1", "@woocommerce/api": "0.2.0", @@ -94,7 +94,7 @@ "@wordpress/data-controls": "2.2.7", "@wordpress/dependency-extraction-webpack-plugin": "3.2.1", "@wordpress/dom": "3.16.0", - "@wordpress/e2e-test-utils": "^8.4.0", + "@wordpress/e2e-test-utils": "8.4.0", "@wordpress/e2e-tests": "4.6.0", "@wordpress/element": "4.0.4", "@wordpress/env": "^4.9.0", @@ -4221,18 +4221,16 @@ }, "node_modules/@kwsites/file-exists": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", - "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.1.1" } }, "node_modules/@kwsites/promise-deferred": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", - "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.3", @@ -5547,10 +5545,9 @@ "peer": true }, "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "version": "2.1.1", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -11291,7 +11288,7 @@ } }, "node_modules/@types/wordpress__notices": { - "version": "3.3.0", + "version": "3.5.0", "dev": true, "license": "MIT", "dependencies": { @@ -13380,27 +13377,27 @@ } }, "node_modules/@wordpress/e2e-test-utils/node_modules/@wordpress/api-fetch": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.17.0.tgz", - "integrity": "sha512-FiK0OmkWOxoEa1XwsPhkDCRZk7QNFOOxidsavU76DG1AHqy+gVeYbbQxC1npZSQZpW6nl4mSkzv81kE3IQONuA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.19.0.tgz", + "integrity": "sha512-nidem0S47aulcXzIjy5oQrC/nKrVtSkEEE0nmHQAp/bx2ZYBu7UwByiTfbI3bxLKRPhtdgLBkQfyA7eUlegGPQ==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.20.0", - "@wordpress/url": "^3.21.0" + "@wordpress/i18n": "^4.22.0", + "@wordpress/url": "^3.23.0" }, "engines": { "node": ">=12" } }, "node_modules/@wordpress/e2e-test-utils/node_modules/@wordpress/i18n": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.20.0.tgz", - "integrity": "sha512-jCM5z2p7If5q/T+PqAYaM9oe4N04D4wvH+2gE08ava2w7ORkVHoe1uCyWNcVBEswpFAnLCG+c6Lsbs8m3Go+aA==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.22.0.tgz", + "integrity": "sha512-b1nQJhrBilDj3oJql9k9dzlPEJ5vWd36Q0ri0znLBOJUOq2J0jgKwgtC84dun77kBb9Upfi4NZNiBI8OuSbiuA==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.20.0", + "@wordpress/hooks": "^3.22.0", "gettext-parser": "^1.3.1", "memize": "^1.1.0", "sprintf-js": "^1.1.1", @@ -13414,9 +13411,9 @@ } }, "node_modules/@wordpress/e2e-test-utils/node_modules/@wordpress/url": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.21.0.tgz", - "integrity": "sha512-lCsM9RG0U8rYeLJ9T9BCX4v+kis2Cif5zbHA/wLhjO1ndfMmdOIOycEiTl1d9wOyjau97jKZTMqf2xF4HH5ZHA==", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.23.0.tgz", + "integrity": "sha512-JBNrzSUg7+b4cpJQjDVTHAw8x77EcdLWOAxLlKqI37Pd2EHUZXWnlVU5EqbNLLhXVJ+/6QMzS3QqNILhjIiqdw==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -13428,9 +13425,8 @@ }, "node_modules/@wordpress/e2e-test-utils/node_modules/change-case": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", - "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", "dev": true, + "license": "MIT", "dependencies": { "camel-case": "^4.1.2", "capital-case": "^1.0.4", @@ -13448,9 +13444,8 @@ }, "node_modules/@wordpress/e2e-test-utils/node_modules/constant-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", - "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", "dev": true, + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -13459,9 +13454,8 @@ }, "node_modules/@wordpress/e2e-test-utils/node_modules/dot-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -13482,9 +13476,8 @@ }, "node_modules/@wordpress/e2e-test-utils/node_modules/path-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", - "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", "dev": true, + "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -13492,9 +13485,8 @@ }, "node_modules/@wordpress/e2e-test-utils/node_modules/sentence-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", - "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", "dev": true, + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -13503,9 +13495,8 @@ }, "node_modules/@wordpress/e2e-test-utils/node_modules/snake-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", "dev": true, + "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -13513,18 +13504,16 @@ }, "node_modules/@wordpress/e2e-test-utils/node_modules/upper-case": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", - "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } }, "node_modules/@wordpress/e2e-test-utils/node_modules/upper-case-first": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -13891,14 +13880,13 @@ } }, "node_modules/@wordpress/e2e-tests/node_modules/@wordpress/api-fetch": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.17.0.tgz", - "integrity": "sha512-FiK0OmkWOxoEa1XwsPhkDCRZk7QNFOOxidsavU76DG1AHqy+gVeYbbQxC1npZSQZpW6nl4mSkzv81kE3IQONuA==", + "version": "6.16.0", "dev": true, + "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.20.0", - "@wordpress/url": "^3.21.0" + "@wordpress/i18n": "^4.19.0", + "@wordpress/url": "^3.20.0" }, "engines": { "node": ">=12" @@ -13951,9 +13939,8 @@ }, "node_modules/@wordpress/e2e-tests/node_modules/@wordpress/e2e-test-utils": { "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-7.11.0.tgz", - "integrity": "sha512-4SbCQnCOi83F+xQwGdMgfJhNfL1925Qd/FYRh2rZ9pdXskb6Ml3rK8tCL951ereBWoYwyeb7DDllmiMnm9iACw==", "dev": true, + "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", "@wordpress/api-fetch": "^6.12.0", @@ -14031,13 +14018,12 @@ } }, "node_modules/@wordpress/e2e-tests/node_modules/@wordpress/i18n": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.20.0.tgz", - "integrity": "sha512-jCM5z2p7If5q/T+PqAYaM9oe4N04D4wvH+2gE08ava2w7ORkVHoe1uCyWNcVBEswpFAnLCG+c6Lsbs8m3Go+aA==", + "version": "4.19.0", "dev": true, + "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.20.0", + "@wordpress/hooks": "^3.19.0", "gettext-parser": "^1.3.1", "memize": "^1.1.0", "sprintf-js": "^1.1.1", @@ -14148,10 +14134,9 @@ } }, "node_modules/@wordpress/e2e-tests/node_modules/@wordpress/url": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.21.0.tgz", - "integrity": "sha512-lCsM9RG0U8rYeLJ9T9BCX4v+kis2Cif5zbHA/wLhjO1ndfMmdOIOycEiTl1d9wOyjau97jKZTMqf2xF4HH5ZHA==", + "version": "3.20.0", "dev": true, + "license": "GPL-2.0-or-later", "dependencies": { "@babel/runtime": "^7.16.0", "remove-accents": "^0.4.2" @@ -14204,9 +14189,8 @@ }, "node_modules/@wordpress/e2e-tests/node_modules/change-case": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", - "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", "dev": true, + "license": "MIT", "dependencies": { "camel-case": "^4.1.2", "capital-case": "^1.0.4", @@ -14242,9 +14226,8 @@ }, "node_modules/@wordpress/e2e-tests/node_modules/constant-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", - "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", "dev": true, + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -14319,9 +14302,8 @@ }, "node_modules/@wordpress/e2e-tests/node_modules/dot-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -14400,9 +14382,8 @@ }, "node_modules/@wordpress/e2e-tests/node_modules/form-data": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -14912,9 +14893,8 @@ }, "node_modules/@wordpress/e2e-tests/node_modules/path-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", - "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", "dev": true, + "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -15073,9 +15053,8 @@ }, "node_modules/@wordpress/e2e-tests/node_modules/sentence-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", - "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", "dev": true, + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -15122,9 +15101,8 @@ }, "node_modules/@wordpress/e2e-tests/node_modules/snake-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", "dev": true, + "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -15237,18 +15215,16 @@ }, "node_modules/@wordpress/e2e-tests/node_modules/upper-case": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", - "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } }, "node_modules/@wordpress/e2e-tests/node_modules/upper-case-first": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -15443,9 +15419,8 @@ }, "node_modules/@wordpress/env": { "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-4.9.0.tgz", - "integrity": "sha512-C2g5aOYxl1Bd9lypvEMjXZ1s1Gx/JHpFWuPlCAI8gAzwzB9jCIZkqpU85GsGScpZLAANS/N7wF3LMY68UkN9fQ==", "dev": true, + "license": "GPL-2.0-or-later", "dependencies": { "chalk": "^4.0.0", "copy-dir": "^1.3.0", @@ -15464,96 +15439,6 @@ "wp-env": "bin/wp-env" } }, - "node_modules/@wordpress/env/node_modules/@sindresorhus/is": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", - "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@wordpress/env/node_modules/cacheable-lookup": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz", - "integrity": "sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg==", - "dev": true, - "dependencies": { - "@types/keyv": "^3.1.1", - "keyv": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wordpress/env/node_modules/decompress-response": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz", - "integrity": "sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==", - "dev": true, - "dependencies": { - "mimic-response": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@wordpress/env/node_modules/got": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/got/-/got-10.7.0.tgz", - "integrity": "sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^2.0.0", - "@szmarczak/http-timer": "^4.0.0", - "@types/cacheable-request": "^6.0.1", - "cacheable-lookup": "^2.0.0", - "cacheable-request": "^7.0.1", - "decompress-response": "^5.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^5.0.0", - "lowercase-keys": "^2.0.0", - "mimic-response": "^2.1.0", - "p-cancelable": "^2.0.0", - "p-event": "^4.0.0", - "responselike": "^2.0.0", - "to-readable-stream": "^2.0.0", - "type-fest": "^0.10.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/@wordpress/env/node_modules/mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wordpress/env/node_modules/type-fest": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.10.0.tgz", - "integrity": "sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@wordpress/escape-html": { "version": "2.14.0", "license": "GPL-2.0-or-later", @@ -15621,9 +15506,9 @@ } }, "node_modules/@wordpress/hooks": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.20.0.tgz", - "integrity": "sha512-OMOJwmbubrKueXhXEyBNU8CXBycawmtXCWbhqgYYbihgecB7cSZ1kAAPz+Oi/5j+3+XDfSlZXgWM1lCwvfnzPQ==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.22.0.tgz", + "integrity": "sha512-0pjpXzUDiiIlQGRcOCHO5N73eto367KrevFhTPn8NSK8rhNqL7XaA3YJRIBemViwsk1GaPUzheg9E3UmIL0W4g==", "dependencies": { "@babel/runtime": "^7.16.0" }, @@ -15795,12 +15680,12 @@ "license": "MIT" }, "node_modules/@wordpress/keycodes": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.20.0.tgz", - "integrity": "sha512-2a7+HOAOvT7Y9sccRTHQytQo+bNza1kvL4E2D+bYCt3x0s2F9EmBUGy+CScvhy4IHInvemaUviwyUBZDM30pUw==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.22.0.tgz", + "integrity": "sha512-nWEVm1hJdcDh5EJ6IEO4chqsZxDCt5qYyaUPjzFDtEM65abcMnbE7rBT36WP17slSJlPN8Y8HldajERwvKXR6Q==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.20.0", + "@wordpress/i18n": "^4.22.0", "change-case": "^4.1.2", "lodash": "^4.17.21" }, @@ -15809,12 +15694,12 @@ } }, "node_modules/@wordpress/keycodes/node_modules/@wordpress/i18n": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.20.0.tgz", - "integrity": "sha512-jCM5z2p7If5q/T+PqAYaM9oe4N04D4wvH+2gE08ava2w7ORkVHoe1uCyWNcVBEswpFAnLCG+c6Lsbs8m3Go+aA==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.22.0.tgz", + "integrity": "sha512-b1nQJhrBilDj3oJql9k9dzlPEJ5vWd36Q0ri0znLBOJUOq2J0jgKwgtC84dun77kBb9Upfi4NZNiBI8OuSbiuA==", "dependencies": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.20.0", + "@wordpress/hooks": "^3.22.0", "gettext-parser": "^1.3.1", "memize": "^1.1.0", "sprintf-js": "^1.1.1", @@ -15829,8 +15714,7 @@ }, "node_modules/@wordpress/keycodes/node_modules/change-case": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", - "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "license": "MIT", "dependencies": { "camel-case": "^4.1.2", "capital-case": "^1.0.4", @@ -15848,8 +15732,7 @@ }, "node_modules/@wordpress/keycodes/node_modules/constant-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", - "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -15858,8 +15741,7 @@ }, "node_modules/@wordpress/keycodes/node_modules/dot-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -15867,8 +15749,7 @@ }, "node_modules/@wordpress/keycodes/node_modules/path-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", - "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -15876,8 +15757,7 @@ }, "node_modules/@wordpress/keycodes/node_modules/sentence-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", - "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -15886,8 +15766,7 @@ }, "node_modules/@wordpress/keycodes/node_modules/snake-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -15895,16 +15774,14 @@ }, "node_modules/@wordpress/keycodes/node_modules/upper-case": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", - "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } }, "node_modules/@wordpress/keycodes/node_modules/upper-case-first": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -20320,12 +20197,15 @@ } }, "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "version": "2.0.1", "dev": true, + "license": "MIT", + "dependencies": { + "@types/keyv": "^3.1.1", + "keyv": "^4.0.0" + }, "engines": { - "node": ">=10.6.0" + "node": ">=10" } }, "node_modules/cacheable-request": { @@ -20476,8 +20356,7 @@ }, "node_modules/capital-case": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", - "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -20486,8 +20365,7 @@ }, "node_modules/capital-case/node_modules/upper-case-first": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "license": "MIT", "dependencies": { "tslib": "^2.0.3" } @@ -20715,9 +20593,8 @@ }, "node_modules/chardet": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/charenc": { "version": "0.0.2", @@ -21128,9 +21005,8 @@ }, "node_modules/cli-width": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true, + "license": "ISC", "engines": { "node": ">= 10" } @@ -21829,9 +21705,8 @@ }, "node_modules/copy-dir": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/copy-dir/-/copy-dir-1.3.0.tgz", - "integrity": "sha512-Q4+qBFnN4bwGwvtXXzbp4P/4iNk0MaiGAzvQ8OiMtlLjkIKjmNN689uVzShSM0908q7GoFHXIPx4zi75ocoaHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/copy-webpack-plugin": { "version": "6.4.1", @@ -23565,18 +23440,14 @@ } }, "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "version": "5.0.0", "dev": true, + "license": "MIT", "dependencies": { - "mimic-response": "^3.1.0" + "mimic-response": "^2.0.0" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/dedent": { @@ -24381,9 +24252,8 @@ }, "node_modules/docker-compose": { "version": "0.22.2", - "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.22.2.tgz", - "integrity": "sha512-iXWb5+LiYmylIMFXvGTYsjI1F+Xyx78Jm/uj1dxwwZLbWkUdH6yOXY5Nr3RjbYX15EgbGJCq78d29CmWQQQMPg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6.0.0" } @@ -26224,9 +26094,8 @@ }, "node_modules/external-editor": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "dev": true, + "license": "MIT", "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -26474,9 +26343,8 @@ }, "node_modules/figures": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -26489,9 +26357,8 @@ }, "node_modules/figures/node_modules/escape-string-regexp": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -27773,6 +27640,17 @@ "node": ">=12" } }, + "node_modules/github-label-sync/node_modules/@sindresorhus/is": { + "version": "4.6.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/github-label-sync/node_modules/ajv": { "version": "8.11.0", "dev": true, @@ -27788,6 +27666,14 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/github-label-sync/node_modules/cacheable-lookup": { + "version": "5.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, "node_modules/github-label-sync/node_modules/commander": { "version": "6.2.1", "dev": true, @@ -27796,11 +27682,60 @@ "node": ">= 6" } }, + "node_modules/github-label-sync/node_modules/decompress-response": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/github-label-sync/node_modules/got": { + "version": "11.8.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, "node_modules/github-label-sync/node_modules/json-schema-traverse": { "version": "1.0.0", "dev": true, "license": "MIT" }, + "node_modules/github-label-sync/node_modules/mimic-response": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/github-slugger": { "version": "1.4.0", "dev": true, @@ -27987,30 +27922,44 @@ } }, "node_modules/got": { - "version": "11.8.5", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", - "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "version": "10.7.0", "dev": true, + "license": "MIT", "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", + "@sindresorhus/is": "^2.0.0", + "@szmarczak/http-timer": "^4.0.0", "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", + "cacheable-lookup": "^2.0.0", + "cacheable-request": "^7.0.1", + "decompress-response": "^5.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^5.0.0", "lowercase-keys": "^2.0.0", + "mimic-response": "^2.1.0", "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "p-event": "^4.0.0", + "responselike": "^2.0.0", + "to-readable-stream": "^2.0.0", + "type-fest": "^0.10.0" }, "engines": { - "node": ">=10.19.0" + "node": ">=10" }, "funding": { "url": "https://github.com/sindresorhus/got?sponsor=1" } }, + "node_modules/got/node_modules/type-fest": { + "version": "0.10.0", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.10", "devOptional": true, @@ -28433,8 +28382,7 @@ }, "node_modules/header-case": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", - "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "license": "MIT", "dependencies": { "capital-case": "^1.0.4", "tslib": "^2.0.3" @@ -28577,8 +28525,7 @@ }, "node_modules/html-dom-parser": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-3.1.2.tgz", - "integrity": "sha512-mLTtl3pVn3HnqZSZzW3xVs/mJAKrG1yIw3wlp+9bdoZHHLaBRvELdpfShiPVLyjPypq1Fugv2KMDoGHW4lVXnw==", + "license": "MIT", "dependencies": { "domhandler": "5.0.3", "htmlparser2": "8.0.1" @@ -28586,8 +28533,7 @@ }, "node_modules/html-dom-parser/node_modules/dom-serializer": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -28599,8 +28545,7 @@ }, "node_modules/html-dom-parser/node_modules/domhandler": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -28613,8 +28558,7 @@ }, "node_modules/html-dom-parser/node_modules/domutils": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", - "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -28626,8 +28570,7 @@ }, "node_modules/html-dom-parser/node_modules/entities": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -28637,8 +28580,6 @@ }, "node_modules/html-dom-parser/node_modules/htmlparser2": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", - "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -28646,6 +28587,7 @@ "url": "https://github.com/sponsors/fb55" } ], + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -28716,8 +28658,7 @@ }, "node_modules/html-react-parser": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-3.0.4.tgz", - "integrity": "sha512-va68PSmC7uA6PbOEc9yuw5Mu3OHPXmFKUpkLGvUPdTuNrZ0CJZk1s/8X/FaHjswK/6uZghu2U02tJjussT8+uw==", + "license": "MIT", "dependencies": { "domhandler": "5.0.3", "html-dom-parser": "3.1.2", @@ -28730,8 +28671,7 @@ }, "node_modules/html-react-parser/node_modules/domhandler": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -29446,9 +29386,8 @@ }, "node_modules/inquirer": { "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.0", @@ -35144,9 +35083,8 @@ }, "node_modules/log-symbols": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^2.4.2" }, @@ -35156,9 +35094,8 @@ }, "node_modules/log-symbols/node_modules/ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -35168,9 +35105,8 @@ }, "node_modules/log-symbols/node_modules/chalk": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -35182,42 +35118,37 @@ }, "node_modules/log-symbols/node_modules/color-convert": { "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "1.1.3" } }, "node_modules/log-symbols/node_modules/color-name": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/log-symbols/node_modules/escape-string-regexp": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/log-symbols/node_modules/has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/log-symbols/node_modules/supports-color": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -37214,12 +37145,11 @@ } }, "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "version": "2.1.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -38459,9 +38389,8 @@ }, "node_modules/ora": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-4.1.1.tgz", - "integrity": "sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^3.0.0", "cli-cursor": "^3.1.0", @@ -38481,9 +38410,8 @@ }, "node_modules/ora/node_modules/chalk": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -42217,8 +42145,7 @@ }, "node_modules/react-property": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz", - "integrity": "sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==" + "license": "MIT" }, "node_modules/react-refresh": { "version": "0.11.0", @@ -43492,9 +43419,8 @@ }, "node_modules/run-async": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -43573,9 +43499,8 @@ }, "node_modules/rxjs": { "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^1.9.0" }, @@ -43585,9 +43510,8 @@ }, "node_modules/rxjs/node_modules/tslib": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "dev": true, + "license": "0BSD" }, "node_modules/safe-buffer": { "version": "5.1.2", @@ -44449,18 +44373,17 @@ "license": "ISC" }, "node_modules/simple-git": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.14.1.tgz", - "integrity": "sha512-1ThF4PamK9wBORVGMK9HK5si4zoGS2GpRO7tkAFObA4FZv6dKaCVHLQT+8zlgiBm6K2h+wEU9yOaFCu/SR3OyA==", + "version": "3.7.1", "dev": true, + "license": "MIT", "dependencies": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", - "debug": "^4.3.4" + "debug": "^4.3.3" }, "funding": { "type": "github", - "url": "https://github.com/steveukx/git-js?sponsor=1" + "url": "https://github.com/sponsors/steveukx/" } }, "node_modules/simple-html-tokenizer": { @@ -45491,8 +45414,7 @@ }, "node_modules/style-to-js": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.1.tgz", - "integrity": "sha512-RJ18Z9t2B02sYhZtfWKQq5uplVctgvjTfLWT7+Eb1zjUjIrWzX5SdlkwLGQozrqarTmEzJJ/YmdNJCUNI47elg==", + "license": "MIT", "dependencies": { "style-to-object": "0.3.0" } @@ -46579,9 +46501,8 @@ }, "node_modules/to-readable-stream": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-2.1.0.tgz", - "integrity": "sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -52768,8 +52689,6 @@ }, "@kwsites/file-exists": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", - "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", "dev": true, "requires": { "debug": "^4.1.1" @@ -52777,8 +52696,6 @@ }, "@kwsites/promise-deferred": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", - "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", "dev": true }, "@leichtgewicht/ip-codec": { @@ -53733,9 +53650,7 @@ "peer": true }, "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "version": "2.1.1", "dev": true }, "@sinonjs/commons": { @@ -57583,7 +57498,7 @@ } }, "@types/wordpress__notices": { - "version": "3.3.0", + "version": "3.5.0", "dev": true, "requires": { "@types/react": "*" @@ -59088,24 +59003,24 @@ }, "dependencies": { "@wordpress/api-fetch": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.17.0.tgz", - "integrity": "sha512-FiK0OmkWOxoEa1XwsPhkDCRZk7QNFOOxidsavU76DG1AHqy+gVeYbbQxC1npZSQZpW6nl4mSkzv81kE3IQONuA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.19.0.tgz", + "integrity": "sha512-nidem0S47aulcXzIjy5oQrC/nKrVtSkEEE0nmHQAp/bx2ZYBu7UwByiTfbI3bxLKRPhtdgLBkQfyA7eUlegGPQ==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.20.0", - "@wordpress/url": "^3.21.0" + "@wordpress/i18n": "^4.22.0", + "@wordpress/url": "^3.23.0" } }, "@wordpress/i18n": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.20.0.tgz", - "integrity": "sha512-jCM5z2p7If5q/T+PqAYaM9oe4N04D4wvH+2gE08ava2w7ORkVHoe1uCyWNcVBEswpFAnLCG+c6Lsbs8m3Go+aA==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.22.0.tgz", + "integrity": "sha512-b1nQJhrBilDj3oJql9k9dzlPEJ5vWd36Q0ri0znLBOJUOq2J0jgKwgtC84dun77kBb9Upfi4NZNiBI8OuSbiuA==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.20.0", + "@wordpress/hooks": "^3.22.0", "gettext-parser": "^1.3.1", "memize": "^1.1.0", "sprintf-js": "^1.1.1", @@ -59113,9 +59028,9 @@ } }, "@wordpress/url": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.21.0.tgz", - "integrity": "sha512-lCsM9RG0U8rYeLJ9T9BCX4v+kis2Cif5zbHA/wLhjO1ndfMmdOIOycEiTl1d9wOyjau97jKZTMqf2xF4HH5ZHA==", + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.23.0.tgz", + "integrity": "sha512-JBNrzSUg7+b4cpJQjDVTHAw8x77EcdLWOAxLlKqI37Pd2EHUZXWnlVU5EqbNLLhXVJ+/6QMzS3QqNILhjIiqdw==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", @@ -59124,8 +59039,6 @@ }, "change-case": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", - "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", "dev": true, "requires": { "camel-case": "^4.1.2", @@ -59144,8 +59057,6 @@ }, "constant-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", - "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", "dev": true, "requires": { "no-case": "^3.0.4", @@ -59155,8 +59066,6 @@ }, "dot-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, "requires": { "no-case": "^3.0.4", @@ -59174,8 +59083,6 @@ }, "path-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", - "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", "dev": true, "requires": { "dot-case": "^3.0.4", @@ -59184,8 +59091,6 @@ }, "sentence-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", - "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", "dev": true, "requires": { "no-case": "^3.0.4", @@ -59195,8 +59100,6 @@ }, "snake-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", "dev": true, "requires": { "dot-case": "^3.0.4", @@ -59205,8 +59108,6 @@ }, "upper-case": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", - "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", "dev": true, "requires": { "tslib": "^2.0.3" @@ -59214,8 +59115,6 @@ }, "upper-case-first": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", "dev": true, "requires": { "tslib": "^2.0.3" @@ -59496,14 +59395,12 @@ "requires": {} }, "@wordpress/api-fetch": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@wordpress/api-fetch/-/api-fetch-6.17.0.tgz", - "integrity": "sha512-FiK0OmkWOxoEa1XwsPhkDCRZk7QNFOOxidsavU76DG1AHqy+gVeYbbQxC1npZSQZpW6nl4mSkzv81kE3IQONuA==", + "version": "6.16.0", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.20.0", - "@wordpress/url": "^3.21.0" + "@wordpress/i18n": "^4.19.0", + "@wordpress/url": "^3.20.0" } }, "@wordpress/babel-preset-default": { @@ -59538,8 +59435,6 @@ }, "@wordpress/e2e-test-utils": { "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils/-/e2e-test-utils-7.11.0.tgz", - "integrity": "sha512-4SbCQnCOi83F+xQwGdMgfJhNfL1925Qd/FYRh2rZ9pdXskb6Ml3rK8tCL951ereBWoYwyeb7DDllmiMnm9iACw==", "dev": true, "requires": { "@babel/runtime": "^7.16.0", @@ -59588,13 +59483,11 @@ } }, "@wordpress/i18n": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.20.0.tgz", - "integrity": "sha512-jCM5z2p7If5q/T+PqAYaM9oe4N04D4wvH+2gE08ava2w7ORkVHoe1uCyWNcVBEswpFAnLCG+c6Lsbs8m3Go+aA==", + "version": "4.19.0", "dev": true, "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.20.0", + "@wordpress/hooks": "^3.19.0", "gettext-parser": "^1.3.1", "memize": "^1.1.0", "sprintf-js": "^1.1.1", @@ -59674,9 +59567,7 @@ } }, "@wordpress/url": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/@wordpress/url/-/url-3.21.0.tgz", - "integrity": "sha512-lCsM9RG0U8rYeLJ9T9BCX4v+kis2Cif5zbHA/wLhjO1ndfMmdOIOycEiTl1d9wOyjau97jKZTMqf2xF4HH5ZHA==", + "version": "3.20.0", "dev": true, "requires": { "@babel/runtime": "^7.16.0", @@ -59710,8 +59601,6 @@ }, "change-case": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", - "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", "dev": true, "requires": { "camel-case": "^4.1.2", @@ -59742,8 +59631,6 @@ }, "constant-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", - "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", "dev": true, "requires": { "no-case": "^3.0.4", @@ -59791,8 +59678,6 @@ }, "dot-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, "requires": { "no-case": "^3.0.4", @@ -59847,8 +59732,6 @@ }, "form-data": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, "requires": { "asynckit": "^0.4.0", @@ -60194,8 +60077,6 @@ }, "path-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", - "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", "dev": true, "requires": { "dot-case": "^3.0.4", @@ -60271,8 +60152,6 @@ }, "sentence-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", - "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", "dev": true, "requires": { "no-case": "^3.0.4", @@ -60304,8 +60183,6 @@ }, "snake-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", "dev": true, "requires": { "dot-case": "^3.0.4", @@ -60365,8 +60242,6 @@ }, "upper-case": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", - "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", "dev": true, "requires": { "tslib": "^2.0.3" @@ -60374,8 +60249,6 @@ }, "upper-case-first": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", "dev": true, "requires": { "tslib": "^2.0.3" @@ -60502,8 +60375,6 @@ }, "@wordpress/env": { "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-4.9.0.tgz", - "integrity": "sha512-C2g5aOYxl1Bd9lypvEMjXZ1s1Gx/JHpFWuPlCAI8gAzwzB9jCIZkqpU85GsGScpZLAANS/N7wF3LMY68UkN9fQ==", "dev": true, "requires": { "chalk": "^4.0.0", @@ -60518,68 +60389,6 @@ "simple-git": "^3.5.0", "terminal-link": "^2.0.0", "yargs": "^17.3.0" - }, - "dependencies": { - "@sindresorhus/is": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", - "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==", - "dev": true - }, - "cacheable-lookup": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz", - "integrity": "sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg==", - "dev": true, - "requires": { - "@types/keyv": "^3.1.1", - "keyv": "^4.0.0" - } - }, - "decompress-response": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz", - "integrity": "sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==", - "dev": true, - "requires": { - "mimic-response": "^2.0.0" - } - }, - "got": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/got/-/got-10.7.0.tgz", - "integrity": "sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg==", - "dev": true, - "requires": { - "@sindresorhus/is": "^2.0.0", - "@szmarczak/http-timer": "^4.0.0", - "@types/cacheable-request": "^6.0.1", - "cacheable-lookup": "^2.0.0", - "cacheable-request": "^7.0.1", - "decompress-response": "^5.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^5.0.0", - "lowercase-keys": "^2.0.0", - "mimic-response": "^2.1.0", - "p-cancelable": "^2.0.0", - "p-event": "^4.0.0", - "responselike": "^2.0.0", - "to-readable-stream": "^2.0.0", - "type-fest": "^0.10.0" - } - }, - "mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", - "dev": true - }, - "type-fest": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.10.0.tgz", - "integrity": "sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw==", - "dev": true - } } }, "@wordpress/escape-html": { @@ -60621,9 +60430,9 @@ } }, "@wordpress/hooks": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.20.0.tgz", - "integrity": "sha512-OMOJwmbubrKueXhXEyBNU8CXBycawmtXCWbhqgYYbihgecB7cSZ1kAAPz+Oi/5j+3+XDfSlZXgWM1lCwvfnzPQ==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@wordpress/hooks/-/hooks-3.22.0.tgz", + "integrity": "sha512-0pjpXzUDiiIlQGRcOCHO5N73eto367KrevFhTPn8NSK8rhNqL7XaA3YJRIBemViwsk1GaPUzheg9E3UmIL0W4g==", "requires": { "@babel/runtime": "^7.16.0" } @@ -60728,23 +60537,23 @@ } }, "@wordpress/keycodes": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.20.0.tgz", - "integrity": "sha512-2a7+HOAOvT7Y9sccRTHQytQo+bNza1kvL4E2D+bYCt3x0s2F9EmBUGy+CScvhy4IHInvemaUviwyUBZDM30pUw==", + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@wordpress/keycodes/-/keycodes-3.22.0.tgz", + "integrity": "sha512-nWEVm1hJdcDh5EJ6IEO4chqsZxDCt5qYyaUPjzFDtEM65abcMnbE7rBT36WP17slSJlPN8Y8HldajERwvKXR6Q==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/i18n": "^4.20.0", + "@wordpress/i18n": "^4.22.0", "change-case": "^4.1.2", "lodash": "^4.17.21" }, "dependencies": { "@wordpress/i18n": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.20.0.tgz", - "integrity": "sha512-jCM5z2p7If5q/T+PqAYaM9oe4N04D4wvH+2gE08ava2w7ORkVHoe1uCyWNcVBEswpFAnLCG+c6Lsbs8m3Go+aA==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@wordpress/i18n/-/i18n-4.22.0.tgz", + "integrity": "sha512-b1nQJhrBilDj3oJql9k9dzlPEJ5vWd36Q0ri0znLBOJUOq2J0jgKwgtC84dun77kBb9Upfi4NZNiBI8OuSbiuA==", "requires": { "@babel/runtime": "^7.16.0", - "@wordpress/hooks": "^3.20.0", + "@wordpress/hooks": "^3.22.0", "gettext-parser": "^1.3.1", "memize": "^1.1.0", "sprintf-js": "^1.1.1", @@ -60753,8 +60562,6 @@ }, "change-case": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", - "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", "requires": { "camel-case": "^4.1.2", "capital-case": "^1.0.4", @@ -60772,8 +60579,6 @@ }, "constant-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", - "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", "requires": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -60782,8 +60587,6 @@ }, "dot-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "requires": { "no-case": "^3.0.4", "tslib": "^2.0.3" @@ -60791,8 +60594,6 @@ }, "path-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", - "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", "requires": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -60800,8 +60601,6 @@ }, "sentence-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", - "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", "requires": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -60810,8 +60609,6 @@ }, "snake-case": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", - "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", "requires": { "dot-case": "^3.0.4", "tslib": "^2.0.3" @@ -60819,16 +60616,12 @@ }, "upper-case": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", - "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", "requires": { "tslib": "^2.0.3" } }, "upper-case-first": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", "requires": { "tslib": "^2.0.3" } @@ -63839,10 +63632,12 @@ } }, "cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true + "version": "2.0.1", + "dev": true, + "requires": { + "@types/keyv": "^3.1.1", + "keyv": "^4.0.0" + } }, "cacheable-request": { "version": "7.0.2", @@ -63936,8 +63731,6 @@ }, "capital-case": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", - "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", "requires": { "no-case": "^3.0.4", "tslib": "^2.0.3", @@ -63946,8 +63739,6 @@ "dependencies": { "upper-case-first": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", - "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", "requires": { "tslib": "^2.0.3" } @@ -64112,8 +63903,6 @@ }, "chardet": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, "charenc": { @@ -64373,8 +64162,6 @@ }, "cli-width": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true }, "clipboard": { @@ -64869,8 +64656,6 @@ }, "copy-dir": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/copy-dir/-/copy-dir-1.3.0.tgz", - "integrity": "sha512-Q4+qBFnN4bwGwvtXXzbp4P/4iNk0MaiGAzvQ8OiMtlLjkIKjmNN689uVzShSM0908q7GoFHXIPx4zi75ocoaHw==", "dev": true }, "copy-webpack-plugin": { @@ -65983,12 +65768,10 @@ "dev": true }, "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "version": "5.0.0", "dev": true, "requires": { - "mimic-response": "^3.1.0" + "mimic-response": "^2.0.0" } }, "dedent": { @@ -66504,8 +66287,6 @@ }, "docker-compose": { "version": "0.22.2", - "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.22.2.tgz", - "integrity": "sha512-iXWb5+LiYmylIMFXvGTYsjI1F+Xyx78Jm/uj1dxwwZLbWkUdH6yOXY5Nr3RjbYX15EgbGJCq78d29CmWQQQMPg==", "dev": true }, "doctrine": { @@ -67748,8 +67529,6 @@ }, "external-editor": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "dev": true, "requires": { "chardet": "^0.7.0", @@ -67937,8 +67716,6 @@ }, "figures": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, "requires": { "escape-string-regexp": "^1.0.5" @@ -67946,8 +67723,6 @@ "dependencies": { "escape-string-regexp": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true } } @@ -68775,6 +68550,10 @@ "octonode": "^0.10.2" }, "dependencies": { + "@sindresorhus/is": { + "version": "4.6.0", + "dev": true + }, "ajv": { "version": "8.11.0", "dev": true, @@ -68785,13 +68564,45 @@ "uri-js": "^4.2.2" } }, + "cacheable-lookup": { + "version": "5.0.4", + "dev": true + }, "commander": { "version": "6.2.1", "dev": true }, + "decompress-response": { + "version": "6.0.0", + "dev": true, + "requires": { + "mimic-response": "^3.1.0" + } + }, + "got": { + "version": "11.8.3", + "dev": true, + "requires": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + } + }, "json-schema-traverse": { "version": "1.0.0", "dev": true + }, + "mimic-response": { + "version": "3.1.0", + "dev": true } } }, @@ -68916,22 +68727,30 @@ } }, "got": { - "version": "11.8.5", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", - "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "version": "10.7.0", "dev": true, "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", + "@sindresorhus/is": "^2.0.0", + "@szmarczak/http-timer": "^4.0.0", "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", + "cacheable-lookup": "^2.0.0", + "cacheable-request": "^7.0.1", + "decompress-response": "^5.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^5.0.0", "lowercase-keys": "^2.0.0", + "mimic-response": "^2.1.0", "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "p-event": "^4.0.0", + "responselike": "^2.0.0", + "to-readable-stream": "^2.0.0", + "type-fest": "^0.10.0" + }, + "dependencies": { + "type-fest": { + "version": "0.10.0", + "dev": true + } } }, "graceful-fs": { @@ -69203,8 +69022,6 @@ }, "header-case": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", - "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", "requires": { "capital-case": "^1.0.4", "tslib": "^2.0.3" @@ -69317,8 +69134,6 @@ }, "html-dom-parser": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-3.1.2.tgz", - "integrity": "sha512-mLTtl3pVn3HnqZSZzW3xVs/mJAKrG1yIw3wlp+9bdoZHHLaBRvELdpfShiPVLyjPypq1Fugv2KMDoGHW4lVXnw==", "requires": { "domhandler": "5.0.3", "htmlparser2": "8.0.1" @@ -69326,8 +69141,6 @@ "dependencies": { "dom-serializer": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "requires": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -69336,16 +69149,12 @@ }, "domhandler": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "requires": { "domelementtype": "^2.3.0" } }, "domutils": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", - "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==", "requires": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -69353,14 +69162,10 @@ } }, "entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==" + "version": "4.4.0" }, "htmlparser2": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz", - "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==", "requires": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -69414,8 +69219,6 @@ }, "html-react-parser": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-3.0.4.tgz", - "integrity": "sha512-va68PSmC7uA6PbOEc9yuw5Mu3OHPXmFKUpkLGvUPdTuNrZ0CJZk1s/8X/FaHjswK/6uZghu2U02tJjussT8+uw==", "requires": { "domhandler": "5.0.3", "html-dom-parser": "3.1.2", @@ -69425,8 +69228,6 @@ "dependencies": { "domhandler": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "requires": { "domelementtype": "^2.3.0" } @@ -69872,8 +69673,6 @@ }, "inquirer": { "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", "dev": true, "requires": { "ansi-escapes": "^4.2.1", @@ -73804,8 +73603,6 @@ }, "log-symbols": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", - "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", "dev": true, "requires": { "chalk": "^2.4.2" @@ -73813,8 +73610,6 @@ "dependencies": { "ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { "color-convert": "^1.9.0" @@ -73822,8 +73617,6 @@ }, "chalk": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -73833,8 +73626,6 @@ }, "color-convert": { "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "requires": { "color-name": "1.1.3" @@ -73842,26 +73633,18 @@ }, "color-name": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, "escape-string-regexp": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true }, "has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, "supports-color": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { "has-flag": "^3.0.0" @@ -75306,9 +75089,7 @@ "dev": true }, "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "version": "2.1.0", "dev": true }, "min-document": { @@ -76142,8 +75923,6 @@ }, "ora": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-4.1.1.tgz", - "integrity": "sha512-sjYP8QyVWBpBZWD6Vr1M/KwknSw6kJOz41tvGMlwWeClHBtYKTbHMki1PsLZnxKpXMPbTKv9b3pjQu3REib96A==", "dev": true, "requires": { "chalk": "^3.0.0", @@ -76158,8 +75937,6 @@ "dependencies": { "chalk": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -78621,9 +78398,7 @@ } }, "react-property": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz", - "integrity": "sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==" + "version": "2.0.0" }, "react-refresh": { "version": "0.11.0", @@ -79483,8 +79258,6 @@ }, "run-async": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", "dev": true }, "run-con": { @@ -79532,8 +79305,6 @@ }, "rxjs": { "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -79541,8 +79312,6 @@ "dependencies": { "tslib": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true } } @@ -80146,14 +79915,12 @@ "devOptional": true }, "simple-git": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.14.1.tgz", - "integrity": "sha512-1ThF4PamK9wBORVGMK9HK5si4zoGS2GpRO7tkAFObA4FZv6dKaCVHLQT+8zlgiBm6K2h+wEU9yOaFCu/SR3OyA==", + "version": "3.7.1", "dev": true, "requires": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", - "debug": "^4.3.4" + "debug": "^4.3.3" } }, "simple-html-tokenizer": { @@ -80869,8 +80636,6 @@ }, "style-to-js": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.1.tgz", - "integrity": "sha512-RJ18Z9t2B02sYhZtfWKQq5uplVctgvjTfLWT7+Eb1zjUjIrWzX5SdlkwLGQozrqarTmEzJJ/YmdNJCUNI47elg==", "requires": { "style-to-object": "0.3.0" } @@ -81609,8 +81374,6 @@ }, "to-readable-stream": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-2.1.0.tgz", - "integrity": "sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w==", "dev": true }, "to-regex": { diff --git a/package.json b/package.json index b5fc852880c..9150ba1a742 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "@types/wordpress__data": "^6.0.1", "@types/wordpress__data-controls": "2.2.0", "@types/wordpress__editor": "^11.0.0", - "@types/wordpress__notices": "^3.3.0", + "@types/wordpress__notices": "^3.5.0", "@typescript-eslint/eslint-plugin": "5.30.5", "@typescript-eslint/parser": "5.35.1", "@woocommerce/api": "0.2.0", diff --git a/packages/checkout/components/index.js b/packages/checkout/components/index.js index 945b51ea786..42aa464f0d2 100644 --- a/packages/checkout/components/index.js +++ b/packages/checkout/components/index.js @@ -6,6 +6,7 @@ export { default as ExperimentalOrderShippingPackages } from './order-shipping-p export { default as Panel } from './panel'; export { default as Button } from './button'; export { default as Label } from './label'; +export { default as StoreNoticesContainer } from './store-notices-container'; export { default as CheckboxControl } from './checkbox-control'; export { default as ValidatedTextInput } from './text-input/validated-text-input'; export { default as TextInput } from './text-input/text-input'; diff --git a/assets/js/base/context/providers/store-notices/components/store-notices-container.js b/packages/checkout/components/store-notices-container/index.tsx similarity index 72% rename from assets/js/base/context/providers/store-notices/components/store-notices-container.js rename to packages/checkout/components/store-notices-container/index.tsx index 7a3146a95e0..12faf4790cd 100644 --- a/assets/js/base/context/providers/store-notices/components/store-notices-container.js +++ b/packages/checkout/components/store-notices-container/index.tsx @@ -1,12 +1,12 @@ /** * External dependencies */ -import PropTypes from 'prop-types'; import classnames from 'classnames'; import { Notice } from 'wordpress-components'; import { sanitizeHTML } from '@woocommerce/utils'; import { useDispatch, useSelect } from '@wordpress/data'; import { PAYMENT_STORE_KEY } from '@woocommerce/block-data'; +import type { Notice as NoticeType } from '@wordpress/notices'; /** * Internal dependencies @@ -26,11 +26,28 @@ const getWooClassName = ( { status = 'default' } ) => { return ''; }; +interface StoreNoticesContainerProps { + className?: string; + context?: string; + additionalNotices?: NoticeType[]; +} + +/** + * Component that displays notices from the core/notices data store. See + * https://developer.wordpress.org/block-editor/reference-guides/data/data-core-notices/ for more information on this + * data store. + * + * @param props + * @param props.className Class name to add to the container. + * @param props.context Context to show notices from. + * @param props.additionalNotices Additional notices to display. + * @function Object() { [native code] } + */ export const StoreNoticesContainer = ( { className, context = 'default', additionalNotices = [], -} ) => { +}: StoreNoticesContainerProps ): JSX.Element | null => { const isExpressPaymentMethodActive = useSelect( ( select ) => select( PAYMENT_STORE_KEY ).isExpressPaymentMethodActive() ); @@ -76,17 +93,4 @@ export const StoreNoticesContainer = ( { ); }; -StoreNoticesContainer.propTypes = { - className: PropTypes.string, - notices: PropTypes.arrayOf( - PropTypes.shape( { - content: PropTypes.string.isRequired, - id: PropTypes.string.isRequired, - status: PropTypes.string.isRequired, - isDismissible: PropTypes.bool, - type: PropTypes.oneOf( [ 'default', 'snackbar' ] ), - } ) - ), -}; - export default StoreNoticesContainer; diff --git a/assets/js/base/context/providers/store-notices/components/style.scss b/packages/checkout/components/store-notices-container/style.scss similarity index 100% rename from assets/js/base/context/providers/store-notices/components/style.scss rename to packages/checkout/components/store-notices-container/style.scss diff --git a/packages/checkout/components/store-notices-container/test/index.tsx b/packages/checkout/components/store-notices-container/test/index.tsx new file mode 100644 index 00000000000..7a3e01a3f43 --- /dev/null +++ b/packages/checkout/components/store-notices-container/test/index.tsx @@ -0,0 +1,104 @@ +/** + * External dependencies + */ +import { store as noticesStore } from '@wordpress/notices'; +import { dispatch, select } from '@wordpress/data'; +import { act, render, screen, waitFor } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import StoreNoticesContainer from '../index'; + +describe( 'StoreNoticesContainer', () => { + it( 'Shows notices from the correct context', async () => { + dispatch( noticesStore ).createErrorNotice( 'Custom test error', { + id: 'custom-test-error', + context: 'test-context', + } ); + render( ); + expect( screen.getAllByText( /Custom test error/i ) ).toHaveLength( 2 ); + // Clean up notices. + await act( () => + dispatch( noticesStore ).removeNotice( + 'custom-test-error', + 'test-context' + ) + ); + await waitFor( () => { + return ( + select( noticesStore ).getNotices( 'test-context' ).length === 0 + ); + } ); + } ); + + it( 'Does not show notices from other contexts', async () => { + dispatch( noticesStore ).createErrorNotice( 'Custom test error 2', { + id: 'custom-test-error-2', + context: 'test-context', + } ); + render( ); + expect( screen.queryAllByText( /Custom test error 2/i ) ).toHaveLength( + 0 + ); + // Clean up notices. + await act( () => + dispatch( noticesStore ).removeNotice( + 'custom-test-error-2', + 'test-context' + ) + ); + await waitFor( () => { + return ( + select( noticesStore ).getNotices( 'test-context' ).length === 0 + ); + } ); + } ); + + it( 'Does not show snackbar notices', async () => { + dispatch( noticesStore ).createErrorNotice( 'Custom test error 2', { + id: 'custom-test-error-2', + context: 'test-context', + type: 'snackbar', + } ); + render( ); + expect( screen.queryAllByText( /Custom test error 2/i ) ).toHaveLength( + 0 + ); + // Clean up notices. + await act( () => + dispatch( noticesStore ).removeNotice( + 'custom-test-error-2', + 'test-context' + ) + ); + await waitFor( () => { + return ( + select( noticesStore ).getNotices( 'test-context' ).length === 0 + ); + } ); + } ); + + it( 'Shows additional notices', () => { + render( + + ); + expect( screen.getAllByText( /Additional test error/i ) ).toHaveLength( + 2 + ); + } ); +} ); From 618e0aa5d3e6a994b34b62f4fb576a78cdf71052 Mon Sep 17 00:00:00 2001 From: Saad Tarhi Date: Thu, 17 Nov 2022 14:41:15 +0100 Subject: [PATCH 09/27] =?UTF-8?q?Fix=20missing=20translations=20in=20inspe?= =?UTF-8?q?ctor=20=E2=80=94=20Cross-Sells=20Block=20(#7616)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix CartCrossSellsBlock inspector's translation * Fix CartCrossSellsProductsBlock inspector's translation * bot: update checkstyle.xml Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Niels Lange --- .../cart-cross-sells-block/block.json | 1 + .../cart-cross-sells-block/index.tsx | 3 +-- .../cart-cross-sells-products/block.json | 1 + .../cart-cross-sells-products/index.tsx | 3 +-- checkstyle.xml | 21 +++++++++---------- src/BlockTypes/Cart.php | 2 ++ src/BlockTypes/CartCrossSellsBlock.php | 14 +++++++++++++ .../CartCrossSellsProductsBlock.php | 14 +++++++++++++ 8 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 src/BlockTypes/CartCrossSellsBlock.php create mode 100644 src/BlockTypes/CartCrossSellsProductsBlock.php diff --git a/assets/js/blocks/cart/inner-blocks/cart-cross-sells-block/block.json b/assets/js/blocks/cart/inner-blocks/cart-cross-sells-block/block.json index 4fd84e1420e..8ddaae21761 100644 --- a/assets/js/blocks/cart/inner-blocks/cart-cross-sells-block/block.json +++ b/assets/js/blocks/cart/inner-blocks/cart-cross-sells-block/block.json @@ -13,5 +13,6 @@ }, "parent": [ "woocommerce/cart-items-block" ], "textdomain": "woo-gutenberg-products-block", + "$schema": "https://schemas.wp.org/trunk/block.json", "apiVersion": 2 } diff --git a/assets/js/blocks/cart/inner-blocks/cart-cross-sells-block/index.tsx b/assets/js/blocks/cart/inner-blocks/cart-cross-sells-block/index.tsx index 60630fb161a..8551d3d897b 100644 --- a/assets/js/blocks/cart/inner-blocks/cart-cross-sells-block/index.tsx +++ b/assets/js/blocks/cart/inner-blocks/cart-cross-sells-block/index.tsx @@ -8,9 +8,8 @@ import { registerBlockType } from '@wordpress/blocks'; * Internal dependencies */ import { Edit, Save } from './edit'; -import metadata from './block.json'; -registerBlockType( metadata, { +registerBlockType( 'woocommerce/cart-cross-sells-block', { icon: { src: ( - + Argument of type '{ icon: { src: JSX.Element; }; edit: () => JSX.Element; save: () => JSX.Element; }' is not assignable to parameter of type 'BlockConfiguration<{}>'. + Type '{ icon: { src: Element; }; edit: () => Element; save: () => Element; }' is missing the following properties from type 'Pick<Block<{}>, "title" | "category" | "attributes">': title, category, attributes" source="TS2769" /> - + Argument of type '{ icon: { src: JSX.Element; }; edit: ({ attributes, setAttributes }: Props) => JSX.Element; save: () => JSX.Element; }' is not assignable to parameter of type 'BlockConfiguration<{ className?: string; columns: number; }>'. + Type '{ icon: { src: Element; }; edit: ({ attributes, setAttributes }: Props) => Element; save: () => Element; }' is missing the following properties from type 'Pick<Block<{ className?: string; columns: number; }>, "title" | "category" | "attributes">': title, category, attributes" source="TS2769" /> + ); +}; + +interface ImageProps { + image?: null | { + alt?: string | undefined; + id: number; + name: string; + sizes?: string | undefined; + src?: string | undefined; + srcset?: string | undefined; + thumbnail?: string | undefined; + }; + loaded: boolean; + showFullSize: boolean; + fallbackAlt: string; +} + +const Image = ( { + image, + loaded, + showFullSize, + fallbackAlt, +}: ImageProps ): JSX.Element => { + const { thumbnail, src, srcset, sizes, alt } = image || {}; + const imageProps = { + alt: alt || fallbackAlt, + hidden: ! loaded, + src: thumbnail, + ...( showFullSize && { src, srcSet: srcset, sizes } ), + }; + + return ( + <> + { imageProps.src && ( + /* eslint-disable-next-line jsx-a11y/alt-text */ + + ) } + { ! image && } + + ); +}; + +type Props = BlockAttributes & HTMLAttributes< HTMLDivElement >; + +export const Block = ( props: Props ): JSX.Element | null => { const { className, imageSizing = 'full-size', @@ -44,12 +86,9 @@ export const Block = ( props ) => { showSaleBadge, saleBadgeAlign = 'right', } = props; - const { parentClassName } = useInnerBlockLayoutContext(); const { product, isLoading } = useProductDataContext(); - const { dispatchStoreEvent } = useStoreEvents(); - const typographyProps = useTypographyProps( props ); const borderProps = useBorderProps( props ); const spacingProps = useSpacingProps( props ); @@ -128,39 +167,4 @@ export const Block = ( props ) => { ); }; -const ImagePlaceholder = () => { - // The alt text is left empty on purpose, as it's considered a decorative image. - // More can be found here: https://www.w3.org/WAI/tutorials/images/decorative/. - // Github discussion for a context: https://github.com/woocommerce/woocommerce-blocks/pull/7651#discussion_r1019560494. - return ; -}; - -const Image = ( { image, loaded, showFullSize, fallbackAlt } ) => { - const { thumbnail, src, srcset, sizes, alt } = image || {}; - const imageProps = { - alt: alt || fallbackAlt, - hidden: ! loaded, - src: thumbnail, - ...( showFullSize && { src, srcSet: srcset, sizes } ), - }; - - return ( - <> - { imageProps.src && ( - /* eslint-disable-next-line jsx-a11y/alt-text */ - - ) } - { ! image && } - - ); -}; - -Block.propTypes = { - className: PropTypes.string, - fallbackAlt: PropTypes.string, - showProductLink: PropTypes.bool, - showSaleBadge: PropTypes.bool, - saleBadgeAlign: PropTypes.string, -}; - export default withProductDataContext( Block ); diff --git a/assets/js/atomic/blocks/product-elements/image/constants.tsx b/assets/js/atomic/blocks/product-elements/image/constants.tsx new file mode 100644 index 00000000000..5ccf1075463 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/image/constants.tsx @@ -0,0 +1,17 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; +import { image, Icon } from '@wordpress/icons'; + +export const BLOCK_TITLE: string = __( + 'Product Image', + 'woo-gutenberg-products-block' +); +export const BLOCK_ICON: JSX.Element = ( + +); +export const BLOCK_DESCRIPTION: string = __( + 'Display the main product image.', + 'woo-gutenberg-products-block' +); diff --git a/assets/js/atomic/blocks/product-elements/image/edit.js b/assets/js/atomic/blocks/product-elements/image/edit.tsx similarity index 79% rename from assets/js/atomic/blocks/product-elements/image/edit.js rename to assets/js/atomic/blocks/product-elements/image/edit.tsx index 7d18c6461cf..c0cc7e76a20 100644 --- a/assets/js/atomic/blocks/product-elements/image/edit.js +++ b/assets/js/atomic/blocks/product-elements/image/edit.tsx @@ -6,12 +6,18 @@ import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; import { createInterpolateElement, useEffect } from '@wordpress/element'; import { getAdminLink, getSettingWithCoercion } from '@woocommerce/settings'; import { isBoolean } from '@woocommerce/types'; +import type { BlockEditProps } from '@wordpress/blocks'; +import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types'; import { Disabled, PanelBody, ToggleControl, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - Ignoring because `__experimentalToggleGroupControl` is not yet in the type definitions. // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalToggleGroupControl as ToggleGroupControl, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - Ignoring because `__experimentalToggleGroupControl` is not yet in the type definitions. // eslint-disable-next-line @wordpress/no-unsafe-wp-apis __experimentalToggleGroupControlOption as ToggleGroupControlOption, } from '@wordpress/components'; @@ -20,26 +26,37 @@ import { * Internal dependencies */ import Block from './block'; +import withProductSelector from '../shared/with-product-selector'; +import { + BLOCK_TITLE as label, + BLOCK_ICON as icon, + BLOCK_DESCRIPTION as description, +} from './constants'; +import type { BlockAttributes } from './types'; + +type SaleBadgeAlignProps = 'left' | 'center' | 'right'; +type ImageSizingProps = 'full-size' | 'cropped'; -const Edit = ( { attributes, setAttributes, context } ) => { +const Edit = ( { + attributes, + setAttributes, + context, +}: BlockEditProps< BlockAttributes > & { context: Context } ): JSX.Element => { const { showProductLink, imageSizing, showSaleBadge, saleBadgeAlign } = attributes; - const blockProps = useBlockProps(); - const isDescendentOfQueryLoop = Number.isFinite( context.queryId ); - - useEffect( - () => setAttributes( { isDescendentOfQueryLoop } ), - [ setAttributes, isDescendentOfQueryLoop ] - ); - const isBlockThemeEnabled = getSettingWithCoercion( 'is_block_theme_enabled', false, isBoolean ); + useEffect( + () => setAttributes( { isDescendentOfQueryLoop } ), + [ setAttributes, isDescendentOfQueryLoop ] + ); + useEffect( () => { if ( isBlockThemeEnabled && attributes.imageSizing !== 'full-size' ) { setAttributes( { imageSizing: 'full-size' } ); @@ -91,7 +108,7 @@ const Edit = ( { attributes, setAttributes, context } ) => { 'woo-gutenberg-products-block' ) } value={ saleBadgeAlign } - onChange={ ( value ) => + onChange={ ( value: SaleBadgeAlignProps ) => setAttributes( { saleBadgeAlign: value } ) } > @@ -143,7 +160,7 @@ const Edit = ( { attributes, setAttributes, context } ) => { } ) } value={ imageSizing } - onChange={ ( value ) => + onChange={ ( value: ImageSizingProps ) => setAttributes( { imageSizing: value } ) } > @@ -172,4 +189,4 @@ const Edit = ( { attributes, setAttributes, context } ) => { ); }; -export default Edit; +export default withProductSelector( { icon, label, description } )( Edit ); diff --git a/assets/js/atomic/blocks/product-elements/image/frontend.js b/assets/js/atomic/blocks/product-elements/image/frontend.ts similarity index 84% rename from assets/js/atomic/blocks/product-elements/image/frontend.js rename to assets/js/atomic/blocks/product-elements/image/frontend.ts index 2add7452a10..b6c773996b2 100644 --- a/assets/js/atomic/blocks/product-elements/image/frontend.js +++ b/assets/js/atomic/blocks/product-elements/image/frontend.ts @@ -7,6 +7,6 @@ import { withFilteredAttributes } from '@woocommerce/shared-hocs'; * Internal dependencies */ import Block from './block'; -import { attributes } from './attributes'; +import attributes from './attributes'; export default withFilteredAttributes( attributes )( Block ); diff --git a/assets/js/atomic/blocks/product-elements/image/index.js b/assets/js/atomic/blocks/product-elements/image/index.ts similarity index 53% rename from assets/js/atomic/blocks/product-elements/image/index.js rename to assets/js/atomic/blocks/product-elements/image/index.ts index 98c166a69cd..05cc47bfda0 100644 --- a/assets/js/atomic/blocks/product-elements/image/index.js +++ b/assets/js/atomic/blocks/product-elements/image/index.ts @@ -2,8 +2,7 @@ * External dependencies */ import { registerBlockType } from '@wordpress/blocks'; -import { image, Icon } from '@wordpress/icons'; -import { __ } from '@wordpress/i18n'; +import type { BlockConfiguration } from '@wordpress/blocks'; /** * Internal dependencies @@ -11,26 +10,26 @@ import { __ } from '@wordpress/i18n'; import edit from './edit'; import { supports } from './supports'; -import { attributes } from './attributes'; +import attributes from './attributes'; import sharedConfig from '../shared/config'; +import { + BLOCK_TITLE as title, + BLOCK_ICON as icon, + BLOCK_DESCRIPTION as description, +} from './constants'; -const blockConfig = { +type CustomBlockConfiguration = BlockConfiguration & { + ancestor: string[]; +}; + +const blockConfig: CustomBlockConfiguration = { + ...sharedConfig, apiVersion: 2, name: 'woocommerce/product-image', - title: __( 'Product Image', 'woo-gutenberg-products-block' ), - icon: { - src: ( - - ), - }, + title, + icon: { src: icon }, keywords: [ 'WooCommerce' ], - description: __( - 'Display the main product image.', - 'woo-gutenberg-products-block' - ), + description, usesContext: [ 'query', 'queryId', 'postId' ], ancestor: [ '@woocommerce/all-products', @@ -43,7 +42,4 @@ const blockConfig = { edit, }; -registerBlockType( 'woocommerce/product-image', { - ...sharedConfig, - ...blockConfig, -} ); +registerBlockType( 'woocommerce/product-image', { ...blockConfig } ); diff --git a/assets/js/atomic/blocks/product-elements/image/test/block.test.js b/assets/js/atomic/blocks/product-elements/image/test/block.test.js index d9ae60071f2..a7d7d776587 100644 --- a/assets/js/atomic/blocks/product-elements/image/test/block.test.js +++ b/assets/js/atomic/blocks/product-elements/image/test/block.test.js @@ -66,7 +66,14 @@ describe( 'Product Image Block', () => { product={ productWithImages } isLoading={ false } > - + ); @@ -93,7 +100,14 @@ describe( 'Product Image Block', () => { product={ productWithoutImages } isLoading={ false } > - + ); @@ -119,7 +133,14 @@ describe( 'Product Image Block', () => { product={ productWithImages } isLoading={ false } > - + ); const image = component.getByTestId( 'product-image' ); @@ -142,7 +163,14 @@ describe( 'Product Image Block', () => { product={ productWithoutImages } isLoading={ false } > - + ); @@ -163,7 +191,14 @@ describe( 'Product Image Block', () => { product={ productWithoutImages } isLoading={ false } > - + ); diff --git a/assets/js/atomic/blocks/product-elements/image/types.ts b/assets/js/atomic/blocks/product-elements/image/types.ts new file mode 100644 index 00000000000..3792bb7f611 --- /dev/null +++ b/assets/js/atomic/blocks/product-elements/image/types.ts @@ -0,0 +1,16 @@ +export interface BlockAttributes { + // The product ID. + productId: number; + // CSS Class name for the component. + className?: string | undefined; + // Whether or not to display a link to the product page. + showProductLink: boolean; + // Whether or not to display the on sale badge. + showSaleBadge: boolean; + // How should the sale badge be aligned if displayed. + saleBadgeAlign: 'left' | 'center' | 'right'; + // Size of image to use. + imageSizing: 'full-size' | 'cropped'; + // Whether or not be a children of Query Loop Block. + isDescendentOfQueryLoop: boolean; +} diff --git a/assets/js/blocks/cart/cart-cross-sells-product-list/cart-cross-sells-product.tsx b/assets/js/blocks/cart/cart-cross-sells-product-list/cart-cross-sells-product.tsx index 067844d2116..c6b24f4e8f3 100644 --- a/assets/js/blocks/cart/cart-cross-sells-product-list/cart-cross-sells-product.tsx +++ b/assets/js/blocks/cart/cart-cross-sells-product-list/cart-cross-sells-product.tsx @@ -41,6 +41,11 @@ const CartCrossSellsProduct = ( { - +
{ product.is_in_stock ? ( diff --git a/checkstyle.xml b/checkstyle.xml index 1e784643960..cf75274f51d 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -1107,12 +1107,6 @@
- - - - - - - - - - - - - - - - - - @@ -2199,9 +2170,6 @@ Argument of type '{ icon: { src: JSX.Element; }; edit: () => JSX.Element; save: () => JSX.Element; }' is not assignable to parameter of type 'BlockConfiguration<{}>'. Type '{ icon: { src: Element; }; edit: () => Element; save: () => Element; }' is missing the following properties from type 'Pick<Block<{}>, "title" | "category" | "attributes">': title, category, attributes" source="TS2769" /> - - - Date: Fri, 18 Nov 2022 12:13:00 +0000 Subject: [PATCH 14/27] Move paymentResult to the payment store (#7692) * Move paymentResult to the payment store * bot: update checkstyle.xml * Update docs * Fix typerror * bot: update checkstyle.xml * bot: update checkstyle.xml Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Tarun Vijwani --- .../cart-checkout/checkout-events/index.tsx | 1 - assets/js/data/checkout/action-types.ts | 1 - assets/js/data/checkout/actions.ts | 16 ------ assets/js/data/checkout/default-state.ts | 4 -- assets/js/data/checkout/reducers.ts | 12 ----- assets/js/data/checkout/test/reducer.ts | 22 -------- assets/js/data/checkout/thunks.ts | 23 +++++--- assets/js/data/checkout/types.ts | 3 +- assets/js/data/payment/action-types.ts | 1 + assets/js/data/payment/actions.ts | 11 ++++ assets/js/data/payment/default-state.ts | 8 +-- assets/js/data/payment/reducers.ts | 18 ++++--- assets/js/data/payment/selectors.ts | 54 +++++++++---------- assets/js/data/payment/test/reducers.js | 21 ++++++++ checkstyle.xml | 46 ++++++++-------- .../checkout/checkout-api.md | 2 - .../extensibility/data-store/payment.md | 25 +++++++++ 17 files changed, 139 insertions(+), 129 deletions(-) diff --git a/assets/js/base/context/providers/cart-checkout/checkout-events/index.tsx b/assets/js/base/context/providers/cart-checkout/checkout-events/index.tsx index c07a0325da0..24e2e07c799 100644 --- a/assets/js/base/context/providers/cart-checkout/checkout-events/index.tsx +++ b/assets/js/base/context/providers/cart-checkout/checkout-events/index.tsx @@ -197,7 +197,6 @@ export const CheckoutEventsProvider = ( { checkoutState.orderId, checkoutState.customerId, checkoutState.orderNotes, - checkoutState.paymentResult, previousStatus, previousHasError, createErrorNotice, diff --git a/assets/js/data/checkout/action-types.ts b/assets/js/data/checkout/action-types.ts index 757ce54b161..4a06285a5f7 100644 --- a/assets/js/data/checkout/action-types.ts +++ b/assets/js/data/checkout/action-types.ts @@ -4,7 +4,6 @@ export const ACTION_TYPES = { SET_COMPLETE: 'SET_CHECKOUT_COMPLETE', SET_BEFORE_PROCESSING: 'SET_BEFORE_PROCESSING', SET_AFTER_PROCESSING: 'SET_AFTER_PROCESSING', - SET_PAYMENT_RESULT: 'SET_PAYMENT_RESULT', SET_PROCESSING: 'SET_CHECKOUT_IS_PROCESSING', SET_HAS_ERROR: 'SET_CHECKOUT_HAS_ERROR', SET_CUSTOMER_ID: 'SET_CHECKOUT_CUSTOMER_ID', diff --git a/assets/js/data/checkout/actions.ts b/assets/js/data/checkout/actions.ts index 954b3a28a94..bd4863046c8 100644 --- a/assets/js/data/checkout/actions.ts +++ b/assets/js/data/checkout/actions.ts @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { PaymentResult } from '@woocommerce/types'; - /** * Internal dependencies */ @@ -60,16 +55,6 @@ export const __internalSetRedirectUrl = ( redirectUrl: string ) => ( { redirectUrl, } ); -/** - * Store the result of the payment attempt from the /checkout StoreApi call - * - * @param data The result of the payment attempt through the StoreApi /checkout endpoints - */ -export const __internalSetPaymentResult = ( data: PaymentResult ) => ( { - type: types.SET_PAYMENT_RESULT, - data, -} ); - /** * Set whether the checkout has an error or not * @@ -157,7 +142,6 @@ export type CheckoutAction = | typeof __internalSetIdle | typeof __internalSetComplete | typeof __internalSetProcessing - | typeof __internalSetPaymentResult | typeof __internalSetBeforeProcessing | typeof __internalSetAfterProcessing | typeof __internalSetRedirectUrl diff --git a/assets/js/data/checkout/default-state.ts b/assets/js/data/checkout/default-state.ts index e8b39de06e5..53a346c5aba 100644 --- a/assets/js/data/checkout/default-state.ts +++ b/assets/js/data/checkout/default-state.ts @@ -2,7 +2,6 @@ * External dependencies */ import { isSameAddress } from '@woocommerce/base-utils'; -import { PaymentResult } from '@woocommerce/types'; /** * Internal dependencies @@ -14,8 +13,6 @@ export type CheckoutState = { status: STATUS; // If any of the totals, taxes, shipping, etc need to be calculated, the count will be increased here calculatingCount: number; - // The result of the payment processing - paymentResult: PaymentResult | null; // True when the checkout is in an error state. Whatever caused the error (validation/payment method) will likely have triggered a notice. hasError: boolean; // This is the url that checkout will redirect to when it's ready. @@ -47,6 +44,5 @@ export const defaultState: CheckoutState = { checkoutData.shipping_address ), shouldCreateAccount: false, - paymentResult: null, extensionData: {}, }; diff --git a/assets/js/data/checkout/reducers.ts b/assets/js/data/checkout/reducers.ts index 78051fa3b03..b4e28116110 100644 --- a/assets/js/data/checkout/reducers.ts +++ b/assets/js/data/checkout/reducers.ts @@ -1,8 +1,3 @@ -/** - * External dependencies - */ -import { PaymentResult } from '@woocommerce/types'; - /** * Internal dependencies */ @@ -35,13 +30,6 @@ const reducer = ( state = defaultState, action: CheckoutAction ) => { : state; break; - case types.SET_PAYMENT_RESULT: - newState = { - ...state, - paymentResult: action.data as PaymentResult, - }; - break; - case types.SET_COMPLETE: newState = { ...state, diff --git a/assets/js/data/checkout/test/reducer.ts b/assets/js/data/checkout/test/reducer.ts index 5304cb559e0..bdaf595431e 100644 --- a/assets/js/data/checkout/test/reducer.ts +++ b/assets/js/data/checkout/test/reducer.ts @@ -37,28 +37,6 @@ describe.only( 'Checkout Store Reducer', () => { ).toEqual( expectedState ); } ); - it( 'should handle SET_PAYMENT_RESULT', () => { - const mockResponse = { - message: 'success', - redirectUrl: 'https://example.com', - paymentStatus: 'not set' as const, - paymentDetails: {}, - }; - - const expectedState = { - ...defaultState, - status: STATUS.IDLE, - paymentResult: mockResponse, - }; - - expect( - reducer( - defaultState, - actions.__internalSetPaymentResult( mockResponse ) - ) - ).toEqual( expectedState ); - } ); - it( 'should handle SET_COMPLETE', () => { const expectedState = { ...defaultState, diff --git a/assets/js/data/checkout/thunks.ts b/assets/js/data/checkout/thunks.ts index 86731851534..cbb4233f72c 100644 --- a/assets/js/data/checkout/thunks.ts +++ b/assets/js/data/checkout/thunks.ts @@ -3,10 +3,12 @@ */ import type { CheckoutResponse } from '@woocommerce/types'; import { store as noticesStore } from '@wordpress/notices'; +import { dispatch as wpDispatch, select as wpSelect } from '@wordpress/data'; /** * Internal dependencies */ +import { STORE_KEY as PAYMENT_STORE_KEY } from '../payment/constants'; import { removeNoticesByStatus } from '../../utils/notices'; import { getPaymentResultFromCheckoutResponse, @@ -40,7 +42,11 @@ export const __internalProcessCheckoutResponse = ( } ) => { const paymentResult = getPaymentResultFromCheckoutResponse( response ); dispatch.__internalSetRedirectUrl( paymentResult?.redirectUrl || '' ); - dispatch.__internalSetPaymentResult( paymentResult ); + // The local `dispatch` here is bound to the actions of the data store. We need to use the global dispatch here + // to dispatch an action on a different store. + wpDispatch( PAYMENT_STORE_KEY ).__internalSetPaymentResult( + paymentResult + ); dispatch.__internalSetAfterProcessing(); }; }; @@ -90,15 +96,16 @@ export const __internalEmitAfterProcessingEvents: emitAfterProcessingEventsType ( { observers, notices } ) => { return ( { select, dispatch, registry } ) => { const { createErrorNotice } = registry.dispatch( noticesStore ); - const state = select.getCheckoutState(); + const checkoutState = select.getCheckoutState(); const data = { - redirectUrl: state.redirectUrl, - orderId: state.orderId, - customerId: state.customerId, - orderNotes: state.orderNotes, - processingResponse: state.paymentResult, + redirectUrl: checkoutState.redirectUrl, + orderId: checkoutState.orderId, + customerId: checkoutState.customerId, + orderNotes: checkoutState.orderNotes, + processingResponse: + wpSelect( PAYMENT_STORE_KEY ).getPaymentResult(), }; - if ( state.hasError ) { + if ( checkoutState.hasError ) { // allow payment methods or other things to customize the error // with a fallback if nothing customizes it. emitEventWithAbort( diff --git a/assets/js/data/checkout/types.ts b/assets/js/data/checkout/types.ts index 6fada6d6207..6057407e1da 100644 --- a/assets/js/data/checkout/types.ts +++ b/assets/js/data/checkout/types.ts @@ -9,6 +9,7 @@ import { DataRegistry } from '@wordpress/data'; */ import type { EventObserversType } from '../../base/context/event-emit/types'; import type { CheckoutState } from './default-state'; +import type { PaymentState } from '../payment/default-state'; import type { DispatchFromMap, SelectFromMap } from '../mapped-types'; import * as selectors from './selectors'; import * as actions from './actions'; @@ -19,7 +20,7 @@ export type CheckoutAfterProcessingWithErrorEventData = { orderId: CheckoutState[ 'orderId' ]; customerId: CheckoutState[ 'customerId' ]; orderNotes: CheckoutState[ 'orderNotes' ]; - processingResponse: CheckoutState[ 'paymentResult' ]; + processingResponse: PaymentState[ 'paymentResult' ]; }; export type CheckoutAndPaymentNotices = { checkoutNotices: Notice[]; diff --git a/assets/js/data/payment/action-types.ts b/assets/js/data/payment/action-types.ts index 74f8043187e..f73208e3e83 100644 --- a/assets/js/data/payment/action-types.ts +++ b/assets/js/data/payment/action-types.ts @@ -15,4 +15,5 @@ export enum ACTION_TYPES { REMOVE_AVAILABLE_EXPRESS_PAYMENT_METHOD = 'REMOVE_AVAILABLE_EXPRESS_PAYMENT_METHOD', INITIALIZE_PAYMENT_METHODS = 'INITIALIZE_PAYMENT_METHODS', SET_PAYMENT_METHOD_DATA = 'SET_PAYMENT_METHOD_DATA', + SET_PAYMENT_RESULT = 'SET_PAYMENT_RESULT', } diff --git a/assets/js/data/payment/actions.ts b/assets/js/data/payment/actions.ts index 675730fad26..d7467efb7f1 100644 --- a/assets/js/data/payment/actions.ts +++ b/assets/js/data/payment/actions.ts @@ -5,6 +5,7 @@ import { PlainPaymentMethods, PlainExpressPaymentMethods, } from '@woocommerce/type-defs/payments'; +import type { PaymentResult } from '@woocommerce/types'; /** * Internal dependencies @@ -112,6 +113,16 @@ export const __internalSetPaymentMethodData = ( paymentMethodData, } ); +/** + * Store the result of the payment attempt from the /checkout StoreApi call + * + * @param data The result of the payment attempt through the StoreApi /checkout endpoints + */ +export const __internalSetPaymentResult = ( data: PaymentResult ) => ( { + type: ACTION_TYPES.SET_PAYMENT_RESULT, + data, +} ); + /** * Set the available payment methods. * An available payment method is one that has been validated and can make a payment. diff --git a/assets/js/data/payment/default-state.ts b/assets/js/data/payment/default-state.ts index 0ce03d4afc5..d998d2d6162 100644 --- a/assets/js/data/payment/default-state.ts +++ b/assets/js/data/payment/default-state.ts @@ -1,7 +1,7 @@ /** * External dependencies */ -import type { EmptyObjectType } from '@woocommerce/types'; +import type { EmptyObjectType, PaymentResult } from '@woocommerce/types'; import { getSetting } from '@woocommerce/settings'; import { PlainPaymentMethods, @@ -14,7 +14,7 @@ import { import { SavedPaymentMethod } from './types'; import { STATUS as PAYMENT_STATUS } from './constants'; -export interface PaymentMethodDataState { +export interface PaymentState { status: string; activePaymentMethod: string; activeSavedToken: string; @@ -25,11 +25,12 @@ export interface PaymentMethodDataState { | Record< string, SavedPaymentMethod[] > | EmptyObjectType; paymentMethodData: Record< string, unknown >; + paymentResult: PaymentResult | null; paymentMethodsInitialized: boolean; expressPaymentMethodsInitialized: boolean; shouldSavePaymentMethod: boolean; } -export const defaultPaymentMethodDataState: PaymentMethodDataState = { +export const defaultPaymentState: PaymentState = { status: PAYMENT_STATUS.PRISTINE, activePaymentMethod: '', activeSavedToken: '', @@ -39,6 +40,7 @@ export const defaultPaymentMethodDataState: PaymentMethodDataState = { Record< string, SavedPaymentMethod[] > | EmptyObjectType >( 'customerPaymentMethods', {} ), paymentMethodData: {}, + paymentResult: null, paymentMethodsInitialized: false, expressPaymentMethodsInitialized: false, shouldSavePaymentMethod: false, diff --git a/assets/js/data/payment/reducers.ts b/assets/js/data/payment/reducers.ts index 758c39ffa02..18723fb3785 100644 --- a/assets/js/data/payment/reducers.ts +++ b/assets/js/data/payment/reducers.ts @@ -2,20 +2,17 @@ * External dependencies */ import type { Reducer } from 'redux'; -import { objectHasProp } from '@woocommerce/types'; +import { objectHasProp, PaymentResult } from '@woocommerce/types'; /** * Internal dependencies */ -import { - defaultPaymentMethodDataState, - PaymentMethodDataState, -} from './default-state'; +import { defaultPaymentState, PaymentState } from './default-state'; import { ACTION_TYPES } from './action-types'; import { STATUS } from './constants'; -const reducer: Reducer< PaymentMethodDataState > = ( - state = defaultPaymentMethodDataState, +const reducer: Reducer< PaymentState > = ( + state = defaultPaymentState, action ) => { let newState = state; @@ -76,6 +73,13 @@ const reducer: Reducer< PaymentMethodDataState > = ( }; break; + case ACTION_TYPES.SET_PAYMENT_RESULT: + newState = { + ...state, + paymentResult: action.data as PaymentResult, + }; + break; + case ACTION_TYPES.REMOVE_AVAILABLE_PAYMENT_METHOD: const previousAvailablePaymentMethods = { ...state.availablePaymentMethods, diff --git a/assets/js/data/payment/selectors.ts b/assets/js/data/payment/selectors.ts index 75418d7bb98..34dbec0dc88 100644 --- a/assets/js/data/payment/selectors.ts +++ b/assets/js/data/payment/selectors.ts @@ -7,29 +7,29 @@ import deprecated from '@wordpress/deprecated'; /** * Internal dependencies */ -import { PaymentMethodDataState } from './default-state'; +import { PaymentState } from './default-state'; import { filterActiveSavedPaymentMethods } from './utils/filter-active-saved-payment-methods'; import { STATUS as PAYMENT_STATUS } from './constants'; -export const isPaymentPristine = ( state: PaymentMethodDataState ) => +export const isPaymentPristine = ( state: PaymentState ) => state.status === PAYMENT_STATUS.PRISTINE; -export const isPaymentStarted = ( state: PaymentMethodDataState ) => +export const isPaymentStarted = ( state: PaymentState ) => state.status === PAYMENT_STATUS.STARTED; -export const isPaymentProcessing = ( state: PaymentMethodDataState ) => +export const isPaymentProcessing = ( state: PaymentState ) => state.status === PAYMENT_STATUS.PROCESSING; -export const isPaymentSuccess = ( state: PaymentMethodDataState ) => +export const isPaymentSuccess = ( state: PaymentState ) => state.status === PAYMENT_STATUS.SUCCESS; -export const hasPaymentError = ( state: PaymentMethodDataState ) => +export const hasPaymentError = ( state: PaymentState ) => state.status === PAYMENT_STATUS.ERROR; -export const isPaymentFailed = ( state: PaymentMethodDataState ) => +export const isPaymentFailed = ( state: PaymentState ) => state.status === PAYMENT_STATUS.FAILED; -export const isPaymentFinished = ( state: PaymentMethodDataState ) => { +export const isPaymentFinished = ( state: PaymentState ) => { return ( state.status === PAYMENT_STATUS.SUCCESS || state.status === PAYMENT_STATUS.ERROR || @@ -37,40 +37,36 @@ export const isPaymentFinished = ( state: PaymentMethodDataState ) => { ); }; -export const isExpressPaymentMethodActive = ( - state: PaymentMethodDataState -) => { +export const isExpressPaymentMethodActive = ( state: PaymentState ) => { return Object.keys( state.availableExpressPaymentMethods ).includes( state.activePaymentMethod ); }; -export const getActiveSavedToken = ( state: PaymentMethodDataState ) => { +export const getActiveSavedToken = ( state: PaymentState ) => { return typeof state.paymentMethodData === 'object' && objectHasProp( state.paymentMethodData, 'token' ) ? state.paymentMethodData.token + '' : ''; }; -export const getActivePaymentMethod = ( state: PaymentMethodDataState ) => { +export const getActivePaymentMethod = ( state: PaymentState ) => { return state.activePaymentMethod; }; -export const getAvailablePaymentMethods = ( state: PaymentMethodDataState ) => { +export const getAvailablePaymentMethods = ( state: PaymentState ) => { return state.availablePaymentMethods; }; -export const getAvailableExpressPaymentMethods = ( - state: PaymentMethodDataState -) => { +export const getAvailableExpressPaymentMethods = ( state: PaymentState ) => { return state.availableExpressPaymentMethods; }; -export const getPaymentMethodData = ( state: PaymentMethodDataState ) => { +export const getPaymentMethodData = ( state: PaymentState ) => { return state.paymentMethodData; }; -export const getSavedPaymentMethods = ( state: PaymentMethodDataState ) => { +export const getSavedPaymentMethods = ( state: PaymentState ) => { return state.savedPaymentMethods; }; @@ -78,9 +74,7 @@ export const getSavedPaymentMethods = ( state: PaymentMethodDataState ) => { * Filters the list of saved payment methods and returns only the ones which * are active and supported by the payment gateway */ -export const getActiveSavedPaymentMethods = ( - state: PaymentMethodDataState -) => { +export const getActiveSavedPaymentMethods = ( state: PaymentState ) => { const availablePaymentMethodKeys = Object.keys( state.availablePaymentMethods ); @@ -91,13 +85,11 @@ export const getActiveSavedPaymentMethods = ( ); }; -export const paymentMethodsInitialized = ( state: PaymentMethodDataState ) => { +export const paymentMethodsInitialized = ( state: PaymentState ) => { return state.paymentMethodsInitialized; }; -export const expressPaymentMethodsInitialized = ( - state: PaymentMethodDataState -) => { +export const expressPaymentMethodsInitialized = ( state: PaymentState ) => { return state.expressPaymentMethodsInitialized; }; @@ -105,7 +97,7 @@ export const expressPaymentMethodsInitialized = ( * @deprecated - use these selectors instead: isPaymentPristine, isPaymentStarted, isPaymentProcessing, * isPaymentFinished, hasPaymentError, isPaymentSuccess, isPaymentFailed */ -export const getCurrentStatus = ( state: PaymentMethodDataState ) => { +export const getCurrentStatus = ( state: PaymentState ) => { deprecated( 'getCurrentStatus', { since: '8.9.0', alternative: @@ -126,10 +118,14 @@ export const getCurrentStatus = ( state: PaymentMethodDataState ) => { }; }; -export const getShouldSavePaymentMethod = ( state: PaymentMethodDataState ) => { +export const getShouldSavePaymentMethod = ( state: PaymentState ) => { return state.shouldSavePaymentMethod; }; -export const getState = ( state: PaymentMethodDataState ) => { +export const getPaymentResult = ( state: PaymentState ) => { + return state.paymentResult; +}; + +export const getState = ( state: PaymentState ) => { return state; }; diff --git a/assets/js/data/payment/test/reducers.js b/assets/js/data/payment/test/reducers.js index f4575a06c93..2dc393912d6 100644 --- a/assets/js/data/payment/test/reducers.js +++ b/assets/js/data/payment/test/reducers.js @@ -180,4 +180,25 @@ describe( 'paymentMethodDataReducer', () => { activeSavedToken: '', } ); } ); + + it( 'should handle SET_PAYMENT_RESULT', () => { + const mockResponse = { + message: 'success', + redirectUrl: 'https://example.com', + paymentStatus: 'not set', + paymentDetails: {}, + }; + + const expectedState = { + ...originalState, + paymentResult: mockResponse, + }; + + expect( + reducer( originalState, { + type: ACTION_TYPES.SET_PAYMENT_RESULT, + data: mockResponse, + } ) + ).toEqual( expectedState ); + } ); } ); diff --git a/checkstyle.xml b/checkstyle.xml index cf75274f51d..cf45d060794 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -645,22 +645,6 @@ - - - - - - - - - - - @@ -696,15 +680,31 @@ - - - - - - + + + + + + + + + + + + + + + + + -> $GITHUB_OUTPUT + - name: Set up Composer caching - uses: actions/cache@v2 + uses: actions/cache@v3 env: cache-name: cache-composer-dependencies with: diff --git a/.github/workflows/php-js-e2e-tests.yml b/.github/workflows/php-js-e2e-tests.yml index 3ae7b570532..f895ed627d3 100644 --- a/.github/workflows/php-js-e2e-tests.yml +++ b/.github/workflows/php-js-e2e-tests.yml @@ -44,8 +44,9 @@ jobs: - name: Get Composer Cache Directory id: composer-cache run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" - - uses: actions/cache@v2 + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -124,8 +125,8 @@ jobs: - name: Get Composer Cache Directory id: composer-cache run: | - echo "::set-output name=dir::$(composer config cache-files-dir)" - - uses: actions/cache@v2 + echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT + - uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} diff --git a/.github/workflows/typescript-monitoring.yml b/.github/workflows/typescript-monitoring.yml index db19212bba3..7c36bbf9925 100644 --- a/.github/workflows/typescript-monitoring.yml +++ b/.github/workflows/typescript-monitoring.yml @@ -13,7 +13,7 @@ jobs: ref: ${{ github.event.pull_request.head.ref }} - name: Cache node modules - uses: actions/cache@v2 + uses: actions/cache@v3 env: cache-name: cache-node-modules with: @@ -51,7 +51,7 @@ jobs: - name: Check if the checklist.xml file is changed id: verify_diff run: | - git diff --quiet checkstyle.xml || echo "::set-output name=is_different_checkstyle::true" + git diff --quiet checkstyle.xml || echo "is_different_checkstyle=true" >> $GITHUB_OUTPUT - name: Commit the new checklist.xml file if: steps.verify_diff.outputs.is_different_checkstyle == 'true' diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index df22efb65fb..e9cd2f144bd 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -43,7 +43,7 @@ jobs: - name: Get Composer Cache Directory id: composer-cache run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} @@ -100,7 +100,7 @@ jobs: run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT - name: Set up Composer caching - uses: actions/cache@v2 + uses: actions/cache@v3 env: cache-name: cache-composer-dependencies with: diff --git a/tests/e2e/specs/shopper/cart-checkout/cart.test.js b/tests/e2e/specs/shopper/cart-checkout/cart.test.js index 9cc51df58fb..075d44434e9 100644 --- a/tests/e2e/specs/shopper/cart-checkout/cart.test.js +++ b/tests/e2e/specs/shopper/cart-checkout/cart.test.js @@ -105,7 +105,6 @@ describe( 'Shopper → Cart', () => { await shopper.block.productIsInCart( SIMPLE_VIRTUAL_PRODUCT_NAME, 4 ); } ); - // eslint-disable-next-line jest/expect-expect it( 'User can see Cross-Sells products block', async () => { await shopper.block.emptyCart(); await shopper.block.goToShop(); @@ -119,7 +118,9 @@ describe( 'Shopper → Cart', () => { await page.waitForSelector( '.wp-block-woocommerce-cart-line-items-block tr:nth-child(2)' ); - await shopper.block.productIsInCart( '32GB USB Stick', 1 ); + await expect( + shopper.block.productIsInCart( '32GB USB Stick', 1 ) + ).toBeTruthy(); } ); it( 'User can proceed to checkout', async () => { From bad1af811e3f9babfa1152a64f8da1d583820260 Mon Sep 17 00:00:00 2001 From: Luigi Teschio Date: Fri, 18 Nov 2022 16:44:39 +0100 Subject: [PATCH 16/27] Product Query - Add e2e tests for the filter by price block (#7351) * Product Query - Add e2e tests for the Filter By Price block * fix comment * remove not used import * update description * update functions after merge * improve check --- src/BlockTypes/ProductQuery.php | 2 +- .../shopper/filter-products-by-price.test.ts | 121 +++++++++++++++++- .../shopper/filter-products-by-rating.test.ts | 2 +- 3 files changed, 119 insertions(+), 6 deletions(-) diff --git a/src/BlockTypes/ProductQuery.php b/src/BlockTypes/ProductQuery.php index cf1f7b5f7f6..28afc7d08d3 100644 --- a/src/BlockTypes/ProductQuery.php +++ b/src/BlockTypes/ProductQuery.php @@ -327,7 +327,7 @@ private function get_filter_by_price_query() { $max_price_query = empty( $max_price ) ? array() : [ 'key' => '_price', 'value' => $max_price, - 'compare' => '<=', + 'compare' => '<', 'type' => 'numeric', ]; diff --git a/tests/e2e/specs/shopper/filter-products-by-price.test.ts b/tests/e2e/specs/shopper/filter-products-by-price.test.ts index b6cee917e7a..0f37d4cd0ac 100644 --- a/tests/e2e/specs/shopper/filter-products-by-price.test.ts +++ b/tests/e2e/specs/shopper/filter-products-by-price.test.ts @@ -24,7 +24,7 @@ import { useTheme, waitForAllProductsBlockLoaded, } from '../../utils'; -import { clickLink } from '../../../utils'; +import { clickLink, saveOrPublish } from '../../../utils'; const block = { name: 'Filter by Price', @@ -38,6 +38,7 @@ const block = { frontend: { priceMaxAmount: '.wc-block-price-filter__amount--max', productsList: '.wc-block-grid__products > li', + queryProductsList: '.wp-block-post-template > li', classicProductsList: '.products.columns-3 > li', submitButton: '.wc-block-components-filter-submit-button', }, @@ -83,7 +84,7 @@ describe( `${ block.name } Block`, () => { await page.goto( link ); } ); - it( 'should render', async () => { + it( 'should render products', async () => { await waitForAllProductsBlockLoaded(); const products = await page.$$( selectors.frontend.productsList ); @@ -139,7 +140,7 @@ describe( `${ block.name } Block`, () => { await deleteAllTemplates( 'wp_template_part' ); } ); - it( 'should render', async () => { + it( 'should render products', async () => { const products = await page.$$( selectors.frontend.classicProductsList ); @@ -179,7 +180,7 @@ describe( `${ block.name } Block`, () => { await expect( page ).toMatch( block.foundProduct ); } ); - it( 'should refresh the page only if the user click on button', async () => { + it( 'should refresh the page only if the user clicks on button', async () => { await goToTemplateEditor( { postId: productCatalogTemplateId, } ); @@ -201,10 +202,13 @@ describe( `${ block.name } Block`, () => { await page.waitForSelector( block.class + '.is-loading', { hidden: true, } ); + expect( isRefreshed ).not.toBeCalled(); await setMaxPrice(); + expect( isRefreshed ).not.toBeCalled(); + await clickLink( selectors.frontend.submitButton ); await page.waitForSelector( @@ -226,4 +230,113 @@ describe( `${ block.name } Block`, () => { ); } ); } ); + + describe( 'with Product Query Block', () => { + let editorPageUrl = ''; + let frontedPageUrl = ''; + beforeAll( async () => { + await switchUserToAdmin(); + await createNewPost( { + postType: 'post', + title: block.name, + } ); + + await insertBlock( 'Product Query' ); + await insertBlock( block.name ); + await insertBlock( 'Active Product Filters' ); + await page.waitForNetworkIdle(); + await publishPost(); + + editorPageUrl = page.url(); + frontedPageUrl = await page.evaluate( () => + wp.data.select( 'core/editor' ).getPermalink() + ); + await page.goto( frontedPageUrl ); + } ); + + it( 'should render products', async () => { + const products = await page.$$( + selectors.frontend.queryProductsList + ); + + expect( products ).toHaveLength( 5 ); + } ); + + it( 'should show only products that match the filter', async () => { + const isRefreshed = jest.fn( () => void 0 ); + page.on( 'load', isRefreshed ); + + await page.waitForSelector( block.class + '.is-loading', { + hidden: true, + } ); + + await expect( page ).toMatch( block.foundProduct ); + expect( isRefreshed ).not.toBeCalled(); + + await Promise.all( [ setMaxPrice(), page.waitForNavigation() ] ); + + await page.waitForSelector( selectors.frontend.queryProductsList ); + const products = await page.$$( + selectors.frontend.queryProductsList + ); + + const pageURL = page.url(); + const parsedURL = new URL( pageURL ); + + expect( isRefreshed ).toBeCalledTimes( 1 ); + expect( products ).toHaveLength( 1 ); + + expect( parsedURL.search ).toEqual( + block.urlSearchParamWhenFilterIsApplied + ); + await expect( page ).toMatch( block.foundProduct ); + } ); + + it( 'should refresh the page only if the user click on button', async () => { + await page.goto( editorPageUrl ); + + await openBlockEditorSettings(); + await selectBlockByName( block.slug ); + await page.waitForXPath( + block.selectors.editor.filterButtonToggle + ); + const [ filterButtonToggle ] = await page.$x( + block.selectors.editor.filterButtonToggle + ); + await filterButtonToggle.click(); + + await saveOrPublish(); + await page.goto( frontedPageUrl ); + + const isRefreshed = jest.fn( () => void 0 ); + page.on( 'load', isRefreshed ); + await page.waitForSelector( block.class + '.is-loading', { + hidden: true, + } ); + + expect( isRefreshed ).not.toBeCalled(); + + await setMaxPrice(); + + expect( isRefreshed ).not.toBeCalled(); + + await clickLink( selectors.frontend.submitButton ); + + await page.waitForSelector( selectors.frontend.queryProductsList ); + + const products = await page.$$( + selectors.frontend.queryProductsList + ); + + const pageURL = page.url(); + const parsedURL = new URL( pageURL ); + + expect( isRefreshed ).toBeCalledTimes( 1 ); + expect( products ).toHaveLength( 1 ); + await expect( page ).toMatch( block.foundProduct ); + expect( parsedURL.search ).toEqual( + block.urlSearchParamWhenFilterIsApplied + ); + } ); + } ); } ); diff --git a/tests/e2e/specs/shopper/filter-products-by-rating.test.ts b/tests/e2e/specs/shopper/filter-products-by-rating.test.ts index a58b81c30be..5e5eeba928e 100644 --- a/tests/e2e/specs/shopper/filter-products-by-rating.test.ts +++ b/tests/e2e/specs/shopper/filter-products-by-rating.test.ts @@ -157,7 +157,7 @@ describe( `${ block.name } Block`, () => { await waitForCanvas(); await selectBlockByName( block.slug ); - await openBlockEditorSettings( { isFSEEditor: true } ); + await openBlockEditorSettings(); await page.waitForXPath( block.selectors.editor.filterButtonToggle ); From cef076a63513e1595bbc2f19a5804187b8a41b51 Mon Sep 17 00:00:00 2001 From: Tung Du Date: Mon, 21 Nov 2022 08:08:12 +0700 Subject: [PATCH 17/27] Product Query E2E tests: Sale and Stock status filters tests (#7684) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../product-query/product-filters.test.ts | 271 ++++++++++++++++++ tests/utils/find-tools-panel-with-title.ts | 10 + tests/utils/get-fixture-products-data.ts | 5 +- tests/utils/get-form-element-id-by-label.ts | 18 ++ tests/utils/get-toggle-id-by-label.ts | 24 +- tests/utils/index.js | 2 + 6 files changed, 318 insertions(+), 12 deletions(-) create mode 100644 tests/e2e/specs/backend/product-query/product-filters.test.ts create mode 100644 tests/utils/find-tools-panel-with-title.ts create mode 100644 tests/utils/get-form-element-id-by-label.ts diff --git a/tests/e2e/specs/backend/product-query/product-filters.test.ts b/tests/e2e/specs/backend/product-query/product-filters.test.ts new file mode 100644 index 00000000000..6fe09b21881 --- /dev/null +++ b/tests/e2e/specs/backend/product-query/product-filters.test.ts @@ -0,0 +1,271 @@ +/** + * External dependencies + */ +import { canvas, setPostContent, insertBlock } from '@wordpress/e2e-test-utils'; +import { + visitBlockPage, + saveOrPublish, + selectBlockByName, + findToolsPanelWithTitle, + getFixtureProductsData, + getFormElementIdByLabel, + shopper, + getToggleIdByLabel, +} from '@woocommerce/blocks-test-utils'; +import { ElementHandle } from 'puppeteer'; +import { setCheckbox, unsetCheckbox } from '@woocommerce/e2e-utils'; + +/** + * Internal dependencies + */ +import { + GUTENBERG_EDITOR_CONTEXT, + describeOrSkip, + waitForCanvas, + openBlockEditorSettings, +} from '../../../utils'; + +const block = { + name: 'Product Query', + slug: 'core/query', + class: '.wp-block-query', +}; + +/** + * Selectors used for interacting with the block in the editor. These selectors + * can be changed upstream in Gutenberg, so we scope them here for + * maintainability. + * + * There are also some labels that are used repeatedly, but we don't scope them + * in favor of readability. Unlike selectors, those label are visible to end + * users, so it's easier to understand what's going on if we don't scope them. + * Those labels can get upated in the future, but the tests will fail and we'll + * know to update them. + */ +const SELECTORS = { + productFiltersDropdownButton: ( + { expanded }: { expanded: boolean } = { expanded: false } + ) => + `.components-tools-panel-header .components-dropdown-menu button[aria-expanded="${ expanded }"]`, + productFiltersDropdown: + '.components-dropdown-menu__menu[aria-label="Product filters options"]', + productFiltersDropdownItem: '.components-menu-item__button', + editorPreview: { + productsGrid: 'ul.wp-block-post-template', + productsGridItem: + 'ul.wp-block-post-template > li.block-editor-block-preview__live-content', + }, + productsGrid: `${ block.class } ul.wp-block-post-template`, + productsGridItem: `${ block.class } ul.wp-block-post-template > li.product`, + formTokenFieldLabel: '.components-form-token-field__label', + tokenRemoveButton: '.components-form-token-field__remove-token', +}; + +const toggleProductFilter = async ( filterName: string ) => { + const $productFiltersPanel = await findToolsPanelWithTitle( + 'Product filters' + ); + await expect( $productFiltersPanel ).toClick( + SELECTORS.productFiltersDropdownButton() + ); + await canvas().waitForSelector( SELECTORS.productFiltersDropdown ); + await expect( canvas() ).toClick( SELECTORS.productFiltersDropdownItem, { + text: filterName, + } ); + await expect( $productFiltersPanel ).toClick( + SELECTORS.productFiltersDropdownButton( { expanded: true } ) + ); +}; + +const resetProductQueryBlockPage = async () => { + await visitBlockPage( `${ block.name } Block` ); + await waitForCanvas(); + await setPostContent( '' ); + await insertBlock( block.name ); + await saveOrPublish(); +}; + +const getPreviewProducts = async (): Promise< ElementHandle[] > => { + await canvas().waitForSelector( SELECTORS.editorPreview.productsGrid ); + return await canvas().$$( SELECTORS.editorPreview.productsGridItem ); +}; + +const getFrontEndProducts = async (): Promise< ElementHandle[] > => { + await canvas().waitForSelector( SELECTORS.productsGrid ); + return await canvas().$$( SELECTORS.productsGridItem ); +}; + +describeOrSkip( GUTENBERG_EDITOR_CONTEXT === 'gutenberg' )( + 'Product Query > Products Filters', + () => { + let $productFiltersPanel: ElementHandle< Node >; + beforeEach( async () => { + /** + * Reset the block page before each test to ensure the block is + * inserted in a known state. This is also needed to ensure each + * test can be run individually. + */ + await resetProductQueryBlockPage(); + await openBlockEditorSettings(); + await selectBlockByName( block.slug ); + $productFiltersPanel = await findToolsPanelWithTitle( + 'Product filters' + ); + } ); + + /** + * Reset the content of Product Query Block page after this test suite + * to avoid breaking other tests. + */ + afterAll( async () => { + await resetProductQueryBlockPage(); + } ); + + describe( 'Sale Status', () => { + it( 'Sale status is disabled by default', async () => { + await expect( $productFiltersPanel ).not.toMatch( + 'Show only products on sale' + ); + } ); + + it( 'Can add and remove Sale Status filter', async () => { + await toggleProductFilter( 'Sale status' ); + await expect( $productFiltersPanel ).toMatch( + 'Show only products on sale' + ); + await toggleProductFilter( 'Sale status' ); + await expect( $productFiltersPanel ).not.toMatch( + 'Show only products on sale' + ); + } ); + + it( 'Editor preview shows correct products corresponding to the value `Show only products on sale`', async () => { + const defaultCount = getFixtureProductsData().length; + const saleCount = getFixtureProductsData( 'sale_price' ).length; + expect( await getPreviewProducts() ).toHaveLength( + defaultCount + ); + await toggleProductFilter( 'Sale status' ); + await setCheckbox( + await getToggleIdByLabel( 'Show only products on sale' ) + ); + expect( await getPreviewProducts() ).toHaveLength( saleCount ); + await unsetCheckbox( + await getToggleIdByLabel( 'Show only products on sale' ) + ); + expect( await getPreviewProducts() ).toHaveLength( + defaultCount + ); + } ); + + it( 'Works on the front end', async () => { + await toggleProductFilter( 'Sale status' ); + await setCheckbox( + await getToggleIdByLabel( 'Show only products on sale' ) + ); + await canvas().waitForSelector( + SELECTORS.editorPreview.productsGrid + ); + await saveOrPublish(); + await shopper.block.goToBlockPage( block.name ); + const saleCount = getFixtureProductsData( 'sale_price' ).length; + expect( await getFrontEndProducts() ).toHaveLength( saleCount ); + } ); + } ); + + describe( 'Stock Status', () => { + it( 'Stock status is enabled by default', async () => { + await expect( $productFiltersPanel ).toMatchElement( + SELECTORS.formTokenFieldLabel, + { text: 'Stock status' } + ); + } ); + + it( 'Can add and remove Stock Status filter', async () => { + await toggleProductFilter( 'Stock status' ); + await expect( $productFiltersPanel ).not.toMatchElement( + SELECTORS.formTokenFieldLabel, + { text: 'Stock status' } + ); + await toggleProductFilter( 'Stock status' ); + await expect( $productFiltersPanel ).toMatchElement( + SELECTORS.formTokenFieldLabel, + { text: 'Stock status' } + ); + } ); + + it( 'All statuses are enabled by default', async () => { + await expect( $productFiltersPanel ).toMatch( 'In stock' ); + await expect( $productFiltersPanel ).toMatch( 'Out of stock' ); + await expect( $productFiltersPanel ).toMatch( 'On backorder' ); + } ); + + it( 'Editor preview shows all products by default', async () => { + const defaultCount = getFixtureProductsData().length; + + expect( await getPreviewProducts() ).toHaveLength( + defaultCount + ); + } ); + + /** + * Skipping this test for now as Product Query doesn't show correct set of products based on stock status. + * + * @see https://github.com/woocommerce/woocommerce-blocks/pull/7682 + */ + it.skip( 'Editor preview shows correct products that has enabled stock statuses', async () => { + const $$tokenRemoveButtons = await $productFiltersPanel.$$( + SELECTORS.tokenRemoveButton + ); + for ( const $el of $$tokenRemoveButtons ) { + await $el.click(); + } + + const $stockStatusInput = await canvas().$( + await getFormElementIdByLabel( + 'Stock status', + SELECTORS.formTokenFieldLabel.replace( '.', '' ) + ) + ); + await $stockStatusInput.click(); + await canvas().keyboard.type( 'Out of Stock' ); + await canvas().keyboard.press( 'Enter' ); + const outOfStockCount = getFixtureProductsData( + 'stock_status' + ).filter( ( status ) => status === 'outofstock' ).length; + expect( await getPreviewProducts() ).toHaveLength( + outOfStockCount + ); + } ); + + it( 'Works on the front end', async () => { + const tokenRemoveButtons = await $productFiltersPanel.$$( + SELECTORS.tokenRemoveButton + ); + for ( const el of tokenRemoveButtons ) { + await el.click(); + } + const $stockStatusInput = await canvas().$( + await getFormElementIdByLabel( + 'Stock status', + SELECTORS.formTokenFieldLabel.replace( '.', '' ) + ) + ); + await $stockStatusInput.click(); + await canvas().keyboard.type( 'Out of stock' ); + await canvas().keyboard.press( 'Enter' ); + await canvas().waitForSelector( + SELECTORS.editorPreview.productsGrid + ); + await saveOrPublish(); + await shopper.block.goToBlockPage( block.name ); + const outOfStockCount = getFixtureProductsData( + 'stock_status' + ).filter( ( status ) => status === 'outofstock' ).length; + expect( await getFrontEndProducts() ).toHaveLength( + outOfStockCount + ); + } ); + } ); + } +); diff --git a/tests/utils/find-tools-panel-with-title.ts b/tests/utils/find-tools-panel-with-title.ts new file mode 100644 index 00000000000..9620d217dd1 --- /dev/null +++ b/tests/utils/find-tools-panel-with-title.ts @@ -0,0 +1,10 @@ +/** + * External dependencies + */ +import { canvas } from '@wordpress/e2e-test-utils'; + +export const findToolsPanelWithTitle = async ( panelTitle: string ) => { + const panelToggleSelector = `//div[contains(@class, "components-tools-panel-header")]//h2[contains(@class, "components-heading") and contains(text(),"${ panelTitle }")]`; + const panelSelector = `${ panelToggleSelector }/ancestor::*[contains(concat(" ", @class, " "), " components-tools-panel ")]`; + return await canvas().waitForXPath( panelSelector ); +}; diff --git a/tests/utils/get-fixture-products-data.ts b/tests/utils/get-fixture-products-data.ts index feee6eaf3cc..02a727e04c1 100644 --- a/tests/utils/get-fixture-products-data.ts +++ b/tests/utils/get-fixture-products-data.ts @@ -6,7 +6,10 @@ import { Products } from '../e2e/fixtures/fixture-data'; /** * Get products data by key from fixtures. */ -export const getFixtureProductsData = ( key: string ) => { +export const getFixtureProductsData = ( key = '' ) => { + if ( ! key ) { + return Products(); + } return Products() .map( ( product ) => product[ key ] ) .filter( Boolean ); diff --git a/tests/utils/get-form-element-id-by-label.ts b/tests/utils/get-form-element-id-by-label.ts new file mode 100644 index 00000000000..a99721471dd --- /dev/null +++ b/tests/utils/get-form-element-id-by-label.ts @@ -0,0 +1,18 @@ +/** + * External dependencies + */ +import { canvas } from '@wordpress/e2e-test-utils'; + +export const getFormElementIdByLabel = async ( + text: string, + className: string +) => { + const labelElement = await canvas().waitForXPath( + `//label[contains(text(), "${ text }") and contains(@class, "${ className }")]`, + { visible: true } + ); + return await canvas().evaluate( + ( label ) => `#${ label.getAttribute( 'for' ) }`, + labelElement + ); +}; diff --git a/tests/utils/get-toggle-id-by-label.ts b/tests/utils/get-toggle-id-by-label.ts index 51195446905..814204e6bf2 100644 --- a/tests/utils/get-toggle-id-by-label.ts +++ b/tests/utils/get-toggle-id-by-label.ts @@ -1,7 +1,13 @@ +/** + * External dependencies + */ +import { canvas } from '@wordpress/e2e-test-utils'; + /** * Internal dependencies */ import { DEFAULT_TIMEOUT } from './constants'; +import { getFormElementIdByLabel } from './get-form-element-id-by-label'; /** * Get the ID of the setting toogle so test can manipulate the toggle using @@ -12,24 +18,20 @@ import { DEFAULT_TIMEOUT } from './constants'; * check if the node still attached to the document before returning its * ID. If the node is detached, it means that the toggle is rendered, then * we retry by calling this function again with increased retry argument. We - * will retry until the timeout is reached. + * will retry until the default timeout is reached, which is 30s. */ export const getToggleIdByLabel = async ( label: string, retry = 0 ): Promise< string > => { const delay = 1000; - const labelElement = await page.waitForXPath( - `//label[contains(text(), "${ label }") and contains(@class, "components-toggle-control__label")]`, - { visible: true } - ); - const checkboxId = await page.evaluate( - ( toggleLabel ) => `#${ toggleLabel.getAttribute( 'for' ) }`, - labelElement - ); // Wait a bit for toggle to finish rerendering. - await page.waitForTimeout( delay ); - const checkbox = await page.$( checkboxId ); + await canvas().waitForTimeout( delay ); + const checkboxId = await getFormElementIdByLabel( + label, + 'components-toggle-control__label' + ); + const checkbox = await canvas().$( checkboxId ); if ( ! checkbox ) { if ( retry * delay < DEFAULT_TIMEOUT ) { return await getToggleIdByLabel( label, retry + 1 ); diff --git a/tests/utils/index.js b/tests/utils/index.js index b30ce1b84fd..0727d21e73e 100644 --- a/tests/utils/index.js +++ b/tests/utils/index.js @@ -18,6 +18,8 @@ export * from './taxes'; export * from './constants'; export { insertInnerBlock } from './insert-inner-block'; export { getFixtureProductsData } from './get-fixture-products-data'; +export { findToolsPanelWithTitle } from './find-tools-panel-with-title'; +export { getFormElementIdByLabel } from './get-form-element-id-by-label'; export { getToggleIdByLabel } from './get-toggle-id-by-label'; export { insertBlockUsingQuickInserter } from './insert-block-using-quick-inserter'; export { insertBlockUsingSlash } from './insert-block-using-slash'; From 56c717209afd53f45fc5d0204aaccb9d5628e1fa Mon Sep 17 00:00:00 2001 From: Tung Du Date: Mon, 21 Nov 2022 19:07:23 +0700 Subject: [PATCH 18/27] Refactor TypeScripts error monitoring workflow to avoid tracking checkstyle.xml (#7717) --- .github/monitor-typescript-errors/action.yml | 7 +- .github/monitor-typescript-errors/index.js | 23 +- .../monitor-typescript-errors/utils/github.js | 25 - .github/workflows/typescript-monitoring.yml | 32 +- checkstyle.xml | 4185 ----------------- 5 files changed, 26 insertions(+), 4246 deletions(-) delete mode 100644 checkstyle.xml diff --git a/.github/monitor-typescript-errors/action.yml b/.github/monitor-typescript-errors/action.yml index 28a6786160b..160f7287dfc 100644 --- a/.github/monitor-typescript-errors/action.yml +++ b/.github/monitor-typescript-errors/action.yml @@ -4,8 +4,11 @@ inputs: repo-token: description: 'GitHub token' required: true - compare: - description: 'Path checkstyle.xml file' + checkstyle: + description: 'Path checkstyle.xml file of current PR/branch' + required: true + checkstyle-trunk: + description: 'Path checkstyle.xml file of trunk' required: true runs: diff --git a/.github/monitor-typescript-errors/index.js b/.github/monitor-typescript-errors/index.js index 03d5610c762..cde3469857e 100644 --- a/.github/monitor-typescript-errors/index.js +++ b/.github/monitor-typescript-errors/index.js @@ -4,7 +4,7 @@ const { setFailed, getInput } = require( '@actions/core' ); const { parseXml, getFilesWithNewErrors } = require( './utils/xml' ); const { generateMarkdownMessage } = require( './utils/markdown' ); const { addRecord } = require( './utils/airtable' ); -const { getFileContent, addComment } = require( './utils/github' ); +const { addComment } = require( './utils/github' ); const runner = async () => { const token = getInput( 'repo-token', { required: true } ); @@ -12,27 +12,18 @@ const runner = async () => { const payload = context.payload; const repo = payload.repository.name; const owner = payload.repository.owner.login; - const fileName = getInput( 'compare', { + const fileName = getInput( 'checkstyle', { + required: true, + } ); + const trunkFileName = getInput( 'checkstyle-trunk', { required: true, } ); const newCheckStyleFile = fs.readFileSync( fileName ); const newCheckStyleFileParsed = parseXml( newCheckStyleFile ); - const currentCheckStyleFile = await getFileContent( { - octokit, - owner, - repo, - fileName, - onFail: setFailed, - } ); - - if ( ! currentCheckStyleFile.data ) { - setFailed( 'No Content Available' ); - return; - } - + const currentCheckStyleFile = fs.readFileSync( trunkFileName ); const currentCheckStyleFileContentParsed = parseXml( - currentCheckStyleFile.data + currentCheckStyleFile ); const { header } = generateMarkdownMessage( newCheckStyleFileParsed ); diff --git a/.github/monitor-typescript-errors/utils/github.js b/.github/monitor-typescript-errors/utils/github.js index 381b81d5577..2b49d0f3aae 100644 --- a/.github/monitor-typescript-errors/utils/github.js +++ b/.github/monitor-typescript-errors/utils/github.js @@ -1,28 +1,3 @@ -exports.getFileContent = async ( { - octokit, - owner, - repo, - fileName, - onFail, -} ) => { - try { - return await octokit.rest.repos.getContent( { - owner, - repo, - path: fileName, - mediaType: { - format: 'raw', - }, - } ); - } catch ( err ) { - if ( err.status === '404' ) { - return; - } - - onFail( err.message ); - } -}; - const getReportCommentId = async ( { octokit, owner, repo, payload } ) => { const currentComments = await octokit.rest.issues.listComments( { owner, diff --git a/.github/workflows/typescript-monitoring.yml b/.github/workflows/typescript-monitoring.yml index 7c36bbf9925..6afbf1cf2a8 100644 --- a/.github/workflows/typescript-monitoring.yml +++ b/.github/workflows/typescript-monitoring.yml @@ -8,9 +8,9 @@ jobs: check-typescript-errors-with-trunk: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: - ref: ${{ github.event.pull_request.head.ref }} + ref: 'trunk' - name: Cache node modules uses: actions/cache@v3 @@ -30,10 +30,19 @@ jobs: node-version-file: '.nvmrc' cache: 'npm' - - name: npm install + - name: Generate checkstyle for trunk run: | npm ci npm run ts:log-errors + mv checkstyle.xml $HOME/checkstyle-trunk.xml + + - uses: actions/checkout@v3 + + - name: Generate checkstyle for current PR + run: | + npm ci + npm run ts:log-errors + mv $HOME/checkstyle-trunk.xml checkstyle-trunk.xml - name: Get branch name id: branch-name @@ -43,21 +52,8 @@ jobs: uses: ./.github/monitor-typescript-errors with: repo-token: '${{ secrets.GITHUB_TOKEN }}' - compare: checkstyle.xml + checkstyle: checkstyle.xml + checkstyle-trunk: checkstyle-trunk.xml env: AIRTABLE_API_KEY: ${{ secrets.AIRTABLE_API_KEY }} CURRENT_BRANCH: ${{ steps.branch-name.outputs.current_branch }} - - - name: Check if the checklist.xml file is changed - id: verify_diff - run: | - git diff --quiet checkstyle.xml || echo "is_different_checkstyle=true" >> $GITHUB_OUTPUT - - - name: Commit the new checklist.xml file - if: steps.verify_diff.outputs.is_different_checkstyle == 'true' - run: | - git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git add checkstyle.xml - git commit -m "bot: update checkstyle.xml" - git push diff --git a/checkstyle.xml b/checkstyle.xml deleted file mode 100644 index cf45d060794..00000000000 --- a/checkstyle.xml +++ /dev/null @@ -1,4185 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 2ac02ad4418c3839556eac8f2f469fc36752e078 Mon Sep 17 00:00:00 2001 From: Thomas Roberts <5656702+opr@users.noreply.github.com> Date: Mon, 21 Nov 2022 12:13:24 +0000 Subject: [PATCH 19/27] Add tests to ensure argument passed to `canMakePayment` is correct (#7478) * Add tests to ensure argument passed to canMakePayment is correct * bot: update checkstyle.xml * bot: update checkstyle.xml * Fix import and type after rebase * bot: update checkstyle.xml * Trigger Build Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../payment/test/check-payment-methods.tsx | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 assets/js/data/payment/test/check-payment-methods.tsx diff --git a/assets/js/data/payment/test/check-payment-methods.tsx b/assets/js/data/payment/test/check-payment-methods.tsx new file mode 100644 index 00000000000..9bdf7ef0cf1 --- /dev/null +++ b/assets/js/data/payment/test/check-payment-methods.tsx @@ -0,0 +1,160 @@ +/** + * External dependencies + */ +import * as wpDataFunctions from '@wordpress/data'; +import { PAYMENT_STORE_KEY } from '@woocommerce/block-data'; +import { + registerPaymentMethod, + registerExpressPaymentMethod, + __experimentalDeRegisterPaymentMethod, + __experimentalDeRegisterExpressPaymentMethod, +} from '@woocommerce/blocks-registry'; +import { CanMakePaymentArgument } from '@woocommerce/type-defs/payments'; + +/** + * Internal dependencies + */ +import { checkPaymentMethodsCanPay } from '../utils/check-payment-methods'; + +const requiredKeyCheck = ( args: CanMakePaymentArgument ) => { + const requiredKeys = [ + 'billingData', + 'billingAddress', + 'cart', + 'cartNeedsShipping', + 'cartTotals', + 'paymentRequirements', + 'selectedShippingMethods', + 'shippingAddress', + ]; + const argKeys = Object.keys( args ); + + const requiredCartKeys = [ + 'cartCoupons', + 'cartItems', + 'crossSellsProducts', + 'cartFees', + 'cartItemsCount', + 'cartItemsWeight', + 'cartNeedsPayment', + 'cartNeedsShipping', + 'cartItemErrors', + 'cartTotals', + 'cartIsLoading', + 'cartErrors', + 'billingData', + 'billingAddress', + 'shippingAddress', + 'extensions', + 'shippingRates', + 'isLoadingRates', + 'cartHasCalculatedShipping', + 'paymentRequirements', + 'receiveCart', + ]; + const cartKeys = Object.keys( args.cart ); + const requiredTotalsKeys = [ + 'total_items', + 'total_items_tax', + 'total_fees', + 'total_fees_tax', + 'total_discount', + 'total_discount_tax', + 'total_shipping', + 'total_shipping_tax', + 'total_price', + 'total_tax', + 'tax_lines', + 'currency_code', + 'currency_symbol', + 'currency_minor_unit', + 'currency_decimal_separator', + 'currency_thousand_separator', + 'currency_prefix', + 'currency_suffix', + ]; + const totalsKeys = Object.keys( args.cartTotals ); + return ( + requiredKeys.every( ( key ) => argKeys.includes( key ) ) && + requiredTotalsKeys.every( ( key ) => totalsKeys.includes( key ) ) && + requiredCartKeys.every( ( key ) => cartKeys.includes( key ) ) + ); +}; + +const mockedCanMakePayment = jest.fn().mockImplementation( requiredKeyCheck ); +const mockedExpressCanMakePayment = jest + .fn() + .mockImplementation( requiredKeyCheck ); + +const registerMockPaymentMethods = ( savedCards = true ) => { + [ 'credit-card' ].forEach( ( name ) => { + registerPaymentMethod( { + name, + label: name, + content:
A payment method
, + edit:
A payment method
, + icons: null, + canMakePayment: mockedCanMakePayment, + supports: { + showSavedCards: savedCards, + showSaveOption: true, + features: [ 'products' ], + }, + ariaLabel: name, + } ); + } ); + [ 'express-payment' ].forEach( ( name ) => { + const Content = ( { + onClose = () => void null, + onClick = () => void null, + } ) => { + return ( + <> + + + + ); + }; + registerExpressPaymentMethod( { + name, + content: , + edit:
An express payment method
, + canMakePayment: mockedExpressCanMakePayment, + paymentMethodId: name, + supports: { + features: [ 'products' ], + }, + } ); + } ); + wpDataFunctions + .dispatch( PAYMENT_STORE_KEY ) + .__internalUpdateAvailablePaymentMethods(); +}; + +const resetMockPaymentMethods = () => { + [ 'cheque', 'bacs', 'credit-card' ].forEach( ( name ) => { + __experimentalDeRegisterPaymentMethod( name ); + } ); + [ 'express-payment' ].forEach( ( name ) => { + __experimentalDeRegisterExpressPaymentMethod( name ); + } ); +}; + +describe( 'checkPaymentMethods', () => { + beforeEach( registerMockPaymentMethods ); + afterEach( resetMockPaymentMethods ); + + it( `Sends correct arguments to regular payment methods' canMakePayment functions`, async () => { + await checkPaymentMethodsCanPay(); + expect( mockedCanMakePayment ).toHaveReturnedWith( true ); + } ); + + it( `Sends correct arguments to express payment methods' canMakePayment functions`, async () => { + await checkPaymentMethodsCanPay( true ); + expect( mockedExpressCanMakePayment ).toHaveReturnedWith( true ); + } ); +} ); From 28fd155ac19c3cd53a003031715ef2388ab62afd Mon Sep 17 00:00:00 2001 From: Tarun Vijwani Date: Mon, 21 Nov 2022 18:04:53 +0400 Subject: [PATCH 20/27] Change action type name for use shipping as billing option (#7695) --- assets/js/data/checkout/action-types.ts | 3 +-- assets/js/data/checkout/actions.ts | 2 +- assets/js/data/checkout/reducers.ts | 2 +- assets/js/data/checkout/test/reducer.ts | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/assets/js/data/checkout/action-types.ts b/assets/js/data/checkout/action-types.ts index 4a06285a5f7..23eeae4570c 100644 --- a/assets/js/data/checkout/action-types.ts +++ b/assets/js/data/checkout/action-types.ts @@ -10,8 +10,7 @@ export const ACTION_TYPES = { SET_ORDER_NOTES: 'SET_CHECKOUT_ORDER_NOTES', INCREMENT_CALCULATING: 'INCREMENT_CALCULATING', DECREMENT_CALCULATING: 'DECREMENT_CALCULATING', - SET_SHIPPING_ADDRESS_AS_BILLING_ADDRESS: - 'SET_SHIPPING_ADDRESS_AS_BILLING_ADDRESS', + SET_USE_SHIPPING_AS_BILLING: 'SET_USE_SHIPPING_AS_BILLING', SET_SHOULD_CREATE_ACCOUNT: 'SET_SHOULD_CREATE_ACCOUNT', SET_EXTENSION_DATA: 'SET_EXTENSION_DATA', SET_IS_CART: 'SET_IS_CART', diff --git a/assets/js/data/checkout/actions.ts b/assets/js/data/checkout/actions.ts index bd4863046c8..148d6705bab 100644 --- a/assets/js/data/checkout/actions.ts +++ b/assets/js/data/checkout/actions.ts @@ -99,7 +99,7 @@ export const __internalSetCustomerId = ( customerId: number ) => ( { export const __internalSetUseShippingAsBilling = ( useShippingAsBilling: boolean ) => ( { - type: types.SET_SHIPPING_ADDRESS_AS_BILLING_ADDRESS, + type: types.SET_USE_SHIPPING_AS_BILLING, useShippingAsBilling, } ); diff --git a/assets/js/data/checkout/reducers.ts b/assets/js/data/checkout/reducers.ts index b4e28116110..ceeda9d57f6 100644 --- a/assets/js/data/checkout/reducers.ts +++ b/assets/js/data/checkout/reducers.ts @@ -98,7 +98,7 @@ const reducer = ( state = defaultState, action: CheckoutAction ) => { } break; - case types.SET_SHIPPING_ADDRESS_AS_BILLING_ADDRESS: + case types.SET_USE_SHIPPING_AS_BILLING: if ( action.useShippingAsBilling !== undefined && action.useShippingAsBilling !== state.useShippingAsBilling diff --git a/assets/js/data/checkout/test/reducer.ts b/assets/js/data/checkout/test/reducer.ts index bdaf595431e..127fde0a649 100644 --- a/assets/js/data/checkout/test/reducer.ts +++ b/assets/js/data/checkout/test/reducer.ts @@ -173,7 +173,7 @@ describe.only( 'Checkout Store Reducer', () => { ).toEqual( expectedState ); } ); - it( 'should handle SET_SHIPPING_ADDRESS_AS_BILLING_ADDRESS', () => { + it( 'should handle SET_USE_SHIPPING_AS_BILLING', () => { const expectedState = { ...defaultState, status: STATUS.IDLE, From 03da59183954388ee0ac27d451d189415496429d Mon Sep 17 00:00:00 2001 From: Lucio Giannotta Date: Mon, 21 Nov 2022 19:21:58 +0100 Subject: [PATCH 21/27] =?UTF-8?q?Product=20Query:=20Add=20order=20by=20?= =?UTF-8?q?=E2=80=9Cbest=20selling=E2=80=9D=20as=20a=20preset=20(#7687)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add support for “Popular Presets” for PQ block This commits achieves the following: * Adds a section in the inspector control called “Popular Presets”, which contains a dropdown with popular presets. * Adds support for the first preset: “Best selling products”. By selecting this, users can sort products by total sales. * Switches the order of the custom inspector controls and the default Query Loop inspector controls: our controls will be now on top as per the latest design spec (see pdnLyh-2By-p2). * Restricts the allowed Query parameters to the sort orders we want to allow according to the latest design spec (disabling title and date). * Removes the core “Order By” dropdown. * Refactor `setCustomQueryAttribute` to `setQueryAttribute` because since a few iterations, our custom query attributes are not deeply nested anymore, and this function can be used for the normal query too. * Add back-end support for sorting by Best Selling via the Product Query block * Adds the `popularity` value as an allowed value for `orderby` on `product` REST API calls. * Handles the query differently if the `orderby` value is one among the custom ones. --- assets/js/blocks/product-query/constants.ts | 8 ++- .../product-query/inspector-controls.tsx | 23 +++---- .../inspector-controls/popular-presets.tsx | 62 +++++++++++++++++++ assets/js/blocks/product-query/types.ts | 15 ++++- assets/js/blocks/product-query/utils.tsx | 11 ++-- src/BlockTypes/ProductQuery.php | 57 ++++++++++++++++- 6 files changed, 152 insertions(+), 24 deletions(-) create mode 100644 assets/js/blocks/product-query/inspector-controls/popular-presets.tsx diff --git a/assets/js/blocks/product-query/constants.ts b/assets/js/blocks/product-query/constants.ts index 04ecd6114fd..c5732551805 100644 --- a/assets/js/blocks/product-query/constants.ts +++ b/assets/js/blocks/product-query/constants.ts @@ -20,9 +20,13 @@ function objectOmit< T, K extends keyof T >( obj: T, key: K ) { export const QUERY_LOOP_ID = 'core/query'; -export const DEFAULT_CORE_ALLOWED_CONTROLS = [ 'order', 'taxQuery', 'search' ]; +export const DEFAULT_CORE_ALLOWED_CONTROLS = [ 'taxQuery', 'search' ]; -export const ALL_PRODUCT_QUERY_CONTROLS = [ 'onSale', 'stockStatus' ]; +export const ALL_PRODUCT_QUERY_CONTROLS = [ + 'presets', + 'onSale', + 'stockStatus', +]; export const DEFAULT_ALLOWED_CONTROLS = [ ...DEFAULT_CORE_ALLOWED_CONTROLS, diff --git a/assets/js/blocks/product-query/inspector-controls.tsx b/assets/js/blocks/product-query/inspector-controls.tsx index 72a4b173c44..222c2dfcbb3 100644 --- a/assets/js/blocks/product-query/inspector-controls.tsx +++ b/assets/js/blocks/product-query/inspector-controls.tsx @@ -26,7 +26,7 @@ import { } from './types'; import { isWooQueryBlockVariation, - setCustomQueryAttribute, + setQueryAttribute, useAllowedControls, } from './utils'; import { @@ -34,6 +34,7 @@ import { QUERY_LOOP_ID, STOCK_STATUS_OPTIONS, } from './constants'; +import { PopularPresets } from './inspector-controls/popular-presets'; const NAMESPACED_CONTROLS = ALL_PRODUCT_QUERY_CONTROLS.map( ( id ) => @@ -82,7 +83,7 @@ function getStockStatusIdByLabel( statusLabel: FormTokenField.Value ) { )?.[ 0 ]; } -export const INSPECTOR_CONTROLS = { +export const TOOLS_PANEL_CONTROLS = { onSale: ( props: ProductQueryBlock ) => { const { query } = props.attributes; @@ -98,7 +99,7 @@ export const INSPECTOR_CONTROLS = { ) } checked={ query.__woocommerceOnSale || false } onChange={ ( __woocommerceOnSale ) => { - setCustomQueryAttribute( props, { + setQueryAttribute( props, { __woocommerceOnSale, } ); } } @@ -124,7 +125,7 @@ export const INSPECTOR_CONTROLS = { .map( getStockStatusIdByLabel ) .filter( Boolean ) as string[]; - setCustomQueryAttribute( props, { + setQueryAttribute( props, { __woocommerceStockStatus, } ); } } @@ -154,22 +155,21 @@ export const withProductQueryControls = return isWooQueryBlockVariation( props ) ? ( <> - + { allowedControls?.includes( 'presets' ) && ( + + ) } { - setCustomQueryAttribute( - props, - defaultWooQueryParams - ); + setQueryAttribute( props, defaultWooQueryParams ); } } > - { Object.entries( INSPECTOR_CONTROLS ).map( + { Object.entries( TOOLS_PANEL_CONTROLS ).map( ( [ key, Control ] ) => allowedControls?.includes( key ) ? ( @@ -177,6 +177,7 @@ export const withProductQueryControls = ) } + ) : ( diff --git a/assets/js/blocks/product-query/inspector-controls/popular-presets.tsx b/assets/js/blocks/product-query/inspector-controls/popular-presets.tsx new file mode 100644 index 00000000000..bd6e13d097d --- /dev/null +++ b/assets/js/blocks/product-query/inspector-controls/popular-presets.tsx @@ -0,0 +1,62 @@ +/** + * External dependencies + */ +import { CustomSelectControl, PanelBody } from '@wordpress/components'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { ProductQueryBlock, ProductQueryBlockQuery } from '../types'; +import { setQueryAttribute } from '../utils'; + +const PRESETS = [ + { key: 'date/desc', name: __( 'Newest', 'woo-gutenberg-products-block' ) }, + { + key: 'popularity/desc', + name: __( 'Best Selling', 'woo-gutenberg-products-block' ), + }, +]; + +export function PopularPresets( props: ProductQueryBlock ) { + const { query } = props.attributes; + + return ( + +

+ { __( + 'Arrange products by popular pre-sets.', + 'woo-gutenberg-products-block' + ) } +

+ { + if ( ! option.selectedItem?.key ) return; + + const [ orderBy, order ] = option.selectedItem?.key?.split( + '/' + ) as [ + ProductQueryBlockQuery[ 'orderBy' ], + ProductQueryBlockQuery[ 'order' ] + ]; + + setQueryAttribute( props, { order, orderBy } ); + } } + options={ PRESETS } + value={ PRESETS.find( + ( option ) => + option.key === `${ query.orderBy }/${ query.order }` + ) } + /> +
+ ); +} diff --git a/assets/js/blocks/product-query/types.ts b/assets/js/blocks/product-query/types.ts index 7f032049691..30f95d1abe9 100644 --- a/assets/js/blocks/product-query/types.ts +++ b/assets/js/blocks/product-query/types.ts @@ -9,6 +9,13 @@ import type { EditorBlock } from '@woocommerce/types'; // will help signify our intentions. /* eslint-disable @typescript-eslint/naming-convention */ export interface ProductQueryArguments { + /** + * Available sorting options specific to the Product Query block + * + * Other sorting options may be possible, but we are restricting + * the choice to those. + */ + orderBy: 'date' | 'popularity'; /** * Display only products on sale. * @@ -52,7 +59,11 @@ export interface ProductQueryArguments { export type ProductQueryBlock = EditorBlock< QueryBlockAttributes >; -export type ProductQueryBlockQuery = QueryBlockQuery & ProductQueryArguments; +export type ProductQueryBlockQuery = Omit< + QueryBlockQuery, + keyof ProductQueryArguments +> & + ProductQueryArguments; export interface QueryBlockAttributes { allowedControls?: string[]; @@ -81,7 +92,7 @@ export interface QueryBlockQuery { } export interface ProductQueryContext { - query?: QueryBlockQuery & ProductQueryArguments; + query?: ProductQueryBlockQuery; queryId?: number; } diff --git a/assets/js/blocks/product-query/utils.tsx b/assets/js/blocks/product-query/utils.tsx index f9c0a45bac0..122ffa2251b 100644 --- a/assets/js/blocks/product-query/utils.tsx +++ b/assets/js/blocks/product-query/utils.tsx @@ -9,8 +9,8 @@ import { store as WP_BLOCKS_STORE } from '@wordpress/blocks'; */ import { QUERY_LOOP_ID } from './constants'; import { - ProductQueryArguments, ProductQueryBlock, + ProductQueryBlockQuery, QueryVariation, } from './types'; @@ -40,14 +40,11 @@ export function isWooQueryBlockVariation( block: ProductQueryBlock ) { /** * Sets the new query arguments of a Product Query block * - * Because we add a new set of deeply nested attributes to the query - * block, this utility function makes it easier to change just the - * options relating to our custom query, while keeping the code - * clean. + * Shorthand for setting new nested query parameters. */ -export function setCustomQueryAttribute( +export function setQueryAttribute( block: ProductQueryBlock, - queryParams: Partial< ProductQueryArguments > + queryParams: Partial< ProductQueryBlockQuery > ) { const { query } = block.attributes; diff --git a/src/BlockTypes/ProductQuery.php b/src/BlockTypes/ProductQuery.php index 28afc7d08d3..56b4763fedb 100644 --- a/src/BlockTypes/ProductQuery.php +++ b/src/BlockTypes/ProductQuery.php @@ -3,6 +3,7 @@ // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_tax_query // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query +// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key /** * ProductQuery class. @@ -22,6 +23,13 @@ class ProductQuery extends AbstractBlock { */ protected $parsed_block; + /** + * Orderby options not natively supported by WordPress REST API + * + * @var array + */ + protected $custom_order_opts = array( 'popularity' ); + /** * All the query args related to the filter by attributes block. * @@ -46,6 +54,7 @@ protected function initialize() { 2 ); add_filter( 'rest_product_query', array( $this, 'update_rest_query' ), 10, 2 ); + add_filter( 'rest_product_collection_params', array( $this, 'extend_rest_query_allowed_params' ), 10, 1 ); } /** @@ -94,8 +103,9 @@ public function update_query( $pre_render, $parsed_block ) { */ public function update_rest_query( $args, $request ) { $on_sale_query = $request->get_param( '__woocommerceOnSale' ) !== 'true' ? array() : $this->get_on_sale_products_query(); + $orderby_query = $this->get_custom_orderby_query( $request->get_param( 'orderby' ) ); - return array_merge( $args, $on_sale_query ); + return array_merge( $args, $on_sale_query, $orderby_query ); } /** @@ -124,6 +134,12 @@ public function build_query( $query ) { $queries_by_attributes = $this->get_queries_by_attributes( $parsed_block ); $queries_by_filters = $this->get_queries_by_applied_filters(); + $orderby_query = $this->get_custom_orderby_query( $query['orderby'] ); + + $base_query = array_merge( + $common_query_values, + $orderby_query + ); return array_reduce( array_merge( @@ -133,7 +149,7 @@ public function build_query( $query ) { function( $acc, $query ) { return $this->merge_queries( $acc, $query ); }, - $common_query_values + $base_query ); } @@ -182,6 +198,21 @@ private function merge_queries( $a, $b ) { return $a; } + /** + * Extends allowed `collection_params` for the REST API + * + * By itself, the REST API doesn't accept custom `orderby` values, + * even if they are supported by a custom post type. + * + * @param array $params A list of allowed `orderby` values. + * + * @return array + */ + public function extend_rest_query_allowed_params( $params ) { + $params['orderby']['enum'] = array_merge( $params['orderby']['enum'], $this->custom_order_opts ); + return $params; + } + /** * Return a query for on sale products. * @@ -193,6 +224,28 @@ private function get_on_sale_products_query() { ); } + /** + * Return query params to support custom sort values + * + * @param string $orderby Sort order option. + * + * @return array + */ + private function get_custom_orderby_query( $orderby ) { + if ( ! in_array( $orderby, $this->custom_order_opts, true ) ) { + return array( 'orderby' => $orderby ); + } + + $meta_keys = array( + 'popularity' => 'total_sales', + ); + + return array( + 'meta_key' => $meta_keys[ $orderby ], + 'orderby' => 'meta_value_num', + ); + } + /** * Return a query for products depending on their stock status. * From 773b39574308780614089e976b11ffdd208a09ba Mon Sep 17 00:00:00 2001 From: Tung Du Date: Tue, 22 Nov 2022 21:28:27 +0700 Subject: [PATCH 22/27] E2E: Change label of product filters from `Product filters` to `Advanced Filters` (#7726) * E2E: Change label of product filters from `Product filters` to `Advanced Filters` to match with the updates from #7687 * rename test file and test title to advanced filters * E2E: Fix flaky test related to `waitForAllProductsBlockLoaded` (#7727) * explain the reason for try/catch --- ...ct-filters.test.ts => advanced-filters.ts} | 8 ++++---- tests/e2e/utils.js | 19 ++++++++++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) rename tests/e2e/specs/backend/product-query/{product-filters.test.ts => advanced-filters.ts} (98%) diff --git a/tests/e2e/specs/backend/product-query/product-filters.test.ts b/tests/e2e/specs/backend/product-query/advanced-filters.ts similarity index 98% rename from tests/e2e/specs/backend/product-query/product-filters.test.ts rename to tests/e2e/specs/backend/product-query/advanced-filters.ts index 6fe09b21881..bdb341c81ae 100644 --- a/tests/e2e/specs/backend/product-query/product-filters.test.ts +++ b/tests/e2e/specs/backend/product-query/advanced-filters.ts @@ -48,7 +48,7 @@ const SELECTORS = { ) => `.components-tools-panel-header .components-dropdown-menu button[aria-expanded="${ expanded }"]`, productFiltersDropdown: - '.components-dropdown-menu__menu[aria-label="Product filters options"]', + '.components-dropdown-menu__menu[aria-label="Advanced Filters options"]', productFiltersDropdownItem: '.components-menu-item__button', editorPreview: { productsGrid: 'ul.wp-block-post-template', @@ -63,7 +63,7 @@ const SELECTORS = { const toggleProductFilter = async ( filterName: string ) => { const $productFiltersPanel = await findToolsPanelWithTitle( - 'Product filters' + 'Advanced Filters' ); await expect( $productFiltersPanel ).toClick( SELECTORS.productFiltersDropdownButton() @@ -96,7 +96,7 @@ const getFrontEndProducts = async (): Promise< ElementHandle[] > => { }; describeOrSkip( GUTENBERG_EDITOR_CONTEXT === 'gutenberg' )( - 'Product Query > Products Filters', + 'Product Query > Advanced Filters', () => { let $productFiltersPanel: ElementHandle< Node >; beforeEach( async () => { @@ -109,7 +109,7 @@ describeOrSkip( GUTENBERG_EDITOR_CONTEXT === 'gutenberg' )( await openBlockEditorSettings(); await selectBlockByName( block.slug ); $productFiltersPanel = await findToolsPanelWithTitle( - 'Product filters' + 'Advanced Filters' ); } ); diff --git a/tests/e2e/utils.js b/tests/e2e/utils.js index 93a6b3906bf..0bbc19f0a48 100644 --- a/tests/e2e/utils.js +++ b/tests/e2e/utils.js @@ -430,9 +430,22 @@ export const openBlockEditorSettings = async () => { * Wait for all Products Block is loaded completely: when the skeleton disappears, and the products are visible */ export const waitForAllProductsBlockLoaded = async () => { - await page.waitForSelector( - '.wc-block-grid__products.is-loading-products' - ); + /** + * We use try with empty catch block here to avoid the race condition + * between the block loading and the test execution. After user actions, + * the products may or may not finish loading at the time we try to wait for + * the loading class. + * + * We need to wait for the loading class to be added then removed because + * only waiting for the loading class to be removed could result in a false + * positive pass. + */ + try { + await page.waitForSelector( + '.wc-block-grid__products.is-loading-products' + ); + } catch ( ok ) {} + await page.waitForSelector( '.wc-block-grid__products:not(.is-loading-products)' ); From 410bf875519961e062651ca2bd706677f7324133 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 23 Nov 2022 12:37:31 +0100 Subject: [PATCH 23/27] Release: 9.0.0 (#7725) * Empty commit for release pull request * Update compatibility sections and add release 9.0.0 changelog * Add testing instructions for the 9.0.0 release * Update testing instructions for the 9.0.0 release * Remove testing instructions for an unmerged PR * Bumping version strings to new version. Co-authored-by: github-actions Co-authored-by: Daniel Dudzic --- .../testing/releases/900.md | 96 +++++++++++++++++++ .../testing/releases/README.md | 1 + package.json | 2 +- readme.txt | 32 ++++++- src/Package.php | 2 +- woocommerce-gutenberg-products-block.php | 10 +- 6 files changed, 133 insertions(+), 10 deletions(-) create mode 100644 docs/internal-developers/testing/releases/900.md diff --git a/docs/internal-developers/testing/releases/900.md b/docs/internal-developers/testing/releases/900.md new file mode 100644 index 00000000000..62317444f16 --- /dev/null +++ b/docs/internal-developers/testing/releases/900.md @@ -0,0 +1,96 @@ +# Testing notes and ZIP for release 9.0.0 + +Zip file for testing: [woocommerce-gutenberg-products-block.zip](https://github.com/woocommerce/woocommerce-blocks/files/10061884/woocommerce-gutenberg-products-block.zip) + +## Feature plugin and package inclusion in WooCommerce + +### Fix skewed placeholder of a Product Image block. ([7651](https://github.com/woocommerce/woocommerce-blocks/pull/7651)) + +Prerequisites: + +0. Make sure you have a product without a highlighted image (you can create a new product or remove the image from an existing one). + +Case 1: image is wider than 500px: + +1. Go to Appearance > Site Editor > Templates > Product Catalog. +2. Remove the legacy placeholder block. +3. Add blocks that use Product Image placeholder: + +- Single Product +- All Products +- Product Query + +4. ✅ Check on the wide-screen (image should have more than 500px width) if the placeholder image is a square (not skewed) + +Case 2: image is narrower than 500px: + +1. Add a Columns block. +2. Inside one of the columns, add the Single Product block, to another one add Product Query block. Columns block limits the available space for a product to display (you should achieve less than 500px width for product image) +3. ✅ Check if the placeholder image is a square (not skewed) + +### Fix missing translations in the inspector (editor mode) for the Cart Cross-Sells Blocks. ([7616](https://github.com/woocommerce/woocommerce-blocks/pull/7616)) + +1. Change your site language (I tested using the Spanish language — Español). Make sure the translation files are updated (Check Dashboard -> updates). +2. Go to the `Cart Block` (Editor mode) +3. Select the `Cart Cross-Sell` and `Cart Cross-Sells products` blocks, and make sure the title & description are translated in the inspector. See the image below: + +![image](https://user-images.githubusercontent.com/14235870/200624981-e42e7f16-36de-4eb7-961b-800b79f268ff.png) + +### Move paymentResult to the payment store. ([7692](https://github.com/woocommerce/woocommerce-blocks/pull/7692)) + +1. Checkout successfully using the stripe payment method (4242 4242 4242 4242) +2. Checkout unsuccessfully using stripe (4000 000 000 0002) +3. Both should not throw any console errors #and step 2 should display a "Something went wrong" error at the top of the Checkout block and a "Card declined" error in the payment method block. + +Make loading placeholder colors match the current font color for the theme. ([7658](https://github.com/woocommerce/woocommerce-blocks/pull/7658)) + +### Add the `Products by Attribute` template. ([7660](https://github.com/woocommerce/woocommerce-blocks/pull/7660)) + +1. Make sure you are using a blocks theme. +2. Go to the site editor and click on `Browse all templates` (or `site-editor.php?postType=wp_template`). +3. Check that the `Products by Attribute` template shows on the list. +4. Click on it and make sure it shows the legacy grid block. +5. Edit the template and add a block on top of it and save. +6. Go to `Products`>`Attribute` (`/wp-admin/edit.php?post_type=product&page=product_attributes`). +7. Edit one of the attributes, check the `Enable archives?` checkbox, and save. +8. Go back to the attributes list and click `Configure terms` on the attribute you just edited it. +9. Click on `View` on one of the terms to view it in the frontend. +10. Make sure you see the edits you made on the `Product by Attribute` template. + +### Make loading placeholder colors match the current font color for the theme. ([7658](https://github.com/woocommerce/woocommerce-blocks/pull/7658)) + +1. Change the font color of your theme (via Appearance > Site Editor > Styles) to something that is not black or white (ie: yellow); +2. Add all filter blocks (Filter by Price, Filter By Attribute, etc) and the All Products block to a post or page; +3. Visit the page that you added the blocks; +4. Make sure that the colors for the loading placeholder matches the current font color. + +### Add a `clearValidationErrors` action to the `wc/store/validation` data store. ([7601](https://github.com/woocommerce/woocommerce-blocks/pull/7601)) + +1. Use the Checkout block, enter invalid information in fields and ensure validation errors still show. (e.g. enter an invalid ZIP code for UK by just using numbers) +6. Leave fields blank and ensure validation shows. +7. Fix the errors and ensure you can check out. + +### Add `ValidatedTextInput` and `ValidationInputError` to the `@woocommerce/blocks-checkout` package. ([7583](https://github.com/woocommerce/woocommerce-blocks/pull/7583)) + +1. Enter an invalid ZIP code, ensure the error appears. +2. Leave the First name/Last name fields blank, ensure errors appear under them. + +### Add `StoreNoticesContainer` to the `@woocommerce/blocks-checkout` package. ([7558](https://github.com/woocommerce/woocommerce-blocks/pull/7558)) + +1. Add some items and go to the Cart block. +2. In a new tab, open edit one of the items in your cart. Set it to out of stock. +3. Without reloading, go back to the Cart tab, and try to increase the quantity of an item. Ensure you see an error above the Cart block. +4. Set the item back to in stock, and proceed to the Checkout block. +5. Enter an invalid ZIP code, ensure a notice appears above the Checkout block. +6. Using Stripe, enter this card number, it will be declined: `4000 0000 0000 9995` ensure the error appears in the payment methods area. + +![image](https://user-images.githubusercontent.com/5656702/199278579-9c61adab-b9f7-4aa5-a0e3-88c8cd589ff0.png) + +### Change action type name for use shipping as billing option. ([7695](https://github.com/woocommerce/woocommerce-blocks/pull/7695)) + +1. Add a physical product to your basket. +2. Go to Checkout +3. Shipping details should be displayed. Enter some details here. +4. Untick "Use same address for billing" +5. Enter different billing details +6. Place the order and check the shipping and billing details are different diff --git a/docs/internal-developers/testing/releases/README.md b/docs/internal-developers/testing/releases/README.md index e780adc8a33..61c75a971ad 100644 --- a/docs/internal-developers/testing/releases/README.md +++ b/docs/internal-developers/testing/releases/README.md @@ -103,3 +103,4 @@ Every release includes specific testing instructions for new features and bug fi - [8.8.2](./882.md) - [8.9.0](./890.md) - [8.9.1](./891.md) +- [9.0.0](./900.md) diff --git a/package.json b/package.json index 9150ba1a742..9bc645b6ebf 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@woocommerce/block-library", "title": "WooCommerce Blocks", "author": "Automattic", - "version": "9.0.0-dev", + "version": "9.0.0", "description": "WooCommerce blocks for the Gutenberg editor.", "homepage": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/", "keywords": [ diff --git a/readme.txt b/readme.txt index 1af560a512d..f5db45ee356 100644 --- a/readme.txt +++ b/readme.txt @@ -1,10 +1,10 @@ === WooCommerce Blocks === Contributors: automattic, woocommerce, claudiulodro, tiagonoronha, jameskoster, ryelle, levinmedia, aljullu, mikejolley, nerrad, joshuawold, assassinateur, haszari, mppfeiffer, nielslange, opr18, ralucastn, tjcafferkey Tags: gutenberg, woocommerce, woo commerce, products, blocks, woocommerce blocks -Requires at least: 6.1 -Tested up to: 6.1 +Requires at least: 6.1.1 +Tested up to: 6.1.1 Requires PHP: 7.0 -Stable tag: 9.0.0-dev +Stable tag: 9.0.0 License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -79,6 +79,32 @@ Release and roadmap notes available on the [WooCommerce Developers Blog](https:/ == Changelog == += 9.0.0 - 2022-11-21 = + +#### Bug Fixes + +- Fix skewed placeholder of a Product Image block. ([7651](https://github.com/woocommerce/woocommerce-blocks/pull/7651)) +- Fix missing translations in the inspector (editor mode) for the Cart Cross-Sells Blocks. ([7616](https://github.com/woocommerce/woocommerce-blocks/pull/7616)) + +#### Enhancements + +- Move paymentResult to the payment store. ([7692](https://github.com/woocommerce/woocommerce-blocks/pull/7692)) +- Add the `Products by Attribute` template. ([7660](https://github.com/woocommerce/woocommerce-blocks/pull/7660)) +- Make loading placeholder colors match the current font color for the theme. ([7658](https://github.com/woocommerce/woocommerce-blocks/pull/7658)) +- Remove cart fragments support to improve performance in product blocks. ([7644](https://github.com/woocommerce/woocommerce-blocks/pull/7644)) +- Add a `clearValidationErrors` action to the `wc/store/validation` data store. ([7601](https://github.com/woocommerce/woocommerce-blocks/pull/7601)) +- Add `ValidatedTextInput` and `ValidationInputError` to the `@woocommerce/blocks-checkout` package. ([7583](https://github.com/woocommerce/woocommerce-blocks/pull/7583)) +- React Based Local Pickup Settings Screen. ([7581](https://github.com/woocommerce/woocommerce-blocks/pull/7581)) +- Convert product-elements/image to TypeScript. ([7572](https://github.com/woocommerce/woocommerce-blocks/pull/7572)) +- Add `StoreNoticesContainer` to the `@woocommerce/blocks-checkout` package. ([7558](https://github.com/woocommerce/woocommerce-blocks/pull/7558)) +- Convert product-elements/price to TypeScript. ([7534](https://github.com/woocommerce/woocommerce-blocks/pull/7534)) +- Adds the option of providing a custom class for the product details on the Cart Block. ([7328](https://github.com/woocommerce/woocommerce-blocks/pull/7328)) + +#### Various + +- Change action type name for use shipping as billing option. ([7695](https://github.com/woocommerce/woocommerce-blocks/pull/7695)) +- Block Checkout: Apply selected Local Pickup rate to entire order (all packages). ([7484](https://github.com/woocommerce/woocommerce-blocks/pull/7484)) + = 8.9.1 - 2022-11-14 = #### Bug fixes diff --git a/src/Package.php b/src/Package.php index 452280b5f04..11b7284e5a6 100644 --- a/src/Package.php +++ b/src/Package.php @@ -109,7 +109,7 @@ public static function container( $reset = false ) { NewPackage::class, function ( $container ) { // leave for automated version bumping. - $version = '9.0.0-dev'; + $version = '9.0.0'; return new NewPackage( $version, dirname( __DIR__ ), diff --git a/woocommerce-gutenberg-products-block.php b/woocommerce-gutenberg-products-block.php index 367d78cf8d9..2253a0ca773 100644 --- a/woocommerce-gutenberg-products-block.php +++ b/woocommerce-gutenberg-products-block.php @@ -3,14 +3,14 @@ * Plugin Name: WooCommerce Blocks * Plugin URI: https://github.com/woocommerce/woocommerce-gutenberg-products-block * Description: WooCommerce blocks for the Gutenberg editor. - * Version: 9.0.0-dev + * Version: 9.0.0 * Author: Automattic * Author URI: https://woocommerce.com * Text Domain: woo-gutenberg-products-block - * Requires at least: 6.1 + * Requires at least: 6.1.1 * Requires PHP: 7.0 - * WC requires at least: 6.9 - * WC tested up to: 7.0 + * WC requires at least: 7.0 + * WC tested up to: 7.1 * * @package WooCommerce\Blocks * @internal This file is only used when running as a feature plugin. @@ -18,7 +18,7 @@ defined( 'ABSPATH' ) || exit; -$minimum_wp_version = '6.1'; +$minimum_wp_version = '6.1.1'; if ( ! defined( 'WC_BLOCKS_IS_FEATURE_PLUGIN' ) ) { define( 'WC_BLOCKS_IS_FEATURE_PLUGIN', true ); From ca4bad37ac91e251026f41b347aa771f99881de3 Mon Sep 17 00:00:00 2001 From: Manish Menaria Date: Wed, 23 Nov 2022 17:12:55 +0530 Subject: [PATCH 24/27] Add product query support for Sale badge block (#7709) * Add product query support for Sale badge block On the client side, when the Sale badge block is used within the product query block, the markup will be rendered on the server side - No javascript related to Sale badge block will be rendered. * Add support for additional CSS class(es) ADDITIONAL CSS CLASS(ES)(available in advanced toggle in sidebar) should be added to the container div * Convert preset to css variable for padding We are getting padding value in preset format like this: "var:preset|spacing|50" Therefore I added a function to convert it to CSS variable like this: "var(--wp--preset--spacing--50)" i.e. "var:preset|spacing|50" -> "var(--wp--preset--spacing--50)" * Add reference for preset to css variable conversion logic --- .../product-elements/sale-badge/attributes.ts | 4 + .../product-elements/sale-badge/edit.tsx | 27 +- .../product-elements/sale-badge/index.ts | 8 +- .../product-elements/sale-badge/types.ts | 1 + checkstyle.xml | 4215 +++++++++++++++++ src/BlockTypes/ProductSaleBadge.php | 55 +- src/Utils/StyleAttributesUtils.php | 37 +- 7 files changed, 4334 insertions(+), 13 deletions(-) create mode 100644 checkstyle.xml diff --git a/assets/js/atomic/blocks/product-elements/sale-badge/attributes.ts b/assets/js/atomic/blocks/product-elements/sale-badge/attributes.ts index caed2652df1..372f22f2568 100644 --- a/assets/js/atomic/blocks/product-elements/sale-badge/attributes.ts +++ b/assets/js/atomic/blocks/product-elements/sale-badge/attributes.ts @@ -3,6 +3,10 @@ export const blockAttributes: Record< string, Record< string, unknown > > = { type: 'number', default: 0, }, + isDescendentOfQueryLoop: { + type: 'boolean', + default: false, + }, }; export default blockAttributes; diff --git a/assets/js/atomic/blocks/product-elements/sale-badge/edit.tsx b/assets/js/atomic/blocks/product-elements/sale-badge/edit.tsx index c2f32eeb241..6f3a0621d88 100644 --- a/assets/js/atomic/blocks/product-elements/sale-badge/edit.tsx +++ b/assets/js/atomic/blocks/product-elements/sale-badge/edit.tsx @@ -2,6 +2,9 @@ * External dependencies */ import { useBlockProps } from '@wordpress/block-editor'; +import type { BlockEditProps } from '@wordpress/blocks'; +import { ProductQueryContext as Context } from '@woocommerce/blocks/product-query/types'; +import { useEffect } from 'react'; /** * Internal dependencies @@ -15,15 +18,27 @@ import { } from './constants'; import type { BlockAttributes } from './types'; -interface Props { - attributes: BlockAttributes; -} - -const Edit = ( { attributes }: Props ): JSX.Element => { +const Edit = ( { + attributes, + setAttributes, + context, +}: BlockEditProps< BlockAttributes > & { context: Context } ): JSX.Element => { const blockProps = useBlockProps(); + + const blockAttrs = { + ...attributes, + ...context, + }; + const isDescendentOfQueryLoop = Number.isFinite( context.queryId ); + + useEffect( + () => setAttributes( { isDescendentOfQueryLoop } ), + [ setAttributes, isDescendentOfQueryLoop ] + ); + return (
- +
); }; diff --git a/assets/js/atomic/blocks/product-elements/sale-badge/index.ts b/assets/js/atomic/blocks/product-elements/sale-badge/index.ts index e9763565aed..318b44047de 100644 --- a/assets/js/atomic/blocks/product-elements/sale-badge/index.ts +++ b/assets/js/atomic/blocks/product-elements/sale-badge/index.ts @@ -15,7 +15,6 @@ import { BLOCK_ICON as icon, BLOCK_DESCRIPTION as description, } from './constants'; -import { Save } from './save'; import { supports } from './support'; const blockConfig: BlockConfiguration = { @@ -27,7 +26,12 @@ const blockConfig: BlockConfiguration = { supports, attributes, edit, - save: Save, + usesContext: [ 'query', 'queryId', 'postId' ], + ancestor: [ + '@woocommerce/all-products', + '@woocommerce/single-product', + 'core/post-template', + ], }; registerBlockType( 'woocommerce/product-sale-badge', { ...blockConfig } ); diff --git a/assets/js/atomic/blocks/product-elements/sale-badge/types.ts b/assets/js/atomic/blocks/product-elements/sale-badge/types.ts index a95b28e08e9..e638757b452 100644 --- a/assets/js/atomic/blocks/product-elements/sale-badge/types.ts +++ b/assets/js/atomic/blocks/product-elements/sale-badge/types.ts @@ -1,4 +1,5 @@ export interface BlockAttributes { productId: number; align: 'left' | 'center' | 'right'; + isDescendentOfQueryLoop: boolean; } diff --git a/checkstyle.xml b/checkstyle.xml new file mode 100644 index 00000000000..279e9c683d4 --- /dev/null +++ b/checkstyle.xml @@ -0,0 +1,4215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BlockTypes/ProductSaleBadge.php b/src/BlockTypes/ProductSaleBadge.php index f5eb05bb838..68826e8f442 100644 --- a/src/BlockTypes/ProductSaleBadge.php +++ b/src/BlockTypes/ProductSaleBadge.php @@ -1,6 +1,8 @@ register_chunk_translations( [ $this->block_name ] ); + return null; + } + + /** + * Register the context. + */ + protected function get_block_type_uses_context() { + return [ 'query', 'queryId', 'postId' ]; + } + + /** + * Include and render the block. + * + * @param array $attributes Block attributes. Default empty array. + * @param string $content Block content. Default empty string. + * @param WP_Block $block Block instance. + * @return string Rendered block type output. + */ + protected function render( $attributes, $content, $block ) { + if ( ! empty( $content ) ) { + parent::register_block_type_assets(); + $this->register_chunk_translations( [ $this->block_name ] ); + return $content; + } + + $post_id = $block->context['postId']; + $product = wc_get_product( $post_id ); + $is_on_sale = $product->is_on_sale(); + + if ( ! $is_on_sale ) { + return null; + } + + $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); + $classname = isset( $attributes['className'] ) ? $attributes['className'] : ''; + + $output = ''; + $output .= '
'; + $output .= ''; + $output .= '' . __( + 'Product on sale', + 'woo-gutenberg-products-block' + ) . ''; + $output .= '
'; + + return $output; } } diff --git a/src/Utils/StyleAttributesUtils.php b/src/Utils/StyleAttributesUtils.php index 8e8093f1e5a..cb46c807fed 100644 --- a/src/Utils/StyleAttributesUtils.php +++ b/src/Utils/StyleAttributesUtils.php @@ -327,6 +327,27 @@ public static function get_align_class_and_style( $attributes ) { return null; } + + /** + * If spacing value is in preset format, convert it to a CSS var. Else return same value + * For example: + * "var:preset|spacing|50" -> "var(--wp--preset--spacing--50)" + * "50px" -> "50px" + * + * @param string $spacing_value value to be processed. + * + * @return (string) + */ + public static function get_spacing_value( $spacing_value ) { + // Used following code as reference: https://github.com/WordPress/gutenberg/blob/cff6d70d6ff5a26e212958623dc3130569f95685/lib/block-supports/layout.php/#L219-L225. + if ( is_string( $spacing_value ) && str_contains( $spacing_value, 'var:preset|spacing|' ) ) { + $spacing_value = str_replace( 'var:preset|spacing|', '', 'var:preset|spacing|50' ); + return sprintf( 'var(--wp--preset--spacing--%s)', $spacing_value ); + } + + return $spacing_value; + } + /** * Get class and style for padding from attributes. * @@ -341,9 +362,23 @@ public static function get_padding_class_and_style( $attributes ) { return null; } + $padding_top = $padding['top'] ? self::get_spacing_value( $padding['top'] ) : null; + $padding_right = $padding['right'] ? self::get_spacing_value( $padding['right'] ) : null; + $padding_bottom = $padding['bottom'] ? self::get_spacing_value( $padding['bottom'] ) : null; + $padding_left = $padding['left'] ? self::get_spacing_value( $padding['left'] ) : null; + return array( 'class' => null, - 'style' => sprintf( 'padding: %s;', implode( ' ', $padding ) ), + 'style' => sprintf( + 'padding-top:%s; + padding-right:%s; + padding-bottom:%s; + padding-left:%s;', + $padding_top, + $padding_right, + $padding_bottom, + $padding_left + ), ); } From b781afe0bd4a36b9be1a6ea8908992b43e7e966a Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Wed, 23 Nov 2022 18:01:04 +0100 Subject: [PATCH 25/27] Post 9.0.0 release version bump (#7737) --- package-lock.json | 4 ++-- package.json | 2 +- readme.txt | 2 +- src/Package.php | 2 +- woocommerce-gutenberg-products-block.php | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index bf6c6b0b471..3fa9018dfdb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@woocommerce/block-library", - "version": "9.0.0-dev", + "version": "9.1.0-dev", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@woocommerce/block-library", - "version": "9.0.0-dev", + "version": "9.1.0-dev", "hasInstallScript": true, "license": "GPL-3.0+", "dependencies": { diff --git a/package.json b/package.json index 9bc645b6ebf..b082c928cd6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@woocommerce/block-library", "title": "WooCommerce Blocks", "author": "Automattic", - "version": "9.0.0", + "version": "9.1.0-dev", "description": "WooCommerce blocks for the Gutenberg editor.", "homepage": "https://github.com/woocommerce/woocommerce-gutenberg-products-block/", "keywords": [ diff --git a/readme.txt b/readme.txt index f5db45ee356..496357f3621 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: gutenberg, woocommerce, woo commerce, products, blocks, woocommerce blocks Requires at least: 6.1.1 Tested up to: 6.1.1 Requires PHP: 7.0 -Stable tag: 9.0.0 +Stable tag: 9.1.0-dev License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html diff --git a/src/Package.php b/src/Package.php index 11b7284e5a6..819ae4dbfdf 100644 --- a/src/Package.php +++ b/src/Package.php @@ -109,7 +109,7 @@ public static function container( $reset = false ) { NewPackage::class, function ( $container ) { // leave for automated version bumping. - $version = '9.0.0'; + $version = '9.1.0-dev'; return new NewPackage( $version, dirname( __DIR__ ), diff --git a/woocommerce-gutenberg-products-block.php b/woocommerce-gutenberg-products-block.php index 2253a0ca773..78995dcd921 100644 --- a/woocommerce-gutenberg-products-block.php +++ b/woocommerce-gutenberg-products-block.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce Blocks * Plugin URI: https://github.com/woocommerce/woocommerce-gutenberg-products-block * Description: WooCommerce blocks for the Gutenberg editor. - * Version: 9.0.0 + * Version: 9.1.0-dev * Author: Automattic * Author URI: https://woocommerce.com * Text Domain: woo-gutenberg-products-block From 9295dde65c6a35021b087bb705a4b1392c298bb9 Mon Sep 17 00:00:00 2001 From: Lucio Giannotta Date: Wed, 23 Nov 2022 20:28:17 +0100 Subject: [PATCH 26/27] =?UTF-8?q?Product=20Query:=20Add=20order=20by=20?= =?UTF-8?q?=E2=80=9CTop=20rated=E2=80=9D=20as=20a=20preset=20(#7715)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add safety guard to `extend_rest_query_allowed_params` --- .../product-query/inspector-controls/popular-presets.tsx | 4 ++++ src/BlockTypes/ProductQuery.php | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/assets/js/blocks/product-query/inspector-controls/popular-presets.tsx b/assets/js/blocks/product-query/inspector-controls/popular-presets.tsx index bd6e13d097d..d4af544f022 100644 --- a/assets/js/blocks/product-query/inspector-controls/popular-presets.tsx +++ b/assets/js/blocks/product-query/inspector-controls/popular-presets.tsx @@ -16,6 +16,10 @@ const PRESETS = [ key: 'popularity/desc', name: __( 'Best Selling', 'woo-gutenberg-products-block' ), }, + { + key: 'rating/desc', + name: __( 'Top Rated', 'woo-gutenberg-products-block' ), + }, ]; export function PopularPresets( props: ProductQueryBlock ) { diff --git a/src/BlockTypes/ProductQuery.php b/src/BlockTypes/ProductQuery.php index 56b4763fedb..a3622b51e29 100644 --- a/src/BlockTypes/ProductQuery.php +++ b/src/BlockTypes/ProductQuery.php @@ -28,7 +28,7 @@ class ProductQuery extends AbstractBlock { * * @var array */ - protected $custom_order_opts = array( 'popularity' ); + protected $custom_order_opts = array( 'popularity', 'rating' ); /** * All the query args related to the filter by attributes block. @@ -209,7 +209,9 @@ private function merge_queries( $a, $b ) { * @return array */ public function extend_rest_query_allowed_params( $params ) { - $params['orderby']['enum'] = array_merge( $params['orderby']['enum'], $this->custom_order_opts ); + $original_enum = isset( $params['orderby']['enum'] ) ? $params['orderby']['enum'] : array(); + + $params['orderby']['enum'] = array_merge( $original_enum, $this->custom_order_opts ); return $params; } @@ -238,6 +240,7 @@ private function get_custom_orderby_query( $orderby ) { $meta_keys = array( 'popularity' => 'total_sales', + 'rating' => '_wc_average_rating', ); return array( From 78228fb4323bdec4fccf68ae5ce67e118296dc93 Mon Sep 17 00:00:00 2001 From: Luigi Teschio Date: Thu, 24 Nov 2022 14:42:58 +0100 Subject: [PATCH 27/27] Product Query - Add E2E tests for the Filter by Attribute block (#7405) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Product Query: Fix pagination issue * Product Query - Add support for the Filter By Price Block #6790 Product Query - Add support for the Filter By Price Block * fix query relation * fix on sale query * Product Query - Add support for the Filter By Attributes block #6790 Product Query - Add support for the Filter By Attributes block * fix bugged pagination and on-sale filter after refactor * address feedback * Product Query - Add e2e tests for the Filter By Price block * Product Query - Add e2e tests for the Filter By Attribute block * fix comment * fix comment * remove not used import * remove not used import * address feedback Co-authored-by: Albert Juhé Lluveras --- .../filter-products-by-attribute.test.ts | 122 +++++++++++++++++- 1 file changed, 119 insertions(+), 3 deletions(-) diff --git a/tests/e2e/specs/shopper/filter-products-by-attribute.test.ts b/tests/e2e/specs/shopper/filter-products-by-attribute.test.ts index 7a5a618e6f3..e6d45ec8999 100644 --- a/tests/e2e/specs/shopper/filter-products-by-attribute.test.ts +++ b/tests/e2e/specs/shopper/filter-products-by-attribute.test.ts @@ -25,6 +25,7 @@ import { useTheme, waitForAllProductsBlockLoaded, } from '../../utils'; +import { saveOrPublish } from '../../../utils'; const block = { name: 'Filter by Attribute', @@ -42,6 +43,7 @@ const block = { firstAttributeInTheList: '.wc-block-attribute-filter-list > li:not([class^="is-loading"])', productsList: '.wc-block-grid__products > li', + queryProductsList: '.wp-block-post-template > li', classicProductsList: '.products.columns-3 > li', filter: "input[id='128gb']", submitButton: '.wc-block-components-filter-submit-button', @@ -87,7 +89,7 @@ describe( `${ block.name } Block`, () => { await page.goto( link ); } ); - it( 'should render', async () => { + it( 'should render products', async () => { await waitForAllProductsBlockLoaded(); const products = await page.$$( selectors.frontend.productsList ); @@ -142,7 +144,7 @@ describe( `${ block.name } Block`, () => { await goToShopPage(); } ); - it( 'should render', async () => { + it( 'should render products', async () => { const products = await page.$$( selectors.frontend.classicProductsList ); @@ -182,7 +184,7 @@ describe( `${ block.name } Block`, () => { ); } ); - it( 'should refresh the page only if the user click on button', async () => { + it( 'should refresh the page only if the user clicks on button', async () => { await goToTemplateEditor( { postId: productCatalogTemplateId, } ); @@ -227,4 +229,118 @@ describe( `${ block.name } Block`, () => { ); } ); } ); + + describe( 'with Product Query Block', () => { + let editorPageUrl = ''; + let frontedPageUrl = ''; + + useTheme( 'emptytheme' ); + beforeAll( async () => { + await switchUserToAdmin(); + await createNewPost( { + postType: 'post', + title: block.name, + } ); + + await insertBlock( 'Product Query' ); + await insertBlock( block.name ); + await page.waitForNetworkIdle(); + + // It seems that .click doesn't work well with radio input element. + await page.$eval( + block.selectors.editor.firstAttributeInTheList, + ( el ) => ( el as HTMLInputElement ).click() + ); + await page.click( selectors.editor.doneButton ); + await publishPost(); + + editorPageUrl = page.url(); + frontedPageUrl = await page.evaluate( () => + wp.data.select( 'core/editor' ).getPermalink() + ); + await page.goto( frontedPageUrl ); + } ); + + it( 'should render products', async () => { + const products = await page.$$( + selectors.frontend.queryProductsList + ); + + expect( products ).toHaveLength( 5 ); + } ); + + it( 'should show only products that match the filter', async () => { + const isRefreshed = jest.fn( () => void 0 ); + page.on( 'load', isRefreshed ); + + await page.waitForSelector( block.class + '.is-loading', { + hidden: true, + } ); + + expect( isRefreshed ).not.toBeCalled(); + + await page.waitForSelector( selectors.frontend.filter ); + + await Promise.all( [ + page.waitForNavigation(), + page.click( selectors.frontend.filter ), + ] ); + + const products = await page.$$( + selectors.frontend.queryProductsList + ); + + const pageURL = page.url(); + const parsedURL = new URL( pageURL ); + + expect( isRefreshed ).toBeCalledTimes( 1 ); + expect( products ).toHaveLength( 1 ); + await expect( page ).toMatch( block.foundProduct ); + expect( parsedURL.search ).toEqual( + block.urlSearchParamWhenFilterIsApplied + ); + } ); + + it( 'should refresh the page only if the user clicks on button', async () => { + await page.goto( editorPageUrl ); + await openBlockEditorSettings(); + await selectBlockByName( block.slug ); + const [ filterButtonToggle ] = await page.$x( + block.selectors.editor.filterButtonToggle + ); + await filterButtonToggle.click(); + await saveOrPublish(); + await page.goto( frontedPageUrl ); + + const isRefreshed = jest.fn( () => void 0 ); + page.on( 'load', isRefreshed ); + await page.waitForSelector( block.class + '.is-loading', { + hidden: true, + } ); + await page.waitForSelector( selectors.frontend.filter ); + await page.click( selectors.frontend.filter ); + + expect( isRefreshed ).not.toBeCalled(); + + await Promise.all( [ + page.waitForNavigation( { + waitUntil: 'networkidle0', + } ), + page.click( selectors.frontend.submitButton ), + ] ); + + const products = await page.$$( + selectors.frontend.queryProductsList + ); + const pageURL = page.url(); + const parsedURL = new URL( pageURL ); + + expect( isRefreshed ).toBeCalledTimes( 1 ); + expect( products ).toHaveLength( 1 ); + await expect( page ).toMatch( block.foundProduct ); + expect( parsedURL.search ).toEqual( + block.urlSearchParamWhenFilterIsApplied + ); + } ); + } ); } );