Commit fe2c177f974 for woocommerce
commit fe2c177f974b34ba711828968978ea14c09f8cd1
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date: Thu Sep 24 17:07:48 2026 +0300
Revert "[tests] Move 7 Cart and Checkout notice E2E tests to PHPUnit" (#69045)
revert: Revert "[tests] Move 7 Cart and Checkout notice E2E tests to PHPUnit" (#68580)
This reverts commit 8ca7212b6052981c7521a4df03794da862e2787e.
Rubik asked for the E2E migration's test changes in its areas to be
reverted until the team can review them. #68580 is one of the 11 PRs on
the revert list Rubik agreed on 2026-09-24.
The revert brings back the browser tests the PR removed or cut down and
removes the lower-layer tests it added in their place. Only test code
and changelog entries change; nothing ships.
Refs TESTOPS-234
Refs #68580
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/cart/cart-checkout-block-notices.shopper.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/cart/cart-checkout-block-notices.shopper.block_theme.spec.ts
index b2678d644ef..08e43e64a71 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/cart/cart-checkout-block-notices.shopper.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/cart/cart-checkout-block-notices.shopper.block_theme.spec.ts
@@ -6,7 +6,9 @@ import {
test as base,
wpCLI,
BLOCK_THEME_SLUG,
+ BLOCK_CHILD_THEME_WITH_BLOCK_NOTICES_FILTER_SLUG,
BLOCK_CHILD_THEME_WITH_BLOCK_NOTICES_TEMPLATE_SLUG,
+ BLOCK_CHILD_THEME_WITH_CLASSIC_NOTICES_TEMPLATE_SLUG,
} from '@woocommerce/e2e-utils';
/**
@@ -42,6 +44,54 @@ test.describe( 'Shopper → Notice Templates', () => {
await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
} );
+ test( 'default block notice templates, except for coupon errors, are visible', async ( {
+ frontendUtils,
+ page,
+ } ) => {
+ await frontendUtils.goToCartShortcode();
+ await page.getByPlaceholder( 'Coupon code' ).fill( 'testcoupon' );
+ await page.getByRole( 'button', { name: 'Apply coupon' } ).click();
+
+ await expect(
+ page.getByText( 'Coupon code applied successfully.', {
+ exact: true,
+ } )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the block notices, and that the SVG is visible.
+ await expect(
+ page.locator( '.wc-block-components-notice-banner.is-success svg' )
+ ).toBeVisible();
+
+ await page.reload();
+ await page.getByPlaceholder( 'Coupon code' ).fill( 'testcoupon' );
+ await page.getByRole( 'button', { name: 'Apply coupon' } ).click();
+
+ await expect(
+ page.getByText( 'Coupon code "testcoupon" already applied!', {
+ exact: true,
+ } )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the block notices, and that the SVG is hidden.
+ await expect(
+ page.locator( '.wc-block-components-notice-banner.is-error svg' )
+ ).toBeHidden();
+
+ await page.getByLabel( 'Remove Polo from cart' ).click();
+
+ await expect(
+ page.getByText( 'Your cart is currently empty.', {
+ exact: true,
+ } )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the block notices, and that the SVG is visible.
+ await expect(
+ page.locator( '.wc-block-components-notice-banner.is-success svg' )
+ ).toBeVisible();
+ } );
+
test( 'custom block notice templates, except for coupon errors, are visible by template overwrite', async ( {
requestUtils,
frontendUtils,
@@ -60,6 +110,8 @@ test.describe( 'Shopper → Notice Templates', () => {
'BLOCK SUCCESS NOTICE: Coupon code applied successfully.'
)
).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the block notices, and that the SVG is visible.
await expect(
page.locator( '.wc-block-components-notice-banner.is-success svg' )
).toBeVisible();
@@ -73,6 +125,8 @@ test.describe( 'Shopper → Notice Templates', () => {
'BLOCK ERROR NOTICE: Coupon code "testcoupon" already applied!'
)
).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the block notices, and that the SVG is hidden.
await expect(
page.locator( '.wc-block-components-notice-banner.is-error svg' )
).toBeHidden();
@@ -82,6 +136,118 @@ test.describe( 'Shopper → Notice Templates', () => {
await expect(
page.getByText( 'BLOCK INFO NOTICE: Your cart is currently empty.' )
).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the block notices, and that the SVG is visible.
+ await expect(
+ page.locator( '.wc-block-components-notice-banner.is-success svg' )
+ ).toBeVisible();
+
+ await requestUtils.activateTheme( BLOCK_THEME_SLUG );
+ } );
+
+ test( 'classic notice templates, except for coupon errors, are visible by template overwrite', async ( {
+ requestUtils,
+ frontendUtils,
+ page,
+ } ) => {
+ await requestUtils.activateTheme(
+ BLOCK_CHILD_THEME_WITH_CLASSIC_NOTICES_TEMPLATE_SLUG
+ );
+
+ await frontendUtils.goToCartShortcode();
+ await page.getByPlaceholder( 'Coupon code' ).fill( 'testcoupon' );
+ await page.getByRole( 'button', { name: 'Apply coupon' } ).click();
+
+ await expect(
+ page.getByText(
+ 'CLASSIC SUCCESS NOTICE: Coupon code applied successfully.'
+ )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the classic notices.
+ await expect(
+ page.locator( '.woocommerce-notices-wrapper .woocommerce-message' )
+ ).toBeVisible();
+
+ await page.reload();
+ await page.getByPlaceholder( 'Coupon code' ).fill( 'testcoupon' );
+ await page.getByRole( 'button', { name: 'Apply coupon' } ).click();
+
+ await expect(
+ page.getByText(
+ 'CLASSIC ERROR NOTICE: Coupon code "testcoupon" already applied!'
+ )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the classic notices.
+ await expect(
+ page.locator( '.woocommerce-notices-wrapper .woocommerce-error' )
+ ).toBeHidden();
+
+ await page.getByLabel( 'Remove Polo from cart' ).click();
+
+ await expect(
+ page.getByText(
+ 'CLASSIC INFO NOTICE: Your cart is currently empty.'
+ )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the classic notices.
+ await expect(
+ page.locator( '.woocommerce-notices-wrapper .woocommerce-info' )
+ ).toBeVisible();
+
+ await requestUtils.activateTheme( BLOCK_THEME_SLUG );
+ } );
+
+ test( 'default classic notice templates cannot be triggered by filter', async ( {
+ requestUtils,
+ frontendUtils,
+ page,
+ } ) => {
+ await requestUtils.activateTheme(
+ BLOCK_CHILD_THEME_WITH_BLOCK_NOTICES_FILTER_SLUG
+ );
+
+ await frontendUtils.goToCartShortcode();
+ await page.getByPlaceholder( 'Coupon code' ).fill( 'testcoupon' );
+ await page.getByRole( 'button', { name: 'Apply coupon' } ).click();
+
+ await expect(
+ page.getByText( 'Coupon code applied successfully.', {
+ exact: true,
+ } )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes and that the SVG is visible.
+ await expect(
+ page.locator( '.wc-block-components-notice-banner.is-success svg' )
+ ).toBeVisible();
+
+ await page.reload();
+ await page.getByPlaceholder( 'Coupon code' ).fill( 'testcoupon' );
+ await page.getByRole( 'button', { name: 'Apply coupon' } ).click();
+
+ await expect(
+ page.getByText( 'Coupon code "testcoupon" already applied!', {
+ exact: true,
+ } )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes and that the SVG is hidden.
+ await expect(
+ page.locator( '.wc-block-components-notice-banner.is-error svg' )
+ ).toBeHidden();
+
+ await page.getByLabel( 'Remove Polo from cart' ).click();
+
+ await expect(
+ page.getByText( 'Your cart is currently empty.', {
+ exact: true,
+ } )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes and that the SVG is visible.
await expect(
page.locator( '.wc-block-components-notice-banner.is-success svg' )
).toBeVisible();
@@ -105,9 +271,12 @@ test.describe( 'Shopper → Notice Templates', () => {
}
)
).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the block notices, and that the SVG is hidden.
await expect(
page.locator( '.wc-block-components-notice-banner.is-error svg' )
).toBeHidden();
+
await expect( page.locator( '.coupon-error-notice' ) ).toBeVisible();
} );
} );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/cart/cart-checkout-block-notices.shopper.classic_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/cart/cart-checkout-block-notices.shopper.classic_theme.spec.ts
index bc6a902899f..a66c4af8177 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/cart/cart-checkout-block-notices.shopper.classic_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/cart/cart-checkout-block-notices.shopper.classic_theme.spec.ts
@@ -6,14 +6,19 @@ import {
test as base,
wpCLI,
CLASSIC_THEME_SLUG,
+ CLASSIC_CHILD_THEME_WITH_CLASSIC_NOTICES_TEMPLATE_SLUG,
CLASSIC_CHILD_THEME_WITH_BLOCK_NOTICES_FILTER_SLUG,
+ CLASSIC_CHILD_THEME_WITH_BLOCK_NOTICES_TEMPLATE_SLUG,
} from '@woocommerce/e2e-utils';
/**
* Internal dependencies
*/
import { CheckoutPage } from '../checkout/checkout.page';
-import { REGULAR_PRICED_PRODUCT_NAME } from '../checkout/constants';
+import {
+ REGULAR_PRICED_PRODUCT_NAME,
+ INVALID_COUPON,
+} from '../checkout/constants';
const test = base.extend< { checkoutPageObject: CheckoutPage } >( {
checkoutPageObject: async ( { page }, use ) => {
@@ -41,6 +46,160 @@ test.describe( 'Shopper → Notice Templates', () => {
await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
} );
+ test( 'default classic notice templates, except for coupon errors, are visible', async ( {
+ frontendUtils,
+ page,
+ } ) => {
+ await frontendUtils.goToCartShortcode();
+ await page.getByPlaceholder( 'Coupon code' ).fill( 'testcoupon' );
+ await page.getByRole( 'button', { name: 'Apply coupon' } ).click();
+
+ await expect(
+ page.getByText( 'Coupon code applied successfully.', {
+ exact: true,
+ } )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the classic notices.
+ await expect(
+ page.locator( '.woocommerce-notices-wrapper .woocommerce-message' )
+ ).toBeVisible();
+
+ await page.reload();
+ await page.getByPlaceholder( 'Coupon code' ).fill( 'testcoupon' );
+ await page.getByRole( 'button', { name: 'Apply coupon' } ).click();
+
+ await expect(
+ page.getByText( 'Coupon code "testcoupon" already applied!', {
+ exact: true,
+ } )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the classic notices.
+ await expect(
+ page.locator( '.woocommerce-notices-wrapper .woocommerce-error' )
+ ).toBeHidden();
+
+ await page.getByLabel( 'Remove Polo from cart' ).click();
+
+ await expect(
+ page.getByText( 'Your cart is currently empty.', { exact: true } )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the classic notices.
+ await expect(
+ page.locator( '.woocommerce-notices-wrapper .woocommerce-info' )
+ ).toBeVisible();
+ } );
+
+ test( 'custom classic notice templates, except for coupon errors, are visible by template overwrite', async ( {
+ requestUtils,
+ frontendUtils,
+ page,
+ } ) => {
+ await requestUtils.activateTheme(
+ CLASSIC_CHILD_THEME_WITH_CLASSIC_NOTICES_TEMPLATE_SLUG
+ );
+
+ await frontendUtils.goToCartShortcode();
+ await page.getByPlaceholder( 'Coupon code' ).fill( 'testcoupon' );
+ await page.getByRole( 'button', { name: 'Apply coupon' } ).click();
+
+ await expect(
+ page.getByText(
+ 'CLASSIC SUCCESS NOTICE: Coupon code applied successfully.'
+ )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the classic notices.
+ await expect(
+ page.locator( '.woocommerce-notices-wrapper .woocommerce-message' )
+ ).toBeVisible();
+
+ await page.reload();
+ await page.getByPlaceholder( 'Coupon code' ).fill( 'testcoupon' );
+ await page.getByRole( 'button', { name: 'Apply coupon' } ).click();
+
+ await expect(
+ page.getByText(
+ 'CLASSIC ERROR NOTICE: Coupon code "testcoupon" already applied!'
+ )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the classic notices.
+ await expect(
+ page.locator( '.woocommerce-notices-wrapper .woocommerce-error' )
+ ).toBeHidden();
+
+ await page.getByLabel( 'Remove Polo from cart' ).click();
+
+ await expect(
+ page.getByText(
+ 'CLASSIC INFO NOTICE: Your cart is currently empty.'
+ )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the classic notices.
+ await expect(
+ page.locator( '.woocommerce-notices-wrapper .woocommerce-info' )
+ ).toBeVisible();
+
+ await requestUtils.activateTheme( CLASSIC_THEME_SLUG );
+ } );
+
+ test( 'custom block notice templates, except for coupon errors, are visible by template overwrite', async ( {
+ requestUtils,
+ frontendUtils,
+ page,
+ } ) => {
+ await requestUtils.activateTheme(
+ CLASSIC_CHILD_THEME_WITH_BLOCK_NOTICES_TEMPLATE_SLUG
+ );
+
+ await frontendUtils.goToCartShortcode();
+ await page.getByPlaceholder( 'Coupon code' ).fill( 'testcoupon' );
+ await page.getByRole( 'button', { name: 'Apply coupon' } ).click();
+
+ await expect(
+ page.getByText(
+ 'BLOCK SUCCESS NOTICE: Coupon code applied successfully.'
+ )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the block notices, and that the SVG is visible.
+ await expect(
+ page.locator( '.wc-block-components-notice-banner.is-success svg' )
+ ).toBeVisible();
+
+ await page.reload();
+ await page.getByPlaceholder( 'Coupon code' ).fill( 'testcoupon' );
+ await page.getByRole( 'button', { name: 'Apply coupon' } ).click();
+
+ await expect(
+ page.getByText(
+ 'BLOCK ERROR NOTICE: Coupon code "testcoupon" already applied!'
+ )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the block notices, and that the SVG is hidden.
+ await expect(
+ page.locator( '.wc-block-components-notice-banner.is-error svg' )
+ ).toBeHidden();
+
+ await page.getByLabel( 'Remove Polo from cart' ).click();
+
+ await expect(
+ page.getByText( 'BLOCK INFO NOTICE: Your cart is currently empty.' )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the block notices, and that the SVG is visible.
+ await expect(
+ page.locator( '.wc-block-components-notice-banner.is-success svg' )
+ ).toBeVisible();
+
+ await requestUtils.activateTheme( CLASSIC_THEME_SLUG );
+ } );
+
test( 'default block notice templates, except for coupon errors, are visible by filter', async ( {
requestUtils,
frontendUtils,
@@ -57,6 +216,8 @@ test.describe( 'Shopper → Notice Templates', () => {
await expect(
page.getByText( 'Coupon code applied successfully.' )
).toBeVisible();
+
+ // We're explicitly checking the CSS classes and that the SVG is visible.
await expect(
page.locator( '.wc-block-components-notice-banner.is-success svg' )
).toBeVisible();
@@ -68,6 +229,8 @@ test.describe( 'Shopper → Notice Templates', () => {
await expect(
page.getByText( 'Coupon code "testcoupon" already applied!' )
).toBeVisible();
+
+ // We're explicitly checking the CSS classes and that the SVG is hidden.
await expect(
page.locator( '.wc-block-components-notice-banner.is-error svg' )
).toBeHidden();
@@ -77,10 +240,37 @@ test.describe( 'Shopper → Notice Templates', () => {
await expect(
page.getByText( 'Your cart is currently empty.' )
).toBeVisible();
+
+ // We're explicitly checking the CSS classes and that the SVG is visible.
await expect(
page.locator( '.wc-block-components-notice-banner.is-success svg' )
).toBeVisible();
await requestUtils.activateTheme( CLASSIC_THEME_SLUG );
} );
+
+ test( 'coupon inline notice is visible', async ( {
+ frontendUtils,
+ page,
+ } ) => {
+ await frontendUtils.goToCartShortcode();
+ await page.getByPlaceholder( 'Coupon code' ).fill( INVALID_COUPON );
+ await page.getByRole( 'button', { name: 'Apply coupon' } ).click();
+
+ await expect(
+ page.getByText(
+ `Coupon "${ INVALID_COUPON }" cannot be applied because it does not exist.`,
+ {
+ exact: true,
+ }
+ )
+ ).toBeVisible();
+
+ // We're explicitly checking the CSS classes of the block notices, and that the SVG is hidden.
+ await expect(
+ page.locator( '.wc-block-components-notice-banner.is-error svg' )
+ ).toBeHidden();
+
+ await expect( page.locator( '.coupon-error-notice' ) ).toBeVisible();
+ } );
} );
diff --git a/plugins/woocommerce/tests/php/src/Blocks/Domain/Services/NoticesTest.php b/plugins/woocommerce/tests/php/src/Blocks/Domain/Services/NoticesTest.php
deleted file mode 100644
index 639f8fe51fa..00000000000
--- a/plugins/woocommerce/tests/php/src/Blocks/Domain/Services/NoticesTest.php
+++ /dev/null
@@ -1,486 +0,0 @@
-<?php
-declare( strict_types = 1 );
-
-namespace Automattic\WooCommerce\Tests\Blocks\Domain\Services;
-
-use Automattic\WooCommerce\Blocks\Domain\Package;
-use Automattic\WooCommerce\Blocks\Domain\Services\Notices;
-use WC_Unit_Test_Case;
-use WP_Hook;
-
-/**
- * Tests for the Notices service.
- */
-class NoticesTest extends WC_Unit_Test_Case {
-
- /**
- * Original active theme stylesheet slug.
- *
- * @var string|null
- */
- private static $original_theme = null;
-
- /**
- * Hooks restored after each test.
- *
- * @var string[]
- */
- private const SNAPSHOT_HOOKS = array(
- 'after_setup_theme',
- 'wc_get_template',
- 'wp_head',
- 'woocommerce_kses_notice_allowed_tags',
- 'woocommerce_use_block_notices_in_classic_theme',
- 'doing_it_wrong_trigger_error',
- );
-
- /**
- * Action counts restored after each test.
- *
- * @var string[]
- */
- private const SNAPSHOT_ACTION_COUNTS = array(
- 'after_setup_theme',
- 'wp_head',
- );
-
- /**
- * Original hook state.
- *
- * @var array<string, WP_Hook|null>
- */
- private $hook_snapshots = array();
-
- /**
- * Original action counts.
- *
- * @var array<string, int|null>
- */
- private $action_counts = array();
-
- /**
- * Original style queue state.
- *
- * @var array<string, mixed>
- */
- private $style_snapshot = array();
-
- /**
- * Theme directories registered before each test.
- *
- * @var string[]
- */
- private $theme_directories = array();
-
- /**
- * Set up test fixtures.
- */
- public function setUp(): void {
- parent::setUp();
-
- global $wp_theme_directories;
-
- $this->theme_directories = $wp_theme_directories;
- register_theme_directory( trailingslashit( WC_ABSPATH ) . 'tests/e2e/themes/blocks' );
- search_theme_directories( true );
- if ( null === self::$original_theme ) {
- self::$original_theme = get_stylesheet();
- }
- $this->snapshot_hooks();
- $this->snapshot_action_counts();
- $this->snapshot_styles();
- $this->reset_wc_blocks_style_queue();
- wc_clear_template_cache();
- }
-
- /**
- * Restore test fixtures.
- */
- public function tearDown(): void {
- global $wp_theme_directories;
-
- if ( null !== self::$original_theme && get_stylesheet() !== self::$original_theme ) {
- switch_theme( self::$original_theme );
- }
-
- $this->restore_hooks();
- $this->restore_action_counts();
- $this->restore_styles();
- wc_clear_template_cache();
- $wp_theme_directories = $this->theme_directories; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Restore the exact theme roots registered before this test.
- search_theme_directories( true );
-
- parent::tearDown();
- }
-
- /**
- * Restore the original theme after the class completes.
- */
- public static function tearDownAfterClass(): void {
- if ( null !== self::$original_theme && get_stylesheet() !== self::$original_theme ) {
- switch_theme( self::$original_theme );
- }
- }
-
- /**
- * @testdox Notices activate the expected hooks, locate the expected template, and enqueue styles for every block-theme branch.
- */
- public function test_block_theme_notice_template_matrix(): void {
- $this->setExpectedIncorrectUsage( 'WP_Block_Templates_Registry::register' );
- add_filter( 'doing_it_wrong_trigger_error', array( $this, 'suppress_block_template_registry_notice' ), 10, 4 );
-
- foreach ( array( false, true ) as $opt_in_filter ) {
- foreach ( array( 'none', 'block', 'classic' ) as $fixture_kind ) {
- foreach ( array( 'notices/error.php', 'notices/notice.php', 'notices/success.php' ) as $template_name ) {
- $this->assert_notice_template_row( 'twentytwentyfour', $opt_in_filter, $fixture_kind, $template_name );
- }
- }
- }
- }
-
- /**
- * @testdox Notices activate the expected hooks, locate the expected template, and enqueue styles for every classic-theme branch.
- */
- public function test_classic_theme_notice_template_matrix(): void {
- foreach ( array( false, true ) as $opt_in_filter ) {
- foreach ( array( 'none', 'block', 'classic' ) as $fixture_kind ) {
- foreach ( array( 'notices/error.php', 'notices/notice.php', 'notices/success.php' ) as $template_name ) {
- $this->assert_notice_template_row( 'storefront', $opt_in_filter, $fixture_kind, $template_name );
- }
- }
- }
- }
-
- /**
- * @testdox Notices leave unrelated templates untouched.
- */
- public function test_unrelated_templates_pass_through_unchanged(): void {
- $base_template = wc_locate_template( 'cart/cart-empty.php' );
- $resolved = ( new Notices( new Package( 'test', WC_ABSPATH ) ) )->get_notices_template(
- $base_template,
- 'cart/cart-empty.php',
- array(),
- '',
- trailingslashit( WC_ABSPATH ) . 'templates/'
- );
-
- $this->assertSame( $base_template, $resolved, 'The service should not replace templates outside the three notice files.' );
- $this->assertFalse( wp_style_is( 'wc-blocks-style', 'enqueued' ), 'Passing through an unrelated template must not enqueue block notice styles.' );
- }
-
- /**
- * @testdox Notices preserve existing allowed tags and add the exact SVG/path allow-list.
- */
- public function test_notice_kses_tags_preserve_existing_tags_and_add_svg_support(): void {
- $sut = new Notices( new Package( 'test', WC_ABSPATH ) );
- $allowed_tags = array(
- 'a' => array(
- 'href' => true,
- ),
- );
-
- $sut->init();
-
- /**
- * Run the notices allow-list filter through the live callback stack.
- *
- * @since 11.1.0
- */
- $result = apply_filters( 'woocommerce_kses_notice_allowed_tags', $allowed_tags );
-
- $this->assertArrayHasKey( 'a', $result, 'Existing allow-listed tags should be preserved.' );
- $this->assertSame( $allowed_tags['a'], $result['a'], 'Existing allow-listed tag attributes should remain unchanged.' );
- $this->assertSame(
- array(
- 'aria-hidden' => true,
- 'xmlns' => true,
- 'width' => true,
- 'height' => true,
- 'viewbox' => true,
- 'focusable' => true,
- ),
- $result['svg'] ?? null,
- 'The filter should add the exact SVG attributes used by block notices.'
- );
- $this->assertSame(
- array(
- 'd' => true,
- ),
- $result['path'] ?? null,
- 'The filter should add the exact SVG path attributes used by block notices.'
- );
- }
-
- /**
- * Snapshot the hooks touched by the suite.
- */
- private function snapshot_hooks(): void {
- foreach ( self::SNAPSHOT_HOOKS as $hook_name ) {
- $this->hook_snapshots[ $hook_name ] = isset( $GLOBALS['wp_filter'][ $hook_name ] ) ? clone $GLOBALS['wp_filter'][ $hook_name ] : null;
- }
- }
-
- /**
- * Restore the hooks touched by the suite.
- */
- private function restore_hooks(): void {
- foreach ( self::SNAPSHOT_HOOKS as $hook_name ) {
- if ( $this->hook_snapshots[ $hook_name ] instanceof WP_Hook ) {
- $GLOBALS['wp_filter'][ $hook_name ] = clone $this->hook_snapshots[ $hook_name ]; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Restore the exact hook stack captured at setUp.
- } elseif ( isset( $GLOBALS['wp_filter'][ $hook_name ] ) ) {
- unset( $GLOBALS['wp_filter'][ $hook_name ] ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Remove callbacks registered by this test.
- }
- }
- }
-
- /**
- * Snapshot the action counts touched by the suite.
- */
- private function snapshot_action_counts(): void {
- foreach ( self::SNAPSHOT_ACTION_COUNTS as $action_name ) {
- $this->action_counts[ $action_name ] = $GLOBALS['wp_actions'][ $action_name ] ?? null;
- }
- }
-
- /**
- * Restore the action counts touched by the suite.
- */
- private function restore_action_counts(): void {
- foreach ( self::SNAPSHOT_ACTION_COUNTS as $action_name ) {
- if ( null !== $this->action_counts[ $action_name ] ) {
- $GLOBALS['wp_actions'][ $action_name ] = $this->action_counts[ $action_name ]; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Restore the action count captured at setUp.
- } elseif ( isset( $GLOBALS['wp_actions'][ $action_name ] ) ) {
- unset( $GLOBALS['wp_actions'][ $action_name ] ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Remove counts introduced by this test.
- }
- }
- }
-
- /**
- * Snapshot the style queue state touched by the suite.
- */
- private function snapshot_styles(): void {
- $wp_styles = wp_styles();
- $this->style_snapshot = array(
- 'queue' => $wp_styles->queue,
- 'done' => $wp_styles->done,
- 'to_do' => $wp_styles->to_do,
- 'groups' => $wp_styles->groups,
- 'args' => $wp_styles->args,
- );
- }
-
- /**
- * Restore the style queue state touched by the suite.
- */
- private function restore_styles(): void {
- $wp_styles = wp_styles();
- $wp_styles->queue = $this->style_snapshot['queue'];
- $wp_styles->done = $this->style_snapshot['done'];
- $wp_styles->to_do = $this->style_snapshot['to_do'];
- $wp_styles->groups = $this->style_snapshot['groups'];
- $wp_styles->args = $this->style_snapshot['args'];
- }
-
- /**
- * Remove wc-blocks-style from the working queue so each test starts clean.
- */
- private function reset_wc_blocks_style_queue(): void {
- $wp_styles = wp_styles();
- $wp_styles->queue = array_values( array_diff( $wp_styles->queue, array( 'wc-blocks-style' ) ) );
- $wp_styles->done = array_values( array_diff( $wp_styles->done, array( 'wc-blocks-style' ) ) );
- $wp_styles->to_do = array_values( array_diff( $wp_styles->to_do, array( 'wc-blocks-style' ) ) );
- unset( $wp_styles->args['wc-blocks-style'] );
- }
-
- /**
- * Assert one row from the notices matrix.
- *
- * @param string $theme_slug Active base theme slug.
- * @param bool $opt_in_filter Whether the classic-theme opt-in filter returns true.
- * @param string $fixture_kind Fixture type.
- * @param string $template_name Notice template to resolve.
- */
- private function assert_notice_template_row( string $theme_slug, bool $opt_in_filter, string $fixture_kind, string $template_name ): void {
- $this->restore_hooks();
- $this->restore_action_counts();
- $this->restore_styles();
- $this->reset_wc_blocks_style_queue();
- wc_clear_template_cache();
-
- $sut = new Notices( new Package( 'test', WC_ABSPATH ) );
- $active_theme_slug = $this->get_active_theme_slug( $theme_slug, $fixture_kind );
-
- if ( 'twentytwentyfour' === $theme_slug ) {
- add_filter( 'doing_it_wrong_trigger_error', array( $this, 'suppress_block_template_registry_notice' ), 10, 4 );
- }
-
- switch_theme( $active_theme_slug );
- add_filter(
- 'woocommerce_use_block_notices_in_classic_theme',
- $opt_in_filter ? '__return_true' : '__return_false'
- );
-
- $sut->init();
-
- $this->assertSame(
- 10,
- has_filter( 'woocommerce_kses_notice_allowed_tags', array( $sut, 'add_kses_notice_allowed_tags' ) ),
- 'The SVG/path allow-list callback should always be registered at priority 10.'
- );
-
- /**
- * Trigger the service's theme-setup gate after init() has registered its callbacks.
- *
- * @since 11.1.0
- */
- do_action( 'after_setup_theme' );
-
- $service_is_active = wp_is_block_theme() || $opt_in_filter;
- $expected_path = $this->get_expected_template_path( $theme_slug, $fixture_kind, $template_name, $service_is_active );
- $result = $this->resolve_notice_template( $template_name );
-
- $this->assertSame(
- $service_is_active ? 10 : false,
- has_filter( 'wc_get_template', array( $sut, 'get_notices_template' ) ),
- 'The wc_get_template callback should match the theme/filter activation branch.'
- );
- $this->assertSame(
- $service_is_active ? 10 : false,
- has_action( 'wp_head', array( $sut, 'enqueue_notice_styles' ) ),
- 'The wp_head enqueue callback should match the theme/filter activation branch.'
- );
- $this->assertSame(
- $expected_path,
- $result['located'],
- 'The resolved template path should follow the exact theme/filter/fixture matrix.'
- );
- $this->assertSame(
- $service_is_active && 'none' === $fixture_kind,
- $result['style_enqueued_after_resolution'],
- 'The packaged block template should enqueue wc-blocks-style during resolution only when no child override handled the template.'
- );
-
- ob_start();
- /**
- * Mirror the front-end head render so the shared stylesheet enqueue can run.
- *
- * @since 11.1.0
- */
- do_action( 'wp_head' );
- ob_end_clean();
-
- $this->assertSame(
- $service_is_active,
- wp_style_is( 'wc-blocks-style', 'enqueued' ),
- 'The shared notice stylesheet should only be enqueued after wp_head when block notices are active.'
- );
- }
-
- /**
- * Resolve a notice template through the real Woo template loader.
- *
- * @param string $template_name Template name.
- * @return array{located: string, style_enqueued_after_resolution: bool}
- */
- private function resolve_notice_template( string $template_name ): array {
- $captured_template = '';
- $capture_callback = static function ( $name, $template_path, $located ) use ( $template_name, &$captured_template ): void {
- if ( $template_name === $name ) {
- $captured_template = $located;
- }
- };
-
- wc_clear_template_cache();
- add_action( 'woocommerce_before_template_part', $capture_callback, 10, 3 );
-
- try {
- wc_get_template_html(
- $template_name,
- array(
- 'notices' => array(
- array(
- 'notice' => 'Test notice',
- 'data' => array(),
- ),
- ),
- )
- );
- } finally {
- remove_action( 'woocommerce_before_template_part', $capture_callback, 10 );
- }
-
- $this->assertNotSame( '', $captured_template, 'Resolving the notice template should capture the located path from the real Woo template loader.' );
-
- return array(
- 'located' => $captured_template,
- 'style_enqueued_after_resolution' => wp_style_is( 'wc-blocks-style', 'enqueued' ),
- );
- }
-
- /**
- * Resolve the active theme slug for a matrix row.
- *
- * @param string $theme_slug Active theme slug.
- * @param string $fixture_kind Fixture type.
- * @return string
- */
- private function get_active_theme_slug( string $theme_slug, string $fixture_kind ): string {
- if ( 'none' === $fixture_kind ) {
- return $theme_slug;
- }
-
- return 'twentytwentyfour' === $theme_slug
- ? sprintf( 'twentytwentyfour-child__%s-notices-template', $fixture_kind )
- : sprintf( 'storefront-child__%s-notices-template', $fixture_kind );
- }
-
- /**
- * Compute the exact expected resolved template path for a matrix row.
- *
- * @param string $theme_slug Active theme slug.
- * @param string $fixture_kind Fixture type.
- * @param string $template_name Template name.
- * @param bool $service_is_active Whether block notices are active.
- * @return string
- */
- private function get_expected_template_path(
- string $theme_slug,
- string $fixture_kind,
- string $template_name,
- bool $service_is_active
- ): string {
- if ( 'none' !== $fixture_kind ) {
- $active_theme_slug = $this->get_active_theme_slug( $theme_slug, $fixture_kind );
-
- return trailingslashit( get_theme_root( $active_theme_slug ) ) . $active_theme_slug . '/woocommerce/' . $template_name;
- }
-
- if ( $service_is_active ) {
- return trailingslashit( WC_ABSPATH ) . 'templates/block-' . $template_name;
- }
-
- return trailingslashit( WC_ABSPATH ) . 'templates/' . $template_name;
- }
-
- /**
- * Suppress the unrelated block template registry duplicate warning during block-theme setup.
- *
- * @param bool $trigger_error Whether WordPress should trigger the warning.
- * @param string $function_name Function reporting incorrect usage.
- * @param string $message Warning message.
- * @param string $version Version that introduced the warning.
- * @return bool
- */
- public function suppress_block_template_registry_notice( bool $trigger_error, string $function_name, string $message, string $version ): bool {
- unset( $version );
-
- if (
- 'WP_Block_Templates_Registry::register' === $function_name
- && str_contains( $message, 'Template "woocommerce//archive-product" is already registered.' )
- ) {
- return false;
- }
-
- return $trigger_error;
- }
-}