Commit d2e7a559e16 for woocommerce

commit d2e7a559e16155fe80a27f0dc4af83d357a0070b
Author: Taha Paksu <3295+tpaksu@users.noreply.github.com>
Date:   Mon Oct 12 13:40:47 2026 +0300

    [tests] Add JS test for the checkout shipping-method block (#69646)

diff --git a/plugins/woocommerce/changelog/69646-wooplug-7841-shipping-tests-block b/plugins/woocommerce/changelog/69646-wooplug-7841-shipping-tests-block
new file mode 100644
index 00000000000..5e35bc49447
--- /dev/null
+++ b/plugins/woocommerce/changelog/69646-wooplug-7841-shipping-tests-block
@@ -0,0 +1,5 @@
+Significance: patch
+Type: dev
+Comment: Add a JS test for the checkout shipping-method block; no production change.
+
+
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-method-block/test/block.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-method-block/test/block.tsx
index 1b7acf1c1e4..704069a6294 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-method-block/test/block.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-method-block/test/block.tsx
@@ -1,36 +1,181 @@
 /**
  * External dependencies
  */
-import { render } from '@testing-library/react';
+import { render, screen } from '@testing-library/react';
+import { dispatch } from '@wordpress/data';
+import { useShippingData } from '@woocommerce/base-context/hooks';

 /**
  * Internal dependencies
  */
 import Block from '../block';
+import {
+	generateShippingPackage,
+	generateShippingRate,
+} from '../../../../../mocks/shipping-package';
+
+// Read inside the component rather than at module load, so each test can set it.
+let mockShippingCostRequiresAddress = false;
+
 jest.mock( '@woocommerce/settings', () => {
+	const actualModule = jest.requireActual( '@woocommerce/settings' );
 	return {
-		...jest.requireActual( '@woocommerce/settings' ),
-		getSetting: jest.fn().mockImplementation( ( key, defaultValue ) => {
-			if ( key === 'localPickupText' ) {
+		...actualModule,
+		getSetting: ( name: string, ...rest: unknown[] ) => {
+			if ( name === 'localPickupText' ) {
 				return 'Pickup text from settings';
 			}
-			return defaultValue;
-		} ),
+			if ( name === 'shippingCostRequiresAddress' ) {
+				return mockShippingCostRequiresAddress;
+			}
+			// Read when the module loads, so a pickup_location rate is seen as collectable
+			// and local pickup counts as on.
+			if ( name === 'collectableMethodIds' ) {
+				return [ 'pickup_location' ];
+			}
+			if ( name === 'localPickupEnabled' ) {
+				return true;
+			}
+			return actualModule.getSetting( name, ...rest );
+		},
 	};
 } );
+
+jest.mock( '@woocommerce/base-context/hooks', () => ( {
+	...jest.requireActual( '@woocommerce/base-context/hooks' ),
+	useShippingData: jest.fn(),
+} ) );
+
+// The component selects only from the cart store, to decide whether anything is still
+// deliverable; answer that one and let any other store resolve for real. The validation store
+// the tests drive is read inside cart/utils.ts rather than here, so it works because the mock
+// spreads the real module, not because of this fall-through.
+let mockPackages: ReturnType< typeof generateShippingPackage >[] = [];
+
+jest.mock( '@wordpress/data', () => {
+	const actualModule = jest.requireActual( '@wordpress/data' );
+	return {
+		...actualModule,
+		useSelect: (
+			mapSelect: ( select: typeof actualModule.select ) => unknown
+		) =>
+			mapSelect( ( storeName: { name?: string } | string ) => {
+				const name =
+					typeof storeName === 'string' ? storeName : storeName?.name;
+				if ( name === 'wc/store/cart' ) {
+					return { getShippingRates: () => mockPackages };
+				}
+				return actualModule.select( storeName );
+			} ),
+	};
+} );
+
+const VALIDATION_STORE = 'wc/store/validation';
+
+const deliveryRate = generateShippingRate( {
+	rateId: 'flat_rate:1',
+	name: 'Flat rate',
+	methodID: 'flat_rate',
+	price: '500',
+	instanceID: 1,
+} );
+
+const collectionRate = generateShippingRate( {
+	rateId: 'pickup_location:1',
+	name: 'Pickup',
+	methodID: 'pickup_location',
+	price: '0',
+	instanceID: 0,
+} );
+
+/**
+ * Put the store in one shape and render the two options.
+ *
+ * @param options                     The shopper's situation.
+ * @param options.costRequiresAddress Whether the merchant hid costs until an address is entered.
+ * @param options.addressIncomplete   Whether the shipping address still has a validation error.
+ * @param options.offers              The rates the one package comes back with.
+ * @param options.showPrice           Whether the block is set to show prices.
+ */
+const renderWith = ( {
+	costRequiresAddress = false,
+	addressIncomplete = false,
+	offers,
+	showPrice = true,
+}: {
+	costRequiresAddress?: boolean;
+	addressIncomplete?: boolean;
+	offers: ReturnType< typeof generateShippingRate >[];
+	showPrice?: boolean;
+} ) => {
+	mockShippingCostRequiresAddress = costRequiresAddress;
+	mockPackages = [
+		generateShippingPackage( { packageId: 0, shippingRates: offers } ),
+	];
+
+	dispatch( VALIDATION_STORE ).clearValidationErrors();
+	if ( addressIncomplete ) {
+		dispatch( VALIDATION_STORE ).setValidationErrors( {
+			shipping_city: { message: 'Enter a city', hidden: true },
+		} );
+	}
+
+	( useShippingData as jest.Mock ).mockReturnValue( {
+		shippingRates: mockPackages,
+	} );
+
+	return render(
+		<Block
+			localPickupText="backup local pickup text"
+			onChange={ () => void 0 }
+			showIcon={ false }
+			showPrice={ showPrice }
+			checked={ 'shipping' }
+			shippingText="backup shipping text"
+		/>
+	);
+};
+
+afterEach( () => {
+	mockShippingCostRequiresAddress = false;
+	dispatch( VALIDATION_STORE ).clearValidationErrors();
+} );
+
 describe( 'Block', () => {
-	it( 'Renders the local pickup text from options', async () => {
-		const { getByText } = render(
-			<Block
-				localPickupText="backup local pickup text"
-				onChange={ () => void 0 }
-				showIcon={ false }
-				showPrice={ false }
-				checked={ 'shipping' }
-				shippingText="backup shipping text"
-			/>
-		);
-
-		expect( getByText( 'Pickup text from settings' ) ).toBeInTheDocument();
+	it( 'renders the local pickup text from options', () => {
+		renderWith( {
+			offers: [ deliveryRate, collectionRate ],
+			showPrice: false,
+		} );
+
+		expect(
+			screen.getByText( 'Pickup text from settings' )
+		).toBeInTheDocument();
+	} );
+
+	it( 'tells the shopper the delivery price needs an address when nothing can be delivered', () => {
+		renderWith( { offers: [ collectionRate ] } );
+
+		expect(
+			screen.getByText( 'calculated with an address' )
+		).toBeInTheDocument();
+	} );
+
+	it( 'still shows the delivery price when a rate exists, even with costs hidden and the address incomplete', () => {
+		renderWith( {
+			costRequiresAddress: true,
+			addressIncomplete: true,
+			offers: [ deliveryRate, collectionRate ],
+		} );
+
+		expect(
+			screen.getByRole( 'radio', {
+				name: ( name: string ) =>
+					name.startsWith( 'backup shipping text' ),
+			} )
+		).toHaveTextContent( '$5.00' );
+		expect(
+			screen.queryByText( 'calculated with an address' )
+		).not.toBeInTheDocument();
 	} );
 } );