Commit 31360e0002 for woocommerce
commit 31360e0002d674028941ef8b0f964d02d2721241
Author: Raluca Stan <ralucastn@gmail.com>
Date: Thu Dec 18 22:04:09 2025 +0200
[Cart shortcode] Fix undo cart item removal action (#62472)
* Added a check for $_GET['removed_item'] to skip cleanup when the undo link is being displayed.
* Add changelog
* Fix PHP Stan errors
* update baseline PHPstan
* Add E2E test for cart removal Undo in shortcode cart
* Improve test and check for undo link reset after user navigating away
diff --git a/plugins/woocommerce/changelog/wooplug-5398-undo-item-deletion-from-cart-when-there-was-1-item-in-the b/plugins/woocommerce/changelog/wooplug-5398-undo-item-deletion-from-cart-when-there-was-1-item-in-the
new file mode 100644
index 0000000000..2e188ee51a
--- /dev/null
+++ b/plugins/woocommerce/changelog/wooplug-5398-undo-item-deletion-from-cart-when-there-was-1-item-in-the
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix undo button after cart item removal in the cart shortcode
diff --git a/plugins/woocommerce/includes/class-wc-cart-session.php b/plugins/woocommerce/includes/class-wc-cart-session.php
index fbc9c7e680..426174b8cd 100644
--- a/plugins/woocommerce/includes/class-wc-cart-session.php
+++ b/plugins/woocommerce/includes/class-wc-cart-session.php
@@ -722,15 +722,22 @@ final class WC_Cart_Session {
/**
* Removes items from the removed cart contents on next user initiated request.
*
- * @return bool True if expired items should be removed, false otherwise.
+ * @return void
*/
public function clean_up_removed_cart_contents() {
// Limit to page requests initiated by the user.
$is_page = is_singular() || is_archive() || is_search();
+
if ( is_404() || ! $is_page ) {
return;
}
+ // Don't cleanup if user just removed an item (undo link is being displayed).
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ if ( isset( $_GET['removed_item'] ) ) {
+ return;
+ }
+
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['undo_item'] ) ) {
return;
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index dd7d1186b4..70ccf95b30 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -12156,12 +12156,6 @@ parameters:
count: 1
path: includes/class-wc-cart-session.php
- -
- message: '#^Action callback returns bool but should not return anything\.$#'
- identifier: return.void
- count: 1
- path: includes/class-wc-cart-session.php
-
-
message: '#^Call to an undefined method WC_Order_Item\:\:get_product\(\)\.$#'
identifier: method.notFound
@@ -12204,18 +12198,6 @@ parameters:
count: 1
path: includes/class-wc-cart-session.php
- -
- message: '#^Method WC_Cart_Session\:\:clean_up_removed_cart_contents\(\) should return bool but empty return statement found\.$#'
- identifier: return.empty
- count: 2
- path: includes/class-wc-cart-session.php
-
- -
- message: '#^Method WC_Cart_Session\:\:clean_up_removed_cart_contents\(\) should return bool but return statement is missing\.$#'
- identifier: return.missing
- count: 1
- path: includes/class-wc-cart-session.php
-
-
message: '#^Method WC_Cart_Session\:\:dedupe_cookies\(\) has no return type specified\.$#'
identifier: missingType.return
diff --git a/plugins/woocommerce/tests/e2e-pw/tests/cart/cart.spec.js b/plugins/woocommerce/tests/e2e-pw/tests/cart/cart.spec.js
index 6dfd37f355..18682ef6c6 100644
--- a/plugins/woocommerce/tests/e2e-pw/tests/cart/cart.spec.js
+++ b/plugins/woocommerce/tests/e2e-pw/tests/cart/cart.spec.js
@@ -124,6 +124,81 @@ const test = baseTest.extend( {
/* endregion */
/* region tests */
+test(
+ 'can undo product removal in classic cart',
+ { tag: [ tags.PAYMENTS, tags.SERVICES, tags.HPOS ] },
+ async ( { page, products, tax } ) => {
+ const slug = CLASSIC_CART_PAGE.slug;
+
+ await test.step( 'add product to cart', async () => {
+ await addAProductToCart( page, products[ 0 ].id, 1 );
+ await page.goto( slug );
+ await checkCartContent(
+ true,
+ page,
+ [ { data: products[ 0 ], qty: 1 } ],
+ tax
+ );
+ } );
+
+ await test.step( 'remove product and verify undo link appears', async () => {
+ await page
+ .getByLabel( /Remove .* from cart/ )
+ .first()
+ .click();
+
+ // Verify the product was removed
+ await checkCartContent( true, page, [], tax );
+
+ // Verify the undo link appears
+ await expect(
+ page.getByRole( 'link', { name: 'Undo?' } )
+ ).toBeVisible();
+ } );
+
+ await test.step( 'click undo to restore product', async () => {
+ await page.getByRole( 'link', { name: 'Undo?' } ).click();
+
+ // Verify the product is back in the cart
+ await checkCartContent(
+ true,
+ page,
+ [ { data: products[ 0 ], qty: 1 } ],
+ tax
+ );
+ } );
+
+ await test.step( 'remove product again after undo', async () => {
+ await page
+ .getByLabel( /Remove .* from cart/ )
+ .first()
+ .click();
+
+ // Verify the product was removed again
+ await checkCartContent( true, page, [], tax );
+
+ // Verify undo link is visible
+ await expect(
+ page.getByRole( 'link', { name: 'Undo?' } )
+ ).toBeVisible();
+ } );
+
+ await test.step( 'verify undo link disappears after navigation', async () => {
+ // Navigate to shop page and return to cart
+ await page.goto( 'shop' );
+ await page.goto( slug );
+
+ // Verify cart is still empty
+ await checkCartContent( true, page, [], tax );
+
+ // Verify undo link is no longer visible (cleanup occurred)
+ await expect(
+ page.getByRole( 'link', { name: 'Undo?' } )
+ ).not.toBeVisible();
+ } );
+ }
+);
+
cartPages.forEach( ( { name, slug } ) => {
test(
`can add and remove products, increase quantity and proceed to checkout - ${ name }`,
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-cart-item-removal-test.php b/plugins/woocommerce/tests/php/includes/class-wc-cart-item-removal-test.php
index 480d530012..3a4c22be42 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-cart-item-removal-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-cart-item-removal-test.php
@@ -246,26 +246,37 @@ class WC_Cart_Item_Removal_Test extends \WC_Unit_Test_Case {
*/
public function data_provider_clean_up_returns_early() {
return array(
- '404 page' => array(
- 'is_404' => true,
- 'is_singular' => false,
- 'is_archive' => false,
- 'is_search' => false,
- 'undo_item' => null,
+ '404 page' => array(
+ 'is_404' => true,
+ 'is_singular' => false,
+ 'is_archive' => false,
+ 'is_search' => false,
+ 'undo_item' => null,
+ 'removed_item' => null,
),
- 'non-page request' => array(
- 'is_404' => false,
- 'is_singular' => false,
- 'is_archive' => false,
- 'is_search' => false,
- 'undo_item' => null,
+ 'non-page request' => array(
+ 'is_404' => false,
+ 'is_singular' => false,
+ 'is_archive' => false,
+ 'is_search' => false,
+ 'undo_item' => null,
+ 'removed_item' => null,
),
- 'undo_item parameter' => array(
- 'is_404' => false,
- 'is_singular' => true,
- 'is_archive' => false,
- 'is_search' => false,
- 'undo_item' => 'test_key',
+ 'undo_item parameter' => array(
+ 'is_404' => false,
+ 'is_singular' => true,
+ 'is_archive' => false,
+ 'is_search' => false,
+ 'undo_item' => 'test_key',
+ 'removed_item' => null,
+ ),
+ 'removed_item parameter' => array(
+ 'is_404' => false,
+ 'is_singular' => true,
+ 'is_archive' => false,
+ 'is_search' => false,
+ 'undo_item' => null,
+ 'removed_item' => '1',
),
);
}
@@ -274,13 +285,14 @@ class WC_Cart_Item_Removal_Test extends \WC_Unit_Test_Case {
* Test clean_up_removed_cart_contents returns early for various conditions.
*
* @dataProvider data_provider_clean_up_returns_early
- * @param bool $is_404 Whether this is a 404 page.
- * @param bool $is_singular Whether this is a singular page.
- * @param bool $is_archive Whether this is an archive page.
- * @param bool $is_search Whether this is a search page.
- * @param string $undo_item The undo_item GET parameter value.
+ * @param bool $is_404 Whether this is a 404 page.
+ * @param bool $is_singular Whether this is a singular page.
+ * @param bool $is_archive Whether this is an archive page.
+ * @param bool $is_search Whether this is a search page.
+ * @param string|null $undo_item The undo_item GET parameter value.
+ * @param string|null $removed_item The removed_item GET parameter value.
*/
- public function test_clean_up_removed_cart_contents_returns_early( $is_404, $is_singular, $is_archive, $is_search, $undo_item ) {
+ public function test_clean_up_removed_cart_contents_returns_early( $is_404, $is_singular, $is_archive, $is_search, $undo_item, $removed_item ) {
// Set up removed cart contents.
$cart_item_key = $this->cart->add_to_cart( $this->product->get_id(), 2 );
$this->cart->remove_cart_item( $cart_item_key );
@@ -292,6 +304,10 @@ class WC_Cart_Item_Removal_Test extends \WC_Unit_Test_Case {
$_GET['undo_item'] = $undo_item;
}
+ if ( $removed_item ) {
+ $_GET['removed_item'] = $removed_item;
+ }
+
global $wp_query;
$original_wp_query = $wp_query;
$wp_query->is_404 = $is_404;
@@ -310,6 +326,10 @@ class WC_Cart_Item_Removal_Test extends \WC_Unit_Test_Case {
unset( $_GET['undo_item'] );
}
+ if ( $removed_item ) {
+ unset( $_GET['removed_item'] );
+ }
+
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$wp_query = $original_wp_query;
}