Commit 7d11478585 for woocommerce
commit 7d11478585251e3f8772c0a6eeed8e886a1e970a
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Mon Nov 3 09:39:37 2025 +0100
Add to Cart + Options: support the Product block when a variation is selected (#61639)
* Add to Cart + Options: support the Product block when a variation is selected
* Add changelog file
* Add test
* Cleanup getProductData()
* Cleanup getProductData() (II)
* Improve how max and step are calculated for products
* Fix wrong check
diff --git a/plugins/woocommerce/changelog/fix-56289-add-to-cart-with-options-variation b/plugins/woocommerce/changelog/fix-56289-add-to-cart-with-options-variation
new file mode 100644
index 0000000000..7af364c0c9
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-56289-add-to-cart-with-options-variation
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Add to Cart + Options: support the Product block when a variation is selected
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/frontend.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/frontend.ts
index 8e4aaa5d0c..95e41c80f5 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/frontend.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/frontend.ts
@@ -6,7 +6,6 @@ import type {
Store as WooCommerce,
SelectedAttributes,
ProductData,
- VariationData,
WooCommerceConfig,
} from '@woocommerce/stores/woocommerce/cart';
import '@woocommerce/stores/woocommerce/product-data';
@@ -56,51 +55,46 @@ export const getProductData = (
id: number,
selectedAttributes: SelectedAttributes[]
): NormalizedProductData | NormalizedVariationData | null => {
- let productId = id;
- let productData;
-
const { products } = getConfig( 'woocommerce' ) as WooCommerceConfig;
- let type: ProductData[ 'type' ] | 'variation' = 'simple';
- if ( selectedAttributes && selectedAttributes.length > 0 ) {
- if ( ! products || ! products[ id ] ) {
- return null;
- }
- const variations = products[ id ].variations;
+ if ( ! products || ! products[ id ] ) {
+ return null;
+ }
+
+ let product = {
+ id,
+ ...products[ id ],
+ } as ProductData & { id: number };
+
+ if (
+ product.type === 'variable' &&
+ selectedAttributes &&
+ selectedAttributes.length > 0
+ ) {
const matchedVariation = getMatchedVariation(
- variations,
+ product.variations,
selectedAttributes
);
- if ( matchedVariation?.variation_id ) {
- productId = matchedVariation.variation_id;
- productData = products?.[ id ]?.variations?.[
- matchedVariation?.variation_id
- ] as VariationData;
- type = 'variation';
+ if ( matchedVariation ) {
+ product = {
+ ...matchedVariation,
+ id: matchedVariation.variation_id,
+ type: 'variation',
+ };
}
- } else {
- productData = products?.[ productId ] as ProductData;
- type = productData?.type;
- }
-
- if ( typeof productData !== 'object' || productData === null ) {
- return null;
}
- const min = typeof productData.min === 'number' ? productData.min : 1;
+ const min = typeof product.min === 'number' ? product.min : 1;
const max =
- typeof productData.max === 'number' && productData.max >= 1
- ? productData.max
- : Infinity;
- const step = productData.step || 1;
+ typeof product.max === 'number' ? Math.max( product.max, 0 ) : Infinity;
+ const step =
+ typeof product.step === 'number' && product.step > 0 ? product.step : 1;
return {
- id: productId,
- ...productData,
+ ...product,
min,
max,
step,
- type,
};
};
diff --git a/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts b/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts
index 86b98a0733..c88739ba28 100644
--- a/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts
+++ b/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts
@@ -943,6 +943,26 @@ test.describe( 'Add to Cart + Options Block', () => {
).toBeVisible();
} );
+ test( 'allows adding variations to cart when inside the Product block', async ( {
+ page,
+ pageObject,
+ } ) => {
+ await pageObject.createPostWithProductBlock(
+ 'hoodie',
+ 'hoodie-blue-yes'
+ );
+
+ const addToCartButton = page.getByRole( 'button', {
+ name: 'Add to cart',
+ } );
+
+ await addToCartButton.click();
+
+ await expect(
+ page.getByRole( 'button', { name: '1 in cart', exact: true } )
+ ).toBeVisible();
+ } );
+
test( 'allows adding grouped products to cart when inside the Product block', async ( {
page,
pageObject,
diff --git a/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.page.ts b/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.page.ts
index 7cce872ffa..e1edb1df94 100644
--- a/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.page.ts
+++ b/plugins/woocommerce/client/blocks/tests/e2e/tests/add-to-cart-with-options/add-to-cart-with-options.page.ts
@@ -89,7 +89,7 @@ class AddToCartWithOptionsPage {
await this.updateAddToCartWithOptionsBlock();
}
- async createPostWithProductBlock( product: string ) {
+ async createPostWithProductBlock( product: string, variation?: string ) {
await this.admin.createNewPost();
await this.editor.insertBlock( { name: 'woocommerce/single-product' } );
const singleProductBlock = await this.editor.getBlockByName(
@@ -101,6 +101,13 @@ class AddToCartWithOptionsPage {
.nth( 0 )
.click();
+ if ( variation ) {
+ await singleProductBlock
+ .locator( `input[type="radio"][value="${ variation }"]` )
+ .nth( 0 )
+ .click();
+ }
+
await singleProductBlock.getByText( 'Done' ).click();
await this.updateAddToCartWithOptionsBlock();
diff --git a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/AddToCartWithOptions.php b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/AddToCartWithOptions.php
index d1f8f943bf..25cbbb3d38 100644
--- a/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/AddToCartWithOptions.php
+++ b/plugins/woocommerce/src/Blocks/BlockTypes/AddToCartWithOptions/AddToCartWithOptions.php
@@ -145,7 +145,8 @@ class AddToCartWithOptions extends AbstractBlock {
return '';
}
- $product_type = $product->get_type();
+ // For variations, we display the simple product form.
+ $product_type = ProductType::VARIATION === $product->get_type() ? ProductType::SIMPLE : $product->get_type();
$slug = $product_type . '-product-add-to-cart-with-options';
@@ -288,9 +289,21 @@ class AddToCartWithOptions extends AbstractBlock {
),
)
);
- }
+ } elseif ( $product->is_type( ProductType::VARIATION ) ) {
+ $variation_attributes = $product->get_variation_attributes();
+ $formatted_attributes = array_map(
+ function ( $key, $value ) {
+ return [
+ 'attribute' => $key,
+ 'value' => $value,
+ ];
+ },
+ array_keys( $variation_attributes ),
+ $variation_attributes
+ );
- if ( $product->is_type( ProductType::GROUPED ) ) {
+ $context['selectedAttributes'] = $formatted_attributes;
+ } elseif ( $product->is_type( ProductType::GROUPED ) ) {
// Add context for purchasable child products.
$children_product_data = array();
foreach ( $product->get_children() as $child_product_id ) {
@@ -608,7 +621,7 @@ class AddToCartWithOptions extends AbstractBlock {
*
* @since 9.9.0
*/
- do_action( 'woocommerce_' . $product->get_type() . '_add_to_cart' );
+ do_action( 'woocommerce_' . $product_type . '_add_to_cart' );
add_action( 'woocommerce_' . $product_type . '_add_to_cart', $add_to_cart_fn, 30 );
}
@@ -625,7 +638,7 @@ class AddToCartWithOptions extends AbstractBlock {
*
* @since 9.7.0
*/
- do_action( 'woocommerce_' . $product->get_type() . '_add_to_cart' );
+ do_action( 'woocommerce_' . $product_type . '_add_to_cart' );
$wrapper_attributes = array(
'class' => $classes,