From de13a47a2aea2e542e64b279d298a62c2cd68bf7 Mon Sep 17 00:00:00 2001 From: Alejandro Iglesias Date: Fri, 27 Sep 2024 23:43:02 -0700 Subject: [PATCH 01/26] Pass extra CSS classes to the block (#50662) * Pass extra CSS classes to the block * get className using wrapper method * amends from CR * remove not needed class * Merge remote-tracking branch 'upstream/trunk' into fix/add-extra-css-classes-to-product-image * add missing variables * fix lint error * add changelog * remove not necessary code * add extra_classses parameter --------- Co-authored-by: Luigi Teschio --- ...fix-add-extra-css-classes-to-product-image | 4 ++++ .../src/Blocks/BlockTypes/AddToCartForm.php | 4 +--- .../src/Blocks/BlockTypes/FeaturedItem.php | 5 +---- .../src/Blocks/BlockTypes/MiniCart.php | 7 ++----- .../AbstractOrderConfirmationBlock.php | 7 +------ .../BlockTypes/OrderConfirmation/Status.php | 4 +++- .../src/Blocks/BlockTypes/ProductButton.php | 4 ++-- .../Blocks/BlockTypes/ProductCategories.php | 6 +----- .../src/Blocks/BlockTypes/ProductDetails.php | 9 +++------ .../src/Blocks/BlockTypes/ProductGallery.php | 3 ++- .../Blocks/BlockTypes/ProductGalleryPager.php | 3 ++- .../Blocks/BlockTypes/ProductImageGallery.php | 4 +++- .../Blocks/BlockTypes/ProductResultsCount.php | 1 - .../src/Blocks/BlockTypes/ProductReviews.php | 6 +++--- .../Blocks/BlockTypes/ProductSaleBadge.php | 5 +++-- .../BlockTypes/ProductStockIndicator.php | 1 - .../src/Blocks/Utils/StyleAttributesUtils.php | 20 +++++++++++++++++++ 17 files changed, 51 insertions(+), 42 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-add-extra-css-classes-to-product-image diff --git a/plugins/woocommerce/changelog/fix-add-extra-css-classes-to-product-image b/plugins/woocommerce/changelog/fix-add-extra-css-classes-to-product-image new file mode 100644 index 00000000000..54b208d6aa3 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-add-extra-css-classes-to-product-image @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Render Advanced CSS Classes in the Product Image block diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartForm.php b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartForm.php index e2c02850e3f..8b8aa44c6fc 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartForm.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartForm.php @@ -99,14 +99,12 @@ class AddToCartForm extends AbstractBlock { $product = $this->add_is_descendent_of_single_product_block_hidden_input_to_product_form( $product, $is_descendent_of_single_product_block ); } - $classname = $attributes['className'] ?? ''; $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); $product_classname = $is_descendent_of_single_product_block ? 'product' : ''; $form = sprintf( - '
%5$s
', + '
%4$s
', esc_attr( $classes_and_styles['classes'] ), - esc_attr( $classname ), esc_attr( $product_classname ), esc_attr( $classes_and_styles['styles'] ), $product diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedItem.php b/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedItem.php index 344dc858d5d..235bd9a535b 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedItem.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/FeaturedItem.php @@ -37,6 +37,7 @@ abstract class FeaturedItem extends AbstractDynamicBlock { 'font_size', 'padding', 'text_color', + 'extra_classes', ); /** @@ -272,10 +273,6 @@ abstract class FeaturedItem extends AbstractDynamicBlock { $classes[] = "has-{$attributes['contentAlign']}-content"; } - if ( isset( $attributes['className'] ) ) { - $classes[] = $attributes['className']; - } - $global_style_classes = StyleAttributesUtils::get_classes_by_attributes( $attributes, $this->global_style_wrapper ); $classes[] = $global_style_classes; diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/MiniCart.php b/plugins/woocommerce/src/Blocks/BlockTypes/MiniCart.php index cb5952d2d84..4f9820b193e 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/MiniCart.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/MiniCart.php @@ -414,12 +414,9 @@ class MiniCart extends AbstractBlock { return ''; } - $classes_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array( 'text_color', 'background_color', 'font_size', 'font_weight', 'font_family' ) ); + $classes_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array( 'text_color', 'background_color', 'font_size', 'font_weight', 'font_family', 'extra_classes' ) ); $wrapper_classes = sprintf( 'wc-block-mini-cart wp-block-woocommerce-mini-cart %s', $classes_styles['classes'] ); - if ( ! empty( $attributes['className'] ) ) { - $wrapper_classes .= ' ' . $attributes['className']; - } - $wrapper_styles = $classes_styles['styles']; + $wrapper_styles = $classes_styles['styles']; $icon_color = array_key_exists( 'iconColor', $attributes ) ? esc_attr( $attributes['iconColor']['color'] ) : 'currentColor'; $product_count_color = array_key_exists( 'productCountColor', $attributes ) ? esc_attr( $attributes['productCountColor']['color'] ) : ''; diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/AbstractOrderConfirmationBlock.php b/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/AbstractOrderConfirmationBlock.php index 753e521f516..2afc436432a 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/AbstractOrderConfirmationBlock.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/AbstractOrderConfirmationBlock.php @@ -36,16 +36,11 @@ abstract class AbstractOrderConfirmationBlock extends AbstractBlock { $order = $this->get_order(); $permission = $this->get_view_order_permissions( $order ); $block_content = $order ? $this->render_content( $order, $permission, $attributes, $content ) : $this->render_content_fallback(); - $classname = $attributes['className'] ?? ''; $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); - if ( ! empty( $classes_and_styles['classes'] ) ) { - $classname .= ' ' . $classes_and_styles['classes']; - } - return $block_content ? sprintf( '
%3$s
', - esc_attr( trim( $classname ) ), + esc_attr( $classes_and_styles['classes'] ), esc_attr( $classes_and_styles['styles'] ), $block_content, esc_attr( $this->block_name ), diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/Status.php b/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/Status.php index 047be224e28..01eddceae64 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/Status.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/OrderConfirmation/Status.php @@ -2,6 +2,8 @@ namespace Automattic\WooCommerce\Blocks\BlockTypes\OrderConfirmation; +use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils; + /** * Status class. */ @@ -26,7 +28,7 @@ class Status extends AbstractOrderConfirmationBlock { */ protected function render( $attributes, $content, $block ) { $order = $this->get_order(); - $classname = $attributes['className'] ?? ''; + $classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) ); if ( isset( $attributes['align'] ) ) { $classname .= " align{$attributes['align']}"; diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php index c7d7554c6d7..468e832110c 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductButton.php @@ -100,8 +100,8 @@ class ProductButton extends AbstractBlock { $ajax_add_to_cart_enabled = get_option( 'woocommerce_enable_ajax_add_to_cart' ) === 'yes'; $is_ajax_button = $ajax_add_to_cart_enabled && ! $cart_redirect_after_add && $product->supports( 'ajax_add_to_cart' ) && $product->is_purchasable() && $product->is_in_stock(); $html_element = $is_ajax_button ? 'button' : 'a'; - $styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); - $classname = $attributes['className'] ?? ''; + $styles_and_classes = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) ); + $classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) ); $custom_width_classes = isset( $attributes['width'] ) ? 'has-custom-width wp-block-button__width-' . $attributes['width'] : ''; $custom_align_classes = isset( $attributes['textAlign'] ) ? 'align-' . $attributes['textAlign'] : ''; $html_classes = implode( diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCategories.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCategories.php index 9ad74fd8016..a9ac5c1db33 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductCategories.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductCategories.php @@ -89,7 +89,7 @@ class ProductCategories extends AbstractDynamicBlock { $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, - array( 'line_height', 'text_color', 'font_size' ) + array( 'line_height', 'text_color', 'font_size', 'extra_classes' ) ); $classes = $this->get_container_classes( $attributes ) . ' ' . $classes_and_styles['classes']; @@ -116,10 +116,6 @@ class ProductCategories extends AbstractDynamicBlock { $classes[] = "align{$attributes['align']}"; } - if ( ! empty( $attributes['className'] ) ) { - $classes[] = $attributes['className']; - } - if ( $attributes['isDropdown'] ) { $classes[] = 'is-dropdown'; } else { diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductDetails.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductDetails.php index 907d198dde9..a17a21a4fad 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductDetails.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductDetails.php @@ -76,18 +76,15 @@ class ProductDetails extends AbstractBlock { $tabs = $tabs_html->get_updated_html(); } - $classname = $attributes['className'] ?? ''; - $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); return sprintf( - '
-
- %4$s + '
+
+ %3$s
', esc_attr( $classes_and_styles['classes'] ), - esc_attr( $classname ), esc_attr( $classes_and_styles['styles'] ), $tabs ); diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductGallery.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductGallery.php index 9d52abacc8a..0ecdea898b0 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductGallery.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductGallery.php @@ -3,6 +3,7 @@ namespace Automattic\WooCommerce\Blocks\BlockTypes; use Automattic\WooCommerce\Blocks\Utils\BlockTemplateUtils; use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils; +use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils; /** * ProductGallery class. @@ -125,7 +126,7 @@ class ProductGallery extends AbstractBlock { } $number_of_thumbnails = $block->attributes['thumbnailsNumberOfThumbnails'] ?? 0; - $classname = $attributes['className'] ?? ''; + $classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) ); $dialog = isset( $attributes['mode'] ) && 'full' !== $attributes['mode'] ? $this->render_dialog() : ''; $product_gallery_first_image = ProductGalleryUtils::get_product_gallery_image_ids( $product, 1 ); $product_gallery_first_image_id = reset( $product_gallery_first_image ); diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductGalleryPager.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductGalleryPager.php index 58e268addc0..ac478cc71c0 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductGalleryPager.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductGalleryPager.php @@ -2,6 +2,7 @@ namespace Automattic\WooCommerce\Blocks\BlockTypes; use Automattic\WooCommerce\Blocks\Utils\ProductGalleryUtils; +use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils; /** * ProductGalleryPager class. @@ -55,7 +56,7 @@ class ProductGalleryPager extends AbstractBlock { } $number_of_thumbnails = $block->context['thumbnailsNumberOfThumbnails'] ?? 0; - $classname = $attributes['className'] ?? ''; + $classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) ); $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => trim( $classname ) ) ); $post_id = $block->context['postId'] ?? ''; $product = wc_get_product( $post_id ); diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductImageGallery.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductImageGallery.php index 2c08524fdc0..98dc66b40ed 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductImageGallery.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductImageGallery.php @@ -1,6 +1,8 @@ %2$s %3$s
', esc_attr( $classname ), diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductResultsCount.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductResultsCount.php index 61bbe35fdd1..aa5e8b20e8a 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductResultsCount.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductResultsCount.php @@ -54,7 +54,6 @@ class ProductResultsCount extends AbstractBlock { 'wc-block-product-results-count', 'wp-block-woocommerce-product-results-count', ), - isset( $attributes['className'] ) ? array( $attributes['className'] ) : array(), ); $p->set_attribute( 'class', implode( ' ', $classes ) ); $p->set_attribute( 'style', $parsed_style_attributes['styles'] ); diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductReviews.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductReviews.php index 8675cdc2709..719c9dc758a 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductReviews.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductReviews.php @@ -2,6 +2,8 @@ namespace Automattic\WooCommerce\Blocks\BlockTypes; +use Automattic\WooCommerce\Blocks\Utils\StyleAttributesUtils; + /** * ProductReviews class. */ @@ -40,13 +42,11 @@ class ProductReviews extends AbstractBlock { $reviews = ob_get_clean(); - $classname = $attributes['className'] ?? ''; - return sprintf( '
%2$s
', - esc_attr( $classname ), + StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) ), $reviews ); } diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductSaleBadge.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductSaleBadge.php index 5bd428d21e2..b7e958bd43a 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductSaleBadge.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductSaleBadge.php @@ -110,8 +110,9 @@ class ProductSaleBadge extends AbstractBlock { return null; } - $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); - $classname = isset( $attributes['className'] ) ? $attributes['className'] : ''; + $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes, array(), array( 'extra_classes' ) ); + + $classname = StyleAttributesUtils::get_classes_by_attributes( $attributes, array( 'extra_classes' ) ); $align = isset( $attributes['align'] ) ? $attributes['align'] : ''; diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductStockIndicator.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductStockIndicator.php index 867d6d9d50a..0bc742e2336 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductStockIndicator.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductStockIndicator.php @@ -101,7 +101,6 @@ class ProductStockIndicator extends AbstractBlock { $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); $classnames = isset( $classes_and_styles['classes'] ) ? ' ' . $classes_and_styles['classes'] . ' ' : ''; - $classnames .= isset( $attributes['className'] ) ? ' ' . $attributes['className'] . ' ' : ''; $classnames .= ! $is_in_stock ? ' wc-block-components-product-stock-indicator--out-of-stock ' : ''; $classnames .= $is_in_stock ? ' wc-block-components-product-stock-indicator--in-stock ' : ''; $classnames .= $is_low_stock ? ' wc-block-components-product-stock-indicator--low-stock ' : ''; diff --git a/plugins/woocommerce/src/Blocks/Utils/StyleAttributesUtils.php b/plugins/woocommerce/src/Blocks/Utils/StyleAttributesUtils.php index 5205dcc3691..ce5894ce1c9 100644 --- a/plugins/woocommerce/src/Blocks/Utils/StyleAttributesUtils.php +++ b/plugins/woocommerce/src/Blocks/Utils/StyleAttributesUtils.php @@ -687,6 +687,25 @@ class StyleAttributesUtils { return self::EMPTY_STYLE; } + /** + * Get extra CSS classes from attributes. + * + * @param array $attributes Block attributes. + * @return array + */ + public static function get_classes_from_attributes( $attributes ) { + + $extra_css_classes = $attributes['className'] ?? ''; + + if ( '' !== $extra_css_classes ) { + return array( + 'class' => $extra_css_classes, + 'style' => null, + ); + } + return self::EMPTY_STYLE; + } + /** * Get classes and styles from attributes. * @@ -717,6 +736,7 @@ class StyleAttributesUtils { 'text_color' => self::get_text_color_class_and_style( $attributes ), 'text_decoration' => self::get_text_decoration_class_and_style( $attributes ), 'text_transform' => self::get_text_transform_class_and_style( $attributes ), + 'extra_classes' => self::get_classes_from_attributes( $attributes ), ); if ( ! empty( $properties ) ) { From 2e9ec00dd2864c5c7efde3c115b0937a0d3b34e1 Mon Sep 17 00:00:00 2001 From: Maikel Perez Date: Sat, 28 Sep 2024 13:25:55 -0300 Subject: [PATCH 02/26] CYS: color swatches are not accessible (#51715) * Add a11y to the color swatches * Add changelog file --- .../customize-store/intro/color-palettes.tsx | 94 ++++++++++++++----- .../client/customize-store/intro/intro.scss | 4 + plugins/woocommerce/changelog/add-51574 | 4 + 3 files changed, 78 insertions(+), 24 deletions(-) create mode 100644 plugins/woocommerce/changelog/add-51574 diff --git a/plugins/woocommerce-admin/client/customize-store/intro/color-palettes.tsx b/plugins/woocommerce-admin/client/customize-store/intro/color-palettes.tsx index 94e45e9dd47..cce1a21b986 100644 --- a/plugins/woocommerce-admin/client/customize-store/intro/color-palettes.tsx +++ b/plugins/woocommerce-admin/client/customize-store/intro/color-palettes.tsx @@ -1,8 +1,16 @@ +/** + * External dependencies + */ +import { useInstanceId } from '@wordpress/compose'; +import { __, sprintf } from '@wordpress/i18n'; + /** * Internal dependencies */ import { ColorPalette } from './types'; +const MAX_COLOR_PALETTES = 4; + export const ColorPalettes = ( { colorPalettes, totalPalettes, @@ -10,33 +18,71 @@ export const ColorPalettes = ( { colorPalettes: ColorPalette[]; totalPalettes: number; } ) => { - let extra = null; + const canFit = totalPalettes <= MAX_COLOR_PALETTES; - if ( totalPalettes > 4 ) { - extra =
  • +{ totalPalettes - 4 }
  • ; + const descriptionId = useInstanceId( + ColorPalettes, + 'color-palettes-description' + ) as string; + + function renderMore() { + if ( canFit ) return null; + return ( + + ); + } + + function renderDescription() { + if ( canFit ) return null; + return ( +

    + { sprintf( + /* translators: $d is the total amount of color palettes */ + __( + 'There are a total of %d color palettes', + 'woocommerce' + ), + totalPalettes + ) } +

    + ); } return ( -
      - { colorPalettes.map( ( colorPalette ) => ( -
    • - ) ) } - { extra } -
    + <> +
      + { colorPalettes.map( ( colorPalette ) => ( +
    • + ) ) } + { renderMore() } +
    + + { renderDescription() } + ); }; diff --git a/plugins/woocommerce-admin/client/customize-store/intro/intro.scss b/plugins/woocommerce-admin/client/customize-store/intro/intro.scss index cd47827cdea..144ca7e8b6d 100644 --- a/plugins/woocommerce-admin/client/customize-store/intro/intro.scss +++ b/plugins/woocommerce-admin/client/customize-store/intro/intro.scss @@ -314,6 +314,10 @@ text-align: center; } } + + &-description { + @include visually-hidden(); + } } .theme-card__free { diff --git a/plugins/woocommerce/changelog/add-51574 b/plugins/woocommerce/changelog/add-51574 new file mode 100644 index 00000000000..cb98b7ee856 --- /dev/null +++ b/plugins/woocommerce/changelog/add-51574 @@ -0,0 +1,4 @@ +Significance: patch +Type: add + +Add a11y to the color swatches From 19438505cfe3606954ad06f22d3bb79a2e285171 Mon Sep 17 00:00:00 2001 From: Sam Seay Date: Mon, 30 Sep 2024 17:53:01 +1300 Subject: [PATCH 03/26] #50557 - Order summary Shipping updates (#51608) --- .../totals/footer-item/style.scss | 4 ++++ .../cart-checkout/totals/shipping/index.tsx | 1 + .../totals/shipping/shipping-placeholder.tsx | 11 +++++++--- .../cart-checkout/totals/shipping/style.scss | 5 ++++- .../shipping/test/shipping-placeholder.tsx | 21 +++++++++++++++++-- .../components/totals/item/style.scss | 4 ---- .../51608-dev-checkout-shipping-options-50557 | 4 ++++ 7 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 plugins/woocommerce/changelog/51608-dev-checkout-shipping-options-50557 diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/footer-item/style.scss b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/footer-item/style.scss index a614ebc9f18..08a9a35b4ef 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/footer-item/style.scss +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/footer-item/style.scss @@ -11,4 +11,8 @@ .wc-block-components-totals-footer-item-tax { margin-bottom: 0; } + + .wc-block-components-totals-item__value { + font-weight: bold; + } } diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/index.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/index.tsx index f3ab7641790..3f6ce7cb192 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/index.tsx @@ -114,6 +114,7 @@ export const TotalsShipping = ( { { if ( ! showCalculator ) { + const label = addressProvided + ? __( 'No available delivery option', 'woocommerce' ) + : __( 'Enter address to calculate', 'woocommerce' ); return ( - + { isCheckout - ? __( 'No shipping options available', 'woocommerce' ) + ? label : __( 'Calculated during checkout', 'woocommerce' ) } - + ); } diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/style.scss b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/style.scss index aef027ffdd0..beba6a5a819 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/style.scss +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/style.scss @@ -11,7 +11,6 @@ text-transform: uppercase; } - .wc-block-components-shipping-address { margin-top: $gap; display: block; @@ -50,6 +49,10 @@ opacity: 0.8; } } + + .wc-block-components-shipping-placeholder__value { + @include font-size(small); + } } // Extra classes for specificity. diff --git a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/test/shipping-placeholder.tsx b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/test/shipping-placeholder.tsx index 469bb35d61a..92700f6a76a 100644 --- a/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/test/shipping-placeholder.tsx +++ b/plugins/woocommerce-blocks/assets/js/base/components/cart-checkout/totals/shipping/test/shipping-placeholder.tsx @@ -9,22 +9,24 @@ import { screen, render } from '@testing-library/react'; import ShippingPlaceholder from '../shipping-placeholder'; describe( 'ShippingPlaceholder', () => { - it( 'should show correct text if showCalculator is false', () => { + it( 'should show correct text if showCalculator is false and addressProvided is false', () => { const { rerender } = render( ); expect( - screen.getByText( 'No shipping options available' ) + screen.getByText( 'Enter address to calculate' ) ).toBeInTheDocument(); rerender( @@ -33,4 +35,19 @@ describe( 'ShippingPlaceholder', () => { screen.getByText( 'Calculated during checkout' ) ).toBeInTheDocument(); } ); + + it( 'should show correct text if showCalculator is false and addressProvided is true', () => { + render( + + ); + expect( + screen.getByText( 'No available delivery option' ) + ).toBeInTheDocument(); + } ); } ); diff --git a/plugins/woocommerce-blocks/packages/components/totals/item/style.scss b/plugins/woocommerce-blocks/packages/components/totals/item/style.scss index f350a2984da..5a46563bb8a 100644 --- a/plugins/woocommerce-blocks/packages/components/totals/item/style.scss +++ b/plugins/woocommerce-blocks/packages/components/totals/item/style.scss @@ -9,10 +9,6 @@ flex-grow: 1; } -.wc-block-components-totals-item__value { - font-weight: bold; -} - .wc-block-components-totals-item__description { @include font-size(small); width: 100%; diff --git a/plugins/woocommerce/changelog/51608-dev-checkout-shipping-options-50557 b/plugins/woocommerce/changelog/51608-dev-checkout-shipping-options-50557 new file mode 100644 index 00000000000..c54645541c5 --- /dev/null +++ b/plugins/woocommerce/changelog/51608-dev-checkout-shipping-options-50557 @@ -0,0 +1,4 @@ +Significance: minor +Type: tweak + +Changes to copy and styling of delivery summary in checkout block. \ No newline at end of file From f756b486e42953773242d2ac506713eea6c6d6a4 Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Mon, 30 Sep 2024 08:32:56 +0200 Subject: [PATCH 04/26] [dev] CI: tweaks in assets sizes verification job for better performance and coverage (#51761) In this PR we refine cleaning build artifacts to build-folders only and leverage wireit integration in GitHub actions for leveraging incremental build approach in the job and improving the job running time. --- .github/workflows/pr-assess-bundle-size.yml | 6 ++++-- package.json | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-assess-bundle-size.yml b/.github/workflows/pr-assess-bundle-size.yml index 8e6b2272f8a..df6bb6444fa 100644 --- a/.github/workflows/pr-assess-bundle-size.yml +++ b/.github/workflows/pr-assess-bundle-size.yml @@ -42,6 +42,8 @@ jobs: uses: ./.github/actions/setup-woocommerce-monorepo with: php-version: false + install: '@woocommerce/plugin-woocommerce...' + build: '@woocommerce/plugin-woocommerce' pull-package-deps: '@woocommerce/plugin-woocommerce' - uses: preactjs/compressed-size-action@f780fd104362cfce9e118f9198df2ee37d12946c @@ -49,9 +51,9 @@ jobs: BROWSERSLIST_IGNORE_OLD_DATA: true with: repo-token: '${{ secrets.GITHUB_TOKEN }}' - pattern: './{packages/js/!(*e2e*|*internal*|*test*|*plugin*|*create*),plugins/woocommerce-blocks}/{build,build-style}/**/*.{js,css}' + pattern: './{packages/js/!(*e2e*|*internal*|*test*|*plugin*|*create*),plugins/woocommerce-blocks,plugins/woocommerce-admin,plugins/woocommerce/client/legacy}/{build,build-style}/**/*.{js,css}' install-script: 'pnpm install --filter="@woocommerce/plugin-woocommerce..." --frozen-lockfile --config.dedupe-peer-dependents=false --ignore-scripts' build-script: '--filter="@woocommerce/plugin-woocommerce" build' - clean-script: '--if-present buildclean' + clean-script: '--if-present clean:build' minimum-change-threshold: 100 omit-unchanged: true diff --git a/package.json b/package.json index fb79de58c39..eb8c83b0391 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "lint": "pnpm -r lint", "cherry-pick": "node ./tools/cherry-pick/bin/run", "clean": "rimraf -g '**/node_modules' '**/.wireit' && pnpm store prune", - "buildclean": "git clean --force -d -X ./packages ./plugins ./tools", + "clean:build": "rimraf -g 'packages/js/*/build' 'packages/js/*/build-*' 'packages/js/*/dist' 'plugins/*/build' 'plugins/woocommerce/client/legacy/build' && git clean --force -d -X --quiet ./plugins/woocommerce/assets", "preinstall": "npx only-allow pnpm", "postinstall": "husky", "sync-dependencies": "pnpm exec syncpack -- fix-mismatches", From bdaebbb69a71e5a6fd1c96c4dd4dfbb6fd3026df Mon Sep 17 00:00:00 2001 From: Bart Kalisz Date: Mon, 30 Sep 2024 09:54:46 +0200 Subject: [PATCH 05/26] Blocks E2E: Fix db connection issues (#51658) --- .../collections.block_theme.spec.ts | 348 +++--- .../compatibility-layer.block_theme.spec.ts | 58 +- .../extensibility-events.block_theme.spec.ts | 2 +- .../inspector-controls.block_theme.spec.ts | 1018 ++++++++--------- .../product-collection.block_theme.spec.ts | 453 ++------ .../product-picker.block_theme.spec.ts | 204 ++++ ...er-product-collection.block_theme.spec.ts} | 131 ++- .../tests/e2e/utils/index.ts | 1 - .../tests/e2e/utils/request-utils/index.ts | 27 - .../tests/e2e/utils/storeApi/index.ts | 1 - .../utils/storeApi/store-api-utils.page.ts | 30 - .../tests/e2e/utils/test.ts | 9 +- .../changelog/fix-e2e-db-connection-issue | 4 + 13 files changed, 1113 insertions(+), 1173 deletions(-) create mode 100644 plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-picker.block_theme.spec.ts rename plugins/woocommerce-blocks/tests/e2e/tests/product-collection/{register-product-collection-tester.block_theme.spec.ts => register-product-collection.block_theme.spec.ts} (76%) delete mode 100644 plugins/woocommerce-blocks/tests/e2e/utils/storeApi/index.ts delete mode 100644 plugins/woocommerce-blocks/tests/e2e/utils/storeApi/store-api-utils.page.ts create mode 100644 plugins/woocommerce/changelog/fix-e2e-db-connection-issue diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/collections.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/collections.block_theme.spec.ts index eac6f622591..fc42fb91ce4 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/collections.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/collections.block_theme.spec.ts @@ -19,203 +19,195 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( { }, } ); -test.describe( 'Product Collection', () => { - test.describe( 'Collections', () => { - test( 'New Arrivals Collection can be added and displays proper products', async ( { - pageObject, - } ) => { +test.describe( 'Product Collection: Collections', () => { + test( 'New Arrivals Collection can be added and displays proper products', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock( 'newArrivals' ); + + // New Arrivals are by default filtered to display products from last 7 days. + // Products in our test env have creation date set to much older, hence + // no products are expected to be displayed by default. + await expect( pageObject.products ).toHaveCount( 0 ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.products ).toHaveCount( 0 ); + } ); + + // When creating reviews programmatically the ratings are not propagated + // properly so products order by rating is undeterministic in test env. + // eslint-disable-next-line playwright/no-skipped-test + test.skip( 'Top Rated Collection can be added and displays proper products', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock( 'topRated' ); + + const topRatedProducts = [ + 'V Neck T Shirt', + 'Hoodie', + 'Hoodie with Logo', + 'T-Shirt', + 'Beanie', + ]; + + await expect( pageObject.products ).toHaveCount( 5 ); + await expect( pageObject.productTitles ).toHaveText( topRatedProducts ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.products ).toHaveCount( 5 ); + } ); + + // There's no orders in test env so the order of Best Sellers + // is undeterministic in test env. Requires further work. + // eslint-disable-next-line playwright/no-skipped-test + test.skip( 'Best Sellers Collection can be added and displays proper products', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock( 'bestSellers' ); + + const bestSellersProducts = [ + 'Album', + 'Hoodie', + 'Single', + 'Hoodie with Logo', + 'T-Shirt with Logo', + ]; + + await expect( pageObject.products ).toHaveCount( 5 ); + await expect( pageObject.productTitles ).toHaveText( + bestSellersProducts + ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.products ).toHaveCount( 5 ); + } ); + + test( 'On Sale Collection can be added and displays proper products', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock( 'onSale' ); + + const onSaleProducts = [ + 'Beanie', + 'Beanie with Logo', + 'Belt', + 'Cap', + 'Hoodie', + ]; + + await expect( pageObject.products ).toHaveCount( 5 ); + await expect( pageObject.productTitles ).toHaveText( onSaleProducts ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.products ).toHaveCount( 5 ); + } ); + + test( 'Featured Collection can be added and displays proper products', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock( 'featured' ); + + const featuredProducts = [ + 'Cap', + 'Hoodie with Zipper', + 'Sunglasses', + 'V-Neck T-Shirt', + ]; + + await expect( pageObject.products ).toHaveCount( 4 ); + await expect( pageObject.productTitles ).toHaveText( featuredProducts ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.products ).toHaveCount( 4 ); + } ); + + test( 'Product Catalog Collection can be added in post and syncs query with template', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock( 'productCatalog' ); + + const usePageContextToggle = pageObject + .locateSidebarSettings() + .locator( `${ SELECTORS.usePageContextControl } input` ); + + await expect( usePageContextToggle ).toBeVisible(); + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.products ).toHaveCount( 9 ); + } ); + + test( 'Product Catalog Collection can be added in product archive and syncs query with template', async ( { + pageObject, + editor, + admin, + } ) => { + await admin.visitSiteEditor( { + postId: 'woocommerce/woocommerce//archive-product', + postType: 'wp_template', + canvas: 'edit', + } ); + + await editor.setContent( '' ); + + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInTemplate(); + await editor.openDocumentSettingsSidebar(); + + const sidebarSettings = pageObject.locateSidebarSettings(); + const input = sidebarSettings.locator( + `${ SELECTORS.usePageContextControl } input` + ); + + await expect( input ).toBeChecked(); + } ); + + test.describe( 'Have hidden implementation in UI', () => { + test( 'New Arrivals', async ( { pageObject } ) => { await pageObject.createNewPostAndInsertBlock( 'newArrivals' ); + const input = await pageObject.getOrderByElement(); - // New Arrivals are by default filtered to display products from last 7 days. - // Products in our test env have creation date set to much older, hence - // no products are expected to be displayed by default. - await expect( pageObject.products ).toHaveCount( 0 ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 0 ); + await expect( input ).toBeHidden(); } ); - // When creating reviews programmatically the ratings are not propagated - // properly so products order by rating is undeterministic in test env. - // eslint-disable-next-line playwright/no-skipped-test - test.skip( 'Top Rated Collection can be added and displays proper products', async ( { - pageObject, - } ) => { + test( 'Top Rated', async ( { pageObject } ) => { await pageObject.createNewPostAndInsertBlock( 'topRated' ); + const input = await pageObject.getOrderByElement(); - const topRatedProducts = [ - 'V Neck T Shirt', - 'Hoodie', - 'Hoodie with Logo', - 'T-Shirt', - 'Beanie', - ]; - - await expect( pageObject.products ).toHaveCount( 5 ); - await expect( pageObject.productTitles ).toHaveText( - topRatedProducts - ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 5 ); + await expect( input ).toBeHidden(); } ); - // There's no orders in test env so the order of Best Sellers - // is undeterministic in test env. Requires further work. - // eslint-disable-next-line playwright/no-skipped-test - test.skip( 'Best Sellers Collection can be added and displays proper products', async ( { - pageObject, - } ) => { + test( 'Best Sellers', async ( { pageObject } ) => { await pageObject.createNewPostAndInsertBlock( 'bestSellers' ); + const input = await pageObject.getOrderByElement(); - const bestSellersProducts = [ - 'Album', - 'Hoodie', - 'Single', - 'Hoodie with Logo', - 'T-Shirt with Logo', - ]; - - await expect( pageObject.products ).toHaveCount( 5 ); - await expect( pageObject.productTitles ).toHaveText( - bestSellersProducts - ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 5 ); + await expect( input ).toBeHidden(); } ); - test( 'On Sale Collection can be added and displays proper products', async ( { - pageObject, - } ) => { + test( 'On Sale', async ( { pageObject } ) => { await pageObject.createNewPostAndInsertBlock( 'onSale' ); - - const onSaleProducts = [ - 'Beanie', - 'Beanie with Logo', - 'Belt', - 'Cap', - 'Hoodie', - ]; - - await expect( pageObject.products ).toHaveCount( 5 ); - await expect( pageObject.productTitles ).toHaveText( - onSaleProducts - ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 5 ); - } ); - - test( 'Featured Collection can be added and displays proper products', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock( 'featured' ); - - const featuredProducts = [ - 'Cap', - 'Hoodie with Zipper', - 'Sunglasses', - 'V-Neck T-Shirt', - ]; - - await expect( pageObject.products ).toHaveCount( 4 ); - await expect( pageObject.productTitles ).toHaveText( - featuredProducts - ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 4 ); - } ); - - test( 'Product Catalog Collection can be added in post and syncs query with template', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock( 'productCatalog' ); - - const usePageContextToggle = pageObject - .locateSidebarSettings() - .locator( `${ SELECTORS.usePageContextControl } input` ); - - await expect( usePageContextToggle ).toBeVisible(); - await expect( pageObject.products ).toHaveCount( 9 ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 9 ); - } ); - - test( 'Product Catalog Collection can be added in product archive and syncs query with template', async ( { - pageObject, - editor, - admin, - } ) => { - await admin.visitSiteEditor( { - postId: 'woocommerce/woocommerce//archive-product', - postType: 'wp_template', - canvas: 'edit', - } ); - - await editor.setContent( '' ); - - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInTemplate(); - await editor.openDocumentSettingsSidebar(); - const sidebarSettings = pageObject.locateSidebarSettings(); - const input = sidebarSettings.locator( - `${ SELECTORS.usePageContextControl } input` + const input = sidebarSettings.getByLabel( + SELECTORS.onSaleControlLabel ); - await expect( input ).toBeChecked(); + await expect( input ).toBeHidden(); } ); - test.describe( 'Have hidden implementation in UI', () => { - test( 'New Arrivals', async ( { pageObject } ) => { - await pageObject.createNewPostAndInsertBlock( 'newArrivals' ); - const input = await pageObject.getOrderByElement(); + test( 'Featured', async ( { pageObject } ) => { + await pageObject.createNewPostAndInsertBlock( 'featured' ); + const sidebarSettings = pageObject.locateSidebarSettings(); + const input = sidebarSettings.getByLabel( + SELECTORS.featuredControlLabel + ); - await expect( input ).toBeHidden(); - } ); - - test( 'Top Rated', async ( { pageObject } ) => { - await pageObject.createNewPostAndInsertBlock( 'topRated' ); - const input = await pageObject.getOrderByElement(); - - await expect( input ).toBeHidden(); - } ); - - test( 'Best Sellers', async ( { pageObject } ) => { - await pageObject.createNewPostAndInsertBlock( 'bestSellers' ); - const input = await pageObject.getOrderByElement(); - - await expect( input ).toBeHidden(); - } ); - - test( 'On Sale', async ( { pageObject } ) => { - await pageObject.createNewPostAndInsertBlock( 'onSale' ); - const sidebarSettings = pageObject.locateSidebarSettings(); - const input = sidebarSettings.getByLabel( - SELECTORS.onSaleControlLabel - ); - - await expect( input ).toBeHidden(); - } ); - - test( 'Featured', async ( { pageObject } ) => { - await pageObject.createNewPostAndInsertBlock( 'featured' ); - const sidebarSettings = pageObject.locateSidebarSettings(); - const input = sidebarSettings.getByLabel( - SELECTORS.featuredControlLabel - ); - - await expect( input ).toBeHidden(); - } ); + await expect( input ).toBeHidden(); } ); } ); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/compatibility-layer.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/compatibility-layer.block_theme.spec.ts index fc06004254c..49d17315790 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/compatibility-layer.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/compatibility-layer.block_theme.spec.ts @@ -86,35 +86,33 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( { }, } ); -test.describe( 'Compatibility Layer with Product Collection block', () => { - test.describe( 'Product Archive with Product Collection block', () => { - test.beforeEach( async ( { pageObject, requestUtils } ) => { - await requestUtils.activatePlugin( - 'woocommerce-blocks-test-product-collection-compatibility-layer' - ); - await pageObject.goToProductCatalogFrontend(); - } ); - - for ( const scenario of singleOccurrenceScenarios ) { - test( `${ scenario.title } is attached to the page`, async ( { - pageObject, - } ) => { - const hooks = pageObject.locateByTestId( scenario.dataTestId ); - - await expect( hooks ).toHaveCount( scenario.amount ); - await expect( hooks ).toHaveText( scenario.content ); - } ); - } - - for ( const scenario of multipleOccurrenceScenarios ) { - test( `${ scenario.title } is attached to the page`, async ( { - pageObject, - } ) => { - const hooks = pageObject.locateByTestId( scenario.dataTestId ); - - await expect( hooks ).toHaveCount( scenario.amount ); - await expect( hooks.first() ).toHaveText( scenario.content ); - } ); - } +test.describe( 'Product Collection: Compatibility Layer', () => { + test.beforeEach( async ( { pageObject, requestUtils } ) => { + await requestUtils.activatePlugin( + 'woocommerce-blocks-test-product-collection-compatibility-layer' + ); + await pageObject.goToProductCatalogFrontend(); } ); + + for ( const scenario of singleOccurrenceScenarios ) { + test( `${ scenario.title } is attached to the page`, async ( { + pageObject, + } ) => { + const hooks = pageObject.locateByTestId( scenario.dataTestId ); + + await expect( hooks ).toHaveCount( scenario.amount ); + await expect( hooks ).toHaveText( scenario.content ); + } ); + } + + for ( const scenario of multipleOccurrenceScenarios ) { + test( `${ scenario.title } is attached to the page`, async ( { + pageObject, + } ) => { + const hooks = pageObject.locateByTestId( scenario.dataTestId ); + + await expect( hooks ).toHaveCount( scenario.amount ); + await expect( hooks.first() ).toHaveText( scenario.content ); + } ); + } } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/extensibility-events.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/extensibility-events.block_theme.spec.ts index 15aafd1f88a..8661492c385 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/extensibility-events.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/extensibility-events.block_theme.spec.ts @@ -19,7 +19,7 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( { }, } ); -test.describe( 'Product Collection - extensibility JS events', () => { +test.describe( 'Product Collection: Extensibility Events', () => { test( 'emits wc-blocks_product_list_rendered event on init and on page change', async ( { pageObject, page, diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/inspector-controls.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/inspector-controls.block_theme.spec.ts index 92b1723c6a3..d240f7f7d55 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/inspector-controls.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/inspector-controls.block_theme.spec.ts @@ -19,381 +19,396 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( { }, } ); -test.describe( 'Product Collection', () => { - test.describe( 'Inspector Controls', () => { - test( 'Reflects the correct number of columns according to sidebar settings', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); +test.describe( 'Product Collection: Inspector Controls', () => { + test( 'Reflects the correct number of columns according to sidebar settings', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); - await pageObject.setNumberOfColumns( 2 ); - await expect( pageObject.productTemplate ).toHaveClass( - /columns-2/ - ); + await pageObject.setNumberOfColumns( 2 ); + await expect( pageObject.productTemplate ).toHaveClass( /columns-2/ ); - await pageObject.setNumberOfColumns( 4 ); - await expect( pageObject.productTemplate ).toHaveClass( - /columns-4/ - ); + await pageObject.setNumberOfColumns( 4 ); + await expect( pageObject.productTemplate ).toHaveClass( /columns-4/ ); - await pageObject.publishAndGoToFrontend(); + await pageObject.publishAndGoToFrontend(); - await expect( pageObject.productTemplate ).toHaveClass( - /columns-4/ - ); + await expect( pageObject.productTemplate ).toHaveClass( /columns-4/ ); + } ); + + test( 'Order By - sort products by title in descending order correctly', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + const sortedTitles = [ + 'WordPress Pennant', + 'V-Neck T-Shirt', + 'T-Shirt with Logo', + 'T-Shirt', + /Sunglasses/, // In the frontend it's "Protected: Sunglasses" + 'Single', + 'Polo', + 'Long Sleeve Tee', + 'Logo Collection', + ]; + + await pageObject.setOrderBy( 'title/desc' ); + await expect( pageObject.productTitles ).toHaveText( sortedTitles ); + + await pageObject.publishAndGoToFrontend(); + await expect( pageObject.productTitles ).toHaveText( sortedTitles ); + } ); + + // Products can be filtered based on 'on sale' status. + test( 'Products can be filtered based on "on sale" status', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + let allProducts = pageObject.products; + let saleProducts = pageObject.products.filter( { + hasText: 'Product on sale', } ); - test( 'Order By - sort products by title in descending order correctly', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); + await expect( allProducts ).toHaveCount( 9 ); + await expect( saleProducts ).toHaveCount( 6 ); - const sortedTitles = [ - 'WordPress Pennant', - 'V-Neck T-Shirt', - 'T-Shirt with Logo', - 'T-Shirt', - /Sunglasses/, // In the frontend it's "Protected: Sunglasses" - 'Single', - 'Polo', - 'Long Sleeve Tee', - 'Logo Collection', - ]; - - await pageObject.setOrderBy( 'title/desc' ); - await expect( pageObject.productTitles ).toHaveText( sortedTitles ); - - await pageObject.publishAndGoToFrontend(); - await expect( pageObject.productTitles ).toHaveText( sortedTitles ); + await pageObject.setShowOnlyProductsOnSale( { + onSale: true, } ); - // Products can be filtered based on 'on sale' status. - test( 'Products can be filtered based on "on sale" status', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); + await expect( allProducts ).toHaveCount( 6 ); + await expect( saleProducts ).toHaveCount( 6 ); - let allProducts = pageObject.products; - let saleProducts = pageObject.products.filter( { - hasText: 'Product on sale', - } ); - - await expect( allProducts ).toHaveCount( 9 ); - await expect( saleProducts ).toHaveCount( 6 ); - - await pageObject.setShowOnlyProductsOnSale( { - onSale: true, - } ); - - await expect( allProducts ).toHaveCount( 6 ); - await expect( saleProducts ).toHaveCount( 6 ); - - await pageObject.publishAndGoToFrontend(); - await pageObject.refreshLocators( 'frontend' ); - allProducts = pageObject.products; - saleProducts = pageObject.products.filter( { - hasText: 'Product on sale', - } ); - - await expect( allProducts ).toHaveCount( 6 ); - await expect( saleProducts ).toHaveCount( 6 ); + await pageObject.publishAndGoToFrontend(); + await pageObject.refreshLocators( 'frontend' ); + allProducts = pageObject.products; + saleProducts = pageObject.products.filter( { + hasText: 'Product on sale', } ); - test( 'Products can be filtered based on selection in handpicked products option', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); + await expect( allProducts ).toHaveCount( 6 ); + await expect( saleProducts ).toHaveCount( 6 ); + } ); - await pageObject.addFilter( 'Show Hand-picked Products' ); + test( 'Products can be filtered based on selection in handpicked products option', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); - const filterName = 'Hand-picked Products'; - await pageObject.setFilterComboboxValue( filterName, [ 'Album' ] ); - await expect( pageObject.products ).toHaveCount( 1 ); + await pageObject.addFilter( 'Show Hand-picked Products' ); - const productNames = [ 'Album', 'Cap' ]; - await pageObject.setFilterComboboxValue( filterName, productNames ); - await expect( pageObject.products ).toHaveCount( 2 ); - await expect( pageObject.productTitles ).toHaveText( productNames ); + const filterName = 'Hand-picked Products'; + await pageObject.setFilterComboboxValue( filterName, [ 'Album' ] ); + await expect( pageObject.products ).toHaveCount( 1 ); - await pageObject.publishAndGoToFrontend(); - await expect( pageObject.products ).toHaveCount( 2 ); - await expect( pageObject.productTitles ).toHaveText( productNames ); + const productNames = [ 'Album', 'Cap' ]; + await pageObject.setFilterComboboxValue( filterName, productNames ); + await expect( pageObject.products ).toHaveCount( 2 ); + await expect( pageObject.productTitles ).toHaveText( productNames ); + + await pageObject.publishAndGoToFrontend(); + await expect( pageObject.products ).toHaveCount( 2 ); + await expect( pageObject.productTitles ).toHaveText( productNames ); + } ); + + test( 'Products can be filtered based on keyword.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await pageObject.addFilter( 'Keyword' ); + + await pageObject.setKeyword( 'Album' ); + await expect( pageObject.productTitles ).toHaveText( [ 'Album' ] ); + + await pageObject.setKeyword( 'Cap' ); + await expect( pageObject.productTitles ).toHaveText( [ 'Cap' ] ); + + await pageObject.publishAndGoToFrontend(); + await expect( pageObject.productTitles ).toHaveText( [ 'Cap' ] ); + } ); + + test( 'Products can be filtered based on category.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + const filterName = 'Product categories'; + await pageObject.addFilter( 'Show product categories' ); + await pageObject.setFilterComboboxValue( filterName, [ 'Clothing' ] ); + await expect( pageObject.productTitles ).toHaveText( [ + 'Logo Collection', + ] ); + + await pageObject.setFilterComboboxValue( filterName, [ + 'Accessories', + ] ); + const accessoriesProductNames = [ + 'Beanie', + 'Beanie with Logo', + 'Belt', + 'Cap', + 'Sunglasses', + ]; + await expect( pageObject.productTitles ).toHaveText( + accessoriesProductNames + ); + + await pageObject.publishAndGoToFrontend(); + + const frontendAccessoriesProductNames = [ + 'Beanie', + 'Beanie with Logo', + 'Belt', + 'Cap', + 'Protected: Sunglasses', + ]; + await expect( pageObject.productTitles ).toHaveText( + frontendAccessoriesProductNames + ); + } ); + + test( 'Products can be filtered based on tags.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + const filterName = 'Product tags'; + await pageObject.addFilter( 'Show product tags' ); + await pageObject.setFilterComboboxValue( filterName, [ + 'Recommended', + ] ); + await expect( pageObject.productTitles ).toHaveText( [ + 'Beanie', + 'Hoodie', + ] ); + + await pageObject.publishAndGoToFrontend(); + await expect( pageObject.productTitles ).toHaveText( [ + 'Beanie', + 'Hoodie', + ] ); + } ); + + test( 'Products can be filtered based on product attributes like color, size etc.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await pageObject.addFilter( 'Show Product Attributes' ); + await pageObject.setProductAttribute( 'Color', 'Green' ); + + await expect( pageObject.products ).toHaveCount( 3 ); + + await pageObject.setProductAttribute( 'Size', 'Large' ); + + await expect( pageObject.products ).toHaveCount( 1 ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.products ).toHaveCount( 1 ); + } ); + + test( 'Products can be filtered based on stock status (in stock, out of stock, or backorder).', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await pageObject.setFilterComboboxValue( 'Stock status', [ + 'Out of stock', + ] ); + + await expect( pageObject.productTitles ).toHaveText( [ + 'T-Shirt with Logo', + ] ); + + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.productTitles ).toHaveText( [ + 'T-Shirt with Logo', + ] ); + } ); + + test( 'Products can be filtered based on featured status.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.addFilter( 'Featured' ); + await pageObject.setShowOnlyFeaturedProducts( { + featured: true, } ); - test( 'Products can be filtered based on keyword.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); + // In test data we have only 4 featured products. + await expect( pageObject.products ).toHaveCount( 4 ); - await pageObject.addFilter( 'Keyword' ); + await pageObject.publishAndGoToFrontend(); - await pageObject.setKeyword( 'Album' ); - await expect( pageObject.productTitles ).toHaveText( [ 'Album' ] ); + await expect( pageObject.products ).toHaveCount( 4 ); + } ); - await pageObject.setKeyword( 'Cap' ); - await expect( pageObject.productTitles ).toHaveText( [ 'Cap' ] ); + test( 'Products can be filtered based on created date.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); - await pageObject.publishAndGoToFrontend(); - await expect( pageObject.productTitles ).toHaveText( [ 'Cap' ] ); + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.addFilter( 'Created' ); + await pageObject.setCreatedFilter( { + operator: 'within', + range: 'last3months', } ); - test( 'Products can be filtered based on category.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); + // Products are created with the fixed publish date back in 2019 + // so there's no products published in last 3 months. + await expect( pageObject.products ).toHaveCount( 0 ); - const filterName = 'Product categories'; - await pageObject.addFilter( 'Show product categories' ); - await pageObject.setFilterComboboxValue( filterName, [ - 'Clothing', - ] ); - await expect( pageObject.productTitles ).toHaveText( [ - 'Logo Collection', - ] ); - - await pageObject.setFilterComboboxValue( filterName, [ - 'Accessories', - ] ); - const accessoriesProductNames = [ - 'Beanie', - 'Beanie with Logo', - 'Belt', - 'Cap', - 'Sunglasses', - ]; - await expect( pageObject.productTitles ).toHaveText( - accessoriesProductNames - ); - - await pageObject.publishAndGoToFrontend(); - - const frontendAccessoriesProductNames = [ - 'Beanie', - 'Beanie with Logo', - 'Belt', - 'Cap', - 'Protected: Sunglasses', - ]; - await expect( pageObject.productTitles ).toHaveText( - frontendAccessoriesProductNames - ); + await pageObject.setCreatedFilter( { + operator: 'before', + range: 'last3months', } ); - test( 'Products can be filtered based on tags.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); + await expect( pageObject.products ).toHaveCount( 9 ); - const filterName = 'Product tags'; - await pageObject.addFilter( 'Show product tags' ); - await pageObject.setFilterComboboxValue( filterName, [ - 'Recommended', - ] ); - await expect( pageObject.productTitles ).toHaveText( [ - 'Beanie', - 'Hoodie', - ] ); + await pageObject.publishAndGoToFrontend(); - await pageObject.publishAndGoToFrontend(); - await expect( pageObject.productTitles ).toHaveText( [ - 'Beanie', - 'Hoodie', - ] ); + await expect( pageObject.products ).toHaveCount( 9 ); + } ); + + test( 'Products can be filtered based on price range.', async ( { + pageObject, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.addFilter( 'Price Range' ); + await pageObject.setPriceRange( { + min: '18.33', } ); - test( 'Products can be filtered based on product attributes like color, size etc.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); + await expect( pageObject.products ).toHaveCount( 7 ); - await pageObject.addFilter( 'Show Product Attributes' ); - await pageObject.setProductAttribute( 'Color', 'Green' ); - - await expect( pageObject.products ).toHaveCount( 3 ); - - await pageObject.setProductAttribute( 'Size', 'Large' ); - - await expect( pageObject.products ).toHaveCount( 1 ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 1 ); + await pageObject.setPriceRange( { + min: '15.28', + max: '17.21', } ); - test( 'Products can be filtered based on stock status (in stock, out of stock, or backorder).', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); + await expect( pageObject.products ).toHaveCount( 1 ); - await pageObject.setFilterComboboxValue( 'Stock status', [ - 'Out of stock', - ] ); - - await expect( pageObject.productTitles ).toHaveText( [ - 'T-Shirt with Logo', - ] ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.productTitles ).toHaveText( [ - 'T-Shirt with Logo', - ] ); + await pageObject.setPriceRange( { + max: '17.29', } ); - test( 'Products can be filtered based on featured status.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); + await expect( pageObject.products ).toHaveCount( 4 ); - await expect( pageObject.products ).toHaveCount( 9 ); + await pageObject.publishAndGoToFrontend(); - await pageObject.addFilter( 'Featured' ); - await pageObject.setShowOnlyFeaturedProducts( { - featured: true, - } ); + await expect( pageObject.products ).toHaveCount( 4 ); + } ); - // In test data we have only 4 featured products. - await expect( pageObject.products ).toHaveCount( 4 ); + // See https://github.com/woocommerce/woocommerce/pull/49917 + test( 'Price range is inclusive in both editor and frontend.', async ( { + page, + pageObject, + editor, + } ) => { + await pageObject.createNewPostAndInsertBlock(); - await pageObject.publishAndGoToFrontend(); + await expect( pageObject.products ).toHaveCount( 9 ); - await expect( pageObject.products ).toHaveCount( 4 ); + await pageObject.addFilter( 'Price Range' ); + await pageObject.setPriceRange( { + min: '45', + max: '55', } ); - test( 'Products can be filtered based on created date.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); + // Wait for the products to be filtered. + await expect( pageObject.products ).not.toHaveCount( 9 ); - await expect( pageObject.products ).toHaveCount( 9 ); + await expect( + pageObject.products.filter( { hasText: '$45.00' } ) + ).not.toHaveCount( 0 ); + await expect( + pageObject.products.filter( { hasText: '$55.00' } ) + ).not.toHaveCount( 0 ); - await pageObject.addFilter( 'Created' ); - await pageObject.setCreatedFilter( { - operator: 'within', - range: 'last3months', - } ); - - // Products are created with the fixed publish date back in 2019 - // so there's no products published in last 3 months. - await expect( pageObject.products ).toHaveCount( 0 ); - - await pageObject.setCreatedFilter( { - operator: 'before', - range: 'last3months', - } ); - - await expect( pageObject.products ).toHaveCount( 9 ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 9 ); + // Reset the price range. + await pageObject.setPriceRange( { + min: '0', + max: '0', } ); - test( 'Products can be filtered based on price range.', async ( { - pageObject, - } ) => { - await pageObject.createNewPostAndInsertBlock(); + await expect( pageObject.products ).toHaveCount( 9 ); - await expect( pageObject.products ).toHaveCount( 9 ); - - await pageObject.addFilter( 'Price Range' ); - await pageObject.setPriceRange( { - min: '18.33', - } ); - - await expect( pageObject.products ).toHaveCount( 7 ); - - await pageObject.setPriceRange( { - min: '15.28', - max: '17.21', - } ); - - await expect( pageObject.products ).toHaveCount( 1 ); - - await pageObject.setPriceRange( { - max: '17.29', - } ); - - await expect( pageObject.products ).toHaveCount( 4 ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 4 ); + await editor.insertBlock( { + name: 'woocommerce/filter-wrapper', + attributes: { filterType: 'price-filter' }, } ); - // See https://github.com/woocommerce/woocommerce/pull/49917 - test( 'Price range is inclusive in both editor and frontend.', async ( { - page, - pageObject, - editor, - } ) => { + await pageObject.publishAndGoToFrontend(); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await page + .getByRole( 'textbox', { + name: 'Filter products by minimum', + } ) + .dblclick(); + await page.keyboard.type( '45' ); + + await page + .getByRole( 'textbox', { + name: 'Filter products by maximum', + } ) + .dblclick(); + await page.keyboard.type( '55' ); + + await page.keyboard.press( 'Tab' ); + + // Wait for the products to be filtered. + await expect( pageObject.products ).not.toHaveCount( 9 ); + + await expect( + pageObject.products.filter( { hasText: '$45.00' } ) + ).not.toHaveCount( 0 ); + await expect( + pageObject.products.filter( { hasText: '$55.00' } ) + ).not.toHaveCount( 0 ); + } ); + + test.describe( '"Use page context" control', () => { + test( 'should be visible on posts', async ( { pageObject } ) => { await pageObject.createNewPostAndInsertBlock(); - await expect( pageObject.products ).toHaveCount( 9 ); - - await pageObject.addFilter( 'Price Range' ); - await pageObject.setPriceRange( { - min: '45', - max: '55', - } ); - - // Wait for the products to be filtered. - await expect( pageObject.products ).not.toHaveCount( 9 ); - await expect( - pageObject.products.filter( { hasText: '$45.00' } ) - ).not.toHaveCount( 0 ); - await expect( - pageObject.products.filter( { hasText: '$55.00' } ) - ).not.toHaveCount( 0 ); - - // Reset the price range. - await pageObject.setPriceRange( { - min: '0', - max: '0', - } ); - - await expect( pageObject.products ).toHaveCount( 9 ); - - await editor.insertBlock( { - name: 'woocommerce/filter-wrapper', - attributes: { filterType: 'price-filter' }, - } ); - - await pageObject.publishAndGoToFrontend(); - - await expect( pageObject.products ).toHaveCount( 9 ); - - await page - .getByRole( 'textbox', { - name: 'Filter products by minimum', - } ) - .dblclick(); - await page.keyboard.type( '45' ); - - await page - .getByRole( 'textbox', { - name: 'Filter products by maximum', - } ) - .dblclick(); - await page.keyboard.type( '55' ); - - await page.keyboard.press( 'Tab' ); - - // Wait for the products to be filtered. - await expect( pageObject.products ).not.toHaveCount( 9 ); - - await expect( - pageObject.products.filter( { hasText: '$45.00' } ) - ).not.toHaveCount( 0 ); - await expect( - pageObject.products.filter( { hasText: '$55.00' } ) - ).not.toHaveCount( 0 ); + pageObject + .locateSidebarSettings() + .locator( SELECTORS.usePageContextControl ) + ).toBeVisible(); } ); - test.describe( '"Use page context" control', () => { - test( 'should be visible on posts', async ( { pageObject } ) => { - await pageObject.createNewPostAndInsertBlock(); + [ + 'woocommerce/woocommerce//archive-product', + 'woocommerce/woocommerce//taxonomy-product_cat', + 'woocommerce/woocommerce//taxonomy-product_tag', + 'woocommerce/woocommerce//taxonomy-product_attribute', + 'woocommerce/woocommerce//product-search-results', + ].forEach( ( slug ) => { + test( `should be visible in archive template: ${ slug }`, async ( { + pageObject, + editor, + } ) => { + await pageObject.goToEditorTemplate( slug ); + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInTemplate(); + await pageObject.focusProductCollection(); + await editor.openDocumentSettingsSidebar(); await expect( pageObject @@ -401,239 +416,210 @@ test.describe( 'Product Collection', () => { .locator( SELECTORS.usePageContextControl ) ).toBeVisible(); } ); + } ); - [ - 'woocommerce/woocommerce//archive-product', - 'woocommerce/woocommerce//taxonomy-product_cat', - 'woocommerce/woocommerce//taxonomy-product_tag', - 'woocommerce/woocommerce//taxonomy-product_attribute', - 'woocommerce/woocommerce//product-search-results', - ].forEach( ( slug ) => { - test( `should be visible in archive template: ${ slug }`, async ( { - pageObject, - editor, - } ) => { - await pageObject.goToEditorTemplate( slug ); - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInTemplate(); - await pageObject.focusProductCollection(); - await editor.openDocumentSettingsSidebar(); - - await expect( - pageObject - .locateSidebarSettings() - .locator( SELECTORS.usePageContextControl ) - ).toBeVisible(); - } ); - } ); - - [ - 'woocommerce/woocommerce//single-product', - 'twentytwentyfour//home', - 'twentytwentyfour//index', - ].forEach( ( slug ) => { - test( `should be visible in non-archive template: ${ slug }`, async ( { - pageObject, - editor, - } ) => { - await pageObject.goToEditorTemplate( slug ); - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInTemplate(); - await pageObject.focusProductCollection(); - await editor.openDocumentSettingsSidebar(); - - await expect( - pageObject - .locateSidebarSettings() - .locator( SELECTORS.usePageContextControl ) - ).toBeVisible(); - } ); - } ); - - test( 'should work as expected in Product Catalog template', async ( { + [ + 'woocommerce/woocommerce//single-product', + 'twentytwentyfour//home', + 'twentytwentyfour//index', + ].forEach( ( slug ) => { + test( `should be visible in non-archive template: ${ slug }`, async ( { pageObject, editor, } ) => { - await pageObject.goToEditorTemplate(); + await pageObject.goToEditorTemplate( slug ); + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInTemplate(); await pageObject.focusProductCollection(); await editor.openDocumentSettingsSidebar(); - const sidebarSettings = pageObject.locateSidebarSettings(); - - // Inherit query from template should be visible & enabled by default await expect( - sidebarSettings.locator( SELECTORS.usePageContextControl ) + pageObject + .locateSidebarSettings() + .locator( SELECTORS.usePageContextControl ) ).toBeVisible(); - await expect( - sidebarSettings.locator( - `${ SELECTORS.usePageContextControl } input` - ) - ).toBeChecked(); - - // "On sale control" should be hidden when inherit query from template is enabled - await expect( - sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) - ).toBeHidden(); - - // "On sale control" should be visible when inherit query from template is disabled - await pageObject.setInheritQueryFromTemplate( false ); - await expect( - sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) - ).toBeVisible(); - - // "On sale control" should retain its state when inherit query from template is enabled again - await pageObject.setShowOnlyProductsOnSale( { - onSale: true, - isLocatorsRefreshNeeded: false, - } ); - await expect( - sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) - ).toBeChecked(); - await pageObject.setInheritQueryFromTemplate( true ); - await expect( - sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) - ).toBeHidden(); - await pageObject.setInheritQueryFromTemplate( false ); - await expect( - sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) - ).toBeVisible(); - await expect( - sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) - ).toBeChecked(); } ); + } ); - test( 'is enabled by default unless already enabled elsewhere', async ( { - pageObject, - editor, - } ) => { - const productCollection = editor.canvas.getByLabel( - 'Block: Product Collection', - { exact: true } - ); - const usePageContextToggle = pageObject - .locateSidebarSettings() - .locator( SELECTORS.usePageContextControl ) - .locator( 'input' ); + test( 'should work as expected in Product Catalog template', async ( { + pageObject, + editor, + } ) => { + await pageObject.goToEditorTemplate(); + await pageObject.focusProductCollection(); + await editor.openDocumentSettingsSidebar(); - // First Product Catalog - // Option should be visible & ENABLED by default - await pageObject.goToEditorTemplate(); - await editor.selectBlocks( productCollection.first() ); - await editor.openDocumentSettingsSidebar(); + const sidebarSettings = pageObject.locateSidebarSettings(); - await expect( usePageContextToggle ).toBeChecked(); + // Inherit query from template should be visible & enabled by default + await expect( + sidebarSettings.locator( SELECTORS.usePageContextControl ) + ).toBeVisible(); + await expect( + sidebarSettings.locator( + `${ SELECTORS.usePageContextControl } input` + ) + ).toBeChecked(); - // Second Product Catalog - // Option should be visible & DISABLED by default - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInTemplate( 'productCatalog' ); - await editor.selectBlocks( productCollection.last() ); + // "On sale control" should be hidden when inherit query from template is enabled + await expect( + sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) + ).toBeHidden(); - await expect( usePageContextToggle ).not.toBeChecked(); + // "On sale control" should be visible when inherit query from template is disabled + await pageObject.setInheritQueryFromTemplate( false ); + await expect( + sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) + ).toBeVisible(); - // Disable the option in the first Product Catalog - await editor.selectBlocks( productCollection.first() ); - await usePageContextToggle.click(); - - // Third Product Catalog - // Option should be visible & ENABLED by default - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInTemplate( 'productCatalog' ); - await editor.selectBlocks( productCollection.last() ); - - await expect( usePageContextToggle ).toBeChecked(); + // "On sale control" should retain its state when inherit query from template is enabled again + await pageObject.setShowOnlyProductsOnSale( { + onSale: true, + isLocatorsRefreshNeeded: false, } ); + await expect( + sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) + ).toBeChecked(); + await pageObject.setInheritQueryFromTemplate( true ); + await expect( + sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) + ).toBeHidden(); + await pageObject.setInheritQueryFromTemplate( false ); + await expect( + sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) + ).toBeVisible(); + await expect( + sidebarSettings.getByLabel( SELECTORS.onSaleControlLabel ) + ).toBeChecked(); + } ); - test( 'allows filtering in non-archive context', async ( { - pageObject, - editor, - page, - } ) => { - await pageObject.createNewPostAndInsertBlock(); + test( 'is enabled by default unless already enabled elsewhere', async ( { + pageObject, + editor, + } ) => { + const productCollection = editor.canvas.getByLabel( + 'Block: Product Collection', + { exact: true } + ); + const usePageContextToggle = pageObject + .locateSidebarSettings() + .locator( SELECTORS.usePageContextControl ) + .locator( 'input' ); - await expect( pageObject.products ).toHaveCount( 9 ); + // First Product Catalog + // Option should be visible & ENABLED by default + await pageObject.goToEditorTemplate(); + await editor.selectBlocks( productCollection.first() ); + await editor.openDocumentSettingsSidebar(); - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInPost( 'productCatalog' ); + await expect( usePageContextToggle ).toBeChecked(); - await expect( pageObject.products ).toHaveCount( 18 ); + // Second Product Catalog + // Option should be visible & DISABLED by default + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInTemplate( 'productCatalog' ); + await editor.selectBlocks( productCollection.last() ); - await page.getByLabel( 'Toggle block inserter' ).click(); - await page.getByRole( 'tab', { name: 'Patterns' } ).click(); - await page - .getByPlaceholder( 'Search' ) - .fill( 'product filters' ); - await page.getByLabel( 'Product Filters' ).click(); + await expect( usePageContextToggle ).not.toBeChecked(); - const postId = await editor.publishPost(); - await page.goto( `/?p=${ postId }` ); + // Disable the option in the first Product Catalog + await editor.selectBlocks( productCollection.first() ); + await usePageContextToggle.click(); - const productCollection = page.locator( - '.wp-block-woocommerce-product-collection' - ); + // Third Product Catalog + // Option should be visible & ENABLED by default + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInTemplate( 'productCatalog' ); + await editor.selectBlocks( productCollection.last() ); - await expect( - productCollection.first().locator( SELECTORS.product ) - ).toHaveCount( 9 ); - await expect( - productCollection.last().locator( SELECTORS.product ) - ).toHaveCount( 9 ); + await expect( usePageContextToggle ).toBeChecked(); + } ); - await page - .getByRole( 'textbox', { - name: 'Filter products by maximum', - } ) - .dblclick(); - await page.keyboard.type( '10' ); - await page.keyboard.press( 'Tab' ); + test( 'allows filtering in non-archive context', async ( { + pageObject, + editor, + page, + } ) => { + await pageObject.createNewPostAndInsertBlock(); - await expect( - productCollection.first().locator( SELECTORS.product ) - ).toHaveCount( 1 ); - await expect( - productCollection.last().locator( SELECTORS.product ) - ).toHaveCount( 9 ); - } ); + await expect( pageObject.products ).toHaveCount( 9 ); - test( 'correctly combines editor and front-end filters', async ( { - pageObject, - editor, - page, - } ) => { - await pageObject.createNewPostAndInsertBlock(); + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInPost( 'productCatalog' ); - await expect( pageObject.products ).toHaveCount( 9 ); + await expect( pageObject.products ).toHaveCount( 18 ); - await pageObject.addFilter( 'Show product categories' ); - await pageObject.setFilterComboboxValue( 'Product categories', [ - 'Music', - ] ); + await page.getByLabel( 'Toggle block inserter' ).click(); + await page.getByRole( 'tab', { name: 'Patterns' } ).click(); + await page.getByPlaceholder( 'Search' ).fill( 'product filters' ); + await page.getByLabel( 'Product Filters' ).click(); - await page.getByLabel( 'Toggle block inserter' ).click(); - await page.getByRole( 'tab', { name: 'Patterns' } ).click(); - await page - .getByPlaceholder( 'Search' ) - .fill( 'product filters' ); - await page.getByLabel( 'Product Filters' ).click(); + const postId = await editor.publishPost(); + await page.goto( `/?p=${ postId }` ); - await expect( pageObject.products ).toHaveCount( 2 ); + const productCollection = page.locator( + '.wp-block-woocommerce-product-collection' + ); - const postId = await editor.publishPost(); - await page.goto( `/?p=${ postId }` ); - await pageObject.refreshLocators( 'frontend' ); + await expect( + productCollection.first().locator( SELECTORS.product ) + ).toHaveCount( 9 ); + await expect( + productCollection.last().locator( SELECTORS.product ) + ).toHaveCount( 9 ); - await expect( pageObject.products ).toHaveCount( 2 ); + await page + .getByRole( 'textbox', { + name: 'Filter products by maximum', + } ) + .dblclick(); + await page.keyboard.type( '10' ); + await page.keyboard.press( 'Tab' ); - await page - .getByRole( 'textbox', { - name: 'Filter products by maximum', - } ) - .dblclick(); - await page.keyboard.type( '5' ); - await page.keyboard.press( 'Tab' ); + await expect( + productCollection.first().locator( SELECTORS.product ) + ).toHaveCount( 1 ); + await expect( + productCollection.last().locator( SELECTORS.product ) + ).toHaveCount( 9 ); + } ); - await expect( pageObject.products ).toHaveCount( 1 ); - } ); + test( 'correctly combines editor and front-end filters', async ( { + pageObject, + editor, + page, + } ) => { + await pageObject.createNewPostAndInsertBlock(); + + await expect( pageObject.products ).toHaveCount( 9 ); + + await pageObject.addFilter( 'Show product categories' ); + await pageObject.setFilterComboboxValue( 'Product categories', [ + 'Music', + ] ); + + await page.getByLabel( 'Toggle block inserter' ).click(); + await page.getByRole( 'tab', { name: 'Patterns' } ).click(); + await page.getByPlaceholder( 'Search' ).fill( 'product filters' ); + await page.getByLabel( 'Product Filters' ).click(); + + await expect( pageObject.products ).toHaveCount( 2 ); + + const postId = await editor.publishPost(); + await page.goto( `/?p=${ postId }` ); + await pageObject.refreshLocators( 'frontend' ); + + await expect( pageObject.products ).toHaveCount( 2 ); + + await page + .getByRole( 'textbox', { + name: 'Filter products by maximum', + } ) + .dblclick(); + await page.keyboard.type( '5' ); + await page.keyboard.press( 'Tab' ); + + await expect( pageObject.products ).toHaveCount( 1 ); } ); } ); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts index fe46116ab0a..c1370946dfc 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.block_theme.spec.ts @@ -9,7 +9,6 @@ import { test as base, expect } from '@woocommerce/e2e-utils'; */ import ProductCollectionPage, { BLOCK_LABELS, - Collections, SELECTORS, } from './product-collection.page'; @@ -87,11 +86,7 @@ test.describe( 'Product Collection', () => { await admin.createNewPost(); } ); - test.skip( 'does not render', async ( { - page, - editor, - pageObject, - } ) => { + test( 'does not render', async ( { page, editor, pageObject } ) => { await pageObject.insertProductCollection(); await pageObject.chooseCollectionInPost( 'featured' ); await pageObject.addFilter( 'Price Range' ); @@ -106,7 +101,7 @@ test.describe( 'Product Collection', () => { ).toBeVisible(); // The "No results found" info is rendered in editor for all collections. await expect( - featuredBlock.getByText( 'No results found' ) + featuredBlock.getByText( 'No products to display' ) ).toBeVisible(); await pageObject.publishAndGoToFrontend(); @@ -114,7 +109,9 @@ test.describe( 'Product Collection', () => { const content = page.locator( 'main' ); await expect( content ).not.toContainText( 'Featured products' ); - await expect( content ).not.toContainText( 'No results found' ); + await expect( content ).not.toContainText( + 'No products to display' + ); } ); // This test ensures the runtime render state is correctly reset for @@ -739,7 +736,7 @@ test.describe( 'Product Collection', () => { } ); } ); - const templates = { + const templates = [ // This test is disabled because archives are disabled for attributes by default. This can be uncommented when this is toggled on. //'taxonomy-product_attribute': { // templateTitle: 'Product Attribute', @@ -747,102 +744,104 @@ test.describe( 'Product Collection', () => { // frontendPage: '/product-attribute/color/', // legacyBlockName: 'woocommerce/legacy-template', //}, - 'taxonomy-product_cat': { + { templateTitle: 'Product Category', slug: 'taxonomy-product_cat', frontendPage: '/product-category/music/', legacyBlockName: 'woocommerce/legacy-template', expectedProductsCount: 2, }, - 'taxonomy-product_tag': { + { templateTitle: 'Product Tag', slug: 'taxonomy-product_tag', frontendPage: '/product-tag/recommended/', legacyBlockName: 'woocommerce/legacy-template', expectedProductsCount: 2, }, - 'archive-product': { + { templateTitle: 'Product Catalog', slug: 'archive-product', frontendPage: '/shop/', legacyBlockName: 'woocommerce/legacy-template', expectedProductsCount: 16, }, - 'product-search-results': { + { templateTitle: 'Product Search Results', slug: 'product-search-results', frontendPage: '/?s=shirt&post_type=product', legacyBlockName: 'woocommerce/legacy-template', expectedProductsCount: 3, }, - }; + ]; - for ( const { - templateTitle, - slug, - frontendPage, - legacyBlockName, - expectedProductsCount, - } of Object.values( templates ) ) { - test.describe( `${ templateTitle } template`, () => { - test( 'Product Collection block matches with classic template block', async ( { - pageObject, - requestUtils, - admin, - editor, - page, - } ) => { - await pageObject.refreshLocators( 'frontend' ); + templates.forEach( + ( { + templateTitle, + slug, + frontendPage, + legacyBlockName, + expectedProductsCount, + } ) => { + test.describe( `${ templateTitle } template`, () => { + test( 'Product Collection block matches with classic template block', async ( { + pageObject, + requestUtils, + admin, + editor, + page, + } ) => { + await pageObject.refreshLocators( 'frontend' ); - await page.goto( frontendPage ); + await page.goto( frontendPage ); - const productCollectionProductNames = - await pageObject.getProductNames(); + const productCollectionProductNames = + await pageObject.getProductNames(); - const template = await requestUtils.createTemplate( - 'wp_template', - { - slug, - title: 'classic template test', - content: 'howdy', - } - ); + const template = await requestUtils.createTemplate( + 'wp_template', + { + slug, + title: 'classic template test', + content: 'howdy', + } + ); - await admin.visitSiteEditor( { - postId: template.id, - postType: 'wp_template', - canvas: 'edit', + await admin.visitSiteEditor( { + postId: template.id, + postType: 'wp_template', + canvas: 'edit', + } ); + + await expect( + editor.canvas.getByText( 'howdy' ) + ).toBeVisible(); + + await editor.insertBlock( { name: legacyBlockName } ); + + await editor.saveSiteEditorEntities( { + isOnlyCurrentEntityDirty: true, + } ); + + await page.goto( frontendPage ); + + const classicProducts = page.locator( + '.woocommerce-loop-product__title' + ); + + await expect( classicProducts ).toHaveCount( + expectedProductsCount + ); + + const classicProductsNames = + await classicProducts.allTextContents(); + + expect( classicProductsNames ).toEqual( + productCollectionProductNames + ); } ); - - await expect( - editor.canvas.getByText( 'howdy' ) - ).toBeVisible(); - - await editor.insertBlock( { name: legacyBlockName } ); - - await editor.saveSiteEditorEntities( { - isOnlyCurrentEntityDirty: true, - } ); - - await page.goto( frontendPage ); - - const classicProducts = page.locator( - '.woocommerce-loop-product__title' - ); - - await expect( classicProducts ).toHaveCount( - expectedProductsCount - ); - - const classicProductsNames = - await classicProducts.allTextContents(); - - expect( classicProductsNames ).toEqual( - productCollectionProductNames - ); } ); - } ); - } + } + ); test.describe( 'Editor: In taxonomies templates', () => { test( 'Products by specific category template displays products from this category', async ( { admin, @@ -906,309 +905,3 @@ test.describe( 'Product Collection', () => { } ); } ); } ); - -test.describe( 'Testing "usesReference" argument in "registerProductCollection"', () => { - const MY_REGISTERED_COLLECTIONS = { - myCustomCollectionWithProductContext: { - name: 'My Custom Collection - Product Context', - label: 'Block: My Custom Collection - Product Context', - previewLabelTemplate: [ 'woocommerce/woocommerce//single-product' ], - shouldShowProductPicker: true, - }, - myCustomCollectionWithCartContext: { - name: 'My Custom Collection - Cart Context', - label: 'Block: My Custom Collection - Cart Context', - previewLabelTemplate: [ 'woocommerce/woocommerce//page-cart' ], - shouldShowProductPicker: false, - }, - myCustomCollectionWithOrderContext: { - name: 'My Custom Collection - Order Context', - label: 'Block: My Custom Collection - Order Context', - previewLabelTemplate: [ - 'woocommerce/woocommerce//order-confirmation', - ], - shouldShowProductPicker: false, - }, - myCustomCollectionWithArchiveContext: { - name: 'My Custom Collection - Archive Context', - label: 'Block: My Custom Collection - Archive Context', - previewLabelTemplate: [ - 'woocommerce/woocommerce//taxonomy-product_cat', - ], - shouldShowProductPicker: false, - }, - myCustomCollectionMultipleContexts: { - name: 'My Custom Collection - Multiple Contexts', - label: 'Block: My Custom Collection - Multiple Contexts', - previewLabelTemplate: [ - 'woocommerce/woocommerce//single-product', - 'woocommerce/woocommerce//order-confirmation', - ], - shouldShowProductPicker: true, - }, - }; - - // Activate plugin which registers custom product collections - test.beforeEach( async ( { requestUtils } ) => { - await requestUtils.activatePlugin( - 'register-product-collection-tester' - ); - } ); - - Object.entries( MY_REGISTERED_COLLECTIONS ).forEach( - ( [ key, collection ] ) => { - for ( const template of collection.previewLabelTemplate ) { - test( `Collection "${ collection.name }" should show preview label in "${ template }"`, async ( { - pageObject, - editor, - } ) => { - await pageObject.goToEditorTemplate( template ); - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInTemplate( - key as Collections - ); - - const block = editor.canvas.getByLabel( collection.label ); - const previewButtonLocator = block.getByTestId( - SELECTORS.previewButtonTestID - ); - - await expect( previewButtonLocator ).toBeVisible(); - } ); - } - - test( `Collection "${ collection.name }" should not show preview label in a post`, async ( { - pageObject, - editor, - admin, - } ) => { - await admin.createNewPost(); - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInPost( key as Collections ); - - // Check visibility of product picker - const editorProductPicker = editor.canvas.locator( - SELECTORS.productPicker - ); - const expectedVisibility = collection.shouldShowProductPicker - ? 'toBeVisible' - : 'toBeHidden'; - await expect( editorProductPicker )[ expectedVisibility ](); - - if ( collection.shouldShowProductPicker ) { - await pageObject.chooseProductInEditorProductPickerIfAvailable( - editor.canvas - ); - } - - // At this point, the product picker should be hidden - await expect( editorProductPicker ).toBeHidden(); - - // Check visibility of preview label - const block = editor.canvas.getByLabel( collection.label ); - const previewButtonLocator = block.getByTestId( - SELECTORS.previewButtonTestID - ); - - await expect( previewButtonLocator ).toBeHidden(); - } ); - - test( `Collection "${ collection.name }" should not show preview label in Product Catalog template`, async ( { - pageObject, - editor, - } ) => { - await pageObject.goToProductCatalogAndInsertCollection( - key as Collections - ); - - const block = editor.canvas.getByLabel( collection.label ); - const previewButtonLocator = block.getByTestId( - SELECTORS.previewButtonTestID - ); - - await expect( previewButtonLocator ).toBeHidden(); - } ); - } - ); -} ); - -test.describe( 'Product picker', () => { - const MY_REGISTERED_COLLECTIONS_THAT_NEEDS_PRODUCT = { - myCustomCollectionWithProductContext: { - name: 'My Custom Collection - Product Context', - label: 'Block: My Custom Collection - Product Context', - collection: - 'woocommerce/product-collection/my-custom-collection-product-context', - }, - myCustomCollectionMultipleContexts: { - name: 'My Custom Collection - Multiple Contexts', - label: 'Block: My Custom Collection - Multiple Contexts', - collection: - 'woocommerce/product-collection/my-custom-collection-multiple-contexts', - }, - }; - - // Activate plugin which registers custom product collections - test.beforeEach( async ( { requestUtils } ) => { - await requestUtils.activatePlugin( - 'register-product-collection-tester' - ); - } ); - - Object.entries( MY_REGISTERED_COLLECTIONS_THAT_NEEDS_PRODUCT ).forEach( - ( [ key, collection ] ) => { - test( `For collection "${ collection.name }" - manually selected product reference should be available on Frontend in a post`, async ( { - pageObject, - admin, - page, - editor, - } ) => { - await admin.createNewPost(); - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInPost( key as Collections ); - - // Verify that product picker is shown in Editor - const editorProductPicker = editor.canvas.locator( - SELECTORS.productPicker - ); - await expect( editorProductPicker ).toBeVisible(); - - // Once a product is selected, the product picker should be hidden - await pageObject.chooseProductInEditorProductPickerIfAvailable( - editor.canvas - ); - await expect( editorProductPicker ).toBeHidden(); - - // On Frontend, verify that product reference is a number - await pageObject.publishAndGoToFrontend(); - const collectionWithProductContext = page.locator( - `[data-collection="${ collection.collection }"]` - ); - const queryAttribute = JSON.parse( - ( await collectionWithProductContext.getAttribute( - 'data-query' - ) ) || '{}' - ); - expect( typeof queryAttribute?.productReference ).toBe( - 'number' - ); - } ); - - test( `For collection "${ collection.name }" - changing product using inspector control`, async ( { - pageObject, - admin, - page, - editor, - } ) => { - await admin.createNewPost(); - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInPost( key as Collections ); - - // Verify that product picker is shown in Editor - const editorProductPicker = editor.canvas.locator( - SELECTORS.productPicker - ); - await expect( editorProductPicker ).toBeVisible(); - - // Once a product is selected, the product picker should be hidden - await pageObject.chooseProductInEditorProductPickerIfAvailable( - editor.canvas - ); - await expect( editorProductPicker ).toBeHidden(); - - // Verify that Album is selected - await expect( - admin.page.locator( SELECTORS.linkedProductControl.button ) - ).toContainText( 'Album' ); - - // Change product using inspector control to Beanie - await admin.page - .locator( SELECTORS.linkedProductControl.button ) - .click(); - await admin.page - .locator( SELECTORS.linkedProductControl.popoverContent ) - .getByLabel( 'Beanie', { exact: true } ) - .click(); - await expect( - admin.page.locator( SELECTORS.linkedProductControl.button ) - ).toContainText( 'Beanie' ); - - // On Frontend, verify that product reference is a number - await pageObject.publishAndGoToFrontend(); - const collectionWithProductContext = page.locator( - `[data-collection="${ collection.collection }"]` - ); - const queryAttribute = JSON.parse( - ( await collectionWithProductContext.getAttribute( - 'data-query' - ) ) || '{}' - ); - expect( typeof queryAttribute?.productReference ).toBe( - 'number' - ); - } ); - - test( `For collection "${ collection.name }" - "From current product" is chosen by default`, async ( { - pageObject, - admin, - editor, - } ) => { - await admin.visitSiteEditor( { - postId: `woocommerce/woocommerce//single-product`, - postType: 'wp_template', - canvas: 'edit', - } ); - await editor.canvas.locator( 'body' ).click(); - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInTemplate( - key as Collections - ); - - const productToShowControl = admin.page.getByText( - 'From the current product' - ); - await expect( productToShowControl ).toBeChecked(); - } ); - } - ); - - test( 'Product picker should work as expected while changing collection using "Choose collection" button from Toolbar', async ( { - pageObject, - admin, - editor, - } ) => { - await admin.createNewPost(); - await pageObject.insertProductCollection(); - await pageObject.chooseCollectionInPost( - 'myCustomCollectionWithProductContext' - ); - - // Verify that product picker is shown in Editor - const editorProductPicker = editor.canvas.locator( - SELECTORS.productPicker - ); - await expect( editorProductPicker ).toBeVisible(); - - // Once a product is selected, the product picker should be hidden - await pageObject.chooseProductInEditorProductPickerIfAvailable( - editor.canvas - ); - await expect( editorProductPicker ).toBeHidden(); - - // Change collection using Toolbar - await pageObject.changeCollectionUsingToolbar( - 'myCustomCollectionMultipleContexts' - ); - await expect( editorProductPicker ).toBeVisible(); - - // Once a product is selected, the product picker should be hidden - await pageObject.chooseProductInEditorProductPickerIfAvailable( - editor.canvas - ); - await expect( editorProductPicker ).toBeHidden(); - - // Product picker should be hidden for collections that don't need product - await pageObject.changeCollectionUsingToolbar( 'featured' ); - await expect( editorProductPicker ).toBeHidden(); - } ); -} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-picker.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-picker.block_theme.spec.ts new file mode 100644 index 00000000000..ef4436c2deb --- /dev/null +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-picker.block_theme.spec.ts @@ -0,0 +1,204 @@ +/** + * External dependencies + */ +import { test as base, expect } from '@woocommerce/e2e-utils'; + +/** + * Internal dependencies + */ +import ProductCollectionPage, { + Collections, + SELECTORS, +} from './product-collection.page'; + +const test = base.extend< { pageObject: ProductCollectionPage } >( { + pageObject: async ( { page, admin, editor }, use ) => { + const pageObject = new ProductCollectionPage( { + page, + admin, + editor, + } ); + await use( pageObject ); + }, +} ); + +test.describe( 'Product Collection: Product Picker', () => { + const CUSTOM_COLLECTIONS = [ + { + id: 'myCustomCollectionWithProductContext', + name: 'My Custom Collection - Product Context', + label: 'Block: My Custom Collection - Product Context', + collection: + 'woocommerce/product-collection/my-custom-collection-product-context', + }, + { + id: 'myCustomCollectionMultipleContexts', + name: 'My Custom Collection - Multiple Contexts', + label: 'Block: My Custom Collection - Multiple Contexts', + collection: + 'woocommerce/product-collection/my-custom-collection-multiple-contexts', + }, + ]; + + // Activate plugin which registers custom product collections + test.beforeEach( async ( { requestUtils } ) => { + await requestUtils.activatePlugin( + 'register-product-collection-tester' + ); + } ); + + CUSTOM_COLLECTIONS.forEach( ( collection ) => { + test( `For collection "${ collection.name }" - manually selected product reference should be available on Frontend in a post`, async ( { + pageObject, + admin, + page, + editor, + } ) => { + await admin.createNewPost(); + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInPost( + collection.id as Collections + ); + + // Verify that product picker is shown in Editor + const editorProductPicker = editor.canvas.locator( + SELECTORS.productPicker + ); + await expect( editorProductPicker ).toBeVisible(); + + // Once a product is selected, the product picker should be hidden + await pageObject.chooseProductInEditorProductPickerIfAvailable( + editor.canvas + ); + await expect( editorProductPicker ).toBeHidden(); + + // On Frontend, verify that product reference is a number + await pageObject.publishAndGoToFrontend(); + const collectionWithProductContext = page.locator( + `[data-collection="${ collection.collection }"]` + ); + const queryAttribute = JSON.parse( + ( await collectionWithProductContext.getAttribute( + 'data-query' + ) ) || '{}' + ); + expect( typeof queryAttribute?.productReference ).toBe( 'number' ); + } ); + + test( `For collection "${ collection.name }" - changing product using inspector control`, async ( { + pageObject, + admin, + page, + editor, + } ) => { + await admin.createNewPost(); + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInPost( + collection.id as Collections + ); + + // Verify that product picker is shown in Editor + const editorProductPicker = editor.canvas.locator( + SELECTORS.productPicker + ); + await expect( editorProductPicker ).toBeVisible(); + + // Once a product is selected, the product picker should be hidden + await pageObject.chooseProductInEditorProductPickerIfAvailable( + editor.canvas + ); + await expect( editorProductPicker ).toBeHidden(); + + // Verify that Album is selected + await expect( + admin.page.locator( SELECTORS.linkedProductControl.button ) + ).toContainText( 'Album' ); + + // Change product using inspector control to Beanie + await admin.page + .locator( SELECTORS.linkedProductControl.button ) + .click(); + await admin.page + .locator( SELECTORS.linkedProductControl.popoverContent ) + .getByLabel( 'Beanie', { exact: true } ) + .click(); + await expect( + admin.page.locator( SELECTORS.linkedProductControl.button ) + ).toContainText( 'Beanie' ); + + // On Frontend, verify that product reference is a number + await pageObject.publishAndGoToFrontend(); + const collectionWithProductContext = page.locator( + `[data-collection="${ collection.collection }"]` + ); + const queryAttribute = JSON.parse( + ( await collectionWithProductContext.getAttribute( + 'data-query' + ) ) || '{}' + ); + expect( typeof queryAttribute?.productReference ).toBe( 'number' ); + } ); + + test( `For collection "${ collection.name }" - "From current product" is chosen by default`, async ( { + pageObject, + admin, + editor, + } ) => { + await admin.visitSiteEditor( { + postId: `woocommerce/woocommerce//single-product`, + postType: 'wp_template', + canvas: 'edit', + } ); + await editor.canvas.locator( 'body' ).click(); + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInTemplate( + collection.id as Collections + ); + + const productToShowControl = admin.page.getByText( + 'From the current product' + ); + await expect( productToShowControl ).toBeChecked(); + } ); + } ); + + test( 'Product picker should work as expected while changing collection using "Choose collection" button from Toolbar', async ( { + pageObject, + admin, + editor, + } ) => { + await admin.createNewPost(); + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInPost( + 'myCustomCollectionWithProductContext' + ); + + // Verify that product picker is shown in Editor + const editorProductPicker = editor.canvas.locator( + SELECTORS.productPicker + ); + await expect( editorProductPicker ).toBeVisible(); + + // Once a product is selected, the product picker should be hidden + await pageObject.chooseProductInEditorProductPickerIfAvailable( + editor.canvas + ); + await expect( editorProductPicker ).toBeHidden(); + + // Change collection using Toolbar + await pageObject.changeCollectionUsingToolbar( + 'myCustomCollectionMultipleContexts' + ); + await expect( editorProductPicker ).toBeVisible(); + + // Once a product is selected, the product picker should be hidden + await pageObject.chooseProductInEditorProductPickerIfAvailable( + editor.canvas + ); + await expect( editorProductPicker ).toBeHidden(); + + // Product picker should be hidden for collections that don't need product + await pageObject.changeCollectionUsingToolbar( 'featured' ); + await expect( editorProductPicker ).toBeHidden(); + } ); +} ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/register-product-collection-tester.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/register-product-collection.block_theme.spec.ts similarity index 76% rename from plugins/woocommerce-blocks/tests/e2e/tests/product-collection/register-product-collection-tester.block_theme.spec.ts rename to plugins/woocommerce-blocks/tests/e2e/tests/product-collection/register-product-collection.block_theme.spec.ts index 0337a38df1a..50b47c288af 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/register-product-collection-tester.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/register-product-collection.block_theme.spec.ts @@ -33,7 +33,7 @@ const test = base.extend< { pageObject: ProductCollectionPage } >( { * These E2E tests are for `registerProductCollection` which we are exposing * for 3PDs to register new product collections. */ -test.describe( 'Product Collection registration', () => { +test.describe( 'Product Collection: Register Product Collection', () => { const MY_REGISTERED_COLLECTIONS = { myCustomCollection: { name: 'My Custom Collection', @@ -323,7 +323,7 @@ test.describe( 'Product Collection registration', () => { ], }, ].forEach( ( collection ) => { - for ( const template of collection.previewLabelTemplate ) { + collection.previewLabelTemplate.forEach( ( template ) => { test( `Collection "${ collection.name }" should show preview label in "${ template }"`, async ( { pageObject, editor, @@ -341,7 +341,7 @@ test.describe( 'Product Collection registration', () => { await expect( previewButtonLocator ).toBeVisible(); } ); - } + } ); test( `Collection "${ collection.name }" should not show preview label in a post`, async ( { pageObject, @@ -429,7 +429,7 @@ test.describe( 'Product Collection registration', () => { } ); // Product picker should be shown in Editor - await admin.page.reload(); + await page.reload(); const deletedProductPicker = editor.canvas.getByText( 'Previously selected product' ); @@ -465,4 +465,127 @@ test.describe( 'Product Collection registration', () => { await page.reload(); await expect( deletedProductPicker ).toBeVisible(); } ); + + test.describe( 'with "usesReference" argument', () => { + [ + { + id: 'myCustomCollectionWithProductContext', + name: 'My Custom Collection - Product Context', + label: 'Block: My Custom Collection - Product Context', + previewLabelTemplate: [ + 'woocommerce/woocommerce//single-product', + ], + shouldShowProductPicker: true, + }, + { + id: 'myCustomCollectionWithCartContext', + name: 'My Custom Collection - Cart Context', + label: 'Block: My Custom Collection - Cart Context', + previewLabelTemplate: [ 'woocommerce/woocommerce//page-cart' ], + shouldShowProductPicker: false, + }, + { + id: 'myCustomCollectionWithOrderContext', + name: 'My Custom Collection - Order Context', + label: 'Block: My Custom Collection - Order Context', + previewLabelTemplate: [ + 'woocommerce/woocommerce//order-confirmation', + ], + shouldShowProductPicker: false, + }, + { + id: 'myCustomCollectionWithArchiveContext', + name: 'My Custom Collection - Archive Context', + label: 'Block: My Custom Collection - Archive Context', + previewLabelTemplate: [ + 'woocommerce/woocommerce//taxonomy-product_cat', + ], + shouldShowProductPicker: false, + }, + { + id: 'myCustomCollectionMultipleContexts', + name: 'My Custom Collection - Multiple Contexts', + label: 'Block: My Custom Collection - Multiple Contexts', + previewLabelTemplate: [ + 'woocommerce/woocommerce//single-product', + 'woocommerce/woocommerce//order-confirmation', + ], + shouldShowProductPicker: true, + }, + ].forEach( ( collection ) => { + collection.previewLabelTemplate.forEach( ( template ) => { + test( `Collection "${ collection.name }" should show preview label in "${ template }"`, async ( { + pageObject, + editor, + } ) => { + await pageObject.goToEditorTemplate( template ); + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInTemplate( + collection.id as Collections + ); + + const block = editor.canvas.getByLabel( collection.label ); + const previewButtonLocator = block.getByTestId( + SELECTORS.previewButtonTestID + ); + + await expect( previewButtonLocator ).toBeVisible(); + } ); + } ); + + test( `Collection "${ collection.name }" should not show preview label in a post`, async ( { + pageObject, + editor, + admin, + } ) => { + await admin.createNewPost(); + await pageObject.insertProductCollection(); + await pageObject.chooseCollectionInPost( + collection.id as Collections + ); + + // Check visibility of product picker + const editorProductPicker = editor.canvas.locator( + SELECTORS.productPicker + ); + const expectedVisibility = collection.shouldShowProductPicker + ? 'toBeVisible' + : 'toBeHidden'; + await expect( editorProductPicker )[ expectedVisibility ](); + + if ( collection.shouldShowProductPicker ) { + await pageObject.chooseProductInEditorProductPickerIfAvailable( + editor.canvas + ); + } + + // At this point, the product picker should be hidden + await expect( editorProductPicker ).toBeHidden(); + + // Check visibility of preview label + const block = editor.canvas.getByLabel( collection.label ); + const previewButtonLocator = block.getByTestId( + SELECTORS.previewButtonTestID + ); + + await expect( previewButtonLocator ).toBeHidden(); + } ); + + test( `Collection "${ collection.name }" should not show preview label in Product Catalog template`, async ( { + pageObject, + editor, + } ) => { + await pageObject.goToProductCatalogAndInsertCollection( + collection.id as Collections + ); + + const block = editor.canvas.getByLabel( collection.label ); + const previewButtonLocator = block.getByTestId( + SELECTORS.previewButtonTestID + ); + + await expect( previewButtonLocator ).toBeHidden(); + } ); + } ); + } ); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/utils/index.ts b/plugins/woocommerce-blocks/tests/e2e/utils/index.ts index f315f401f53..786424cb31a 100644 --- a/plugins/woocommerce-blocks/tests/e2e/utils/index.ts +++ b/plugins/woocommerce-blocks/tests/e2e/utils/index.ts @@ -10,7 +10,6 @@ export * from './mini-cart'; export * from './performance'; export * from './request-utils'; export * from './shipping'; -export * from './storeApi'; export * from './test'; diff --git a/plugins/woocommerce-blocks/tests/e2e/utils/request-utils/index.ts b/plugins/woocommerce-blocks/tests/e2e/utils/request-utils/index.ts index 2c437a6cbe4..e6129f1ab1f 100644 --- a/plugins/woocommerce-blocks/tests/e2e/utils/request-utils/index.ts +++ b/plugins/woocommerce-blocks/tests/e2e/utils/request-utils/index.ts @@ -15,33 +15,6 @@ import { } from './templates'; export class RequestUtils extends CoreRequestUtils { - // The `setup` override is necessary only until - // https://github.com/WordPress/gutenberg/pull/59362 is merged. - static async setup( ...args: Parameters< typeof CoreRequestUtils.setup > ) { - const { request, user, storageState, storageStatePath, baseURL } = - await CoreRequestUtils.setup( ...args ); - - // We need those checks to satisfy TypeScript. - if ( ! storageState ) { - throw new Error( 'Storage state is required' ); - } - - if ( ! storageStatePath ) { - throw new Error( 'Storage state path is required' ); - } - - if ( ! baseURL ) { - throw new Error( 'Base URL is required' ); - } - - return new this( request, { - user, - storageState, - storageStatePath, - baseURL, - } ); - } - /** @borrows getTemplates as this.getTemplates */ getTemplates: typeof getTemplates = getTemplates.bind( this ); /** @borrows revertTemplate as this.revertTemplate */ diff --git a/plugins/woocommerce-blocks/tests/e2e/utils/storeApi/index.ts b/plugins/woocommerce-blocks/tests/e2e/utils/storeApi/index.ts deleted file mode 100644 index dccbd4a36e5..00000000000 --- a/plugins/woocommerce-blocks/tests/e2e/utils/storeApi/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './store-api-utils.page'; diff --git a/plugins/woocommerce-blocks/tests/e2e/utils/storeApi/store-api-utils.page.ts b/plugins/woocommerce-blocks/tests/e2e/utils/storeApi/store-api-utils.page.ts deleted file mode 100644 index 443feaae503..00000000000 --- a/plugins/woocommerce-blocks/tests/e2e/utils/storeApi/store-api-utils.page.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * External dependencies - */ -import { RequestUtils } from '@wordpress/e2e-test-utils-playwright'; - -export class StoreApiUtils { - private requestUtils: RequestUtils; - - constructor( requestUtils: RequestUtils ) { - this.requestUtils = requestUtils; - } - - // @todo: It is necessary work to a middleware to avoid this kind of code. - async cleanCart() { - const response = await this.requestUtils.request.get( - '/wp-json/wc/store/cart' - ); - - const { nonce } = response.headers(); - - await this.requestUtils.request.delete( - `/wp-json/wc/store/v1/cart/items`, - { - headers: { - nonce, - }, - } - ); - } -} diff --git a/plugins/woocommerce-blocks/tests/e2e/utils/test.ts b/plugins/woocommerce-blocks/tests/e2e/utils/test.ts index 74493652a49..893e99967d4 100644 --- a/plugins/woocommerce-blocks/tests/e2e/utils/test.ts +++ b/plugins/woocommerce-blocks/tests/e2e/utils/test.ts @@ -16,7 +16,6 @@ import { PerformanceUtils, RequestUtils, ShippingUtils, - StoreApiUtils, } from '@woocommerce/e2e-utils'; /** @@ -108,7 +107,6 @@ const test = base.extend< editor: Editor; pageUtils: PageUtils; frontendUtils: FrontendUtils; - storeApiUtils: StoreApiUtils; performanceUtils: PerformanceUtils; snapshotConfig: void; shippingUtils: ShippingUtils; @@ -135,6 +133,10 @@ const test = base.extend< window.localStorage.clear(); } ); + // Dispose the current APIRequestContext to free up resources. + await page.request.dispose(); + + // Reset the database to the initial state via snapshot import. await wpCLI( `db import ${ DB_EXPORT_FILE }` ); }, pageUtils: async ( { page }, use ) => { @@ -146,9 +148,6 @@ const test = base.extend< performanceUtils: async ( { page }, use ) => { await use( new PerformanceUtils( page ) ); }, - storeApiUtils: async ( { requestUtils }, use ) => { - await use( new StoreApiUtils( requestUtils ) ); - }, shippingUtils: async ( { page, admin }, use ) => { await use( new ShippingUtils( page, admin ) ); }, diff --git a/plugins/woocommerce/changelog/fix-e2e-db-connection-issue b/plugins/woocommerce/changelog/fix-e2e-db-connection-issue new file mode 100644 index 00000000000..2c3baddd9d6 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-e2e-db-connection-issue @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Fix an issue where database is randomly disconnected while running Blocks E2E tests. From bc8067a1ce5c9113a00c696aabf6c422113779e6 Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Mon, 30 Sep 2024 09:57:52 +0200 Subject: [PATCH 06/26] [dev] CI: new version of perfromance metrics job (take 2) (#51710) In this PR, we are re-iterating the job speedup and leveraging single wp-instance, incremental builds, and baseline reports caching. The original benchmarking tool is still available but bypassed as we evaluate results for this iteration approach. --- .github/workflows/ci.yml | 10 +- .github/workflows/scripts/run-metrics.sh | 96 ++++++++++++++----- .../update-49550-rework-perfromance-job | 4 + plugins/woocommerce/package.json | 3 + 4 files changed, 89 insertions(+), 24 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-49550-rework-perfromance-job diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4262ae81b1f..a42b21e2870 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,7 +132,7 @@ jobs: install: '${{ matrix.projectName }}...' build: ${{ ( github.ref_type == 'tag' && 'false' ) || matrix.projectName }} build-type: ${{ ( matrix.testType == 'unit:php' && 'backend' ) || 'full' }} - pull-playwright-cache: ${{ matrix.testEnv.shouldCreate && matrix.testType == 'e2e' }} + pull-playwright-cache: ${{ matrix.testEnv.shouldCreate && ( matrix.testType == 'e2e' || matrix.testType == 'performance' ) }} pull-package-deps: '${{ matrix.projectName }}' - name: 'Update wp-env config' @@ -183,6 +183,14 @@ jobs: with: pattern: 'last-run__${{ strategy.job-index }}' + - name: 'Download Performance metrics baseline' + if: ${{ matrix.report.resultsPath != '' && matrix.testType == 'performance'}} + uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 + with: + path: 'tools/compare-perf/artifacts/*_55f855a2e6d769b5ae44305b2772eb30d3e721df_*' + key: '${{ runner.os }}-woocommerce-performance-metrics-55f855a2e6d769b5ae44305b2772eb30d3e721df' + restore-keys: '${{ runner.os }}-woocommerce-performance-metrics-' + - name: 'Run tests (${{ matrix.testType }})' env: E2E_ENV_KEY: ${{ secrets.E2E_ENV_KEY }} diff --git a/.github/workflows/scripts/run-metrics.sh b/.github/workflows/scripts/run-metrics.sh index dfb6565b3e8..bc8338bf38b 100755 --- a/.github/workflows/scripts/run-metrics.sh +++ b/.github/workflows/scripts/run-metrics.sh @@ -2,31 +2,80 @@ set -eo pipefail -function title() { - echo -e "\n\033[1m$1\033[0m" -} +# The commented variables are for troubleshooting locally. +# GITHUB_EVENT_NAME='pull_request' +# GITHUB_SHA=$(git rev-parse HEAD) +# ARTIFACTS_PATH="$(realpath $(dirname -- ${BASH_SOURCE[0]})/../../../tools/compare-perf)/artifacts" if [[ -z "$GITHUB_EVENT_NAME" ]]; then echo "::error::GITHUB_EVENT_NAME must be set" exit 1 fi -title "Installing NVM" -curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash > /dev/null -export NVM_DIR="$HOME/.nvm" -[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" -echo "Installed version: $(nvm -v)" +function title() { + echo -e "\n\033[1m$1\033[0m" +} -title "Installing dependencies" -pnpm install --frozen-lockfile --filter="compare-perf" > /dev/null +if [ "$GITHUB_EVENT_NAME" == "push" ] || [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then + mkdir -p $ARTIFACTS_PATH && export WP_ARTIFACTS_PATH=$ARTIFACTS_PATH -if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then - title "Comparing performance with trunk" - pnpm --filter="compare-perf" run compare perf $GITHUB_SHA trunk --tests-branch $GITHUB_SHA - -elif [[ "$GITHUB_EVENT_NAME" == "push" ]]; then - title "Comparing performance with base branch" + # It should be 3d7d7f02017383937f1a4158d433d0e5d44b3dc9, but we pick 55f855a2e6d769b5ae44305b2772eb30d3e721df + # where compare-perf reporting mode was introduced for processing the provided reports. + BASE_SHA=55f855a2e6d769b5ae44305b2772eb30d3e721df + HEAD_BRANCH=$(git rev-parse --abbrev-ref HEAD) WP_VERSION=$(awk -F ': ' '/^Tested up to/{print $2}' readme.txt) + title "Comparing performance between: $BASE_SHA@trunk (base) and $GITHUB_SHA@$HEAD_BRANCH (head) on WordPress v$WP_VERSION" + + title "##[group]Setting up necessary tooling" + pnpm --filter="@woocommerce/plugin-woocommerce" test:e2e:install > /dev/null & + pnpm install --filter='compare-perf...' --frozen-lockfile --config.dedupe-peer-dependents=false --ignore-scripts + echo '##[endgroup]' + + if test -n "$(find $ARTIFACTS_PATH -maxdepth 1 -name "*_${GITHUB_SHA}_*" -print -quit)"; then + title "Skipping benchmarking head as benchmarking results already available under $ARTIFACTS_PATH" + else + title "##[group]Building head" + git -c core.hooksPath=/dev/null checkout --quiet $HEAD_BRANCH > /dev/null && echo 'On' $(git rev-parse HEAD) + pnpm run --if-present clean:build + pnpm install --filter='@woocommerce/plugin-woocommerce...' --frozen-lockfile --config.dedupe-peer-dependents=false + pnpm --filter='@woocommerce/plugin-woocommerce' build + echo '##[endgroup]' + + title "##[group]Benchmarking head" + RESULTS_ID="editor_${GITHUB_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor + RESULTS_ID="product-editor_${GITHUB_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor + echo '##[endgroup]' + fi + + if test -n "$(find $ARTIFACTS_PATH -maxdepth 1 -name "*_${BASE_SHA}_*" -print -quit)"; then + title "Skipping benchmarking baseline as benchmarking results already available under $ARTIFACTS_PATH" + else + title "##[group]Checkout baseline" + git fetch --no-tags --quiet origin trunk + echo '##[endgroup]' + + title "##[group]Building baseline" + git -c core.hooksPath=/dev/null checkout --quiet $BASE_SHA > /dev/null && echo 'On' $(git rev-parse HEAD) + pnpm run --if-present clean:build + pnpm install --filter='@woocommerce/plugin-woocommerce...' --frozen-lockfile --config.dedupe-peer-dependents=false + pnpm --filter='@woocommerce/plugin-woocommerce' build + echo '##[endgroup]' + + title "##[group]Benchmarking baseline" + RESULTS_ID="editor_${BASE_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor + RESULTS_ID="product-editor_${BASE_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor + echo '##[endgroup]' + + # This step is intended for running the script locally. + title "##[group]Restoring codebase state back to head" + git -c core.hooksPath=/dev/null checkout --quiet $HEAD_BRANCH > /dev/null && echo 'On' $(git rev-parse HEAD) + pnpm install --frozen-lockfile > /dev/null & + pnpm run --if-present clean:build + echo '##[endgroup]' + fi + + title "##[group]Processing reports under $ARTIFACTS_PATH" + ls -l $ARTIFACTS_PATH # Updating the WP version used for performance jobs means there’s a high # chance that the reference commit used for performance test stability # becomes incompatible with the WP version. So, every time the "Tested up @@ -36,15 +85,16 @@ elif [[ "$GITHUB_EVENT_NAME" == "push" ]]; then # - Be compatible with the new WP version used in the “Tested up to” flag. # - Be tracked on https://www.codevitals.run/project/woo for all existing # metrics. - BASE_SHA=3d7d7f02017383937f1a4158d433d0e5d44b3dc9 - echo "WP_VERSION: $WP_VERSION" IFS=. read -ra WP_VERSION_ARRAY <<< "$WP_VERSION" - WP_MAJOR="${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}" - pnpm --filter="compare-perf" run compare perf $GITHUB_SHA $BASE_SHA --tests-branch $GITHUB_SHA --wp-version "$WP_MAJOR" + pnpm --filter="compare-perf" run compare perf $GITHUB_SHA $BASE_SHA --tests-branch $GITHUB_SHA --wp-version "${WP_VERSION_ARRAY[0]}.${WP_VERSION_ARRAY[1]}" --ci --skip-benchmarking + echo '##[endgroup]' - title "Publish results to CodeVitals" - COMMITTED_AT=$(git show -s $GITHUB_SHA --format="%cI") - pnpm --filter="compare-perf" run log $CODEVITALS_PROJECT_TOKEN trunk $GITHUB_SHA $BASE_SHA $COMMITTED_AT + if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then + title "##[group]Publish results to CodeVitals" + COMMITTED_AT=$(git show -s $GITHUB_SHA --format="%cI") + pnpm --filter="compare-perf" run log $CODEVITALS_PROJECT_TOKEN trunk $GITHUB_SHA $BASE_SHA $COMMITTED_AT + echo '##[endgroup]' + fi else echo "Unsupported event: $GITHUB_EVENT_NAME" fi diff --git a/plugins/woocommerce/changelog/update-49550-rework-perfromance-job b/plugins/woocommerce/changelog/update-49550-rework-perfromance-job new file mode 100644 index 00000000000..62345e0e63d --- /dev/null +++ b/plugins/woocommerce/changelog/update-49550-rework-perfromance-job @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +CI: update perfromance metrics job and improve execution time. diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index 0e4a4da7860..8d7270267ce 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -504,6 +504,9 @@ "tests/metrics/**", ".wp-env.json" ], + "testEnv": { + "start": "env:test" + }, "events": [ "push" ], From 38a2b5b3d82b2a47ec35a643df8a15076430725f Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 30 Sep 2024 01:53:57 -0700 Subject: [PATCH 07/26] Coming Soon: Return empty string from template_include filter instead of null to avoid PHP fatal error with conflicting plugins using strict types (#51751) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Return empty string from template_include filter instead of null * Update phpdoc return tag * Add changefile(s) from automation for the following project(s): woocommerce --------- Co-authored-by: github-actions Co-authored-by: Alba Rincón --- plugins/woocommerce/changelog/51751-patch-3 | 4 ++++ .../src/Internal/ComingSoon/ComingSoonRequestHandler.php | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 plugins/woocommerce/changelog/51751-patch-3 diff --git a/plugins/woocommerce/changelog/51751-patch-3 b/plugins/woocommerce/changelog/51751-patch-3 new file mode 100644 index 00000000000..a3480792da1 --- /dev/null +++ b/plugins/woocommerce/changelog/51751-patch-3 @@ -0,0 +1,4 @@ +Significance: minor +Type: fix + +Return an empty string from `template_include` filter instead of null to avoid PHP fatal error with conflicting plugins using strict types. \ No newline at end of file diff --git a/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonRequestHandler.php b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonRequestHandler.php index 4fe29f00e02..762da791728 100644 --- a/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonRequestHandler.php +++ b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonRequestHandler.php @@ -39,7 +39,7 @@ class ComingSoonRequestHandler { * @internal * * @param string $template The path to the previously determined template. - * @return string|null The path to the 'coming soon' template or null to prevent further template loading in FSE themes. + * @return string The path to the 'coming soon' template or any empty string to prevent further template loading in FSE themes. */ public function handle_template_include( $template ) { global $wp; @@ -91,8 +91,8 @@ class ComingSoonRequestHandler { } if ( $is_fse_theme ) { - // Since we've already rendered a template, return null to ensure no other template is rendered. - return null; + // Since we've already rendered a template, return empty string to ensure no other template is rendered. + return ''; } else { // In non-FSE themes, other templates will still be rendered. // We need to exit to prevent further processing. From 0565c355203d464a1c7df9242c9edb9a0788c0b9 Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Mon, 30 Sep 2024 11:11:10 +0200 Subject: [PATCH 08/26] [dev] Documentation: prompt the required pnpm version in readme. (#51704) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ff249daa69..06872a31ad4 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ To get up and running within the WooCommerce Monorepo, you will need to make sur ### Prerequisites - [NVM](https://github.com/nvm-sh/nvm#installing-and-updating): While you can always install Node through other means, we recommend using NVM to ensure you're aligned with the version used by our development teams. Our repository contains [an `.nvmrc` file](.nvmrc) which helps ensure you are using the correct version of Node. -- [PNPM](https://pnpm.io/installation): Our repository utilizes PNPM to manage project dependencies and run various scripts involved in building and testing projects. +- [PNPM](https://pnpm.io/installation): Our repository utilizes PNPM version 9.1.3 to manage project dependencies and run various scripts involved in building and testing projects. - [PHP 7.4+](https://www.php.net/manual/en/install.php): WooCommerce Core currently features a minimum PHP version of 7.4. It is also needed to run Composer and various project build scripts. See [troubleshooting](DEVELOPMENT.md#troubleshooting) for troubleshooting problems installing PHP. - [Composer](https://getcomposer.org/doc/00-intro.md): We use Composer to manage all of the dependencies for PHP packages and plugins. From 9b3073999bff2eb5d5b6b0893ffd493ec783bbba Mon Sep 17 00:00:00 2001 From: Ivan Stojadinov Date: Mon, 30 Sep 2024 11:17:07 +0200 Subject: [PATCH 09/26] [e2e] External - Expand WPCOM suite, part 5 (#51745) * Handle notice if displayed * Make `Reply to comment` more unique, and wait for comment area to disappear * Skip on WPCOM - error 404 * Fix copy/paste error * Make area more unique * Add changefile(s) from automation for the following project(s): woocommerce * Expand WPCOM suite --------- Co-authored-by: github-actions --- .../51745-e2e-external-expand-wpcom-suite-part5 | 4 ++++ .../envs/default-wpcom/playwright.config.js | 12 ++++++++++++ .../e2e-pw/tests/merchant/product-reviews.spec.js | 15 ++++++++++++++- .../tests/merchant/settings-woo-com.spec.js | 8 +++++++- .../e2e-pw/tests/merchant/users-manage.spec.js | 6 +++++- 5 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 plugins/woocommerce/changelog/51745-e2e-external-expand-wpcom-suite-part5 diff --git a/plugins/woocommerce/changelog/51745-e2e-external-expand-wpcom-suite-part5 b/plugins/woocommerce/changelog/51745-e2e-external-expand-wpcom-suite-part5 new file mode 100644 index 00000000000..b31adc30d0b --- /dev/null +++ b/plugins/woocommerce/changelog/51745-e2e-external-expand-wpcom-suite-part5 @@ -0,0 +1,4 @@ +Significance: patch +Type: update + +Expand the e2e suite we're running on WPCOM part #5. \ No newline at end of file diff --git a/plugins/woocommerce/tests/e2e-pw/envs/default-wpcom/playwright.config.js b/plugins/woocommerce/tests/e2e-pw/envs/default-wpcom/playwright.config.js index a3e2135c0ad..a3f47119ff0 100644 --- a/plugins/woocommerce/tests/e2e-pw/envs/default-wpcom/playwright.config.js +++ b/plugins/woocommerce/tests/e2e-pw/envs/default-wpcom/playwright.config.js @@ -33,6 +33,18 @@ config = { '**/merchant/launch-your-store.spec.js', '**/merchant/lost-password.spec.js', '**/merchant/order-bulk-edit.spec.js', + '**/merchant/product-images.spec.js', + '**/merchant/product-import-csv.spec.js', + '**/merchant/product-linked-products.spec.js', + '**/merchant/product-reviews.spec.js', + '**/merchant/product-search.spec.js', + '**/merchant/product-settings.spec.js', + '**/merchant/settings-general.spec.js', + '**/merchant/settings-shipping.spec.js', + '**/merchant/settings-tax.spec.js', + '**/merchant/settings-woo-com.spec.js', + '**/merchant/users-create.spec.js', + '**/merchant/users-manage.spec.js', ], grepInvert: /@skip-on-default-wpcom/, }, diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-reviews.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-reviews.spec.js index 8b024315391..1f810a6cdf8 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-reviews.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/product-reviews.spec.js @@ -246,6 +246,14 @@ test.describe( 'wp-admin/edit.php?post_type=product&page=product-reviews' ); + // Handle notice if present + await page.addLocatorHandler( + page.getByRole( 'link', { name: 'Dismiss' } ), + async () => { + await page.getByRole( 'link', { name: 'Dismiss' } ).click(); + } + ); + const reviewRow = page.locator( `#comment-${ review.id }` ); await reviewRow.hover(); await reviewRow.getByRole( 'button', { name: 'Reply' } ).click(); @@ -256,7 +264,12 @@ test.describe( const replyText = `Thank you for your feedback! (replied ${ Date.now() })`; await replyTextArea.fill( replyText ); - await page.locator( 'button.save.button.button-primary' ).click(); + await page + .getByRole( 'cell', { name: 'Reply to Comment' } ) + .getByRole( 'button', { name: 'Reply', exact: true } ) + .click(); + + await expect( replyTextArea ).toBeHidden(); const productLink = await reviewRow .locator( 'a.comments-view-item-link' ) diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/settings-woo-com.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/settings-woo-com.spec.js index 2aca4a6eb4a..2ee66f56419 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/settings-woo-com.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/settings-woo-com.spec.js @@ -3,7 +3,13 @@ const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; test.describe( 'WooCommerce woo.com Settings', - { tag: [ '@services', '@skip-on-default-pressable' ] }, + { + tag: [ + '@services', + '@skip-on-default-pressable', + '@skip-on-default-wpcom', + ], + }, () => { test.use( { storageState: process.env.ADMINSTATE } ); diff --git a/plugins/woocommerce/tests/e2e-pw/tests/merchant/users-manage.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/merchant/users-manage.spec.js index 160962718db..00a61eaecf9 100644 --- a/plugins/woocommerce/tests/e2e-pw/tests/merchant/users-manage.spec.js +++ b/plugins/woocommerce/tests/e2e-pw/tests/merchant/users-manage.spec.js @@ -65,7 +65,11 @@ async function userDeletionTest( page, username ) { page.getByRole( 'heading', { name: 'Delete Users' } ) ).toBeVisible(); - await expect( page.getByText( `${ username }` ) ).toBeVisible(); + await expect( + page + .getByText( 'Delete Users You have' ) + .getByText( `${ username }` ) + ).toBeVisible(); await page.getByRole( 'button', { name: 'Confirm Deletion' } ).click(); } ); From 08b8dd99cc318586225e9eacf8389fecbfcf64dc Mon Sep 17 00:00:00 2001 From: Jason Kytros Date: Mon, 30 Sep 2024 13:17:17 +0300 Subject: [PATCH 10/26] Revert packages initialization timeline change (#51728) * Revert packages initialization timeline change * Add changefile(s) from automation for the following project(s): woocommerce * Fix lint errors --------- Co-authored-by: github-actions --- .../changelog/51728-fix-issue-51711 | 4 ++++ .../woocommerce/includes/class-wc-brands.php | 2 +- plugins/woocommerce/src/Internal/Brands.php | 20 ++++++++++++++----- plugins/woocommerce/src/Packages.php | 15 +++++++++++++- 4 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 plugins/woocommerce/changelog/51728-fix-issue-51711 diff --git a/plugins/woocommerce/changelog/51728-fix-issue-51711 b/plugins/woocommerce/changelog/51728-fix-issue-51711 new file mode 100644 index 00000000000..dd4438a354b --- /dev/null +++ b/plugins/woocommerce/changelog/51728-fix-issue-51711 @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak +Comment: This PR reverts a change that hasn't yet been released, so no changelog is required. + diff --git a/plugins/woocommerce/includes/class-wc-brands.php b/plugins/woocommerce/includes/class-wc-brands.php index 71e1fa71299..43732102f01 100644 --- a/plugins/woocommerce/includes/class-wc-brands.php +++ b/plugins/woocommerce/includes/class-wc-brands.php @@ -26,7 +26,7 @@ class WC_Brands { public function __construct() { $this->template_url = apply_filters( 'woocommerce_template_url', 'woocommerce/' ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment - add_action( 'plugins_loaded', array( $this, 'register_hooks' ), 2 ); + add_action( 'plugins_loaded', array( $this, 'register_hooks' ), 11 ); $this->register_shortcodes(); } diff --git a/plugins/woocommerce/src/Internal/Brands.php b/plugins/woocommerce/src/Internal/Brands.php index 7ac3cdbab05..4abd1c6fc0b 100644 --- a/plugins/woocommerce/src/Internal/Brands.php +++ b/plugins/woocommerce/src/Internal/Brands.php @@ -25,11 +25,6 @@ class Brands { return; } - // If the WooCommerce Brands plugin is activated via the WP CLI using the '--skip-plugins' flag, deactivate it here. - if ( function_exists( 'wc_brands_init' ) ) { - remove_action( 'plugins_loaded', 'wc_brands_init', 1 ); - } - include_once WC_ABSPATH . 'includes/class-wc-brands.php'; include_once WC_ABSPATH . 'includes/class-wc-brands-coupons.php'; include_once WC_ABSPATH . 'includes/class-wc-brands-brand-settings-manager.php'; @@ -58,4 +53,19 @@ class Brands { } return ( $assignment <= 6 ); // Considering 5% of the 0-120 range. } + + /** + * If WooCommerce Brands gets activated forcibly, without WooCommerce active (e.g. via '--skip-plugins'), + * remove WooCommerce Brands initialization functions early on in the 'plugins_loaded' timeline. + */ + public static function prepare() { + + if ( ! self::is_enabled() ) { + return; + } + + if ( function_exists( 'wc_brands_init' ) ) { + remove_action( 'plugins_loaded', 'wc_brands_init', 1 ); + } + } } diff --git a/plugins/woocommerce/src/Packages.php b/plugins/woocommerce/src/Packages.php index eb251bbc908..568ec63c509 100644 --- a/plugins/woocommerce/src/Packages.php +++ b/plugins/woocommerce/src/Packages.php @@ -66,7 +66,8 @@ class Packages { * @since 3.7.0 */ public static function init() { - add_action( 'plugins_loaded', array( __CLASS__, 'on_init' ), 0 ); + add_action( 'plugins_loaded', array( __CLASS__, 'prepare_packages' ), -100 ); + add_action( 'plugins_loaded', array( __CLASS__, 'on_init' ), 10 ); // Prevent plugins already merged into WooCommerce core from getting activated as standalone plugins. add_action( 'activate_plugin', array( __CLASS__, 'deactivate_merged_plugins' ) ); @@ -149,6 +150,18 @@ class Packages { return array_key_exists( $package, self::get_enabled_packages() ); } + /** + * Prepare merged packages for initialization. + * Especially useful when running actions early in the 'plugins_loaded' timeline. + */ + public static function prepare_packages() { + foreach ( self::get_enabled_packages() as $package_name => $package_class ) { + if ( method_exists( $package_class, 'prepare' ) ) { + call_user_func( array( $package_class, 'prepare' ) ); + } + } + } + /** * Deactivates merged feature plugins. * From b852aff696f061c6d60e153b6da71dddb9f34ca4 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Mon, 30 Sep 2024 11:54:34 +0100 Subject: [PATCH 11/26] Remove alert role from informational notices (#51651) * Remove alert role from informational notices * changelog * Status role --- plugins/woocommerce/changelog/fix-info-notice-role | 4 ++++ plugins/woocommerce/templates/block-notices/notice.php | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-info-notice-role diff --git a/plugins/woocommerce/changelog/fix-info-notice-role b/plugins/woocommerce/changelog/fix-info-notice-role new file mode 100644 index 00000000000..7ccfdb5a43d --- /dev/null +++ b/plugins/woocommerce/changelog/fix-info-notice-role @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Remove alert role from informational notices diff --git a/plugins/woocommerce/templates/block-notices/notice.php b/plugins/woocommerce/templates/block-notices/notice.php index 61235ce81e6..b0b9cece5b8 100644 --- a/plugins/woocommerce/templates/block-notices/notice.php +++ b/plugins/woocommerce/templates/block-notices/notice.php @@ -12,7 +12,7 @@ * * @see https://docs.woocommerce.com/document/template-structure/ * @package WooCommerce\Templates - * @version 8.6.0 + * @version 9.5.0 */ if ( ! defined( 'ABSPATH' ) ) { @@ -26,7 +26,7 @@ if ( ! $notices ) { ?> -
    role="alert"> +
    role="status"> From 5907114d6eabae41edf39c593a36345b92990b38 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:07:27 +0100 Subject: [PATCH 12/26] Delete changelog files based on PR 51728 (#51770) Delete changelog files for 51728 Co-authored-by: WooCommerce Bot --- plugins/woocommerce/changelog/51728-fix-issue-51711 | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 plugins/woocommerce/changelog/51728-fix-issue-51711 diff --git a/plugins/woocommerce/changelog/51728-fix-issue-51711 b/plugins/woocommerce/changelog/51728-fix-issue-51711 deleted file mode 100644 index dd4438a354b..00000000000 --- a/plugins/woocommerce/changelog/51728-fix-issue-51711 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: tweak -Comment: This PR reverts a change that hasn't yet been released, so no changelog is required. - From 3826b8b2c326e6d60d5ddfe386029b10b7265542 Mon Sep 17 00:00:00 2001 From: louwie17 Date: Mon, 30 Sep 2024 08:48:14 -0300 Subject: [PATCH 13/26] Fix product form metabox issue on Safari 18.0 (#51734) * Fix for styling issue in Safari 18 * Add changelog --- .../changelog/fix-51684_product_form_metabox_issue_on_safari | 4 ++++ plugins/woocommerce/client/legacy/css/admin.scss | 5 +++++ 2 files changed, 9 insertions(+) create mode 100644 plugins/woocommerce/changelog/fix-51684_product_form_metabox_issue_on_safari diff --git a/plugins/woocommerce/changelog/fix-51684_product_form_metabox_issue_on_safari b/plugins/woocommerce/changelog/fix-51684_product_form_metabox_issue_on_safari new file mode 100644 index 00000000000..36d9a34feba --- /dev/null +++ b/plugins/woocommerce/changelog/fix-51684_product_form_metabox_issue_on_safari @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix CSS issue with Safari 18.0 on the product form page. diff --git a/plugins/woocommerce/client/legacy/css/admin.scss b/plugins/woocommerce/client/legacy/css/admin.scss index a3d3689347e..f215d91343d 100644 --- a/plugins/woocommerce/client/legacy/css/admin.scss +++ b/plugins/woocommerce/client/legacy/css/admin.scss @@ -8795,3 +8795,8 @@ table.bar_chart { html:has(#status-table-templates){ scroll-padding-top: 80px; } + +// Fix for Safari bug: https://bugs.webkit.org/show_bug.cgi?id=280063. +.woocommerce-admin-page #postbox-container-2 { + clear: left; +} \ No newline at end of file From 52d0b8c17d6efaff94d95054f3af33f666edb6cb Mon Sep 17 00:00:00 2001 From: Bart Kalisz Date: Mon, 30 Sep 2024 15:04:08 +0200 Subject: [PATCH 14/26] Blocks E2E: Make test plugin namespace consistent (#51771) --- .../index.js => register-product-collection.js} | 0 ...on-tester.php => register-product-collection.php} | 12 +++++------- .../product-picker.block_theme.spec.ts | 2 +- .../register-product-collection.block_theme.spec.ts | 2 +- .../changelog/fix-e2e-test-plugin-namespace | 4 ++++ 5 files changed, 11 insertions(+), 9 deletions(-) rename plugins/woocommerce-blocks/tests/e2e/plugins/{register-product-collection-tester/index.js => register-product-collection.js} (100%) rename plugins/woocommerce-blocks/tests/e2e/plugins/{register-product-collection-tester.php => register-product-collection.php} (66%) create mode 100644 plugins/woocommerce/changelog/fix-e2e-test-plugin-namespace diff --git a/plugins/woocommerce-blocks/tests/e2e/plugins/register-product-collection-tester/index.js b/plugins/woocommerce-blocks/tests/e2e/plugins/register-product-collection.js similarity index 100% rename from plugins/woocommerce-blocks/tests/e2e/plugins/register-product-collection-tester/index.js rename to plugins/woocommerce-blocks/tests/e2e/plugins/register-product-collection.js diff --git a/plugins/woocommerce-blocks/tests/e2e/plugins/register-product-collection-tester.php b/plugins/woocommerce-blocks/tests/e2e/plugins/register-product-collection.php similarity index 66% rename from plugins/woocommerce-blocks/tests/e2e/plugins/register-product-collection-tester.php rename to plugins/woocommerce-blocks/tests/e2e/plugins/register-product-collection.php index 2171cdcc297..e4aedacbe95 100644 --- a/plugins/woocommerce-blocks/tests/e2e/plugins/register-product-collection-tester.php +++ b/plugins/woocommerce-blocks/tests/e2e/plugins/register-product-collection.php @@ -1,13 +1,11 @@ { // Activate plugin which registers custom product collections test.beforeEach( async ( { requestUtils } ) => { await requestUtils.activatePlugin( - 'register-product-collection-tester' + 'woocommerce-blocks-test-register-product-collection' ); } ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/register-product-collection.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/register-product-collection.block_theme.spec.ts index 50b47c288af..9f80f9db671 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/register-product-collection.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/register-product-collection.block_theme.spec.ts @@ -56,7 +56,7 @@ test.describe( 'Product Collection: Register Product Collection', () => { // Activate plugin which registers custom product collections test.beforeEach( async ( { requestUtils } ) => { await requestUtils.activatePlugin( - 'register-product-collection-tester' + 'woocommerce-blocks-test-register-product-collection' ); } ); diff --git a/plugins/woocommerce/changelog/fix-e2e-test-plugin-namespace b/plugins/woocommerce/changelog/fix-e2e-test-plugin-namespace new file mode 100644 index 00000000000..0d4f8105e12 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-e2e-test-plugin-namespace @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Blocks E2E: fix plugin namespace From fae492632a84a6d82793b8c9c2e33bfeb8eebe2b Mon Sep 17 00:00:00 2001 From: Boro Sitnikovski Date: Mon, 30 Sep 2024 15:14:39 +0200 Subject: [PATCH 15/26] Fix subscription status and action items for free (lifetime) subs (#51628) * Fix subscription status and action items for free (lifetime) subs * Changelog * Lint --- .../my-subscriptions/table/rows/functions.tsx | 17 ++++++++++++----- .../changelog/fix-wccom-21789-free-subs-display | 4 ++++ 2 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 plugins/woocommerce/changelog/fix-wccom-21789-free-subs-display diff --git a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/functions.tsx b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/functions.tsx index 0a565cd88f5..b591b860066 100644 --- a/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/functions.tsx +++ b/plugins/woocommerce-admin/client/marketplace/components/my-subscriptions/table/rows/functions.tsx @@ -355,9 +355,16 @@ export function subscriptionStatus( ); } - return subscription.autorenew - ? __( 'Active', 'woocommerce' ) - : __( 'Cancelled', 'woocommerce' ); + let status; + if ( subscription.lifetime ) { + status = __( 'Lifetime', 'woocommerce' ); + } else if ( subscription.autorenew ) { + status = __( 'Active', 'woocommerce' ); + } else { + status = __( 'Cancelled', 'woocommerce' ); + } + + return status; } return { display: getStatus(), @@ -377,7 +384,7 @@ export function actions( subscription: Subscription ): TableRow { let actionButton = null; if ( subscription.product_key === '' ) { actionButton = ; - } else if ( subscription.expired ) { + } else if ( subscription.expired && ! subscription.lifetime ) { actionButton = ; } else if ( subscription.local.installed === false && @@ -391,7 +398,7 @@ export function actions( subscription: Subscription ): TableRow { actionButton = ( ); - } else if ( ! subscription.autorenew ) { + } else if ( ! subscription.autorenew && ! subscription.lifetime ) { actionButton = ; } diff --git a/plugins/woocommerce/changelog/fix-wccom-21789-free-subs-display b/plugins/woocommerce/changelog/fix-wccom-21789-free-subs-display new file mode 100644 index 00000000000..8f2c2642e93 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-wccom-21789-free-subs-display @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fix subscription status and action items for free (lifetime) subscriptions From 93b2ceb04420a4b7118e790adf1da483a2b2207f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 14:55:18 +0100 Subject: [PATCH 16/26] Delete changelog files based on PR 51734 (#51775) Delete changelog files for 51734 Co-authored-by: WooCommerce Bot --- .../changelog/fix-51684_product_form_metabox_issue_on_safari | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 plugins/woocommerce/changelog/fix-51684_product_form_metabox_issue_on_safari diff --git a/plugins/woocommerce/changelog/fix-51684_product_form_metabox_issue_on_safari b/plugins/woocommerce/changelog/fix-51684_product_form_metabox_issue_on_safari deleted file mode 100644 index 36d9a34feba..00000000000 --- a/plugins/woocommerce/changelog/fix-51684_product_form_metabox_issue_on_safari +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Fix CSS issue with Safari 18.0 on the product form page. From d3b0f153ad02faebebaa9f656b4cb4b96ee649e7 Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Mon, 30 Sep 2024 16:39:16 +0200 Subject: [PATCH 17/26] [dev] CI: feedback on updated perfromance metrics job (#51772) --- .github/workflows/ci.yml | 4 ++-- .github/workflows/pr-assess-bundle-size.yml | 3 +++ .github/workflows/pr-build-live-branch.yml | 3 +++ .github/workflows/scripts/run-metrics.sh | 16 ++++++++++++---- .../update-49550-feedback-perfromance-job | 4 ++++ 5 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-49550-feedback-perfromance-job diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a42b21e2870..194fe27f13f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -188,8 +188,8 @@ jobs: uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 with: path: 'tools/compare-perf/artifacts/*_55f855a2e6d769b5ae44305b2772eb30d3e721df_*' - key: '${{ runner.os }}-woocommerce-performance-metrics-55f855a2e6d769b5ae44305b2772eb30d3e721df' - restore-keys: '${{ runner.os }}-woocommerce-performance-metrics-' + key: "${{ runner.os }}-woocommerce-performance-metrics-55f855a2e6d769b5ae44305b2772eb30d3e721df-${{ hashFiles( 'plugins/woocommerce/tests/performance/**/*.js' ) }}" + restore-keys: '${{ runner.os }}-woocommerce-performance-metrics-55f855a2e6d769b5ae44305b2772eb30d3e721df-' - name: 'Run tests (${{ matrix.testType }})' env: diff --git a/.github/workflows/pr-assess-bundle-size.yml b/.github/workflows/pr-assess-bundle-size.yml index df6bb6444fa..d6d9e2298f8 100644 --- a/.github/workflows/pr-assess-bundle-size.yml +++ b/.github/workflows/pr-assess-bundle-size.yml @@ -29,6 +29,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} cancel-in-progress: true +env: + FORCE_COLOR: 1 + jobs: build: name: Check Asset Sizes diff --git a/.github/workflows/pr-build-live-branch.yml b/.github/workflows/pr-build-live-branch.yml index 42c39a49c93..1e92ffc9793 100644 --- a/.github/workflows/pr-build-live-branch.yml +++ b/.github/workflows/pr-build-live-branch.yml @@ -16,6 +16,9 @@ concurrency: group: build-${{ github.event_name == 'push' && github.run_id || 'pr' }}-${{ github.ref }} cancel-in-progress: true +env: + FORCE_COLOR: 1 + permissions: {} jobs: diff --git a/.github/workflows/scripts/run-metrics.sh b/.github/workflows/scripts/run-metrics.sh index bc8338bf38b..87cb315f486 100755 --- a/.github/workflows/scripts/run-metrics.sh +++ b/.github/workflows/scripts/run-metrics.sh @@ -42,8 +42,12 @@ if [ "$GITHUB_EVENT_NAME" == "push" ] || [ "$GITHUB_EVENT_NAME" == "pull_request echo '##[endgroup]' title "##[group]Benchmarking head" - RESULTS_ID="editor_${GITHUB_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor - RESULTS_ID="product-editor_${GITHUB_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor + for ROUND in {1..2}; do + # We can afford only 2 rounds, each one takes ~2 minutes. + echo "$(date +"%T"): Round $ROUND of 2" + RESULTS_ID="editor_${GITHUB_SHA}_round-$ROUND" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor + RESULTS_ID="product-editor_${GITHUB_SHA}_round-$ROUND" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor + done echo '##[endgroup]' fi @@ -62,8 +66,12 @@ if [ "$GITHUB_EVENT_NAME" == "push" ] || [ "$GITHUB_EVENT_NAME" == "pull_request echo '##[endgroup]' title "##[group]Benchmarking baseline" - RESULTS_ID="editor_${BASE_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor - RESULTS_ID="product-editor_${BASE_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor + for ROUND in {1..3}; do + # Each round takes ~2 minutes, running for ~6 minutes presumable generates good enough baseline (cached for 1 week in CI). + echo "$(date +"%T"): Round $ROUND of 3" + RESULTS_ID="editor_${BASE_SHA}_round-$ROUND" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor + RESULTS_ID="product-editor_${BASE_SHA}_round-$ROUND" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor + done echo '##[endgroup]' # This step is intended for running the script locally. diff --git a/plugins/woocommerce/changelog/update-49550-feedback-perfromance-job b/plugins/woocommerce/changelog/update-49550-feedback-perfromance-job new file mode 100644 index 00000000000..6a2e359d672 --- /dev/null +++ b/plugins/woocommerce/changelog/update-49550-feedback-perfromance-job @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +CI: update performance metrics job benchmarking. From a64e227fdcc4539b73d98e7b2a10a7d5d6007c1c Mon Sep 17 00:00:00 2001 From: And Finally Date: Mon, 30 Sep 2024 15:49:58 +0100 Subject: [PATCH 18/26] Fetch promotions in a WP Cron job (#51650) * Getting promotions in a WP Cron job to make them async. We don't want this request to affect the performance of wp-admin. See https://github.com/woocommerce/woocommerce/pull/47262#pullrequestreview-2241835763. * Changelog. * Made callback method `update_promotions` public. * Linter errors. Whitespace. --- ...update-wccom-21774-update-promotions-async | 4 ++ .../class-wc-admin-marketplace-promotions.php | 53 +++++++++++++++---- 2 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-wccom-21774-update-promotions-async diff --git a/plugins/woocommerce/changelog/update-wccom-21774-update-promotions-async b/plugins/woocommerce/changelog/update-wccom-21774-update-promotions-async new file mode 100644 index 00000000000..8109aa78d61 --- /dev/null +++ b/plugins/woocommerce/changelog/update-wccom-21774-update-promotions-async @@ -0,0 +1,4 @@ +Significance: minor +Type: update + +Changed how we fetch WooCommerce promotions. We're doing it async so as not to affect the loading of wp-admin. diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-marketplace-promotions.php b/plugins/woocommerce/includes/admin/class-wc-admin-marketplace-promotions.php index 02aedbd9556..6026fcbb020 100644 --- a/plugins/woocommerce/includes/admin/class-wc-admin-marketplace-promotions.php +++ b/plugins/woocommerce/includes/admin/class-wc-admin-marketplace-promotions.php @@ -15,6 +15,7 @@ if ( ! defined( 'ABSPATH' ) ) { */ class WC_Admin_Marketplace_Promotions { + const CRON_NAME = 'woocommerce_marketplace_cron_fetch_promotions'; const TRANSIENT_NAME = 'woocommerce_marketplace_promotions_v2'; const TRANSIENT_LIFE_SPAN = DAY_IN_SECONDS; const PROMOTIONS_API_URL = 'https://woocommerce.com/wp-json/wccom-extensions/3.0/promotions'; @@ -39,7 +40,16 @@ class WC_Admin_Marketplace_Promotions { public static function init() { // A legacy hook that can be triggered by action scheduler. add_action( 'woocommerce_marketplace_fetch_promotions', array( __CLASS__, 'clear_deprecated_action' ) ); - add_action( 'woocommerce_marketplace_fetch_promotions_clear', array( __CLASS__, 'clear_scheduled_event' ) ); + add_action( + 'woocommerce_marketplace_fetch_promotions_clear', + array( + __CLASS__, + 'clear_deprecated_scheduled_event', + ) + ); + + // Fetch promotions from the API and store them in a transient. + add_action( self::CRON_NAME, array( __CLASS__, 'update_promotions' ) ); if ( defined( 'DOING_AJAX' ) && DOING_AJAX @@ -53,24 +63,33 @@ class WC_Admin_Marketplace_Promotions { return; } - self::maybe_update_promotions(); + self::schedule_cron_event(); + + register_deactivation_hook( WC_PLUGIN_FILE, array( __CLASS__, 'clear_cron_event' ) ); self::$locale = ( self::$locale ?? get_user_locale() ) ?? 'en_US'; self::maybe_show_bubble_promotions(); } /** - * Fetch promotions from the API and store them in a transient. - * Fetching can be suppressed by the `woocommerce_marketplace_suppress_promotions` filter. + * Schedule a daily cron event to fetch promotions. + * + * @version 9.5.0 * * @return void */ - private static function maybe_update_promotions() { - // Fetch promotions if they're not in the transient. - if ( false !== get_transient( self::TRANSIENT_NAME ) ) { - return; + private static function schedule_cron_event() { + if ( ! wp_next_scheduled( self::CRON_NAME ) ) { + wp_schedule_event( time(), 'daily', self::CRON_NAME ); } + } + /** + * Fetch promotions from the API and store them in a transient. + * + * @return void + */ + public static function update_promotions() { // Fetch promotions from the API. $promotions = self::fetch_marketplace_promotions(); set_transient( self::TRANSIENT_NAME, $promotions, self::TRANSIENT_LIFE_SPAN ); @@ -326,12 +345,24 @@ class WC_Admin_Marketplace_Promotions { } /** - * Clear the scheduled action that was used to fetch promotions in WooCommerce 8.8. - * It's no longer needed as a transient is used to store the data. + * When WooCommerce is disabled, clear the WP Cron event we use to fetch promotions. + * + * @version 9.5.0 * * @return void */ - public static function clear_scheduled_event() { + public static function clear_cron_event() { + $timestamp = wp_next_scheduled( self::CRON_NAME ); + wp_unschedule_event( $timestamp, self::CRON_NAME ); + } + + /** + * Clear deprecated scheduled action that was used to fetch promotions in WooCommerce 8.8. + * Replaced with a transient in WooCommerce 9.0. + * + * @return void + */ + public static function clear_deprecated_scheduled_event() { if ( function_exists( 'as_unschedule_all_actions' ) ) { as_unschedule_all_actions( 'woocommerce_marketplace_fetch_promotions' ); } From 632b4e98f4b8fcc9cc0f65658cdc2489c64306bf Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:50:59 +0100 Subject: [PATCH 19/26] Delete changelog files based on PR 51577 (#51777) Delete changelog files for 51577 Co-authored-by: WooCommerce Bot --- plugins/woocommerce/changelog/51577-issue-1570 | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 plugins/woocommerce/changelog/51577-issue-1570 diff --git a/plugins/woocommerce/changelog/51577-issue-1570 b/plugins/woocommerce/changelog/51577-issue-1570 deleted file mode 100644 index 9453f00a502..00000000000 --- a/plugins/woocommerce/changelog/51577-issue-1570 +++ /dev/null @@ -1,4 +0,0 @@ -Significance: patch -Type: fix - -Resolved fatal error when applying Brands-restricted coupon \ No newline at end of file From e48b0352fec13385077176a1a7216dcc1960fbf4 Mon Sep 17 00:00:00 2001 From: Alexandre Lara Date: Mon, 30 Sep 2024 12:41:20 -0300 Subject: [PATCH 20/26] Blocks: Add missing `wp-block-woocommerce-{name}` to Add to Cart with Options, Product Image, Product Rating, Product Rating Stars, Product Rating Counter and Product Image (#51558) * Add missing classnames for WP blocks * Add changefile(s) from automation for the following project(s): woocommerce * Fix unwanted extra whitespace within class property * Remove unnecessary whitespace in class attribute for the Product Image block * Remove unnecessary whitespace in class attribute for the Product Rating block * Remove unnecessary whitespace in class attribute for the Product Rating blocks * Fix php lint errors --------- Co-authored-by: github-actions Co-authored-by: Sam Seay --- ...rm-product-rating-and-product-image-blocks | 4 +++ .../src/Blocks/BlockTypes/AddToCartForm.php | 31 ++++++++++++++----- .../src/Blocks/BlockTypes/ProductImage.php | 24 +++++++++++--- .../src/Blocks/BlockTypes/ProductRating.php | 26 +++++++++++++--- .../BlockTypes/ProductRatingCounter.php | 29 ++++++++++++----- .../Blocks/BlockTypes/ProductRatingStars.php | 26 +++++++++++++--- 6 files changed, 112 insertions(+), 28 deletions(-) create mode 100644 plugins/woocommerce/changelog/51558-feat-49739-add-missing-wp-block-classname-to-add-to-cart-form-product-rating-and-product-image-blocks diff --git a/plugins/woocommerce/changelog/51558-feat-49739-add-missing-wp-block-classname-to-add-to-cart-form-product-rating-and-product-image-blocks b/plugins/woocommerce/changelog/51558-feat-49739-add-missing-wp-block-classname-to-add-to-cart-form-product-rating-and-product-image-blocks new file mode 100644 index 00000000000..a1ebb706b1c --- /dev/null +++ b/plugins/woocommerce/changelog/51558-feat-49739-add-missing-wp-block-classname-to-add-to-cart-form-product-rating-and-product-image-blocks @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Add missing `wp-block-woocommerce-{name}` class to Add to Cart with Options, Product Image, Product Rating, Product Rating Stars, Product Rating Counter and Product Image blocks. \ No newline at end of file diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartForm.php b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartForm.php index 8b8aa44c6fc..079538cc417 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartForm.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartForm.php @@ -1,4 +1,5 @@ get_type() . '_add_to_cart' ); $product = ob_get_clean(); @@ -102,11 +103,27 @@ class AddToCartForm extends AbstractBlock { $classes_and_styles = StyleAttributesUtils::get_classes_and_styles_by_attributes( $attributes ); $product_classname = $is_descendent_of_single_product_block ? 'product' : ''; + $classes = implode( + ' ', + array_filter( + array( + 'wp-block-add-to-cart-form wc-block-add-to-cart-form', + esc_attr( $classes_and_styles['classes'] ), + esc_attr( $product_classname ), + ) + ) + ); + + $wrapper_attributes = get_block_wrapper_attributes( + array( + 'class' => $classes, + 'style' => esc_attr( $classes_and_styles['styles'] ), + ) + ); + $form = sprintf( - '
    %4$s
    ', - esc_attr( $classes_and_styles['classes'] ), - esc_attr( $product_classname ), - esc_attr( $classes_and_styles['styles'] ), + '
    %2$s
    ', + $wrapper_attributes, $product ); diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductImage.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductImage.php index 9aadea3a948..e3875d8ffee 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductImage.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductImage.php @@ -208,13 +208,29 @@ class ProductImage extends AbstractBlock { $post_id = isset( $block->context['postId'] ) ? $block->context['postId'] : ''; $product = wc_get_product( $post_id ); + $classes = implode( + ' ', + array_filter( + array( + 'wc-block-components-product-image wc-block-grid__product-image', + esc_attr( $classes_and_styles['classes'] ), + ) + ) + ); + + $wrapper_attributes = get_block_wrapper_attributes( + array( + 'class' => $classes, + 'style' => esc_attr( $classes_and_styles['styles'] ), + ) + ); + if ( $product ) { return sprintf( - '
    - %3$s + '
    + %2$s
    ', - esc_attr( $classes_and_styles['classes'] ), - esc_attr( $classes_and_styles['styles'] ), + $wrapper_attributes, $this->render_anchor( $product, $this->render_on_sale_badge( $product, $parsed_attributes ), diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductRating.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductRating.php index 1d729677171..9741b4fce7e 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductRating.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductRating.php @@ -202,13 +202,29 @@ class ProductRating extends AbstractBlock { 10 ); + $classes = implode( + ' ', + array_filter( + array( + 'wc-block-components-product-rating wc-block-grid__product-rating', + esc_attr( $text_align_styles_and_classes['class'] ?? '' ), + esc_attr( $styles_and_classes['classes'] ), + ) + ) + ); + + $wrapper_attributes = get_block_wrapper_attributes( + array( + 'class' => $classes, + 'style' => esc_attr( $styles_and_classes['styles'] ?? '' ), + ) + ); + return sprintf( - '
    - %4$s + '
    + %2$s
    ', - esc_attr( $text_align_styles_and_classes['class'] ?? '' ), - esc_attr( $styles_and_classes['classes'] ), - esc_attr( $styles_and_classes['styles'] ?? '' ), + $wrapper_attributes, $rating_html ); } diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductRatingCounter.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductRatingCounter.php index 05dfff98d0f..33318453871 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductRatingCounter.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductRatingCounter.php @@ -132,7 +132,7 @@ class ProductRatingCounter extends AbstractBlock { * @param int $count Total number of ratings. * @return string */ - $filter_rating_html = function( $html, $rating, $count ) use ( $post_id, $product_rating, $product_reviews_count, $is_descendent_of_single_product_block, $is_descendent_of_single_product_template ) { + $filter_rating_html = function ( $html, $rating, $count ) use ( $post_id, $product_rating, $product_reviews_count, $is_descendent_of_single_product_block, $is_descendent_of_single_product_template ) { $product_permalink = get_permalink( $post_id ); $reviews_count = $count; $average_rating = $rating; @@ -193,17 +193,32 @@ class ProductRatingCounter extends AbstractBlock { 10 ); + $classes = implode( + ' ', + array_filter( + array( + 'wc-block-components-product-rating-counter wc-block-grid__product-rating-counter', + esc_attr( $text_align_styles_and_classes['class'] ?? '' ), + esc_attr( $styles_and_classes['classes'] ), + ) + ) + ); + + $wrapper_attributes = get_block_wrapper_attributes( + array( + 'class' => $classes, + 'style' => esc_attr( $styles_and_classes['styles'] ?? '' ), + ) + ); + return sprintf( - '
    - %4$s + '
    + %2$s
    ', - esc_attr( $text_align_styles_and_classes['class'] ?? '' ), - esc_attr( $styles_and_classes['classes'] ), - esc_attr( $styles_and_classes['styles'] ?? '' ), + $wrapper_attributes, $rating_html ); } return ''; } } - diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductRatingStars.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductRatingStars.php index 1f4b1f487be..bb84c2f7466 100644 --- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductRatingStars.php +++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductRatingStars.php @@ -149,13 +149,29 @@ class ProductRatingStars extends AbstractBlock { 10 ); + $classes = implode( + ' ', + array_filter( + array( + 'wc-block-components-product-rating wc-block-grid__product-rating', + esc_attr( $text_align_styles_and_classes['class'] ?? '' ), + esc_attr( $styles_and_classes['classes'] ), + ) + ) + ); + + $wrapper_attributes = get_block_wrapper_attributes( + array( + 'class' => $classes, + 'style' => esc_attr( $styles_and_classes['styles'] ?? '' ), + ) + ); + return sprintf( - '
    - %4$s + '
    + %2$s
    ', - esc_attr( $text_align_styles_and_classes['class'] ?? '' ), - esc_attr( $styles_and_classes['classes'] ), - esc_attr( $styles_and_classes['styles'] ?? '' ), + $wrapper_attributes, $rating_html ); } From ce5f7cb4717305e8e9eede565031df53d17a0c16 Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Tue, 1 Oct 2024 09:05:53 +0200 Subject: [PATCH 21/26] [dev] CI: drop baseline caching in perfromance metrics job (#51804) --- .github/workflows/ci.yml | 8 ---- .github/workflows/scripts/run-metrics.sh | 43 ++++++++----------- .../update-49550-perfromance-job-no-caching | 4 ++ plugins/woocommerce/package.json | 1 - 4 files changed, 21 insertions(+), 35 deletions(-) create mode 100644 plugins/woocommerce/changelog/update-49550-perfromance-job-no-caching diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 194fe27f13f..29380a12e20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -183,14 +183,6 @@ jobs: with: pattern: 'last-run__${{ strategy.job-index }}' - - name: 'Download Performance metrics baseline' - if: ${{ matrix.report.resultsPath != '' && matrix.testType == 'performance'}} - uses: actions/cache@ab5e6d0c87105b4c9c2047343972218f562e4319 - with: - path: 'tools/compare-perf/artifacts/*_55f855a2e6d769b5ae44305b2772eb30d3e721df_*' - key: "${{ runner.os }}-woocommerce-performance-metrics-55f855a2e6d769b5ae44305b2772eb30d3e721df-${{ hashFiles( 'plugins/woocommerce/tests/performance/**/*.js' ) }}" - restore-keys: '${{ runner.os }}-woocommerce-performance-metrics-55f855a2e6d769b5ae44305b2772eb30d3e721df-' - - name: 'Run tests (${{ matrix.testType }})' env: E2E_ENV_KEY: ${{ secrets.E2E_ENV_KEY }} diff --git a/.github/workflows/scripts/run-metrics.sh b/.github/workflows/scripts/run-metrics.sh index 87cb315f486..bc60ead0a80 100755 --- a/.github/workflows/scripts/run-metrics.sh +++ b/.github/workflows/scripts/run-metrics.sh @@ -2,7 +2,7 @@ set -eo pipefail -# The commented variables are for troubleshooting locally. +# The commented variables are for troubleshooting locally. The commented commands below are also for local troubleshooting. # GITHUB_EVENT_NAME='pull_request' # GITHUB_SHA=$(git rev-parse HEAD) # ARTIFACTS_PATH="$(realpath $(dirname -- ${BASH_SOURCE[0]})/../../../tools/compare-perf)/artifacts" @@ -34,20 +34,16 @@ if [ "$GITHUB_EVENT_NAME" == "push" ] || [ "$GITHUB_EVENT_NAME" == "pull_request if test -n "$(find $ARTIFACTS_PATH -maxdepth 1 -name "*_${GITHUB_SHA}_*" -print -quit)"; then title "Skipping benchmarking head as benchmarking results already available under $ARTIFACTS_PATH" else - title "##[group]Building head" - git -c core.hooksPath=/dev/null checkout --quiet $HEAD_BRANCH > /dev/null && echo 'On' $(git rev-parse HEAD) - pnpm run --if-present clean:build - pnpm install --filter='@woocommerce/plugin-woocommerce...' --frozen-lockfile --config.dedupe-peer-dependents=false - pnpm --filter='@woocommerce/plugin-woocommerce' build - echo '##[endgroup]' + # title "##[group]Building head" + # git -c core.hooksPath=/dev/null checkout --quiet $HEAD_BRANCH > /dev/null && echo 'On' $(git rev-parse HEAD) + # pnpm run --if-present clean:build + # pnpm install --filter='@woocommerce/plugin-woocommerce...' --frozen-lockfile --config.dedupe-peer-dependents=false + # pnpm --filter='@woocommerce/plugin-woocommerce' build + # echo '##[endgroup]' title "##[group]Benchmarking head" - for ROUND in {1..2}; do - # We can afford only 2 rounds, each one takes ~2 minutes. - echo "$(date +"%T"): Round $ROUND of 2" - RESULTS_ID="editor_${GITHUB_SHA}_round-$ROUND" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor - RESULTS_ID="product-editor_${GITHUB_SHA}_round-$ROUND" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor - done + RESULTS_ID="editor_${GITHUB_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor + RESULTS_ID="product-editor_${GITHUB_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor echo '##[endgroup]' fi @@ -60,26 +56,21 @@ if [ "$GITHUB_EVENT_NAME" == "push" ] || [ "$GITHUB_EVENT_NAME" == "pull_request title "##[group]Building baseline" git -c core.hooksPath=/dev/null checkout --quiet $BASE_SHA > /dev/null && echo 'On' $(git rev-parse HEAD) - pnpm run --if-present clean:build + pnpm run --if-present clean:build & pnpm install --filter='@woocommerce/plugin-woocommerce...' --frozen-lockfile --config.dedupe-peer-dependents=false pnpm --filter='@woocommerce/plugin-woocommerce' build echo '##[endgroup]' title "##[group]Benchmarking baseline" - for ROUND in {1..3}; do - # Each round takes ~2 minutes, running for ~6 minutes presumable generates good enough baseline (cached for 1 week in CI). - echo "$(date +"%T"): Round $ROUND of 3" - RESULTS_ID="editor_${BASE_SHA}_round-$ROUND" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor - RESULTS_ID="product-editor_${BASE_SHA}_round-$ROUND" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor - done + RESULTS_ID="editor_${BASE_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics editor + RESULTS_ID="product-editor_${BASE_SHA}_round-1" pnpm --filter="@woocommerce/plugin-woocommerce" test:metrics product-editor echo '##[endgroup]' - # This step is intended for running the script locally. - title "##[group]Restoring codebase state back to head" - git -c core.hooksPath=/dev/null checkout --quiet $HEAD_BRANCH > /dev/null && echo 'On' $(git rev-parse HEAD) - pnpm install --frozen-lockfile > /dev/null & - pnpm run --if-present clean:build - echo '##[endgroup]' + # title "##[group]Restoring codebase state back to head" + # git -c core.hooksPath=/dev/null checkout --quiet $HEAD_BRANCH > /dev/null && echo 'On' $(git rev-parse HEAD) + # pnpm install --frozen-lockfile > /dev/null & + # pnpm run --if-present clean:build + # echo '##[endgroup]' fi title "##[group]Processing reports under $ARTIFACTS_PATH" diff --git a/plugins/woocommerce/changelog/update-49550-perfromance-job-no-caching b/plugins/woocommerce/changelog/update-49550-perfromance-job-no-caching new file mode 100644 index 00000000000..ec3f1d153f0 --- /dev/null +++ b/plugins/woocommerce/changelog/update-49550-perfromance-job-no-caching @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +CI: omit caching baseline results in perfromance metrics job. diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index 8d7270267ce..a4f6172c0b6 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -483,7 +483,6 @@ "start": "test:perf:ci-setup" }, "events": [ - "pull_request", "push" ] }, From 53a56cca7724ad0e9610ef41004c97b4efaf8f62 Mon Sep 17 00:00:00 2001 From: Adrian Duffell <9312929+adrianduffell@users.noreply.github.com> Date: Tue, 1 Oct 2024 15:40:43 +0800 Subject: [PATCH 22/26] Add core feature for site visibility badge (#51664) * Add site_visibility_badge feature * Update feature check on badge Previously the WooCommerce Admin LYS feature flag was used, however this is slated to be removed in an upcoming release. This is updated to use the core level feature flag for the badge. * Add changelog * Fix lint issues * Revert "Fix lint issues" This reverts commit cf05d2b74fe54d752c6d76cc00c9315ec9ff8b81. * Lint fixes * lint --- .../woocommerce/changelog/add-badge-feature | 4 +++ .../ComingSoon/ComingSoonAdminBarBadge.php | 7 ++--- .../Internal/Features/FeaturesController.php | 26 ++++++++++++++----- 3 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 plugins/woocommerce/changelog/add-badge-feature diff --git a/plugins/woocommerce/changelog/add-badge-feature b/plugins/woocommerce/changelog/add-badge-feature new file mode 100644 index 00000000000..29ece22fdc4 --- /dev/null +++ b/plugins/woocommerce/changelog/add-badge-feature @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Add core feature for site visibility badge diff --git a/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonAdminBarBadge.php b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonAdminBarBadge.php index 082944cd3b6..140b49a3a6d 100644 --- a/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonAdminBarBadge.php +++ b/plugins/woocommerce/src/Internal/ComingSoon/ComingSoonAdminBarBadge.php @@ -4,7 +4,8 @@ declare( strict_types = 1 ); namespace Automattic\WooCommerce\Internal\ComingSoon; -use Automattic\WooCommerce\Admin\Features\Features; +use Automattic\WooCommerce\Utilities\FeaturesUtil; + /** * Adds hooks to add a badge to the WordPress admin bar showing site visibility. @@ -30,7 +31,7 @@ class ComingSoonAdminBarBadge { */ public function site_visibility_badge( $wp_admin_bar ) { // Early exit if LYS feature is disabled. - if ( ! Features::is_enabled( 'launch-your-store' ) ) { + if ( ! FeaturesUtil::feature_is_enabled( 'site_visibility_badge' ) ) { return; } @@ -68,7 +69,7 @@ class ComingSoonAdminBarBadge { */ public function output_css() { // Early exit if LYS feature is disabled. - if ( ! Features::is_enabled( 'launch-your-store' ) ) { + if ( ! FeaturesUtil::feature_is_enabled( 'site_visibility_badge' ) ) { return; } diff --git a/plugins/woocommerce/src/Internal/Features/FeaturesController.php b/plugins/woocommerce/src/Internal/Features/FeaturesController.php index e777fc63fb8..518c1e7027f 100644 --- a/plugins/woocommerce/src/Internal/Features/FeaturesController.php +++ b/plugins/woocommerce/src/Internal/Features/FeaturesController.php @@ -164,7 +164,7 @@ class FeaturesController { private function get_feature_definitions() { if ( empty( $this->features ) ) { $legacy_features = array( - 'analytics' => array( + 'analytics' => array( 'name' => __( 'Analytics', 'woocommerce' ), 'description' => __( 'Enable WooCommerce Analytics', 'woocommerce' ), 'option_key' => Analytics::TOGGLE_OPTION_NAME, @@ -173,7 +173,7 @@ class FeaturesController { 'disable_ui' => false, 'is_legacy' => true, ), - 'product_block_editor' => array( + 'product_block_editor' => array( 'name' => __( 'New product editor', 'woocommerce' ), 'description' => __( 'Try the new product editor (Beta)', 'woocommerce' ), 'is_experimental' => true, @@ -194,13 +194,13 @@ class FeaturesController { return $string; }, ), - 'cart_checkout_blocks' => array( + 'cart_checkout_blocks' => array( 'name' => __( 'Cart & Checkout Blocks', 'woocommerce' ), 'description' => __( 'Optimize for faster checkout', 'woocommerce' ), 'is_experimental' => false, 'disable_ui' => true, ), - 'marketplace' => array( + 'marketplace' => array( 'name' => __( 'Marketplace', 'woocommerce' ), 'description' => __( 'New, faster way to find extensions and themes for your WooCommerce store', @@ -213,7 +213,7 @@ class FeaturesController { ), // Marked as a legacy feature to avoid compatibility checks, which aren't really relevant to this feature. // https://github.com/woocommerce/woocommerce/pull/39701#discussion_r1376976959. - 'order_attribution' => array( + 'order_attribution' => array( 'name' => __( 'Order Attribution', 'woocommerce' ), 'description' => __( 'Enable this feature to track and credit channels and campaigns that contribute to orders on your site', @@ -224,7 +224,19 @@ class FeaturesController { 'is_legacy' => true, 'is_experimental' => false, ), - 'hpos_fts_indexes' => array( + 'site_visibility_badge' => array( + 'name' => __( 'Site visibility badge', 'woocommerce' ), + 'description' => __( + 'Enable the site visibility badge in the WordPress admin bar', + 'woocommerce' + ), + 'enabled_by_default' => true, + 'disable_ui' => false, + 'is_legacy' => true, + 'is_experimental' => false, + 'disabled' => false, + ), + 'hpos_fts_indexes' => array( 'name' => __( 'HPOS Full text search indexes', 'woocommerce' ), 'description' => __( 'Create and use full text search indexes for orders. This feature only works with high-performance order storage.', @@ -235,7 +247,7 @@ class FeaturesController { 'is_legacy' => true, 'option_key' => CustomOrdersTableController::HPOS_FTS_INDEX_OPTION, ), - 'remote_logging' => array( + 'remote_logging' => array( 'name' => __( 'Remote Logging', 'woocommerce' ), 'description' => __( 'Enable this feature to log errors and related data to Automattic servers for debugging purposes and to improve WooCommerce', From 9b925739fa617ed9f4829fca6ab8c8579e9d8713 Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Tue, 1 Oct 2024 09:55:54 +0200 Subject: [PATCH 23/26] [dev] CI: fix git checkout error in perfromance metrics job (#51805) --- .github/workflows/scripts/run-metrics.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scripts/run-metrics.sh b/.github/workflows/scripts/run-metrics.sh index bc60ead0a80..4dfb4b295df 100755 --- a/.github/workflows/scripts/run-metrics.sh +++ b/.github/workflows/scripts/run-metrics.sh @@ -55,7 +55,7 @@ if [ "$GITHUB_EVENT_NAME" == "push" ] || [ "$GITHUB_EVENT_NAME" == "pull_request echo '##[endgroup]' title "##[group]Building baseline" - git -c core.hooksPath=/dev/null checkout --quiet $BASE_SHA > /dev/null && echo 'On' $(git rev-parse HEAD) + ( git -c core.hooksPath=/dev/null checkout --quiet $BASE_SHA > /dev/null || git reset --hard $BASE_SHA ) && echo 'On' $(git rev-parse HEAD) pnpm run --if-present clean:build & pnpm install --filter='@woocommerce/plugin-woocommerce...' --frozen-lockfile --config.dedupe-peer-dependents=false pnpm --filter='@woocommerce/plugin-woocommerce' build From c17d8519fe1dc3b0ce42efdbea5edf53c540834f Mon Sep 17 00:00:00 2001 From: Vladimir Reznichenko Date: Tue, 1 Oct 2024 10:11:06 +0200 Subject: [PATCH 24/26] [dev] CI: fix git checkout error in perfromance metrics job (take 2) (#51807) --- .github/workflows/scripts/run-metrics.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scripts/run-metrics.sh b/.github/workflows/scripts/run-metrics.sh index 4dfb4b295df..521c042a980 100755 --- a/.github/workflows/scripts/run-metrics.sh +++ b/.github/workflows/scripts/run-metrics.sh @@ -51,7 +51,7 @@ if [ "$GITHUB_EVENT_NAME" == "push" ] || [ "$GITHUB_EVENT_NAME" == "pull_request title "Skipping benchmarking baseline as benchmarking results already available under $ARTIFACTS_PATH" else title "##[group]Checkout baseline" - git fetch --no-tags --quiet origin trunk + git fetch --no-tags --quiet --unshallow origin trunk echo '##[endgroup]' title "##[group]Building baseline" From 516b55b734afa4e87821627bfa63a1964e919864 Mon Sep 17 00:00:00 2001 From: Roy Ho Date: Tue, 1 Oct 2024 06:56:59 -0700 Subject: [PATCH 25/26] Ensure Product Collection filter names are consistently capitalized (#51779) * Ensure Product Collection filter names are consistently capitalized * Add changefile(s) from automation for the following project(s): woocommerce-blocks * Update e2e tests * Add additional types and return empty string --------- Co-authored-by: github-actions --- .../hand-picked-products-control.tsx | 4 ++-- .../inspector-controls/stock-status-control.tsx | 4 ++-- .../taxonomy-controls/index.tsx | 16 +++++++++++++++- .../inspector-controls.block_theme.spec.ts | 4 ++-- .../product-collection.page.ts | 2 +- .../51779-product-collection-standardize-names | 4 ++++ 6 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 plugins/woocommerce/changelog/51779-product-collection-standardize-names diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/hand-picked-products-control.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/hand-picked-products-control.tsx index 52b1174a5ae..436782fd6a1 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/hand-picked-products-control.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/hand-picked-products-control.tsx @@ -151,7 +151,7 @@ export const HandPickedProductsControlField = ( { return ( !! selectedProductIds?.length } onDeselect={ deselectCallback } resetAllFilter={ deselectCallback } diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/stock-status-control.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/stock-status-control.tsx index 86647a9bc9d..dfda601e68d 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/stock-status-control.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/stock-status-control.tsx @@ -49,7 +49,7 @@ const StockStatusControl = ( props: QueryControlProps ) => { return ( ! fastDeepEqual( query.woocommerceStockStatus, @@ -61,7 +61,7 @@ const StockStatusControl = ( props: QueryControlProps ) => { isShownByDefault > { const woocommerceStockStatus = statusLabels .map( getStockStatusIdByLabel ) diff --git a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/taxonomy-controls/index.tsx b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/taxonomy-controls/index.tsx index 8cf8733b659..cb03225e019 100644 --- a/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/taxonomy-controls/index.tsx +++ b/plugins/woocommerce-blocks/assets/js/blocks/product-collection/edit/inspector-controls/taxonomy-controls/index.tsx @@ -48,6 +48,20 @@ function TaxonomyControls( { return null; } + /** + * Normalize the name so first letter of every word is capitalized. + */ + const normalizeName = ( name: string | undefined | null ) => { + if ( ! name ) { + return ''; + } + + return name + .split( ' ' ) + .map( ( word ) => word.charAt( 0 ).toUpperCase() + word.slice( 1 ) ) + .join( ' ' ); + }; + return ( <> { taxonomies.map( ( taxonomy: Taxonomy ) => { @@ -75,7 +89,7 @@ function TaxonomyControls( { return ( termIds.length } onDeselect={ deselectCallback } resetAllFilter={ deselectCallback } diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/inspector-controls.block_theme.spec.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/inspector-controls.block_theme.spec.ts index d240f7f7d55..50a5a6397eb 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/inspector-controls.block_theme.spec.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/inspector-controls.block_theme.spec.ts @@ -97,9 +97,9 @@ test.describe( 'Product Collection: Inspector Controls', () => { } ) => { await pageObject.createNewPostAndInsertBlock(); - await pageObject.addFilter( 'Show Hand-picked Products' ); + await pageObject.addFilter( 'Show Hand-picked' ); - const filterName = 'Hand-picked Products'; + const filterName = 'Hand-picked'; await pageObject.setFilterComboboxValue( filterName, [ 'Album' ] ); await expect( pageObject.products ).toHaveCount( 1 ); diff --git a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts index 3b94f037eeb..598411e881c 100644 --- a/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts +++ b/plugins/woocommerce-blocks/tests/e2e/tests/product-collection/product-collection.page.ts @@ -396,7 +396,7 @@ class ProductCollectionPage { async addFilter( name: - | 'Show Hand-picked Products' + | 'Show Hand-picked' | 'Keyword' | 'Show product categories' | 'Show product tags' diff --git a/plugins/woocommerce/changelog/51779-product-collection-standardize-names b/plugins/woocommerce/changelog/51779-product-collection-standardize-names new file mode 100644 index 00000000000..d8e324eb442 --- /dev/null +++ b/plugins/woocommerce/changelog/51779-product-collection-standardize-names @@ -0,0 +1,4 @@ +Significance: patch +Type: tweak + +Ensure Product Collection filter names are consistently capitalized. \ No newline at end of file From b0e0a733d6ae91e98076785be22e6105ae25f6b7 Mon Sep 17 00:00:00 2001 From: Christopher Allford <6451942+ObliviousHarmony@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:32:24 -0700 Subject: [PATCH 26/26] WooCommerce Asset Build Aliases (#43433) --- .../changelog/43433-add-plugin-component-build-aliases | 4 ++++ plugins/woocommerce/package.json | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 plugins/woocommerce/changelog/43433-add-plugin-component-build-aliases diff --git a/plugins/woocommerce/changelog/43433-add-plugin-component-build-aliases b/plugins/woocommerce/changelog/43433-add-plugin-component-build-aliases new file mode 100644 index 00000000000..52cf7ac1f9b --- /dev/null +++ b/plugins/woocommerce/changelog/43433-add-plugin-component-build-aliases @@ -0,0 +1,4 @@ +Significance: patch +Type: dev +Comment: These are changes to build commands. + diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index a4f6172c0b6..d836ae9c82c 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -11,6 +11,10 @@ "license": "GPL-3.0+", "scripts": { "build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'", + "build:admin": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"@woocommerce/admin-library...\" --filter=\"$npm_package_name\" '/^build:project:.*$/'", + "build:blocks": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"@woocommerce/block-library...\" --filter=\"$npm_package_name\" '/^build:project:.*$/'", + "build:classic-assets": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"@woocommerce/classic-assets...\" --filter=\"$npm_package_name\" '/^build:project:.*$/'", + "build:zip": "./bin/build-zip.sh", "build:project": "pnpm --if-present '/^build:project:.*$/'", "build:project:copy-assets:legacy": "wireit", "build:project:copy-assets:admin": "wireit", @@ -74,6 +78,9 @@ "test:unit:env:watch": "pnpm test:php:env:watch", "update-wp-env": "php ./tests/e2e-pw/bin/update-wp-env.php", "watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'", + "watch:build:admin": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"@woocommerce/admin-library...\" --filter=\"$npm_package_name\" --parallel '/^watch:build:project:.*$/'", + "watch:build:blocks": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"@woocommerce/block-library...\" --filter=\"$npm_package_name\" --parallel '/^watch:build:project:.*$/'", + "watch:build:classic-assets": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"@woocommerce/classic-assets...\" --filter=\"$npm_package_name\" --parallel '/^watch:build:project:.*$/'", "watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'", "watch:build:project:copy-assets": "wireit", "wp-env": "wp-env"