Commit fabe01e5b4a for woocommerce
commit fabe01e5b4a7e855ff692999d32a0c5d822442c4
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Thu Jun 4 16:56:48 2026 +0200
Fix Add to Cart Button not working on Products (Beta) block (#65511)
* Add changelog
* Fix Add to Cart Button inside the Products (Query) block
* Linting
diff --git a/plugins/woocommerce/changelog/fix-products-beta-add-to-cart-button b/plugins/woocommerce/changelog/fix-products-beta-add-to-cart-button
new file mode 100644
index 00000000000..f4e78f61325
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-products-beta-add-to-cart-button
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix Add to Cart Button not working on Products (Beta) block
diff --git a/plugins/woocommerce/client/blocks/tests/e2e/tests/products/products.block_theme.spec.ts b/plugins/woocommerce/client/blocks/tests/e2e/tests/products/products.block_theme.spec.ts
index a376bd354d5..6c4427a6f9a 100644
--- a/plugins/woocommerce/client/blocks/tests/e2e/tests/products/products.block_theme.spec.ts
+++ b/plugins/woocommerce/client/blocks/tests/e2e/tests/products/products.block_theme.spec.ts
@@ -97,6 +97,51 @@ test.describe( `${ blockData.name } Block `, () => {
await expect( advancedFilterOption ).toBeVisible();
await expect( inheritQueryFromTemplateOption ).toBeVisible();
} );
+
+ test( 'product button should add product to the cart when inheriting query from template', async ( {
+ admin,
+ editor,
+ page,
+ frontendUtils,
+ } ) => {
+ await admin.visitSiteEditor( {
+ postId: `${ BLOCK_THEME_SLUG }//archive-product`,
+ postType: 'wp_template',
+ canvas: 'edit',
+ } );
+ await editor.setContent( '' );
+ await insertProductsQuery( editor );
+ await editor.saveSiteEditorEntities( {
+ isOnlyCurrentEntityDirty: true,
+ } );
+ await frontendUtils.goToShop();
+
+ const addToCartButton = page.getByRole( 'button', {
+ name: 'Add to cart: “Single”',
+ } );
+ await addToCartButton.click();
+ await expect( addToCartButton ).toHaveText( '1 in cart' );
+ const cartLink = page.getByRole( 'link', { name: 'View cart' } );
+ await expect( cartLink ).toBeVisible();
+ } );
+
+ test( 'product button should add product to the cart when not inheriting query from template', async ( {
+ admin,
+ editor,
+ page,
+ } ) => {
+ await admin.createNewPost();
+ await insertProductsQuery( editor, { inherit: false } );
+ await editor.publishAndVisitPost();
+
+ const addToCartButton = page.getByRole( 'button', {
+ name: 'Add to cart: “Single”',
+ } );
+ await addToCartButton.click();
+ await expect( addToCartButton ).toHaveText( '1 in cart' );
+ const cartLink = page.getByRole( 'link', { name: 'View cart' } );
+ await expect( cartLink ).toBeVisible();
+ } );
} );
for ( const {
diff --git a/plugins/woocommerce/client/blocks/tests/e2e/tests/products/utils.ts b/plugins/woocommerce/client/blocks/tests/e2e/tests/products/utils.ts
index e5a71b72f26..dec142f1903 100644
--- a/plugins/woocommerce/client/blocks/tests/e2e/tests/products/utils.ts
+++ b/plugins/woocommerce/client/blocks/tests/e2e/tests/products/utils.ts
@@ -38,13 +38,18 @@ export const productQueryInnerBlocksTemplate = [
{ name: 'core/query-no-results' },
];
-export const insertProductsQuery = async ( editor: Editor ) => {
+export const insertProductsQuery = async (
+ editor: Editor,
+ options: {
+ inherit?: boolean;
+ } = {}
+) => {
await editor.insertBlock( {
name: 'core/query',
attributes: {
namespace: 'woocommerce/product-query',
query: {
- inherit: true,
+ inherit: options.inherit ?? true,
},
},
innerBlocks: productQueryInnerBlocksTemplate,
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/ProductQuery.php b/plugins/woocommerce/src/Blocks/BlockTypes/ProductQuery.php
index 68f0b068e12..c88541c7ad2 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/ProductQuery.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/ProductQuery.php
@@ -82,6 +82,12 @@ class ProductQuery extends AbstractBlock {
10,
2
);
+ add_filter(
+ 'render_block_core/post-template',
+ array( $this, 'add_iapi_context' ),
+ 10,
+ 2
+ );
add_filter( 'rest_product_query', array( $this, 'update_rest_query' ), 10, 2 );
add_filter( 'rest_product_collection_params', array( $this, 'extend_rest_query_allowed_params' ), 10, 1 );
}
@@ -166,6 +172,86 @@ class ProductQuery extends AbstractBlock {
return $block_content;
}
+ /**
+ * Add product interactivity directives to each loop item in Products (Beta).
+ *
+ * @internal
+ *
+ * @param string $block_content Rendered block HTML.
+ * @param array $block Parsed block data.
+ * @return string
+ */
+ public function add_iapi_context( string $block_content, array $block ): string {
+ $namespace = $block['attrs']['__woocommerceNamespace'] ?? '';
+ if ( 'woocommerce/product-query/product-template' !== $namespace ) {
+ return $block_content;
+ }
+
+ $processor = new \WP_HTML_Tag_Processor( $block_content );
+
+ while (
+ $processor->next_tag(
+ array(
+ 'tag_name' => 'LI',
+ 'class_name' => 'wp-block-post',
+ )
+ )
+ ) {
+ $class_attribute = $processor->get_attribute( 'class' );
+ $product_id = $this->get_product_id_from_class_attribute( is_string( $class_attribute ) ? $class_attribute : '' );
+
+ if ( ! $product_id || 'product' !== get_post_type( $product_id ) ) {
+ continue;
+ }
+
+ wc_interactivity_api_load_product( 'I acknowledge that using experimental APIs means my theme or plugin will inevitably break in the next version of WooCommerce', $product_id );
+
+ $product_context = array(
+ 'productId' => $product_id,
+ 'variationId' => null,
+ );
+
+ $processor->set_attribute( 'data-wp-interactive', 'woocommerce/products' );
+ $processor->set_attribute(
+ 'data-wp-context',
+ 'woocommerce/products::' . wp_json_encode( $product_context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP )
+ );
+ $processor->set_attribute( 'data-wp-key', 'product-item-' . $product_id );
+ }
+
+ return $processor->get_updated_html();
+ }
+
+ /**
+ * Extract a post ID from the `post-{id}` class WordPress adds to loop items.
+ *
+ * @internal
+ *
+ * @param string $class_attribute The element class attribute.
+ * @return int|null
+ */
+ private function get_product_id_from_class_attribute( $class_attribute ): ?int {
+ if ( '' === $class_attribute ) {
+ return null;
+ }
+
+ $classes = explode( ' ', $class_attribute );
+
+ foreach ( $classes as $class ) {
+ if ( ! str_starts_with( $class, 'post-' ) ) {
+ continue;
+ }
+
+ $product_id = (int) substr( $class, 5 );
+
+ if ( $product_id > 0 ) {
+ return $product_id;
+ }
+ }
+
+ return null;
+ }
+
/**
* Update the query for the product query block.
*