Commit 8c7cbaa95c1 for woocommerce
commit 8c7cbaa95c1ae126a05286ba15b9d04fe381c958
Author: Ajitem Sahasrabuddhe <ajitem.s@outlook.com>
Date: Thu Nov 5 19:27:06 2026 +0530
Delete mutation batcher E2E tests covered by Jest unit tests (#68179)
diff --git a/plugins/woocommerce/changelog/testops-234-remove-redundant-mutation-batcher-tests b/plugins/woocommerce/changelog/testops-234-remove-redundant-mutation-batcher-tests
new file mode 100644
index 00000000000..863855e405b
--- /dev/null
+++ b/plugins/woocommerce/changelog/testops-234-remove-redundant-mutation-batcher-tests
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Remove 6 redundant mutation batcher E2E tests already covered by Jest unit tests.
diff --git a/plugins/woocommerce/tests/e2e/tests/blocks/cart-store/mutation-batcher.block_theme.spec.ts b/plugins/woocommerce/tests/e2e/tests/blocks/cart-store/mutation-batcher.block_theme.spec.ts
index 47f81940be4..3a2bac4f287 100644
--- a/plugins/woocommerce/tests/e2e/tests/blocks/cart-store/mutation-batcher.block_theme.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/blocks/cart-store/mutation-batcher.block_theme.spec.ts
@@ -1,14 +1,11 @@
/**
* Mutation Batcher E2E Tests
*
- * These tests call the iAPI cart store directly via page.evaluate to verify
- * batching behavior at the network level. They're designed as regression
- * tests for the mutation batcher — if internals are refactored, these
- * should still pass.
- *
- * KEY IDEA: The batcher uses queueMicrotask() to collect requests.
- * - Synchronous calls within one microtick → batched into 1 request
- * - Calls separated by await → separate batch requests
+ * Request grouping, state reconciliation and the per-cycle cross-cutting
+ * effects are covered by Jest unit tests in
+ * `client/blocks/assets/js/base/stores/woocommerce/test/`. What is left here
+ * is the one behaviour those cannot reach: a real Interactivity API rollback
+ * repainting rendered block markup after a whole batch fails.
*/
/**
@@ -27,121 +24,6 @@ test.describe( 'Mutation Batcher', () => {
await frontendUtils.goToShop();
} );
- test( 'awaited calls produce separate batch requests', async ( {
- page,
- } ) => {
- const batchRequests: number[] = [];
-
- await page.route( '**/wc/store/v1/batch**', async ( route ) => {
- const body = route.request().postDataJSON();
- batchRequests.push( body?.requests?.length || 0 );
- await route.continue();
- } );
-
- await page.evaluate( async () => {
- const { store } = await import( '@wordpress/interactivity' );
- const unlockKey =
- 'I acknowledge that using a private store means my plugin will inevitably break on the next store release.';
-
- await import( '@woocommerce/stores/woocommerce/cart' );
- const { actions } = store( 'woocommerce', {}, { lock: unlockKey } );
-
- // Each await breaks the microtick — each call becomes its own batch.
- await actions.addCartItem( { id: 18, quantityToAdd: 1 } );
- await actions.addCartItem( { id: 19, quantityToAdd: 1 } );
- await actions.addCartItem( { id: 20, quantityToAdd: 1 } );
- } );
-
- // Each call should have produced its own batch request.
- expect( batchRequests ).toHaveLength( 3 );
- batchRequests.forEach( ( count ) => {
- expect( count ).toBe( 1 );
- } );
- } );
-
- test( 'mixed sync and async calls produce expected batch grouping', async ( {
- page,
- } ) => {
- const batchRequests: number[] = [];
-
- await page.route( '**/wc/store/v1/batch**', async ( route ) => {
- const body = route.request().postDataJSON();
- batchRequests.push( body?.requests?.length || 0 );
- await route.continue();
- } );
-
- await page.evaluate( async () => {
- const { store } = await import( '@wordpress/interactivity' );
- const unlockKey =
- 'I acknowledge that using a private store means my plugin will inevitably break on the next store release.';
-
- await import( '@woocommerce/stores/woocommerce/cart' );
- const { actions } = store( 'woocommerce', {}, { lock: unlockKey } );
-
- // Batch 1: two sync calls
- const p1 = actions.addCartItem( { id: 21, quantityToAdd: 1 } );
- const p2 = actions.addCartItem( { id: 22, quantityToAdd: 1 } );
- await Promise.all( [ p1, p2 ] );
-
- // Batch 2: one call after await
- await actions.addCartItem( { id: 23, quantityToAdd: 1 } );
-
- // Batch 3: three sync calls
- const p3 = actions.addCartItem( { id: 24, quantityToAdd: 1 } );
- const p4 = actions.addCartItem( { id: 25, quantityToAdd: 1 } );
- const p5 = actions.addCartItem( { id: 26, quantityToAdd: 1 } );
- await Promise.all( [ p3, p4, p5 ] );
- } );
-
- // Should produce 3 batches: 2, 1, 3 operations respectively.
- expect( batchRequests ).toHaveLength( 3 );
- expect( batchRequests[ 0 ] ).toBe( 2 );
- expect( batchRequests[ 1 ] ).toBe( 1 );
- expect( batchRequests[ 2 ] ).toBe( 3 );
- } );
-
- test( 'cart state is correct after batched operations', async ( {
- page,
- } ) => {
- const cartItemIds = await page.evaluate( async () => {
- const { store } = await import( '@wordpress/interactivity' );
- const unlockKey =
- 'I acknowledge that using a private store means my plugin will inevitably break on the next store release.';
-
- await import( '@woocommerce/stores/woocommerce/cart' );
- const { actions, state } = store(
- 'woocommerce',
- {},
- { lock: unlockKey }
- );
-
- // Refresh to start with known state.
- await actions.refreshCartItems();
-
- // Remove all existing items to start clean.
- const existingKeys = state.cart.items.map(
- ( item: { key: string } ) => item.key
- );
- for ( const key of existingKeys ) {
- await actions.removeCartItem( key );
- }
-
- // Now add 3 products synchronously (one batch).
- const p1 = actions.addCartItem( { id: 15, quantityToAdd: 1 } );
- const p2 = actions.addCartItem( { id: 16, quantityToAdd: 1 } );
- const p3 = actions.addCartItem( { id: 17, quantityToAdd: 1 } );
- await Promise.all( [ p1, p2, p3 ] );
-
- // Return the product IDs now in the cart.
- return state.cart.items.map( ( item: { id: number } ) => item.id );
- } );
-
- // All 3 products should be in the cart.
- expect( cartItemIds ).toContain( 15 );
- expect( cartItemIds ).toContain( 16 );
- expect( cartItemIds ).toContain( 17 );
- } );
-
test( 'total batch failure rolls back product button UI to pre-failure state', async ( {
page,
frontendUtils,
@@ -191,278 +73,4 @@ test.describe( 'Mutation Batcher', () => {
// to the snapshot taken before any optimistic mutations.
await expect( button ).toHaveText( '1 in cart' );
} );
-
- test.describe( 'cross-cutting side effects fire once per cycle', () => {
- // Cross-cutting effects (the `wc-blocks_store_sync_required` sync
- // event and the legacy `wc-blocks_added_to_cart` event) are wired to
- // the batcher's per-cycle settlement hook, not to each individual
- // request. These tests lock that contract in: no matter how many
- // mutations land in one microtick/cycle, each effect fires at most
- // once for that cycle.
-
- test( 'N concurrent successful adds fire each effect exactly once', async ( {
- page,
- } ) => {
- const batchRequests: number[] = [];
-
- await page.route( '**/wc/store/v1/batch**', async ( route ) => {
- const body = route.request().postDataJSON();
- batchRequests.push( body?.requests?.length || 0 );
- await route.continue();
- } );
-
- const result = await page.evaluate( async () => {
- const { store } = await import( '@wordpress/interactivity' );
- const unlockKey =
- 'I acknowledge that using a private store means my plugin will inevitably break on the next store release.';
-
- await import( '@woocommerce/stores/woocommerce/cart' );
- const { actions, state } = store(
- 'woocommerce',
- {},
- { lock: unlockKey }
- );
-
- // Register listeners before the gesture.
- let syncEvents = 0;
- window.addEventListener(
- 'wc-blocks_store_sync_required',
- ( event ) => {
- if (
- ( event as CustomEvent ).detail?.type ===
- 'from_iAPI'
- ) {
- syncEvents++;
- }
- }
- );
- let addedToCartEvents = 0;
- document.body.addEventListener(
- 'wc-blocks_added_to_cart',
- () => {
- addedToCartEvents++;
- }
- );
-
- // Three concurrent adds for three distinct products, no
- // await between them — one microtick, one cycle.
- const p1 = actions.addCartItem( { id: 15, quantityToAdd: 1 } );
- const p2 = actions.addCartItem( { id: 16, quantityToAdd: 1 } );
- const p3 = actions.addCartItem( { id: 17, quantityToAdd: 1 } );
- await Promise.all( [ p1, p2, p3 ] );
-
- return {
- syncEvents,
- addedToCartEvents,
- cartProductIds: state.cart.items.map(
- ( item: { id: number } ) => item.id
- ),
- };
- } );
-
- // One batch request carrying all three mutations.
- expect( batchRequests ).toHaveLength( 1 );
- expect( batchRequests[ 0 ] ).toBe( 3 );
-
- // Exactly one of each cross-cutting effect for the whole cycle.
- expect( result.syncEvents ).toBe( 1 );
- expect( result.addedToCartEvents ).toBe( 1 );
-
- // All three products landed in the cart.
- expect( result.cartProductIds ).toContain( 15 );
- expect( result.cartProductIds ).toContain( 16 );
- expect( result.cartProductIds ).toContain( 17 );
- } );
-
- test( 'a concurrent remove-only cycle fires one sync event and no legacy event', async ( {
- page,
- } ) => {
- const batchRequests: number[] = [];
-
- await page.route( '**/wc/store/v1/batch**', async ( route ) => {
- const body = route.request().postDataJSON();
- batchRequests.push( body?.requests?.length || 0 );
- await route.continue();
- } );
-
- // Seed a clean cart with several server-keyed lines before the
- // gesture under test, outside the batch-request count below.
- const seededKeys = await page.evaluate( async () => {
- const { store } = await import( '@wordpress/interactivity' );
- const unlockKey =
- 'I acknowledge that using a private store means my plugin will inevitably break on the next store release.';
-
- await import( '@woocommerce/stores/woocommerce/cart' );
- const { actions, state } = store(
- 'woocommerce',
- {},
- { lock: unlockKey }
- );
-
- await actions.refreshCartItems();
- const existingKeys = state.cart.items.map(
- ( item: { key: string } ) => item.key
- );
- for ( const key of existingKeys ) {
- await actions.removeCartItem( key );
- }
-
- await actions.addCartItem( { id: 15, quantityToAdd: 1 } );
- await actions.addCartItem( { id: 16, quantityToAdd: 1 } );
- await actions.addCartItem( { id: 17, quantityToAdd: 1 } );
-
- return state.cart.items.map(
- ( item: { key: string } ) => item.key
- );
- } );
-
- // Only count the batch request produced by the gesture below.
- batchRequests.length = 0;
-
- const result = await page.evaluate( async ( keys ) => {
- const { store } = await import( '@wordpress/interactivity' );
- const unlockKey =
- 'I acknowledge that using a private store means my plugin will inevitably break on the next store release.';
-
- await import( '@woocommerce/stores/woocommerce/cart' );
- const { actions, state } = store(
- 'woocommerce',
- {},
- { lock: unlockKey }
- );
-
- // Register listeners before the gesture.
- let syncEvents = 0;
- window.addEventListener(
- 'wc-blocks_store_sync_required',
- ( event ) => {
- if (
- ( event as CustomEvent ).detail?.type ===
- 'from_iAPI'
- ) {
- syncEvents++;
- }
- }
- );
- let addedToCartEvents = 0;
- document.body.addEventListener(
- 'wc-blocks_added_to_cart',
- () => {
- addedToCartEvents++;
- }
- );
-
- // Remove every seeded line concurrently — one microtick,
- // one cycle, no adds at all.
- const removals = keys.map( ( key: string ) =>
- actions.removeCartItem( key )
- );
- await Promise.all( removals );
-
- return {
- syncEvents,
- addedToCartEvents,
- cartProductIds: state.cart.items.map(
- ( item: { id: number } ) => item.id
- ),
- };
- }, seededKeys );
-
- // One batch request carrying every removal.
- expect( batchRequests ).toHaveLength( 1 );
- expect( batchRequests[ 0 ] ).toBe( seededKeys.length );
-
- // One sync event, but the remove-only cycle never triggers the
- // legacy added-to-cart event.
- expect( result.syncEvents ).toBe( 1 );
- expect( result.addedToCartEvents ).toBe( 0 );
-
- // The targeted lines are gone from the cart.
- expect( result.cartProductIds ).not.toContain( 15 );
- expect( result.cartProductIds ).not.toContain( 16 );
- expect( result.cartProductIds ).not.toContain( 17 );
- } );
-
- test( 'a mixed successful/failed add cycle fires each effect once and preserves the failed item', async ( {
- page,
- } ) => {
- const batchRequests: number[] = [];
-
- await page.route( '**/wc/store/v1/batch**', async ( route ) => {
- const body = route.request().postDataJSON();
- batchRequests.push( body?.requests?.length || 0 );
- await route.continue();
- } );
-
- const result = await page.evaluate( async () => {
- const { store } = await import( '@wordpress/interactivity' );
- const unlockKey =
- 'I acknowledge that using a private store means my plugin will inevitably break on the next store release.';
-
- await import( '@woocommerce/stores/woocommerce/cart' );
- const { actions, state } = store(
- 'woocommerce',
- {},
- { lock: unlockKey }
- );
-
- // Register listeners before the gesture.
- let syncEvents = 0;
- window.addEventListener(
- 'wc-blocks_store_sync_required',
- ( event ) => {
- if (
- ( event as CustomEvent ).detail?.type ===
- 'from_iAPI'
- ) {
- syncEvents++;
- }
- }
- );
- let addedToCartEvents = 0;
- document.body.addEventListener(
- 'wc-blocks_added_to_cart',
- () => {
- addedToCartEvents++;
- }
- );
-
- // Two valid adds and one invalid product id, no await
- // between them — one microtick, one cycle.
- const p1 = actions.addCartItem( { id: 15, quantityToAdd: 1 } );
- const p2 = actions.addCartItem( {
- id: 999999,
- quantityToAdd: 1,
- } ); // Invalid.
- const p3 = actions.addCartItem( { id: 16, quantityToAdd: 1 } );
-
- // addCartItem catches errors internally, so all three
- // promises resolve rather than reject; Promise.allSettled
- // just waits for all three to settle.
- await Promise.allSettled( [ p1, p2, p3 ] );
-
- return {
- syncEvents,
- addedToCartEvents,
- cartProductIds: state.cart.items.map(
- ( item: { id: number } ) => item.id
- ),
- };
- } );
-
- // One batch request carrying all three mutations.
- expect( batchRequests ).toHaveLength( 1 );
- expect( batchRequests[ 0 ] ).toBe( 3 );
-
- // Exactly one of each cross-cutting effect for the whole cycle,
- // since at least one add succeeded.
- expect( result.syncEvents ).toBe( 1 );
- expect( result.addedToCartEvents ).toBe( 1 );
-
- // The valid products landed in the cart, the invalid one did not.
- expect( result.cartProductIds ).toContain( 15 );
- expect( result.cartProductIds ).toContain( 16 );
- expect( result.cartProductIds ).not.toContain( 999999 );
- } );
- } );
} );