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;
 	}