Commit c15e5bdac7d for woocommerce

commit c15e5bdac7ddad21b49cb7348061d5f5f3a25d3d
Author: Albert Juhé Lluveras <contact@albertjuhe.com>
Date:   Thu Jul 2 12:27:59 2026 +0200

    Add to Cart + Options: add extra e2e tests to cover skipped unit tests (#66118)

    * Add to Cart + Options: add extra e2e tests to cover skipped unit tests

    * Add changelog

    * Fix grouped products test

diff --git a/plugins/woocommerce/changelog/fix-66046-add-to-cart-with-options-unit-tests-to-e2e b/plugins/woocommerce/changelog/fix-66046-add-to-cart-with-options-unit-tests-to-e2e
new file mode 100644
index 00000000000..f26ce0d2047
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-66046-add-to-cart-with-options-unit-tests-to-e2e
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Add to Cart + Options: add extra e2e tests to cover skipped unit tests
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/test/block.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/test/block.ts
index 47ccd16ac45..9630e166f37 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/test/block.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/test/block.ts
@@ -2,185 +2,20 @@
  * External dependencies
  */
 import '@testing-library/jest-dom';
-import { act, fireEvent, screen, waitFor } from '@testing-library/react';
+import { screen, waitFor } from '@testing-library/react';
 import { http, HttpResponse } from 'msw';
 import { setupServer } from 'msw/node';
-import { readFileSync } from 'fs';
-import { join } from 'path';
-import { dispatch } from '@wordpress/data';
-import { productsStore } from '@woocommerce/data';
-import { store as coreStore } from '@wordpress/core-data';

 /**
  * Internal dependencies
  */
-import {
-	initializeEditor,
-	selectBlock,
-} from '../../../../../tests/integration/helpers/integration-test-editor';
+import { initializeEditor } from '../../../../../tests/integration/helpers/integration-test-editor';
 import '../';
-import '../quantity-selector';
-import '../../../atomic/blocks/product-elements/button';
-import '../../../atomic/blocks/product-elements/stock-indicator';
-import '../../../atomic/blocks/product-elements/price';
-import '../grouped-product-selector';
-import '../grouped-product-selector/product-item';
-import '../grouped-product-selector/product-item-label';
-import '../grouped-product-selector/product-item-selector';
-import '../variation-selector';
-import '../variation-selector/attribute';
-import '../variation-selector/attribute-name';
-import '../variation-description';

-const mockTemplatePartsHTML: Record< string, string > = {
-	simple: '',
-	external: '',
-	grouped: '',
-	variable: '',
-};
-
-Object.keys( mockTemplatePartsHTML ).forEach( ( key ) => {
-	mockTemplatePartsHTML[ key ] = readFileSync(
-		join(
-			__dirname,
-			`../../../../../../../templates/parts/${ key }-product-add-to-cart-with-options.html`
-		),
-		'utf-8'
-	);
-} );
-
-jest.mock( '@woocommerce/settings', () => {
-	return {
-		...jest.requireActual( '@woocommerce/settings' ),
-		getSetting: jest.fn().mockImplementation( ( key, defaultValue ) => {
-			if ( key === 'productTypes' ) {
-				return {
-					simple: 'Simple product',
-					external: 'External/Affiliate product',
-					grouped: 'Grouped product',
-					variable: 'Variable product',
-				};
-			}
-			if ( key === 'addToCartWithOptionsTemplatePartIds' ) {
-				return {
-					simple: 'woocommerce/woocommerce//simple-product-add-to-cart-with-options',
-					external:
-						'woocommerce/woocommerce//external-product-add-to-cart-with-options',
-					grouped:
-						'woocommerce/woocommerce//grouped-product-add-to-cart-with-options',
-					variable:
-						'woocommerce/woocommerce//variable-product-add-to-cart-with-options',
-				};
-			}
-			return defaultValue;
-		} ),
-	};
-} );
-
-const mockProduct = {
-	id: 82,
-	name: 'Beanie with Logo',
-	type: 'simple',
-	is_in_stock: true,
-	stock_availability: { text: '', class: 'in-stock' },
-};
-
-// Setup MSW.
-const handlers = [
-	http.get( '/wp/v2/types', () => {
-		return HttpResponse.json( {
-			wp_template_part: {
-				slug: 'wp_template_part',
-				rest_base: 'template-parts',
-				rest_namespace: 'wp/v2',
-			},
-		} );
-	} ),
-
-	http.get( '/wc/v3/products', () => {
-		return HttpResponse.json( [ mockProduct ] );
-	} ),
-
-	http.get( '/wc/store/v1/products/:id', () => {
-		return HttpResponse.json( mockProduct );
-	} ),
-
-	http.get( '/wc/v3/products/:id', () => {
-		return HttpResponse.json( mockProduct );
-	} ),
-
-	// @todo When updating the `@wordpress/data` package to 6.7 or later,
-	// this request will need to be updated to match the path in production:
-	// `/wp/v2/template-parts/woocommerce/woocommerce//<template-part-slug>`.
-	http.options( '/wp/v2/[object%20Object]', () => {
-		return HttpResponse.json(
-			{},
-			{
-				headers: {
-					allow: 'GET, POST, PUT, PATCH, DELETE',
-				},
-			}
-		);
-	} ),
-
-	http.get( '/wp/v2/template-parts/*', ( request ) => {
-		if (
-			request.params[ 0 ] ===
-			'woocommerce/woocommerce//simple-product-add-to-cart-with-options'
-		) {
-			return HttpResponse.json( {
-				id: 'woocommerce/woocommerce//simple-product-add-to-cart-with-options',
-				content: {
-					raw: mockTemplatePartsHTML.simple,
-				},
-			} );
-		}
-		if (
-			request.params[ 0 ] ===
-			'woocommerce/woocommerce//external-product-add-to-cart-with-options'
-		) {
-			return HttpResponse.json( {
-				id: 'woocommerce/woocommerce//external-product-add-to-cart-with-options',
-				content: {
-					raw: mockTemplatePartsHTML.external,
-				},
-			} );
-		}
-		if (
-			request.params[ 0 ] ===
-			'woocommerce/woocommerce//grouped-product-add-to-cart-with-options'
-		) {
-			return HttpResponse.json( {
-				id: 'woocommerce/woocommerce//grouped-product-add-to-cart-with-options',
-				content: {
-					raw: mockTemplatePartsHTML.grouped,
-				},
-			} );
-		}
-
-		if (
-			request.params[ 0 ] ===
-			'woocommerce/woocommerce//variable-product-add-to-cart-with-options'
-		) {
-			return HttpResponse.json( {
-				id: 'woocommerce/woocommerce//variable-product-add-to-cart-with-options',
-				content: {
-					raw: mockTemplatePartsHTML.variable,
-				},
-			} );
-		}
-	} ),
-];
-
-const server = setupServer( ...handlers );
+const server = setupServer();

 // Start MSW.
 beforeAll( () => server.listen() );
-afterEach( () => {
-	dispatch( productsStore ).invalidateResolutionForStore();
-	dispatch( coreStore ).invalidateResolutionForStore();
-	server.resetHandlers();
-} );
 afterAll( () => server.close() );

 async function setup() {
@@ -192,149 +27,12 @@ async function setup() {
 	return await initializeEditor( addToCartWithOptionsBlock );
 }

-async function switchProductType( productType: string ) {
-	await selectBlock( 'Block: Add to Cart + Options (Beta)' );
-
-	await act( async () => {
-		fireEvent.click(
-			screen.getByRole( 'button', { name: 'Switch product type' } )
-		);
-	} );
-
-	await act( async () => {
-		fireEvent.click(
-			screen.getByRole( 'menuitem', { name: productType } )
-		);
-	} );
-}
-
 const expectHasBlock = async ( blockName: string ) => {
 	const block = await screen.findAllByLabelText( `Block: ${ blockName }` );
 	expect( block.length ).toBeGreaterThan( 0 );
 };

 describe( 'Add to Cart + Options block', () => {
-	// The wp-6.8 version of @wordpress/private-apis causes a deprecation
-	// warning for __unstableIsPreviewMode that fires non-deterministically
-	// during async block editor setup. Filter it from jest-console's spy
-	// before it checks for unexpected warnings.
-	afterEach( () => {
-		/* eslint-disable no-console */
-		( console.warn as jest.Mock ).mock.calls = (
-			console.warn as jest.Mock
-		 ).mock.calls.filter(
-			( [ firstArg ]: [ unknown ] ) =>
-				! (
-					typeof firstArg === 'string' &&
-					firstArg.includes( '__unstableIsPreviewMode' )
-				)
-		);
-		/* eslint-enable no-console */
-	} );
-
-	// Skipped: wp-6.8's block-editor rendering pipeline no longer renders
-	// inner blocks in Jest's jsdom environment. Gutenberg tests block
-	// rendering via Playwright E2E; these should be migrated similarly.
-	it.skip( 'should render inner blocks for simple and external products', async () => {
-		await setup();
-		await expectHasBlock( 'Add to Cart + Options (Beta)' );
-
-		// Simple products.
-		await expectHasBlock( 'Product Stock Indicator' );
-		await expectHasBlock( 'Product Quantity (Beta)' );
-		await expectHasBlock( 'Add to Cart Button' );
-
-		// External products.
-		await switchProductType( 'External/Affiliate product' );
-
-		await waitFor( () => {
-			expect(
-				screen.queryByLabelText( 'Block: Product Stock Indicator' )
-			).not.toBeInTheDocument();
-		} );
-		await expectHasBlock( 'Add to Cart Button' );
-
-		// wp-6.8: upstream @wordpress/* deprecation warnings that we cannot
-		// opt out of without changing the visual output.
-		expect( console ).toHaveWarned();
-	} );
-
-	// Skipped: wp-6.8's block-editor rendering pipeline no longer renders
-	// inner blocks in Jest's jsdom environment. Gutenberg tests block
-	// rendering via Playwright E2E; these should be migrated similarly.
-	it.skip( 'should render inner blocks for grouped products', async () => {
-		expect.hasAssertions();
-
-		await setup();
-		await expectHasBlock( 'Add to Cart + Options (Beta)' );
-
-		await switchProductType( 'Grouped product' );
-
-		await expectHasBlock( 'Grouped Product Selector (Beta)' );
-		await expectHasBlock( 'Grouped Product: Template (Beta)' );
-		await expectHasBlock( 'Grouped Product: Item Selector (Beta)' );
-		await expectHasBlock( 'Grouped Product: Item Label (Beta)' );
-		await expectHasBlock( 'Product Price' );
-		await expectHasBlock( 'Product Stock Indicator' );
-
-		// wp-6.8: upstream @wordpress/* deprecation warnings that we cannot
-		// opt out of without changing the visual output.
-		expect( console ).toHaveWarned();
-	} );
-
-	// Skipped: wp-6.8's block-editor rendering pipeline no longer renders
-	// inner blocks in Jest's jsdom environment. Gutenberg tests block
-	// rendering via Playwright E2E; these should be migrated similarly.
-	it.skip( 'should render inner blocks for grouped products with no store products', async () => {
-		expect.hasAssertions();
-
-		server.use(
-			http.get( '/wc/v3/products', () => {
-				return HttpResponse.json( [] );
-			} )
-		);
-
-		await setup();
-		await expectHasBlock( 'Add to Cart + Options (Beta)' );
-
-		await switchProductType( 'Grouped product' );
-
-		await expectHasBlock( 'Grouped Product Selector (Beta)' );
-		await expectHasBlock( 'Grouped Product: Template (Beta)' );
-		await expectHasBlock( 'Grouped Product: Item Selector (Beta)' );
-		await expectHasBlock( 'Grouped Product: Item Label (Beta)' );
-		await expectHasBlock( 'Product Price' );
-		await expectHasBlock( 'Product Stock Indicator' );
-
-		// wp-6.8: upstream @wordpress/* deprecation warnings that we cannot
-		// opt out of without changing the visual output.
-		expect( console ).toHaveWarned();
-	} );
-
-	// Skipped: wp-6.8's block-editor rendering pipeline no longer renders
-	// inner blocks in Jest's jsdom environment. Gutenberg tests block
-	// rendering via Playwright E2E; these should be migrated similarly.
-	it.skip( 'should render inner blocks for variable products', async () => {
-		expect.hasAssertions();
-
-		await setup();
-		await expectHasBlock( 'Add to Cart + Options (Beta)' );
-
-		await switchProductType( 'Variable product' );
-
-		await expectHasBlock( 'Variation Selector (Beta)' );
-		await expectHasBlock( 'Variation Selector: Attribute Name (Beta)' );
-		await expectHasBlock( 'Variation Selector: Template (Beta)' );
-		await expectHasBlock( 'Variation Description (Beta)' );
-		await expectHasBlock( 'Product Stock Indicator' );
-		await expectHasBlock( 'Product Quantity (Beta)' );
-		await expectHasBlock( 'Add to Cart Button' );
-
-		// wp-6.8: upstream @wordpress/* deprecation warnings that we cannot
-		// opt out of without changing the visual output.
-		expect( console ).toHaveWarned();
-	} );
-
 	it( 'should render the placeholder when viewed as a user without permissions to edit template parts', async () => {
 		server.use(
 			// @todo When updating the `@wordpress/data` package to 6.7 or later,
@@ -360,9 +58,5 @@ describe( 'Add to Cart + Options block', () => {
 				screen.getByLabelText( 'Add to Cart + Options form' )
 			).toBeInTheDocument()
 		);
-
-		// wp-6.8: upstream @wordpress/* deprecation warnings that we cannot
-		// opt out of without changing the visual output.
-		expect( console ).toHaveWarned();
 	} );
 } );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts
index 52b87431ed3..db82f623786 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-with-options/add-to-cart-with-options.block_theme.spec.ts
@@ -92,6 +92,16 @@ test.describe( 'Add to Cart + Options Block', () => {
 	} ) => {
 		await pageObject.updateSingleProductTemplate();

+		await test.step( 'renders inner blocks for simple products', async () => {
+			await pageObject.expectEditorInnerBlocks( 'simple' );
+		} );
+
+		await test.step( 'renders inner blocks for external products', async () => {
+			await pageObject.switchProductType( 'External/Affiliate product' );
+			await pageObject.expectEditorInnerBlocks( 'external' );
+			await pageObject.switchProductType( 'Simple product' );
+		} );
+
 		await editor.saveSiteEditorEntities( {
 			isOnlyCurrentEntityDirty: true,
 		} );
@@ -200,6 +210,11 @@ test.describe( 'Add to Cart + Options Block', () => {

 		await pageObject.updateSingleProductTemplate();

+		await test.step( 'renders inner blocks for variable products', async () => {
+			await pageObject.switchProductType( 'Variable product' );
+			await pageObject.expectEditorInnerBlocks( 'variable' );
+		} );
+
 		// We update to the Product Gallery block to test that it scrolls to the
 		// correct variation image.
 		const productImageGalleryBlock = await editor.getBlockByName(
@@ -548,6 +563,11 @@ test.describe( 'Add to Cart + Options Block', () => {

 		await pageObject.updateSingleProductTemplate();

+		await test.step( 'renders inner blocks for grouped products', async () => {
+			await pageObject.switchProductType( 'Grouped product' );
+			await pageObject.expectEditorInnerBlocks( 'grouped' );
+		} );
+
 		await editor.saveSiteEditorEntities( {
 			isOnlyCurrentEntityDirty: true,
 		} );
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-with-options/add-to-cart-with-options.page.ts b/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-with-options/add-to-cart-with-options.page.ts
index c20d80dc158..e9393b1d148 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-with-options/add-to-cart-with-options.page.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/add-to-cart-with-options/add-to-cart-with-options.page.ts
@@ -15,6 +15,30 @@ class AddToCartWithOptionsPage {
 	private editor: Editor;
 	BLOCK_SLUG = 'woocommerce/add-to-cart-with-options';
 	BLOCK_NAME = 'Add to Cart + Options (Beta)';
+	EDITOR_INNER_BLOCKS = {
+		simple: [
+			'Product Stock Indicator',
+			'Product Quantity (Beta)',
+			'Add to Cart Button',
+		],
+		external: [ 'Add to Cart Button' ],
+		variable: [
+			'Variation Selector (Beta)',
+			'Variation Selector: Attribute Name (Beta)',
+			'Variation Selector: Template (Beta)',
+			'Variation Description (Beta)',
+			'Product Stock Indicator',
+			'Product Quantity (Beta)',
+			'Add to Cart Button',
+		],
+		grouped: [
+			'Grouped Product Selector (Beta)',
+			'Grouped Product: Template (Beta)',
+			'Grouped Product: Item Selector (Beta)',
+			'Grouped Product: Item Label (Beta)',
+			'Product Price',
+		],
+	};

 	constructor( {
 		page,
@@ -56,11 +80,25 @@ class AddToCartWithOptionsPage {
 				state: 'hidden',
 			} );

-		await addToCartWithOptionsBlock
-			.locator( '.components-spinner' )
-			.waitFor( {
-				state: 'hidden',
-			} );
+		await expect(
+			addToCartWithOptionsBlock.locator( '.components-spinner' )
+		).toHaveCount( 0 );
+	}
+
+	async expectEditorInnerBlocks(
+		productType: 'simple' | 'external' | 'grouped' | 'variable'
+	) {
+		const blockNames = this.EDITOR_INNER_BLOCKS[ productType ];
+		const addToCartWithOptionsBlock = await this.editor.getBlockByName(
+			this.BLOCK_SLUG
+		);
+		for ( const blockName of blockNames ) {
+			await expect(
+				addToCartWithOptionsBlock
+					.getByLabel( `Block: ${ blockName }` )
+					.first()
+			).toBeVisible();
+		}
 	}

 	async insertParagraphInTemplatePart( content: string ) {