Commit 724ad0555ca for woocommerce
commit 724ad0555ca8ea1c66932896f0824a32db2fdc26
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date: Wed Mar 4 11:42:25 2026 +0100
Add to Cart + Options: Fix adding variable products to cart with 'any' as an attribute (#63526)
* Add to Cart + Options: Fix adding variable products to cart with 'any' as an attribute
* Add changelog file
* Fix tests
* Implement CodeRabbit suggestions
diff --git a/plugins/woocommerce/changelog/fix-add-to-cart-with-options-variation-with-any b/plugins/woocommerce/changelog/fix-add-to-cart-with-options-variation-with-any
new file mode 100644
index 00000000000..0717df629f3
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-add-to-cart-with-options-variation-with-any
@@ -0,0 +1,5 @@
+Significance: patch
+Type: fix
+Comment: Add to Cart + Options: Fix adding variable products to cart with 'any' as an attribute
+
+
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/stores/woocommerce/cart.ts b/plugins/woocommerce/client/blocks/assets/js/base/stores/woocommerce/cart.ts
index ba1f5161368..0ca5a694fd3 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/stores/woocommerce/cart.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/base/stores/woocommerce/cart.ts
@@ -242,8 +242,7 @@ const doesCartItemMatchAttributes = (
selectedAttributes.some( ( item: SelectedAttributes ) => {
return (
item.attribute === raw_attribute &&
- ( item.value.toLowerCase() === value.toLowerCase() ||
- ( item.value && value === '' ) ) // Handle "any" attribute type
+ item.value.toLowerCase() === value?.toLowerCase()
);
} )
);
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/utils/variations/attribute-matching.ts b/plugins/woocommerce/client/blocks/assets/js/base/utils/variations/attribute-matching.ts
index 4daf7b9977a..f824ff9b3f7 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/utils/variations/attribute-matching.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/base/utils/variations/attribute-matching.ts
@@ -90,10 +90,11 @@ export const findMatchingVariation = (
).toLowerCase() === attrNameLower
);
- // If variation attribute has empty value, it accepts "Any" value.
- if ( attr.value === '' ) {
+ // If variation attribute is null, it accepts "Any" value.
+ if ( attr.value === null ) {
return (
- selectedAttr !== undefined && selectedAttr.value !== ''
+ selectedAttr !== undefined &&
+ selectedAttr.value !== null
);
}
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/utils/variations/does-cart-item-match-attributes.ts b/plugins/woocommerce/client/blocks/assets/js/base/utils/variations/does-cart-item-match-attributes.ts
index e71fe60b53e..d6878faf126 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/utils/variations/does-cart-item-match-attributes.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/base/utils/variations/does-cart-item-match-attributes.ts
@@ -38,8 +38,7 @@ export const doesCartItemMatchAttributes = (
selectedAttributes.some( ( item: SelectedAttributes ) => {
return (
attributeNamesMatch( item.attribute, raw_attribute ) &&
- ( item.value.toLowerCase() === value.toLowerCase() ||
- ( item.value && value === '' ) ) // Handle "any" attribute type
+ item.value.toLowerCase() === value?.toLowerCase()
);
} )
);
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/utils/variations/test/attribute-matching.ts b/plugins/woocommerce/client/blocks/assets/js/base/utils/variations/test/attribute-matching.ts
index f3eb3c32459..763800f188e 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/utils/variations/test/attribute-matching.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/base/utils/variations/test/attribute-matching.ts
@@ -157,7 +157,7 @@ describe( 'findMatchingVariation', () => {
{
id: 201,
attributes: [
- { name: 'Color', value: '' }, // "Any" color
+ { name: 'Color', value: null }, // "Any" color
{ name: 'Size', value: 'Small' },
],
},
@@ -165,7 +165,7 @@ describe( 'findMatchingVariation', () => {
id: 202,
attributes: [
{ name: 'Color', value: 'Blue' },
- { name: 'Size', value: '' }, // "Any" size
+ { name: 'Size', value: null }, // "Any" size
],
},
],
@@ -183,9 +183,9 @@ describe( 'findMatchingVariation', () => {
expect( result?.id ).toBe( 201 );
} );
- it( 'does not match "Any" attribute when selected value is empty', () => {
+ it( 'does not match "Any" attribute when selected value is null', () => {
const selectedAttributes = [
- { attribute: 'Color', value: '' },
+ { attribute: 'Color', value: null },
{ attribute: 'Size', value: 'Small' },
];
expect(
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts
index 069a35bcb28..ca6b9c77b23 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts
@@ -118,7 +118,7 @@ const isAttributeValueValid = ( {
// Skip variations that don't match the current attribute value.
if (
variationAttrValue !== attributeValue &&
- variationAttrValue !== '' // "" is used for "any".
+ variationAttrValue !== null // null is used for "any".
) {
return false;
}
@@ -138,11 +138,11 @@ const isAttributeValueValid = ( {
) {
return true;
}
- // If the current available variation has an empty value
+ // If the current available variation has a null value
// (matching any), count it if it refers to a different
// attribute or the attribute it refers matches the current
// selection.
- if ( availableVariationAttributeValue === '' ) {
+ if ( availableVariationAttributeValue === null ) {
if (
! attributeNamesMatch(
selectedAttribute.attribute,
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 a9f93a204dd..372deef19ec 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
@@ -329,6 +329,48 @@ test.describe( 'Add to Cart + Options Block', () => {
} );
} );
+ test( 'allows adding variable products that have "any" as a variation attribute', async ( {
+ page,
+ pageObject,
+ editor,
+ } ) => {
+ await pageObject.updateSingleProductTemplate();
+
+ await editor.saveSiteEditorEntities( {
+ isOnlyCurrentEntityDirty: true,
+ } );
+
+ await page.goto( '/product/v-neck-t-shirt/' );
+
+ // The radio input is visually hidden and, thus, not clickable. That's
+ // why we need to select the <label> instead.
+ const colorBlueOption = page.locator( 'label:has-text("Blue")' );
+ const colorRedOption = page.locator( 'label:has-text("Red")' );
+ const sizeLargeOption = page.locator( 'label:has-text("Large")' );
+
+ await colorBlueOption.click();
+ await sizeLargeOption.click();
+
+ // We use the Add to Cart + Options class to make sure we don't select
+ // the Add to Cart button from the Related Products block.
+ const addToCartButton = page
+ .locator( '.wp-block-add-to-cart-with-options' )
+ .getByRole( 'button', { name: 'Add to cart' } );
+
+ // Note: The button is always enabled for accessibility reasons.
+ // Instead, we check directly for the "disabled" class, which grays
+ // out the button.
+ await expect( addToCartButton ).not.toHaveClass( /\bdisabled\b/ );
+
+ await addToCartButton.click();
+
+ await expect( page.getByText( '1 in cart' ) ).toBeVisible();
+
+ await colorRedOption.click();
+
+ await expect( page.getByText( '1 in cart' ) ).toBeHidden();
+ } );
+
test( 'allows adding grouped products to cart', async ( {
page,
pageObject,