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,