Commit 4c322802113 for woocommerce

commit 4c3228021130e04ee3ac49518f9661e4b7a52bc3
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date:   Wed Sep 16 00:54:11 2026 +0300

    [tests] Move grouped product cart removal to PHPUnit (#68653)

    * test(product): Move grouped cart removal below E2E

    The grouped products spec had two titles: one that adds grouped
    children to the cart and one that removes them again. The removal
    title drove the cart page to prove that clearing a grouped child's
    quantity removes that line item. That is the update-cart form
    handler's job, and WC_Cart_Test can ask it directly.

    test_update_cart_action_removes_grouped_child_items posts a real
    update-cart request through WC_Form_Handler and asserts the cart
    contents afterwards. The add title stays in the browser: it is the
    one that proves the grouped product form posts what the handler
    expects.

    The spec goes from 2 titles to 1.

    This file is shared with another batch, and the sharing needed care.
    class-wc-cart-test.php receives one method from this migration commit
    and a different one from `760478af4c`, which belongs to the Add to
    Cart options work and ships separately. Only this batch's method is
    here; `test_add_to_cart_action_handles_grouped_product_quantities`
    is deliberately absent.

    Trunk had also moved on: #67527 added eleven methods to the same
    class after this work branched. Those are all kept. The class goes
    from trunk's 50 methods to 51, and the file's diff against trunk has
    no deleted lines.

    Carries the mega-branch commit:
    - 8e1bd64631 test(product): Move grouped cart removal below E2E

    Refs TESTOPS-288
    Refs #68046

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    * test(cart): Stop the grouped cart test restoring base-class state

    The finally block put back $_GET, $_POST and $_REQUEST, restored
    woocommerce_cart_redirect_after_add, deleted three products, and
    rewrote the cart contents and two session keys.

    clean_up_global_scope() empties the three request globals before every
    test and the rollback covers the option and the products. The cart and
    session restores were doing less than nothing: WC_Unit_Test_Case::
    tearDown() runs clear_wc_singleton_state() after this finally, which
    empties the cart and clears the notices again, so the restored values
    were overwritten a moment later.

    $_SERVER stays, because the base class only resets it for core's own
    suite and this test unsets HTTP_REFERER.

    Refs TESTOPS-288

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    * test(cart): Record why the referer unset cannot be removed

    The unset of HTTP_REFERER reads like leftover request cleanup, and the
    comment above it talked only about $_SERVER not being restored by the
    base class. It is doing something else entirely: after removing a cart
    item, WC_Form_Handler::update_cart_action() calls wp_safe_redirect()
    followed by exit whenever wp_get_referer() returns a value. An exit
    inside PHPUnit ends the whole run, not just this test.

    Nothing in the diff shows that, and this campaign has been deleting
    exactly this shape of line as duplicated teardown, so say what it is for.

    Refs TESTOPS-288

    Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

    ---------

    Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

diff --git a/plugins/woocommerce/changelog/testops-288-product-grouped b/plugins/woocommerce/changelog/testops-288-product-grouped
new file mode 100644
index 00000000000..217d5c56413
--- /dev/null
+++ b/plugins/woocommerce/changelog/testops-288-product-grouped
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+Comment: Move the grouped product cart removal E2E title to PHPUnit; WC_Cart_Test now covers removing grouped children through the real update-cart form handler.
+
diff --git a/plugins/woocommerce/tests/e2e/tests/product/product-grouped.spec.ts b/plugins/woocommerce/tests/e2e/tests/product/product-grouped.spec.ts
index 7aabc327e0c..17da79d03d0 100644
--- a/plugins/woocommerce/tests/e2e/tests/product/product-grouped.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/product/product-grouped.spec.ts
@@ -120,36 +120,5 @@ test.describe(
 				0
 			);
 		} );
-
-		test( 'should be able to remove grouped products from the cart', async ( {
-			page,
-		} ) => {
-			await page.goto( `product/${ groupedProductSlug }` );
-			await page.locator( 'div.quantity input.qty >> nth=0' ).fill( '1' );
-			await page.locator( 'div.quantity input.qty >> nth=1' ).fill( '1' );
-			await page
-				.getByRole( 'button', { name: 'Add to cart', exact: true } )
-				.click();
-
-			await expect(
-				page.getByText(
-					new RegExp(
-						`${ simpleProduct1.name }.*and.*${ simpleProduct2.name }.*have been added to your cart`
-					)
-				)
-			).toBeVisible();
-
-			await page.goto( 'cart/' );
-			await page
-				.getByRole( 'button', { name: 'Remove' } )
-				.first()
-				.click();
-			await page
-				.getByRole( 'button', { name: 'Remove' } )
-				.first()
-				.click();
-
-			await checkCartContent( false, page, [], 0 );
-		} );
 	}
 );
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-cart-test.php b/plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
index d8f8c35fc24..6b8db71a3c9 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-cart-test.php
@@ -2339,4 +2339,99 @@ class WC_Cart_Test extends \WC_Unit_Test_Case {
 			$first_child->delete( true );
 		}
 	}
+
+	/**
+	 * @testdox Should remove grouped child cart items through consecutive form requests.
+	 */
+	public function test_update_cart_action_removes_grouped_child_items(): void {
+		// $_SERVER is the only request global the base class leaves alone; it resets
+		// the other three before every test.
+		$original_server = $GLOBALS['_SERVER'];
+
+		try {
+			// Do not remove this unset as redundant cleanup. It is what keeps the test
+			// alive: after removing an item, update_cart_action() calls wp_safe_redirect()
+			// followed by exit whenever wp_get_referer() is truthy
+			// (includes/class-wc-form-handler.php:818-821). exit inside PHPUnit kills the
+			// whole run, not just this test.
+			unset( $GLOBALS['_SERVER']['HTTP_REFERER'] );
+			update_option( 'woocommerce_cart_redirect_after_add', 'no' );
+			WC()->cart->empty_cart();
+			WC()->session->set( 'wc_notices', null );
+
+			$first_child = WC_Helper_Product::create_simple_product();
+			$first_child->set_regular_price( '10' );
+			$first_child->save();
+
+			$second_child = WC_Helper_Product::create_simple_product();
+			$second_child->set_regular_price( '20' );
+			$second_child->save();
+
+			$grouped_product = new WC_Product_Grouped();
+			$grouped_product->set_name( 'Grouped removal request product' );
+			$grouped_product->set_children(
+				array(
+					$first_child->get_id(),
+					$second_child->get_id(),
+				)
+			);
+			$grouped_product->save();
+
+			$grouped_quantities  = array(
+				$first_child->get_id()  => 1,
+				$second_child->get_id() => 1,
+			);
+			$GLOBALS['_REQUEST'] = array(
+				'add-to-cart' => $grouped_product->get_id(),
+				'quantity'    => $grouped_quantities,
+			);
+			$GLOBALS['_POST']    = array(
+				'quantity' => $grouped_quantities,
+			);
+
+			WC_Form_Handler::add_to_cart_action( false );
+
+			$cart_items                = WC()->cart->get_cart();
+			$cart_item_keys_by_product = array();
+			foreach ( $cart_items as $cart_item_key => $cart_item ) {
+				$cart_item_keys_by_product[ $cart_item['product_id'] ] = $cart_item_key;
+			}
+			$expected_child_ids = array( $first_child->get_id(), $second_child->get_id() );
+			$actual_child_ids   = array_map( 'intval', array_keys( $cart_item_keys_by_product ) );
+			sort( $expected_child_ids );
+			sort( $actual_child_ids );
+
+			$this->assertCount( 2, $cart_items, 'Both grouped children should be cart lines.' );
+			$this->assertSame(
+				$expected_child_ids,
+				$actual_child_ids,
+				'Only the grouped children should be cart lines.'
+			);
+			$this->assertArrayNotHasKey( $grouped_product->get_id(), $cart_item_keys_by_product, 'The grouped parent should not become a cart line.' );
+
+			$cart_item_keys = array_values( $cart_item_keys_by_product );
+			foreach ( $cart_item_keys as $index => $cart_item_key ) {
+				$GLOBALS['_GET']     = array(
+					'remove_item' => $cart_item_key,
+				);
+				$GLOBALS['_POST']    = array();
+				$GLOBALS['_REQUEST'] = array(
+					'_wpnonce'    => wp_create_nonce( 'woocommerce-cart' ),
+					'remove_item' => $cart_item_key,
+				);
+
+				WC_Form_Handler::update_cart_action();
+
+				$remaining_cart_items = WC()->cart->get_cart();
+				if ( 0 === $index ) {
+					$this->assertArrayNotHasKey( $cart_item_key, $remaining_cart_items, 'The first requested cart item should be removed.' );
+					$this->assertSame( array( $cart_item_keys[1] ), array_keys( $remaining_cart_items ), 'Only the other original cart item should remain.' );
+				} else {
+					$this->assertEmpty( $remaining_cart_items, 'The second removal request should empty the cart.' );
+				}
+			}
+		} finally {
+			$GLOBALS['_SERVER'] = $original_server;
+		}
+	}
 }