Commit e765c73c607 for woocommerce

commit e765c73c6079daa4e2fadc57825feac380b3a812
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date:   Thu Nov 5 19:58:24 2026 +0530

    Demote Mini-Cart item data E2E tests to the unit test suite (#68181)

diff --git a/plugins/woocommerce/changelog/testops-234-remove-redundant-mini-cart-item-data-tests b/plugins/woocommerce/changelog/testops-234-remove-redundant-mini-cart-item-data-tests
new file mode 100644
index 00000000000..c0850b5042e
--- /dev/null
+++ b/plugins/woocommerce/changelog/testops-234-remove-redundant-mini-cart-item-data-tests
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Remove 8 redundant Mini-Cart item data E2E tests already covered by unit tests.
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/frontend.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/frontend.ts
index e94c3a6d4a4..1a4b4deb628 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/frontend.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/frontend.ts
@@ -28,10 +28,10 @@ import type { CartItem, Currency } from '@woocommerce/types';
 import { getFocusableElements } from '../../utils/get-focusable-elements';
 import { translateJQueryEventToNative } from '../../base/stores/woocommerce/legacy-events';
 import {
-	getEntryFieldRaw,
 	isItemDataEntryVisible,
 	isLastVisibleEntry,
 	buildCartItemDataAttr,
+	renderEntryFieldHTML,
 } from './utils/item-data';
 import type { ItemData, CartItemDataAttr } from './utils/item-data';

@@ -439,14 +439,6 @@ function resolveDataItemAttr(): ItemData | undefined {
 	return itemData || cartItemState.cartItem[ dataProperty ]?.[ 0 ];
 }

-/**
- * Returns the raw API value for an item_data field. Used by both innerHTML
- * callbacks and the cartItemDataAttr getter.
- */
-function getItemDataRaw( field: 'name' | 'value' ): string {
-	return getEntryFieldRaw( resolveDataItemAttr(), field );
-}
-
 const { state: cartItemState } = store(
 	'woocommerce/mini-cart-products-table-block',
 	{
@@ -960,18 +952,22 @@ const { state: cartItemState } = store(

 			itemDataNameInnerHTML() {
 				const { ref } = getElement();
-				const raw = getItemDataRaw( 'name' );
-				if ( ref && raw ) {
-					ref.innerHTML = trimWords( raw + ':' );
-				}
+				renderEntryFieldHTML(
+					ref,
+					resolveDataItemAttr(),
+					'name',
+					trimWords
+				);
 			},

 			itemDataValueInnerHTML() {
 				const { ref } = getElement();
-				const raw = getItemDataRaw( 'value' );
-				if ( ref && raw ) {
-					ref.innerHTML = trimWords( raw );
-				}
+				renderEntryFieldHTML(
+					ref,
+					resolveDataItemAttr(),
+					'value',
+					trimWords
+				);
 			},

 			filterCartItemClass() {
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/utils/item-data.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/utils/item-data.ts
index b677041b1aa..4afef04e150 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/utils/item-data.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/utils/item-data.ts
@@ -1,11 +1,11 @@
 /**
- * Pure, side-effect-free helpers backing the Mini-Cart product table's
- * per-entry `item_data`/`variation` rendering.
+ * Framework-free helpers backing the Mini-Cart product table's per-entry
+ * `item_data`/`variation` rendering.
  *
  * This module must stay free of top-level side effects and must not import
  * `@wordpress/interactivity` so it can be unit-tested with zero mocking.
- * `frontend.ts`'s store getters resolve the Interactivity context and
- * delegate the actual decision-making to these functions.
+ * `frontend.ts`'s store getters and callbacks resolve the Interactivity
+ * context and delegate the actual decision-making to these functions.
  */

 /**
@@ -175,3 +175,37 @@ export function buildCartItemDataAttr(
 			.toLowerCase() }`,
 	};
 }
+
+/**
+ * Writes an entry's raw field into `el` as HTML, backing the Mini-Cart's
+ * `itemDataNameInnerHTML`/`itemDataValueInnerHTML` callbacks.
+ *
+ * The raw string goes through `innerHTML` on purpose. The Store API returns
+ * `item_data` already escaped and a `display` field may carry real markup,
+ * so entities decode (`1 &lt; 2` reads as `1 < 2`) while entity-encoded tags
+ * stay inert text rather than becoming elements.
+ *
+ * Nothing is written when there is no element or no usable field value, so a
+ * malformed entry leaves the existing markup alone.
+ *
+ * @param {Element | null | undefined} el     Element to write into.
+ * @param {ItemData | undefined}       entry  Entry to read the field from.
+ * @param {'name'|'value'}             field  Which field to render. `name` is
+ *                                            suffixed with `:`.
+ * @param {Function}                   format Optional transform applied to the
+ *                                            HTML before it is written.
+ */
+export function renderEntryFieldHTML(
+	el: Element | null | undefined,
+	entry: ItemData | undefined,
+	field: 'name' | 'value',
+	format: ( html: string ) => string = ( html ) => html
+): void {
+	const raw = getEntryFieldRaw( entry, field );
+
+	if ( ! el || ! raw ) {
+		return;
+	}
+
+	el.innerHTML = format( field === 'name' ? `${ raw }:` : raw );
+}
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/utils/test/item-data.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/utils/test/item-data.ts
index c24f25bd040..87b2b5119a7 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/utils/test/item-data.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/mini-cart/utils/test/item-data.ts
@@ -8,6 +8,7 @@ import {
 	isItemDataEntryVisible,
 	isLastVisibleEntry,
 	buildCartItemDataAttr,
+	renderEntryFieldHTML,
 } from '../item-data';

 /**
@@ -397,3 +398,68 @@ describe( 'buildCartItemDataAttr tests', () => {
 		} );
 	} );
 } );
+
+describe( 'renderEntryFieldHTML tests', () => {
+	let el: HTMLElement;
+
+	beforeEach( () => {
+		el = document.createElement( 'span' );
+	} );
+
+	test( 'renders the display field as real markup', () => {
+		renderEntryFieldHTML(
+			el,
+			entry( 'Engraving', 'Best Wishes', {
+				display: '<em>Best Wishes</em>',
+			} ),
+			'value'
+		);
+
+		expect( el.querySelectorAll( 'em' ) ).toHaveLength( 1 );
+		expect( el.querySelector( 'em' )?.textContent ).toBe( 'Best Wishes' );
+	} );
+
+	test( 'decodes entity-encoded characters in the value', () => {
+		renderEntryFieldHTML( el, entry( 'Size', '1 &lt; 2' ), 'value' );
+
+		expect( el.textContent ).toBe( '1 < 2' );
+	} );
+
+	test( 'leaves entity-encoded tags as text instead of DOM elements', () => {
+		renderEntryFieldHTML(
+			el,
+			entry( 'Note', '&lt;b&gt;important&lt;/b&gt;' ),
+			'value'
+		);
+
+		expect( el.querySelectorAll( 'b' ) ).toHaveLength( 0 );
+		expect( el.textContent ).toBe( '<b>important</b>' );
+	} );
+
+	test( 'suffixes the name field with a colon', () => {
+		renderEntryFieldHTML( el, entry( 'Gift Message' ), 'name' );
+
+		expect( el.textContent ).toBe( 'Gift Message:' );
+	} );
+
+	test( 'writes nothing when there is no usable field value', () => {
+		el.innerHTML = 'untouched';
+
+		renderEntryFieldHTML( el, malformed(), 'value' );
+		renderEntryFieldHTML( el, malformed(), 'name' );
+		renderEntryFieldHTML( el, undefined, 'value' );
+
+		expect( el.innerHTML ).toBe( 'untouched' );
+	} );
+
+	test( 'applies the format callback before writing', () => {
+		renderEntryFieldHTML(
+			el,
+			entry( 'Size', '1 &lt; 2' ),
+			'value',
+			( html ) => `${ html }…`
+		);
+
+		expect( el.textContent ).toBe( '1 < 2…' );
+	} );
+} );
diff --git a/plugins/woocommerce/client/blocks/changelog/testops-234-cover-item-data-inner-html-path b/plugins/woocommerce/client/blocks/changelog/testops-234-cover-item-data-inner-html-path
new file mode 100644
index 00000000000..352f075bb2c
--- /dev/null
+++ b/plugins/woocommerce/client/blocks/changelog/testops-234-cover-item-data-inner-html-path
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Cover the Mini-Cart item data innerHTML rendering path in unit tests.
diff --git a/plugins/woocommerce/tests/e2e/test-plugins/blocks/item-data-display-hidden.php b/plugins/woocommerce/tests/e2e/test-plugins/blocks/item-data-display-hidden.php
deleted file mode 100644
index 4a2061688b6..00000000000
--- a/plugins/woocommerce/tests/e2e/test-plugins/blocks/item-data-display-hidden.php
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-/**
- * Plugin Name: WooCommerce Blocks Test Item Data Display Hidden
- * Description: Adds a trailing explicitly-hidden item_data entry (or two consecutive ones) to cart items for Mini-Cart e2e tests.
- * Plugin URI: https://github.com/woocommerce/woocommerce
- * Author: WooCommerce
- *
- * @package woocommerce-blocks-test-item-data-display-hidden
- */
-
-declare(strict_types=1);
-
-// Only requests from specs that enable this plugin send the cookie.
-if ( ! isset( $_COOKIE['woocommerce-blocks-test-item-data-display-hidden'] ) ) {
-	return;
-}
-
-final class Item_Data_Display_Hidden_Test_Fixture {
-	/**
-	 * Register fixture hooks.
-	 *
-	 * @internal
-	 */
-	public function __construct() {
-		add_action( 'woocommerce_init', array( $this, 'handle_woocommerce_init' ) );
-	}
-
-	/**
-	 * Register the item data filter.
-	 *
-	 * @internal
-	 */
-	public function handle_woocommerce_init(): void {
-		add_filter(
-			'woocommerce_get_item_data',
-			array( $this, 'handle_woocommerce_get_item_data' ),
-			10,
-			2
-		);
-	}
-
-	/**
-	 * Add explicitly hidden item data entries.
-	 *
-	 * @internal
-	 *
-	 * @param array $item_data Existing item data.
-	 * @param array $cart_item Cart item data.
-	 * @return array Filtered item data.
-	 */
-	public function handle_woocommerce_get_item_data( array $item_data, array $cart_item ): array {
-		// A well-formed leading entry, so there is always a defined
-		// "last visible entry" to check the separator against.
-		$item_data[] = array(
-			'key'   => 'Gift Message',
-			'value' => 'Happy Birthday!',
-		);
-
-		// Well-formed (usable key/value) but explicitly hidden.
-		$item_data[] = array(
-			'key'    => 'Secret',
-			'value'  => 'v',
-			'hidden' => true,
-		);
-
-		// A cart line added with quantity 2 instead ends with two
-		// *consecutive* explicitly-hidden entries, so both the
-		// single- and double-trailing-hidden scenarios are covered
-		// by this one fixture.
-		if ( 2 === (int) ( $cart_item['quantity'] ?? 0 ) ) {
-			$item_data[] = array(
-				'key'    => 'Secret 2',
-				'value'  => 'v2',
-				'hidden' => true,
-			);
-		}
-
-		return $item_data;
-	}
-}
-
-new Item_Data_Display_Hidden_Test_Fixture();
diff --git a/plugins/woocommerce/tests/e2e/test-plugins/blocks/item-data-display-malformed.php b/plugins/woocommerce/tests/e2e/test-plugins/blocks/item-data-display-malformed.php
deleted file mode 100644
index 8939373631d..00000000000
--- a/plugins/woocommerce/tests/e2e/test-plugins/blocks/item-data-display-malformed.php
+++ /dev/null
@@ -1,81 +0,0 @@
-<?php
-/**
- * Plugin Name: WooCommerce Blocks Test Item Data Display Malformed
- * Description: Adds a trailing malformed item_data entry (or two consecutive ones) to cart items for Mini-Cart e2e tests.
- * Plugin URI: https://github.com/woocommerce/woocommerce
- * Author: WooCommerce
- *
- * @package woocommerce-blocks-test-item-data-display-malformed
- */
-
-declare(strict_types=1);
-
-// Only requests from specs that enable this plugin send the cookie.
-if ( ! isset( $_COOKIE['woocommerce-blocks-test-item-data-display-malformed'] ) ) {
-	return;
-}
-
-final class Item_Data_Display_Malformed_Test_Fixture {
-	/**
-	 * Register fixture hooks.
-	 *
-	 * @internal
-	 */
-	public function __construct() {
-		add_action( 'woocommerce_init', array( $this, 'handle_woocommerce_init' ) );
-	}
-
-	/**
-	 * Register the item data filter.
-	 *
-	 * @internal
-	 */
-	public function handle_woocommerce_init(): void {
-		add_filter(
-			'woocommerce_get_item_data',
-			array( $this, 'handle_woocommerce_get_item_data' ),
-			10,
-			2
-		);
-	}
-
-	/**
-	 * Add malformed item data entries.
-	 *
-	 * @internal
-	 *
-	 * @param array $item_data Existing item data.
-	 * @param array $cart_item Cart item data.
-	 * @return array Filtered item data.
-	 */
-	public function handle_woocommerce_get_item_data( array $item_data, array $cart_item ): array {
-		// A well-formed leading entry, so there is always a defined
-		// "last visible entry" to check the separator against.
-		$item_data[] = array(
-			'key'   => 'Gift Message',
-			'value' => 'Happy Birthday!',
-		);
-
-		// Malformed: no usable key/attribute/name and no usable
-		// display/value.
-		$item_data[] = array(
-			'key'   => '',
-			'value' => '',
-		);
-
-		// A cart line added with quantity 2 instead ends with two
-		// *consecutive* malformed entries, so both the single- and
-		// double-trailing-malformed scenarios are covered by this
-		// one fixture.
-		if ( 2 === (int) ( $cart_item['quantity'] ?? 0 ) ) {
-			$item_data[] = array(
-				'key'   => '',
-				'value' => '',
-			);
-		}
-
-		return $item_data;
-	}
-}
-
-new Item_Data_Display_Malformed_Test_Fixture();
diff --git a/plugins/woocommerce/tests/e2e/test-plugins/blocks/item-data-display-mixed.php b/plugins/woocommerce/tests/e2e/test-plugins/blocks/item-data-display-mixed.php
deleted file mode 100644
index 4745a4ffcfd..00000000000
--- a/plugins/woocommerce/tests/e2e/test-plugins/blocks/item-data-display-mixed.php
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-/**
- * Plugin Name: WooCommerce Blocks Test Item Data Display Mixed
- * Description: Adds a trailing mix of a malformed entry and an explicitly-hidden entry, in both orders, to cart items for Mini-Cart e2e tests.
- * Plugin URI: https://github.com/woocommerce/woocommerce
- * Author: WooCommerce
- *
- * @package woocommerce-blocks-test-item-data-display-mixed
- */
-
-declare(strict_types=1);
-
-// Only requests from specs that enable this plugin send the cookie.
-if ( ! isset( $_COOKIE['woocommerce-blocks-test-item-data-display-mixed'] ) ) {
-	return;
-}
-
-final class Item_Data_Display_Mixed_Test_Fixture {
-	/**
-	 * Register fixture hooks.
-	 *
-	 * @internal
-	 */
-	public function __construct() {
-		add_action( 'woocommerce_init', array( $this, 'handle_woocommerce_init' ) );
-	}
-
-	/**
-	 * Register the item data filter.
-	 *
-	 * @internal
-	 */
-	public function handle_woocommerce_init(): void {
-		add_filter(
-			'woocommerce_get_item_data',
-			array( $this, 'handle_woocommerce_get_item_data' ),
-			10,
-			2
-		);
-	}
-
-	/**
-	 * Add mixed malformed and hidden item data entries.
-	 *
-	 * @internal
-	 *
-	 * @param array $item_data Existing item data.
-	 * @param array $cart_item Cart item data.
-	 * @return array Filtered item data.
-	 */
-	public function handle_woocommerce_get_item_data( array $item_data, array $cart_item ): array {
-		// A well-formed leading entry, so there is always a defined
-		// "last visible entry" to check the separator against.
-		$item_data[] = array(
-			'key'   => 'Gift Message',
-			'value' => 'Happy Birthday!',
-		);
-
-		// A cart line added with quantity 2 ends with a hidden
-		// entry followed by a malformed one (the reverse order);
-		// otherwise (quantity 1) it ends with a malformed entry
-		// followed by a hidden one. Both orders are covered by
-		// this one fixture.
-		if ( 2 === (int) ( $cart_item['quantity'] ?? 0 ) ) {
-			$item_data[] = array(
-				'key'    => 'Secret',
-				'value'  => 'v',
-				'hidden' => true,
-			);
-			$item_data[] = array(
-				'key'   => '',
-				'value' => '',
-			);
-		} else {
-			$item_data[] = array(
-				'key'   => '',
-				'value' => '',
-			);
-			$item_data[] = array(
-				'key'    => 'Secret',
-				'value'  => 'v',
-				'hidden' => true,
-			);
-		}
-
-		return $item_data;
-	}
-}
-
-new Item_Data_Display_Mixed_Test_Fixture();
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart-item-data.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart-item-data.block_theme.spec.ts
deleted file mode 100644
index 6f452bef48b..00000000000
--- a/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart-item-data.block_theme.spec.ts
+++ /dev/null
@@ -1,475 +0,0 @@
-/**
- * External dependencies
- */
-import { test, expect, enableTestPlugin } from '@woocommerce/e2e-utils';
-import type { Page } from '@playwright/test';
-
-/**
- * Internal dependencies
- */
-import { REGULAR_PRICED_PRODUCT_NAME } from '../checkout/constants';
-
-test.describe( 'Mini-Cart Block (item data)', () => {
-	test.use( {
-		storageState: {
-			origins: [],
-			cookies: [],
-		},
-	} );
-
-	test.beforeEach( async ( { context } ) => {
-		await enableTestPlugin( context, 'item-data-display' );
-	} );
-
-	test( 'should render plain text item data', async ( {
-		page,
-		frontendUtils,
-		miniCartUtils,
-	} ) => {
-		await frontendUtils.goToShop();
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await miniCartUtils.openMiniCart();
-
-		const dialog = page.getByRole( 'dialog' );
-		await expect( dialog ).toBeVisible();
-
-		// Verify the plain text name and value are rendered.
-		await expect(
-			dialog.locator( '.wc-block-components-product-details__name' )
-		).toContainText( [ 'Gift Message' ] );
-		await expect(
-			dialog.locator( '.wc-block-components-product-details__value' )
-		).toContainText( [ 'Happy Birthday!' ] );
-	} );
-
-	test( 'should use display field value when present', async ( {
-		page,
-		frontendUtils,
-		miniCartUtils,
-	} ) => {
-		await frontendUtils.goToShop();
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await miniCartUtils.openMiniCart();
-
-		const dialog = page.getByRole( 'dialog' );
-		await expect( dialog ).toBeVisible();
-
-		// The display field contains "<em>Best Wishes</em>".
-		// Verify the <em> tag is rendered as an actual HTML element.
-		const engravingValue = dialog
-			.locator( '.wc-block-components-product-details__value' )
-			.filter( { hasText: 'Best Wishes' } );
-		await expect( engravingValue ).toBeVisible();
-		await expect( engravingValue.locator( 'em' ) ).toBeVisible();
-		await expect( engravingValue.locator( 'em' ) ).toHaveText(
-			'Best Wishes'
-		);
-	} );
-
-	test( 'should decode HTML entities in item data values', async ( {
-		page,
-		frontendUtils,
-		miniCartUtils,
-	} ) => {
-		await frontendUtils.goToShop();
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await miniCartUtils.openMiniCart();
-
-		const dialog = page.getByRole( 'dialog' );
-		await expect( dialog ).toBeVisible();
-
-		// The value "1 &lt; 2" should display as "1 < 2"
-		// (entity decoded), not as literal "1 &lt; 2".
-		const sizeValue = dialog
-			.locator( '.wc-block-components-product-details__value' )
-			.filter( { hasText: '1' } );
-		await expect( sizeValue ).toBeVisible();
-		await expect( sizeValue ).toContainText( '1 < 2' );
-	} );
-
-	test( 'should not render entity-encoded HTML tags as DOM elements', async ( {
-		page,
-		frontendUtils,
-		miniCartUtils,
-	} ) => {
-		await frontendUtils.goToShop();
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await miniCartUtils.openMiniCart();
-
-		const dialog = page.getByRole( 'dialog' );
-		await expect( dialog ).toBeVisible();
-
-		const noteValue = dialog
-			.locator( '.wc-block-components-product-details__value' )
-			.filter( { hasText: 'important' } );
-		await expect( noteValue ).toBeVisible();
-		await expect( noteValue.locator( 'b' ) ).toHaveCount( 0 );
-	} );
-
-	test( 'should show a separator between item-data entries but not after the last one', async ( {
-		page,
-		frontendUtils,
-		miniCartUtils,
-	} ) => {
-		await frontendUtils.goToShop();
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await miniCartUtils.openMiniCart();
-
-		const dialog = page.getByRole( 'dialog' );
-		await expect( dialog ).toBeVisible();
-
-		// The fixture plugin injects 4 well-formed entries (Gift Message,
-		// Engraving, Size, Note) onto the cart item. Confirm all 4 rendered
-		// before counting separators, so a count mismatch below can only be
-		// attributed to separator placement, not to a missing entry.
-		const entryNames = dialog.locator(
-			'.wc-block-components-product-details__name'
-		);
-		await expect( entryNames ).toHaveCount( 4 );
-
-		// Each entry's trailing separator is a `span[aria-hidden="true"]`;
-		// the store getter that drives its `hidden` binding removes the
-		// `hidden` attribute exactly for the entries that should show a
-		// separator. Count only the ones that are actually visible (not
-		// `hidden`) — using `textContent` with a regex would also match the
-		// text of `hidden` separators and give a false pass/fail.
-		const visibleSeparators = dialog.locator(
-			'.wc-block-components-product-details span[aria-hidden="true"]:not([hidden])'
-		);
-		await expect( visibleSeparators ).toHaveCount( 3 );
-	} );
-} );
-
-/**
- * Registers page-error and console-error listeners for a defect scenario
- * (trailing malformed/hidden item-data entries). Must be called before
- * navigating so nothing that happens during the mini-cart's render is
- * missed — mirrors the listener setup used by the "no client-side error"
- * test in mini-cart.block_theme.spec.ts.
- *
- * @param {Page} page Playwright page to observe.
- * @return {{pageErrors: string[], consoleErrors: string[]}} Arrays that
- *                                                            accumulate as
- *                                                            errors fire.
- */
-const trackErrors = ( page: Page ) => {
-	const pageErrors: string[] = [];
-	const consoleErrors: string[] = [];
-
-	page.on( 'pageerror', ( error ) => {
-		pageErrors.push( error.message );
-	} );
-	page.on( 'console', ( message ) => {
-		// Exclude the browser's own "failed to load resource" network
-		// diagnostics: they are not JS errors raised by page code (no
-		// `console.error` call is involved) and can fire for reasons
-		// entirely unrelated to the item-data/separator logic under test.
-		if (
-			message.type() === 'error' &&
-			! message.text().includes( 'Failed to load resource' )
-		) {
-			consoleErrors.push( message.text() );
-		}
-	} );
-
-	return { pageErrors, consoleErrors };
-};
-
-test.describe( 'Mini-Cart Block (item data - malformed trailing entries)', () => {
-	test.use( {
-		storageState: {
-			origins: [],
-			cookies: [],
-		},
-	} );
-
-	test.beforeEach( async ( { context } ) => {
-		await enableTestPlugin( context, 'item-data-display-malformed' );
-	} );
-
-	test( 'should render no client-side error and no trailing separator when the item data ends with a single malformed entry', async ( {
-		page,
-		frontendUtils,
-		miniCartUtils,
-	} ) => {
-		const { pageErrors, consoleErrors } = trackErrors( page );
-
-		await frontendUtils.goToShop();
-		// Quantity 1 selects the fixture's [visible, malformed] scenario.
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await miniCartUtils.openMiniCart();
-
-		const dialog = page.getByRole( 'dialog' );
-		await expect( dialog ).toBeVisible();
-
-		// Both entries render into the DOM (the malformed one hidden);
-		// confirm the DOM count before checking visibility, so a mismatch
-		// below can only be attributed to visibility/separator logic, not
-		// to a missing entry.
-		const allNames = dialog.locator(
-			'.wc-block-components-product-details__name'
-		);
-		await expect( allNames ).toHaveCount( 2 );
-
-		// The malformed entry renders no visible name/value: only the
-		// well-formed "Gift Message" entry is visible. Using `:visible`
-		// filters out elements hidden via the `hidden` attribute, unlike a
-		// plain `textContent` check which would also match hidden text.
-		const visibleNames = dialog.locator(
-			'.wc-block-components-product-details__name:visible'
-		);
-		await expect( visibleNames ).toHaveCount( 1 );
-		await expect( visibleNames ).toContainText( [ 'Gift Message' ] );
-
-		// The last (and only) visible entry shows no trailing separator,
-		// and no stray separator appears anywhere else in the list. Count
-		// only separators that are actually visible. `:visible` accounts
-		// for ancestor visibility, so a malformed/hidden entry's separator
-		// span — whose own `hidden` attribute is absent because it is not
-		// the last *visible* entry, but whose entry wrapper is hidden — is
-		// correctly excluded. A plain `:not([hidden])` attribute check
-		// would miss the hidden ancestor and a `textContent` regex would
-		// match hidden text.
-		const visibleSeparators = dialog.locator(
-			'.wc-block-components-product-details span[aria-hidden="true"]:visible'
-		);
-		await expect( visibleSeparators ).toHaveCount( 0 );
-
-		expect( pageErrors ).toEqual( [] );
-		expect( consoleErrors ).toEqual( [] );
-	} );
-
-	test( 'should render no client-side error and no trailing separator when the item data ends with two consecutive malformed entries', async ( {
-		page,
-		frontendUtils,
-		miniCartUtils,
-	} ) => {
-		const { pageErrors, consoleErrors } = trackErrors( page );
-
-		await frontendUtils.goToShop();
-		// Adding the same product twice merges into a single cart line with
-		// quantity 2, which selects the fixture's
-		// [visible, malformed, malformed] scenario.
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await miniCartUtils.openMiniCart();
-
-		const dialog = page.getByRole( 'dialog' );
-		await expect( dialog ).toBeVisible();
-
-		const allNames = dialog.locator(
-			'.wc-block-components-product-details__name'
-		);
-		await expect( allNames ).toHaveCount( 3 );
-
-		const visibleNames = dialog.locator(
-			'.wc-block-components-product-details__name:visible'
-		);
-		await expect( visibleNames ).toHaveCount( 1 );
-		await expect( visibleNames ).toContainText( [ 'Gift Message' ] );
-
-		// Count only separators that are actually visible. `:visible`
-		// accounts for ancestor visibility, so a malformed/hidden entry's
-		// separator span (hidden via its entry wrapper, not its own
-		// attribute) is correctly excluded — unlike `:not([hidden])`.
-		const visibleSeparators = dialog.locator(
-			'.wc-block-components-product-details span[aria-hidden="true"]:visible'
-		);
-		await expect( visibleSeparators ).toHaveCount( 0 );
-
-		expect( pageErrors ).toEqual( [] );
-		expect( consoleErrors ).toEqual( [] );
-	} );
-} );
-
-test.describe( 'Mini-Cart Block (item data - hidden trailing entries)', () => {
-	test.use( {
-		storageState: {
-			origins: [],
-			cookies: [],
-		},
-	} );
-
-	test.beforeEach( async ( { context } ) => {
-		await enableTestPlugin( context, 'item-data-display-hidden' );
-	} );
-
-	test( 'should render no client-side error and no trailing separator when the item data ends with a single explicitly-hidden entry', async ( {
-		page,
-		frontendUtils,
-		miniCartUtils,
-	} ) => {
-		const { pageErrors, consoleErrors } = trackErrors( page );
-
-		await frontendUtils.goToShop();
-		// Quantity 1 selects the fixture's [visible, hidden] scenario.
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await miniCartUtils.openMiniCart();
-
-		const dialog = page.getByRole( 'dialog' );
-		await expect( dialog ).toBeVisible();
-
-		const allNames = dialog.locator(
-			'.wc-block-components-product-details__name'
-		);
-		await expect( allNames ).toHaveCount( 2 );
-
-		// The explicitly-hidden entry renders no visible name/value ("Secret"
-		// never appears among the visible entries): only the well-formed
-		// "Gift Message" entry is visible.
-		const visibleNames = dialog.locator(
-			'.wc-block-components-product-details__name:visible'
-		);
-		await expect( visibleNames ).toHaveCount( 1 );
-		await expect( visibleNames ).toContainText( [ 'Gift Message' ] );
-
-		// Count only separators that are actually visible. `:visible`
-		// accounts for ancestor visibility, so a malformed/hidden entry's
-		// separator span (hidden via its entry wrapper, not its own
-		// attribute) is correctly excluded — unlike `:not([hidden])`.
-		const visibleSeparators = dialog.locator(
-			'.wc-block-components-product-details span[aria-hidden="true"]:visible'
-		);
-		await expect( visibleSeparators ).toHaveCount( 0 );
-
-		expect( pageErrors ).toEqual( [] );
-		expect( consoleErrors ).toEqual( [] );
-	} );
-
-	test( 'should render no client-side error and no trailing separator when the item data ends with two consecutive explicitly-hidden entries', async ( {
-		page,
-		frontendUtils,
-		miniCartUtils,
-	} ) => {
-		const { pageErrors, consoleErrors } = trackErrors( page );
-
-		await frontendUtils.goToShop();
-		// Adding the same product twice merges into a single cart line with
-		// quantity 2, which selects the fixture's
-		// [visible, hidden, hidden] scenario.
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await miniCartUtils.openMiniCart();
-
-		const dialog = page.getByRole( 'dialog' );
-		await expect( dialog ).toBeVisible();
-
-		const allNames = dialog.locator(
-			'.wc-block-components-product-details__name'
-		);
-		await expect( allNames ).toHaveCount( 3 );
-
-		const visibleNames = dialog.locator(
-			'.wc-block-components-product-details__name:visible'
-		);
-		await expect( visibleNames ).toHaveCount( 1 );
-		await expect( visibleNames ).toContainText( [ 'Gift Message' ] );
-
-		// Count only separators that are actually visible. `:visible`
-		// accounts for ancestor visibility, so a malformed/hidden entry's
-		// separator span (hidden via its entry wrapper, not its own
-		// attribute) is correctly excluded — unlike `:not([hidden])`.
-		const visibleSeparators = dialog.locator(
-			'.wc-block-components-product-details span[aria-hidden="true"]:visible'
-		);
-		await expect( visibleSeparators ).toHaveCount( 0 );
-
-		expect( pageErrors ).toEqual( [] );
-		expect( consoleErrors ).toEqual( [] );
-	} );
-} );
-
-test.describe( 'Mini-Cart Block (item data - mixed trailing entries)', () => {
-	test.use( {
-		storageState: {
-			origins: [],
-			cookies: [],
-		},
-	} );
-
-	test.beforeEach( async ( { context } ) => {
-		await enableTestPlugin( context, 'item-data-display-mixed' );
-	} );
-
-	test( 'should show no trailing separator when the item data ends with a malformed entry followed by a hidden entry', async ( {
-		page,
-		frontendUtils,
-		miniCartUtils,
-	} ) => {
-		const { pageErrors, consoleErrors } = trackErrors( page );
-
-		await frontendUtils.goToShop();
-		// Quantity 1 selects the fixture's
-		// [visible, malformed, hidden] scenario.
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await miniCartUtils.openMiniCart();
-
-		const dialog = page.getByRole( 'dialog' );
-		await expect( dialog ).toBeVisible();
-
-		const allNames = dialog.locator(
-			'.wc-block-components-product-details__name'
-		);
-		await expect( allNames ).toHaveCount( 3 );
-
-		const visibleNames = dialog.locator(
-			'.wc-block-components-product-details__name:visible'
-		);
-		await expect( visibleNames ).toHaveCount( 1 );
-		await expect( visibleNames ).toContainText( [ 'Gift Message' ] );
-
-		// Count only separators that are actually visible. `:visible`
-		// accounts for ancestor visibility, so a malformed/hidden entry's
-		// separator span (hidden via its entry wrapper, not its own
-		// attribute) is correctly excluded — unlike `:not([hidden])`.
-		const visibleSeparators = dialog.locator(
-			'.wc-block-components-product-details span[aria-hidden="true"]:visible'
-		);
-		await expect( visibleSeparators ).toHaveCount( 0 );
-
-		expect( pageErrors ).toEqual( [] );
-		expect( consoleErrors ).toEqual( [] );
-	} );
-
-	test( 'should show no trailing separator when the item data ends with a hidden entry followed by a malformed entry', async ( {
-		page,
-		frontendUtils,
-		miniCartUtils,
-	} ) => {
-		const { pageErrors, consoleErrors } = trackErrors( page );
-
-		await frontendUtils.goToShop();
-		// Adding the same product twice merges into a single cart line with
-		// quantity 2, which selects the fixture's
-		// [visible, hidden, malformed] scenario (the reverse order).
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await frontendUtils.addToCart( REGULAR_PRICED_PRODUCT_NAME );
-		await miniCartUtils.openMiniCart();
-
-		const dialog = page.getByRole( 'dialog' );
-		await expect( dialog ).toBeVisible();
-
-		const allNames = dialog.locator(
-			'.wc-block-components-product-details__name'
-		);
-		await expect( allNames ).toHaveCount( 3 );
-
-		const visibleNames = dialog.locator(
-			'.wc-block-components-product-details__name:visible'
-		);
-		await expect( visibleNames ).toHaveCount( 1 );
-		await expect( visibleNames ).toContainText( [ 'Gift Message' ] );
-
-		// Count only separators that are actually visible. `:visible`
-		// accounts for ancestor visibility, so a malformed/hidden entry's
-		// separator span (hidden via its entry wrapper, not its own
-		// attribute) is correctly excluded — unlike `:not([hidden])`.
-		const visibleSeparators = dialog.locator(
-			'.wc-block-components-product-details span[aria-hidden="true"]:visible'
-		);
-		await expect( visibleSeparators ).toHaveCount( 0 );
-
-		expect( pageErrors ).toEqual( [] );
-		expect( consoleErrors ).toEqual( [] );
-	} );
-} );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart.block_theme.spec.ts
index 523bc5b1fe7..b48b08352bf 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/mini-cart/mini-cart.block_theme.spec.ts
@@ -1,7 +1,13 @@
 /**
  * External dependencies
  */
-import { test, expect, BlockData, wpCLI } from '@woocommerce/e2e-utils';
+import {
+	test,
+	expect,
+	BlockData,
+	enableTestPlugin,
+	wpCLI,
+} from '@woocommerce/e2e-utils';

 /**
  * Internal dependencies
@@ -188,19 +194,24 @@ test.describe( `${ blockData.name } Block`, () => {
 		await checkProductLink( page );
 	} );

-	test( 'should render the product table with no client-side error for a product with no item data', async ( {
+	test( 'should render the product table and its item data with no client-side error', async ( {
 		page,
+		context,
 		frontendUtils,
 		miniCartUtils,
 	} ) => {
+		// The fixture plugin adds `woocommerce_get_item_data` entries to every
+		// cart item, so this test covers the whole wiring: the PHP filter, the
+		// Store API `item_data` field, and the Interactivity API render. How
+		// each entry is normalized is covered by the unit tests for
+		// `mini-cart/utils/item-data`.
+		await enableTestPlugin( context, 'item-data-display' );
+
 		const pageErrors: string[] = [];
 		const consoleErrors: string[] = [];

 		// Register listeners before navigating so nothing that happens during
-		// the mini-cart's render is missed. This product has no variation and
-		// no `woocommerce_get_item_data` additions (no fixture plugin is
-		// activated in this describe block), which is the plain rendering
-		// path the item-data/separator getters must not throw on.
+		// the mini-cart's render is missed.
 		page.on( 'pageerror', ( error ) => {
 			pageErrors.push( error.message );
 		} );
@@ -231,6 +242,15 @@ test.describe( `${ blockData.name } Block`, () => {
 			page.getByLabel( 'Quantity of Polo in your cart.' )
 		).toHaveValue( '1' );

+		// The filtered entries reach the rendered table.
+		const dialog = page.getByRole( 'dialog' );
+		await expect(
+			dialog.locator( '.wc-block-components-product-details__name' )
+		).toContainText( [ 'Gift Message' ] );
+		await expect(
+			dialog.locator( '.wc-block-components-product-details__value' )
+		).toContainText( [ 'Happy Birthday!' ] );
+
 		expect( pageErrors ).toEqual( [] );
 		expect( consoleErrors ).toEqual( [] );
 	} );
@@ -511,15 +531,13 @@ test.describe( `${ blockData.name } Block (variation attributes)`, () => {
 		},
 	} );

-	test( 'should decode entities in variation attributes rendered via data-wp-text', async ( {
+	test( 'should render variation attributes in the products table', async ( {
 		page,
 		frontendUtils,
 		miniCartUtils,
 	} ) => {
-		// Create a variable product with an attribute value containing an
-		// ampersand, which the API returns as "&amp;". The data-wp-text
-		// path (textContent) relies on the textarea entity-decode step to
-		// display "Red & Blue" instead of literal "Red &amp; Blue".
+		// The API returns the ampersand as "&amp;", so this also checks that
+		// the variation branch of the table decodes it.
 		const cliOutput = await wpCLI(
 			`wc product create --user=1 --name="Test Variable" --type="variable" --attributes='${ JSON.stringify(
 				[
@@ -535,58 +553,24 @@ test.describe( `${ blockData.name } Block (variation attributes)`, () => {
 		const productId = cliOutput.stdout.match(
 			/Created product (\d+)/
 		)?.[ 1 ];
-		await wpCLI(
+		const variationOutput = await wpCLI(
 			`wc product_variation create --user=1 "${ productId }" --regular_price="10" --attributes='${ JSON.stringify(
 				[ { name: 'Shade', option: 'Red & Blue' } ]
 			) }'`
 		);
+		const variationId = variationOutput.stdout.match(
+			/Created product_variation (\d+)/
+		)?.[ 1 ];

-		// Navigate to the product page and add the variation to cart.
-		await page.goto( `/product/test-variable/` );
-		await page
-			.getByLabel( 'Shade', { exact: true } )
-			.selectOption( 'Red & Blue' );
-
-		// The classic variable-product Add to cart button only registers a
-		// selection once the variations script has processed it. Until then the
-		// hidden `variation_id` input stays at "0" and clicking is a silent
-		// no-op (the disabled state is class-based, which Playwright's
-		// actionability checks ignore). Wait for the variation to be registered
-		// (a positive, non-zero variation id) before clicking.
-		// That is necessary but not sufficient: the script sets `variation_id`
-		// synchronously and only drops the `disabled` class ~300ms later, from a
-		// setTimeout, so wait for the button to be enabled as well. Clicking in
-		// that window hits the guard that raises a window.alert Playwright
-		// silently dismisses, and the form never submits.
-		await expect( page.locator( 'input.variation_id' ) ).toHaveValue(
-			/^[1-9][0-9]*$/
+		// Add the variation through the classic add-to-cart URL, since the
+		// product page form is not what this test covers.
+		await page.goto(
+			`/?add-to-cart=${ productId }&variation_id=${ variationId }`
 		);
-		const addToCartButton = page.getByRole( 'button', {
-			name: 'Add to cart',
-			exact: true,
-		} );
-		await expect( addToCartButton ).not.toHaveClass( /\bdisabled\b/ );
-		await addToCartButton.click();
-
-		// Wait for a definitive "added to cart" confirmation before navigating
-		// to the shop. The single-product Add to cart is a form submission; if
-		// we navigate away before it settles the item may not be in the cart
-		// when the mini-cart opens, which is what makes the assertions below
-		// race the add-to-cart request. Scope to the success notice (role=alert)
-		// so we don't match unrelated page text.
-		await expect(
-			page.getByRole( 'alert' ).getByText( /added to your cart/i )
-		).toBeVisible();
-
-		// Open the mini-cart.
 		await frontendUtils.goToShop();
 		await miniCartUtils.openMiniCart();

 		const dialog = page.getByRole( 'dialog' );
-		await expect( dialog ).toBeVisible();
-
-		// Variation attributes are rendered via data-wp-text (textContent).
-		// The entity "&amp;" should be decoded to "&".
 		await expect(
 			dialog.locator( '.wc-block-components-product-details__name' )
 		).toContainText( [ 'Shade' ] );
diff --git a/plugins/woocommerce/tests/e2e/utils/blocks/test-plugins.ts b/plugins/woocommerce/tests/e2e/utils/blocks/test-plugins.ts
index 5118ee40dd7..a2f10e3dd4b 100644
--- a/plugins/woocommerce/tests/e2e/utils/blocks/test-plugins.ts
+++ b/plugins/woocommerce/tests/e2e/utils/blocks/test-plugins.ts
@@ -16,9 +16,6 @@ import { BASE_URL } from './constants';
 export const COOKIE_GATED_TEST_PLUGINS = [
 	'additional-checkout-fields',
 	'item-data-display',
-	'item-data-display-hidden',
-	'item-data-display-malformed',
-	'item-data-display-mixed',
 	'locale-hide-country',
 	'product-collection-compatibility-layer',
 	'single-product-template-compatibility-layer',