Commit ac502060dd8 for woocommerce

commit ac502060dd8929452ca34fa5664efa324f62aa67
Author: Karol Manijak <20098064+kmanijak@users.noreply.github.com>
Date:   Wed Jul 29 07:54:32 2026 +0200

    Fix ESLint warnings in Blocks (#65992)

    * Harden Blocks ESLint warning coverage

    * Add changelog entry for Blocks ESLint hardening

    * Preserve quantity selector prop export

    * Remove quantity selector prop barrel export

    * Fix Blocks JSDoc alignment

    * Revert checkout validation error subscription changes

    * Clarify checkout validation lint suppressions

    * Restore Blocks ESLint warning severities

    * Update changelog for Blocks ESLint warning fixes

    * Restore the shared Blocks ESLint configuration

diff --git a/plugins/woocommerce/changelog/57341-blocks-eslint-warnings b/plugins/woocommerce/changelog/57341-blocks-eslint-warnings
new file mode 100644
index 00000000000..a17337f136f
--- /dev/null
+++ b/plugins/woocommerce/changelog/57341-blocks-eslint-warnings
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Fix ESLint warnings in Blocks.
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/address-autocomplete.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/address-autocomplete.tsx
index 6227a7a9964..f5b1ac2af89 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/address-autocomplete.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/address-autocomplete.tsx
@@ -240,7 +240,8 @@ export const AddressAutocomplete = ( {
 			const parentElement = inputElement.parentElement;
 			if ( parentElement ) {
 				// Store current focus state and cursor position
-				const hasFocus = document.activeElement === inputElement;
+				const hasFocus =
+					inputElement.ownerDocument.activeElement === inputElement;
 				const selectionStart = inputElement.selectionStart;
 				const selectionEnd = inputElement.selectionEnd;

diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/test/integration.tsx b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/test/integration.tsx
index f9f41ac118d..a734377a888 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/test/integration.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/cart-checkout/address-autocomplete/test/integration.tsx
@@ -52,7 +52,6 @@ jest.mock( '@woocommerce/settings', () => ( {
 // jsdom/userEvent don't fully replicate. The suggestion rendering path
 // never fires because the typing guard isn't tripped. These tests should
 // be migrated to Playwright E2E where real browser events are available.
-// eslint-disable-next-line jest/no-disabled-tests
 describe.skip( 'Suggestions - when rendered in AddressAutocomplete component', () => {
 	beforeAll( () => {
 		// Mock use select so we can override it when wc/store/cart or
@@ -611,7 +610,7 @@ describe.skip( 'Suggestions - when rendered in AddressAutocomplete component', (
 			await act( async () => {
 				input.focus();
 			} );
-			expect( document.activeElement ).toBe( input );
+			expect( input ).toHaveFocus();

 			// Type to trigger suggestions
 			await act( async () => {
@@ -631,21 +630,21 @@ describe.skip( 'Suggestions - when rendered in AddressAutocomplete component', (
 			} );

 			// Focus should remain on input
-			expect( document.activeElement ).toBe( input );
+			expect( input ).toHaveFocus();

 			await act( async () => {
 				await userEvent.type( input, '{arrowdown}' );
 			} );

 			// Focus should still be on input
-			expect( document.activeElement ).toBe( input );
+			expect( input ).toHaveFocus();

 			await act( async () => {
 				await userEvent.type( input, '{arrowup}' );
 			} );

 			// Focus should still be on input
-			expect( document.activeElement ).toBe( input );
+			expect( input ).toHaveFocus();
 		} );

 		it( 'Pressing Escape key hides suggestions and resets all ARIA states', async () => {
@@ -720,7 +719,7 @@ describe.skip( 'Suggestions - when rendered in AddressAutocomplete component', (
 			expect( input ).not.toHaveAttribute( 'aria-activedescendant' );

 			// Verify focus remains on input
-			expect( document.activeElement ).toBe( input );
+			expect( input ).toHaveFocus();

 			// Verify input value is preserved
 			expect( input ).toHaveValue( '1234' );
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/components/index.ts b/plugins/woocommerce/client/blocks/assets/js/base/components/index.ts
index 722a7f190c5..aec5e2ccb25 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/components/index.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/base/components/index.ts
@@ -19,8 +19,8 @@ export * from './price-slider';
 export * from './product-list';
 export * from './product-name';
 export * from './product-price';
-export * from './product-rating';
-export * from './quantity-selector';
+export { default as ProductRating } from './product-rating';
+export { default as QuantitySelector } from './quantity-selector';
 export * from './read-more';
 export * from './reviews';
 export * from './select';
diff --git a/plugins/woocommerce/client/blocks/assets/js/base/context/providers/cart-checkout/checkout-events/index.tsx b/plugins/woocommerce/client/blocks/assets/js/base/context/providers/cart-checkout/checkout-events/index.tsx
index 7e3272d7317..bf9cd430cf1 100644
--- a/plugins/woocommerce/client/blocks/assets/js/base/context/providers/cart-checkout/checkout-events/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/base/context/providers/cart-checkout/checkout-events/index.tsx
@@ -133,7 +133,7 @@ export const CheckoutEventsProvider = ( {
 		void __internalSetRegisteredExpressPaymentMethods(
 			convertToPlainExpressPaymentMethods( registeredMethods )
 		);
-	}, [ registeredMethods ] );
+	}, [ __internalSetRegisteredExpressPaymentMethods, registeredMethods ] );

 	// Update the payment method store when paymentMethods or expressPaymentMethods changes.
 	// Ensure this happens in the editor even if paymentMethods is empty. This won't happen instantly when the objects
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts
index 8dca45a7d54..cd10cf2a5f2 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/add-to-cart-with-options/variation-selector/frontend.ts
@@ -510,7 +510,7 @@ const { actions, state } = store< VariableProductAddToCartWithOptionsStore >(
 				}

 				// Let's not do anything if the user is typing in the input.
-				if ( ref === document.activeElement ) {
+				if ( ref === ref.ownerDocument.activeElement ) {
 					return;
 				}

diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/customer-address.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/customer-address.tsx
index ddbf4d529fc..3b4797211ef 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/customer-address.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-billing-address-block/customer-address.tsx
@@ -63,6 +63,7 @@ const CustomerAddress = () => {
 		editingBillingAddress,
 		billingAddress,
 		isInitialized,
+		setEditingBillingAddress,
 		validationErrors,
 	] );

diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/customer-address.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/customer-address.tsx
index c91235dfbc0..4749d57e2b1 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/customer-address.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/checkout/inner-blocks/checkout-shipping-address-block/customer-address.tsx
@@ -65,6 +65,7 @@ const CustomerAddress = () => {
 		editingShippingAddress,
 		shippingAddress,
 		isInitialized,
+		setEditingShippingAddress,
 		validationErrors,
 	] );

diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/classic-template/index.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/classic-template/index.tsx
index 1d45cd0dc6c..954979c4d19 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/classic-template/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/classic-template/index.tsx
@@ -208,7 +208,7 @@ const Edit = ( {
 				template: templateSlug ?? attributes.template,
 				align: attributes.align ?? 'wide',
 			} ),
-		[ attributes.align, attributes.template, setAttributes ]
+		[ attributes.align, attributes.template, setAttributes, templateSlug ]
 	);

 	const { getDescription, getSkeleton, blockifyConfig } =
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/featured-items/use-background-image.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/featured-items/use-background-image.ts
index b1474edbe61..3e137316698 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/featured-items/use-background-image.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/featured-items/use-background-image.ts
@@ -130,9 +130,7 @@ export function useBackgroundImage( {
 					return false;
 				} )();

-				if ( isImageBgTransparent !== hasTransparentPixels ) {
-					setIsImageBgTransparent( hasTransparentPixels );
-				}
+				setIsImageBgTransparent( hasTransparentPixels );
 			};
 		} else {
 			setIsImageBgTransparent( true );
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/use-carousel-layout-adjustments.ts b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/use-carousel-layout-adjustments.ts
index 2a4a40b6095..478615ff2f3 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/use-carousel-layout-adjustments.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/edit/inspector-controls/use-carousel-layout-adjustments.ts
@@ -255,7 +255,7 @@ const useCarouselLayoutAdjustments = (
 	);

 	useEffect( () => {
-		if ( ! clientId ) {
+		if ( ! clientId || ! productCollectionBlock ) {
 			return;
 		}

@@ -280,7 +280,13 @@ const useCarouselLayoutAdjustments = (
 		}

 		previousLayoutType.current = displayLayout.type;
-	}, [ displayLayout.type, clientId, actions, collection ] );
+	}, [
+		displayLayout.type,
+		clientId,
+		actions,
+		collection,
+		productCollectionBlock,
+	] );
 };

 export default useCarouselLayoutAdjustments;
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/utils.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/utils.tsx
index ee48b5339ed..e193990baf6 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/utils.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-collection/utils.tsx
@@ -443,6 +443,7 @@ export const useSetPreviewState = ( {
 		setAttributes,
 		usesReferencePreviewMessage,
 		isUsingReferencePreviewMode,
+		__unstableMarkNextChangeAsNotPersistent,
 	] );

 	// Running setPreviewState function provided by Collection, if it exists.
@@ -504,6 +505,7 @@ export const useSetPreviewState = ( {
 		setAttributes,
 		setPreviewState,
 		isUsingReferencePreviewMode,
+		__unstableMarkNextChangeAsNotPersistent,
 	] );
 };
 export const getDefaultQueryForSettingsSection = (
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/product-search/inspector-controls.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/product-search/inspector-controls.tsx
index 3212dd9e76e..d10acefa8e7 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/product-search/inspector-controls.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/product-search/inspector-controls.tsx
@@ -44,7 +44,7 @@ const ProductSearchControls = ( props: ProductSearchBlockProps ) => {
 		) {
 			setInitialPosition( buttonPosition );
 		}
-	}, [ buttonPosition ] );
+	}, [ buttonPosition, initialPosition ] );

 	return (
 		<InspectorControls group="styles">
diff --git a/plugins/woocommerce/client/blocks/assets/js/blocks/single-product/edit/index.tsx b/plugins/woocommerce/client/blocks/assets/js/blocks/single-product/edit/index.tsx
index c8aa327578e..193c7720e34 100644
--- a/plugins/woocommerce/client/blocks/assets/js/blocks/single-product/edit/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/blocks/single-product/edit/index.tsx
@@ -101,7 +101,7 @@ const Editor = ( {
 	}, [ attributes, productId, productPreview, setAttributes ] );

 	useEffect( () => {
-		if ( isInvalidProductId && ! isEditing ) {
+		if ( isInvalidProductId ) {
 			setIsEditing( true );
 		}
 	}, [ isInvalidProductId ] );
diff --git a/plugins/woocommerce/client/blocks/assets/js/data/cart/thunks.ts b/plugins/woocommerce/client/blocks/assets/js/data/cart/thunks.ts
index d77056087d3..161cf89cf0e 100644
--- a/plugins/woocommerce/client/blocks/assets/js/data/cart/thunks.ts
+++ b/plugins/woocommerce/client/blocks/assets/js/data/cart/thunks.ts
@@ -133,7 +133,7 @@ export const receiveError =
  * Updates the checkout store with the shopper's collection preference based on
  * the selected shipping rates in the cart.
  *
- * @param {CartResponse} response
+ * @param {CartResponse}              response
  * @param {CartThunkArgs['registry']} registry
  */
 const syncPrefersCollectionFromSelectedShippingRates = (
diff --git a/plugins/woocommerce/client/blocks/assets/js/editor-components/default-notice/index.tsx b/plugins/woocommerce/client/blocks/assets/js/editor-components/default-notice/index.tsx
index 708a4d3f7d8..db78eb095c0 100644
--- a/plugins/woocommerce/client/blocks/assets/js/editor-components/default-notice/index.tsx
+++ b/plugins/woocommerce/client/blocks/assets/js/editor-components/default-notice/index.tsx
@@ -32,17 +32,20 @@ export function DefaultNotice( { block }: { block: string } ) {
 	// Everything below works the same for Cart/Checkout
 	const { saveEntityRecord } = useDispatch( coreStore );
 	const { editPost, savePost } = useDispatch( CORE_EDITOR_STORE );
-	const { slug, postPublished, currentPostId } = useSelect( ( select ) => {
-		const { getEntityRecord } = select( coreStore );
-		const editor = select( CORE_EDITOR_STORE );
-		return {
-			slug:
-				getEntityRecord( 'postType', 'page', ORIGINAL_PAGE_ID )?.slug ||
-				block,
-			postPublished: editor?.isCurrentPostPublished?.() ?? false,
-			currentPostId: editor?.getCurrentPostId?.() ?? 0,
-		};
-	}, [] );
+	const { slug, postPublished, currentPostId } = useSelect(
+		( select ) => {
+			const { getEntityRecord } = select( coreStore );
+			const editor = select( CORE_EDITOR_STORE );
+			return {
+				slug:
+					getEntityRecord( 'postType', 'page', ORIGINAL_PAGE_ID )
+						?.slug || block,
+				postPublished: editor?.isCurrentPostPublished?.() ?? false,
+				currentPostId: editor?.getCurrentPostId?.() ?? 0,
+			};
+		},
+		[ ORIGINAL_PAGE_ID, block ]
+	);
 	const [ settingStatus, setStatus ] = useState( 'pristine' );
 	const updatePage = useCallback( () => {
 		setStatus( 'updating' );
diff --git a/plugins/woocommerce/client/blocks/assets/js/types/type-defs/cart.js b/plugins/woocommerce/client/blocks/assets/js/types/type-defs/cart.js
index 67149a1bf0c..fb42fb618b8 100644
--- a/plugins/woocommerce/client/blocks/assets/js/types/type-defs/cart.js
+++ b/plugins/woocommerce/client/blocks/assets/js/types/type-defs/cart.js
@@ -14,15 +14,15 @@
 /**
  * @typedef {Object} CartItemImage
  *
- * @property {number} id        Image id.
- * @property {string} src       Full size image URL.
- * @property {string} thumbnail Thumbnail URL.
+ * @property {number} id               Image id.
+ * @property {string} src              Full size image URL.
+ * @property {string} thumbnail        Thumbnail URL.
  * @property {string} srcset           Full size image srcset for responsive images.
  * @property {string} sizes            Full size image sizes for responsive images.
  * @property {string} thumbnail_srcset Thumbnail srcset for responsive images.
  * @property {string} thumbnail_sizes  Thumbnail sizes for responsive images.
- * @property {string} name      Image name.
- * @property {string} alt       Image alternative text.
+ * @property {string} name             Image name.
+ * @property {string} alt              Image alternative text.
  */

 /**
diff --git a/plugins/woocommerce/client/blocks/packages/components/text-input/stories/validated-text-input.stories.tsx b/plugins/woocommerce/client/blocks/packages/components/text-input/stories/validated-text-input.stories.tsx
index f58fdcdc603..648b5b74be0 100644
--- a/plugins/woocommerce/client/blocks/packages/components/text-input/stories/validated-text-input.stories.tsx
+++ b/plugins/woocommerce/client/blocks/packages/components/text-input/stories/validated-text-input.stories.tsx
@@ -16,6 +16,14 @@ import '../style.scss';
 import '../../validation-input-error/style.scss';
 import { ValidatedTextInputProps } from '../types';

+const getFormattedValue = (
+	val: string | number | readonly string[] | undefined,
+	customFormatter?: ValidatedTextInputProps[ 'customFormatter' ]
+) => {
+	const stringVal = typeof val === 'string' ? val : String( val || '' );
+	return customFormatter ? customFormatter( stringVal ) : stringVal;
+};
+
 export default {
 	title: 'External Components/ValidatedTextInput',
 	component: ValidatedTextInput,
@@ -217,29 +225,20 @@ export default {
 const Template: StoryFn< ValidatedTextInputProps > = ( args ) => {
 	// eslint-disable-next-line @typescript-eslint/no-unused-vars
 	const [ _, updateArgs ] = useArgs();
-	const getFormattedValue = (
-		val: string | number | readonly string[] | undefined
-	) => {
-		const stringVal = typeof val === 'string' ? val : String( val || '' );
-		return args.customFormatter
-			? args.customFormatter( stringVal )
-			: stringVal;
-	};
+	const { customFormatter } = args;

 	const [ inputValue, setInputValue ] = useState(
-		getFormattedValue( args.value )
+		getFormattedValue( args.value, customFormatter )
 	);
 	const { setValidationErrors, showValidationError } =
 		useDispatch( validationStore );

 	useEffect( () => {
-		setInputValue( getFormattedValue( args.value ) );
-	}, [ args.value, args.customFormatter ] );
+		setInputValue( getFormattedValue( args.value, customFormatter ) );
+	}, [ args.value, customFormatter ] );

 	const onChange = ( newValue: string ) => {
-		const formattedValue = args.customFormatter
-			? args.customFormatter( newValue )
-			: newValue;
+		const formattedValue = getFormattedValue( newValue, customFormatter );

 		setInputValue( formattedValue );