Commit 35ed9c1cc00 for woocommerce

commit 35ed9c1cc0025c21facb6096aa376d06dbaa29fc
Author: Tung Du <dinhtungdu@gmail.com>
Date:   Fri Sep 25 10:40:13 2026 +0700

    Revert "[tests] Reduce Product Button E2E tests from 7 to 3" (#68595) (#69088)

    Revert "[tests] Reduce Product Button E2E tests from 7 to 3 (#68595)"

    This reverts commit ddf8dd862b6f064bed0adbe8ebacf61b3e2dc472.

    The removed E2E test covered the add-to-cart text filter with AJAX add to
    cart on, which nothing covers now. Conflict with #68747 resolved by taking
    the pre-#68595 spec, since #68747 only edited a comment #68595 added.


    Claude-Session: https://claude.ai/code/session_014r6PoAedUkWFLNpyerW69N

    Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

diff --git a/plugins/woocommerce/changelog/testops-234-product-button b/plugins/woocommerce/changelog/testops-234-product-button
deleted file mode 100644
index 750550983ba..00000000000
--- a/plugins/woocommerce/changelog/testops-234-product-button
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: dev
-Comment: Reduce Product Button E2E tests from 7 to 3; PHPUnit owns the add-to-cart text filter and the block theme script dequeue.
-
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/product-button/product-button.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/product-button/product-button.block_theme.spec.ts
index 40c41714944..c1d36e06f0e 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/product-button/product-button.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/product-button/product-button.block_theme.spec.ts
@@ -13,6 +13,23 @@ test.describe( `${ blockData.name } Block`, () => {
 		await frontendUtils.goToShop();
 	} );

+	test( 'should be visible', async ( { frontendUtils } ) => {
+		const blocks = await frontendUtils.getBlockByName( blockData.slug );
+		await expect( blocks ).toHaveCount(
+			blockData.selectors.frontend.productsToDisplay
+		);
+	} );
+
+	test( 'should not enqueue add-to-cart-script', async ( { page } ) => {
+		let isScriptEnqueued = false;
+		page.on( 'request', ( request ) => {
+			if ( request.url().includes( 'add-to-cart.min.js' ) )
+				isScriptEnqueued = true;
+		} );
+		await page.reload();
+		expect( isScriptEnqueued ).toBe( false );
+	} );
+
 	test( 'should add product to the cart', async ( {
 		frontendUtils,
 		page,
@@ -50,47 +67,54 @@ test.describe( `${ blockData.name } Block`, () => {
 		page,
 		admin,
 	} ) => {
-		// The setting is store-wide, but the Blocks `page` fixture restores the database
-		// after every test, failed ones included, so the next spec starts with AJAX
-		// enabled again. The `finally` below is only a safety net.
 		await handleAddToCartAjaxSetting( admin, page, {
 			isChecked: true,
 		} );
+		await frontendUtils.goToShop();

-		try {
-			await frontendUtils.goToShop();
+		const blocks = await frontendUtils.getBlockByName( blockData.slug );
+		const block = blocks.first();
+		const button = block.getByRole( 'link' );

-			const blocks = await frontendUtils.getBlockByName( blockData.slug );
-			const block = blocks.first();
-			const button = block.getByRole( 'link' );
+		const productId = await button.getAttribute( 'data-product_id' );

-			const productId = await button.getAttribute( 'data-product_id' );
+		const productNameLocator = page.locator( `li.post-${ productId } h2` );
+		await expect( productNameLocator ).not.toBeEmpty();

-			const productNameLocator = page.locator(
-				`li.post-${ productId } h2`
-			);
-			await expect( productNameLocator ).not.toBeEmpty();
+		const productName =
+			( await productNameLocator.textContent() ) as string;

-			const productName =
-				( await productNameLocator.textContent() ) as string;
+		await block.click();

-			await block.click();
+		await expect(
+			page.locator( `a[href*="cart=${ productId }"]` )
+		).toBeVisible();

-			await expect(
-				page.locator( `a[href*="cart=${ productId }"]` )
-			).toBeVisible();
+		await frontendUtils.goToCheckout();

-			await frontendUtils.goToCheckout();
+		const productElement = page.getByText( productName, {
+			exact: true,
+		} );

-			const productElement = page.getByText( productName, {
-				exact: true,
-			} );
+		await expect( productElement ).toBeVisible();

-			await expect( productElement ).toBeVisible();
-		} finally {
-			await handleAddToCartAjaxSetting( admin, page, {
-				isChecked: false,
-			} );
-		}
+		await handleAddToCartAjaxSetting( admin, page, {
+			isChecked: false,
+		} );
+	} );
+
+	test( 'the filter `woocommerce_product_add_to_cart_text` should be applied', async ( {
+		requestUtils,
+		frontendUtils,
+	} ) => {
+		await requestUtils.activatePlugin(
+			'woocommerce-blocks-test-custom-add-to-cart-button-text'
+		);
+		await frontendUtils.goToShop();
+		const blocks = await frontendUtils.getBlockByName( blockData.slug );
+		const buttonWithNewText = blocks.getByText( 'Buy Now' );
+		await expect( buttonWithNewText ).toHaveCount(
+			blockData.selectors.frontend.productsToDisplay
+		);
 	} );
 } );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/product-button/product-button.classic_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/product-button/product-button.classic_theme.spec.ts
index 74a5f8af618..9ecad5f986b 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/product-button/product-button.classic_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/product-button/product-button.classic_theme.spec.ts
@@ -1,22 +1,41 @@
 /**
  * External dependencies
  */
-import { expect, test, CLASSIC_THEME_SLUG } from '@woocommerce/e2e-utils';
+import {
+	expect,
+	test as base,
+	CLASSIC_THEME_SLUG,
+} from '@woocommerce/e2e-utils';

 /**
  * Internal dependencies
  */
 import { blockData } from './utils';
+import ProductCollectionPage from '../product-collection/product-collection.page';

+const test = base.extend< { productCollectionPage: ProductCollectionPage } >( {
+	productCollectionPage: async ( { page, admin, editor }, use ) => {
+		const pageObject = new ProductCollectionPage( {
+			page,
+			admin,
+			editor,
+		} );
+		await use( pageObject );
+	},
+} );
 test.describe( `${ blockData.name } Block`, () => {
 	test.beforeEach( async ( { page, requestUtils } ) => {
 		await requestUtils.activateTheme( CLASSIC_THEME_SLUG );
 		await page.goto( '/product-collection/' );
 	} );

-	// The block's only classic-theme coverage. Here the legacy `add-to-cart.js`
-	// loads next to the block's own script, and nothing at a lower layer
-	// exercises the two together, so this journey stays in the browser.
+	test( 'should be visible', async ( { frontendUtils } ) => {
+		const blocks = await frontendUtils.getBlockByName( blockData.slug );
+		await expect( blocks ).toHaveCount(
+			blockData.selectors.frontend.productsToDisplay
+		);
+	} );
+
 	test( 'should add product to the cart', async ( {
 		frontendUtils,
 		page,
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/product-button/utils.ts b/plugins/woocommerce/tests/e2e/tests/blocks/product-button/utils.ts
index 0e576977d5c..6ed0388ef0b 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/product-button/utils.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/product-button/utils.ts
@@ -8,6 +8,12 @@ export const blockData = {
 	name: 'Product Button',
 	slug: 'woocommerce/product-button',
 	mainClass: '.wc-block-product-button',
+	selectors: {
+		frontend: {
+			productsToDisplay: 16,
+		},
+		editor: {},
+	},
 };

 export const handleAddToCartAjaxSetting = async (
diff --git a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductButton.php b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductButton.php
index 7d69343184c..870ea066977 100644
--- a/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductButton.php
+++ b/plugins/woocommerce/tests/php/src/Blocks/BlockTypes/ProductButton.php
@@ -4,7 +4,6 @@ declare( strict_types = 1 );

 namespace Automattic\WooCommerce\Tests\Blocks\BlockTypes;

-use Automattic\WooCommerce\Blocks\BlockTypes\ProductButton as ProductButtonBlock;
 use WC_Helper_Product;

 /**
@@ -13,185 +12,49 @@ use WC_Helper_Product;
 class ProductButton extends \WP_UnitTestCase {

 	/**
-	 * Set up test options.
+	 * Previous WooCommerce options to restore after each test.
 	 *
-	 * Both writes land inside the per-test transaction, which tear_down() rolls back.
+	 * @var array<string, mixed>
 	 */
-	protected function setUp(): void {
-		parent::setUp();
-
-		update_option( 'woocommerce_cart_redirect_after_add', 'no' );
-		update_option( 'woocommerce_enable_ajax_add_to_cart', 'no' );
-	}
+	private $previous_options = array();

 	/**
-	 * Render the Product Button block for a product.
-	 *
-	 * @param \WC_Product $product Product to render.
-	 * @return string Rendered block markup.
-	 */
-	private function render_product_button( \WC_Product $product ): string {
-		return do_blocks(
-			'<!-- wp:woocommerce/single-product {"productId":' . $product->get_id() . '} --><!-- wp:woocommerce/product-button /--><!-- /wp:woocommerce/single-product -->'
-		);
-	}
-
-	/**
-	 * @testdox Filtered add-to-cart text renders one visible Product Button for the expected product.
+	 * Set up test options.
 	 */
-	public function test_filtered_add_to_cart_text_renders_visible_button(): void {
-		$had_product      = array_key_exists( 'product', $GLOBALS );
-		$previous_product = $GLOBALS['product'] ?? null;
-		$product          = WC_Helper_Product::create_simple_product(
-			true,
-			array(
-				'name'          => 'Product Button add-to-cart text fixture',
-				'regular_price' => '10',
-			)
-		);
-		$filter           = static function (): string {
-			return 'Buy Now';
-		};
-
-		add_filter( 'woocommerce_product_add_to_cart_text', $filter );
-
-		try {
-			$markup    = $this->render_product_button( $product );
-			$processor = new \WP_HTML_Tag_Processor( $markup );
-			$wrappers  = 0;
-
-			while (
-				$processor->next_tag(
-					array(
-						'tag_name'   => 'div',
-						'class_name' => 'wc-block-components-product-button',
-					)
-				)
-			) {
-				++$wrappers;
-			}
+	protected function setUp(): void {
+		parent::setUp();

-			$this->assertSame( 1, $wrappers, 'The registered composition should render exactly one Product Button wrapper.' );
-
-			$processor = new \WP_HTML_Tag_Processor( $markup );
-			$this->assertTrue(
-				$processor->next_tag(
-					array(
-						'tag_name'   => 'a',
-						'class_name' => 'wc-block-components-product-button__button',
-					)
-				),
-				'The non-AJAX registered Product Button should render a link.'
-			);
-			$this->assertSame( (string) $product->get_id(), $processor->get_attribute( 'data-product_id' ), 'The rendered button should identify the fixture product.' );
-			$this->assertStringContainsString( '>Buy Now</span>', $markup, 'The real product text filter should reach escaped Product Button markup.' );
-		} finally {
-			// _restore_hooks() drops the filter and the rollback drops the product;
-			// the product global is this test's own to put back.
-			if ( $had_product ) {
-				$GLOBALS['product'] = $previous_product; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Restore the exact pre-test product global.
-			} else {
-				unset( $GLOBALS['product'] );
-			}
+		foreach ( array( 'woocommerce_cart_redirect_after_add', 'woocommerce_enable_ajax_add_to_cart' ) as $option_name ) {
+			$this->previous_options[ $option_name ] = get_option( $option_name, null );
+			update_option( $option_name, 'no' );
 		}
 	}

 	/**
-	 * @testdox A block-theme Product Button dequeues the legacy add-to-cart script through its registered frontend action.
+	 * Restore test options.
 	 */
-	public function test_block_theme_dequeues_legacy_add_to_cart_script(): void {
-		global $wp_filter;
-
-		$original_theme      = get_stylesheet();
-		$had_product         = array_key_exists( 'product', $GLOBALS );
-		$previous_product    = $GLOBALS['product'] ?? null;
-		$previous_cart       = WC()->cart;
-		$scripts             = wp_scripts();
-		$previous_queue      = $scripts->queue;
-		$previous_registered = $scripts->registered;
-		$styles              = wp_styles();
-		$previous_styles     = $styles->queue;
-		$previous_style_reg  = $styles->registered;
-		$product             = WC_Helper_Product::create_simple_product(
-			true,
-			array(
-				'name'          => 'Product Button script policy fixture',
-				'regular_price' => '10',
-			)
-		);
-
-		try {
-			switch_theme( 'twentytwentyfour' );
-			$this->assertTrue( wp_is_block_theme(), 'The script policy should run under a real block theme.' );
-
-			// Let WooCommerce enqueue the handle rather than standing in for it. A test
-			// that registers 'wc-add-to-cart' itself pins the same literal on both
-			// sides, so core folding the legacy script into another handle would leave
-			// dequeue_add_to_cart_scripts() a silent no-op with this test still green --
-			// which is the regression the deleted E2E title used to catch.
-			update_option( 'woocommerce_enable_ajax_add_to_cart', 'yes' );
-			\WC_Frontend_Scripts::load_scripts();
-			$this->assertTrue( wp_script_is( 'wc-add-to-cart', 'enqueued' ), 'WooCommerce should enqueue the legacy handle when AJAX add to cart is on.' );
-
-			$markup   = $this->render_product_button( $product );
-			$callback = $this->get_dequeue_callback( $wp_filter['wp_enqueue_scripts'] ?? null );
-			$this->assertNotSame( '', $markup, 'The registered Product Button should render before its frontend action runs.' );
-			$this->assertNotNull( $callback, 'Rendering should queue the Product Button legacy-script callback.' );
-
-			// Fire the hook rather than calling the callback, so the assertion covers the
-			// whole stack: a later callback re-enqueueing the handle would fail here and
-			// would not if the method were invoked on its own. `_restore_hooks()` rewinds
-			// the `$wp_actions` count and `$wp_current_filter` that firing it leaves.
-			// phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment -- Firing core's own frontend action, not declaring one.
-			do_action( 'wp_enqueue_scripts' );
-			$this->assertFalse( wp_script_is( 'wc-add-to-cart', 'enqueued' ), 'The registered block-theme callback should dequeue the legacy handle.' );
-		} finally {
-			// _restore_hooks() rewinds wp_enqueue_scripts and the rollback takes the
-			// product back. WP_Scripts, WC()->cart and the product global survive both:
-			// this class extends WP_UnitTestCase, so there is no WooCommerce teardown.
-			$scripts->queue      = $previous_queue;
-			$scripts->registered = $previous_registered;
-			// Firing wp_enqueue_scripts writes to WP_Styles too -- WooCommerce's own
-			// frontend styles, plus core attaching the theme's global stylesheet to the
-			// shared 'global-styles' handle, which outlives the switch_theme() below.
-			$styles->queue      = $previous_styles;
-			$styles->registered = $previous_style_reg;
-			switch_theme( $original_theme );
-			WC()->cart = $previous_cart;
-
-			if ( $had_product ) {
-				$GLOBALS['product'] = $previous_product; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Restore the exact pre-test product global.
+	protected function tearDown(): void {
+		foreach ( $this->previous_options as $option_name => $value ) {
+			if ( null === $value ) {
+				delete_option( $option_name );
 			} else {
-				unset( $GLOBALS['product'] );
+				update_option( $option_name, $value );
 			}
 		}
+
+		parent::tearDown();
 	}

 	/**
-	 * Find the Product Button's registered dequeue callback.
+	 * Render the Product Button block for a product.
 	 *
-	 * @param \WP_Hook|null $hook Enqueue hook after rendering.
-	 * @return array{ProductButtonBlock, string}|null
+	 * @param \WC_Product $product Product to render.
+	 * @return string Rendered block markup.
 	 */
-	private function get_dequeue_callback( ?\WP_Hook $hook ): ?array {
-		if ( ! $hook ) {
-			return null;
-		}
-
-		foreach ( $hook->callbacks as $callbacks ) {
-			foreach ( $callbacks as $callback ) {
-				$function = $callback['function'];
-				if (
-					is_array( $function ) &&
-					$function[0] instanceof ProductButtonBlock &&
-					'dequeue_add_to_cart_scripts' === $function[1]
-				) {
-					return $function;
-				}
-			}
-		}
-
-		return null;
+	private function render_product_button( \WC_Product $product ): string {
+		return do_blocks(
+			'<!-- wp:woocommerce/single-product {"productId":' . $product->get_id() . '} --><!-- wp:woocommerce/product-button /--><!-- /wp:woocommerce/single-product -->'
+		);
 	}

 	/**
@@ -217,7 +80,11 @@ class ProductButton extends \WP_UnitTestCase {
 		};

 		add_filter( 'woocommerce_loop_add_to_cart_args', $filter );
-		$this->render_product_button( $product );
+		try {
+			$this->render_product_button( $product );
+		} finally {
+			remove_filter( 'woocommerce_loop_add_to_cart_args', $filter );
+		}

 		$this->assertIsArray( $filtered_args );
 		$this->assertArrayHasKey( 'rel', $filtered_args['attributes'] );