Commit 9ceabbb322e for woocommerce

commit 9ceabbb322e2710ff221374d729985f8a420bac8
Author: Oleksandr Aratovskyi <79862886+oaratovskyi@users.noreply.github.com>
Date:   Tue Oct 6 18:07:01 2026 +0300

    Fix keyboard links in payment badge popovers (#69451)

    * Fix keyboard activation of payment badge popover links

    * Test Space on payment badge popover links

    Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

    * Update plugins/woocommerce/changelog/fix-payment-badge-popover-keyboard-links

    Co-authored-by: Daniel Mallory <daniel.mallory@automattic.com>

    ---------

    Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
    Co-authored-by: Daniel Mallory <daniel.mallory@automattic.com>

diff --git a/plugins/woocommerce/changelog/fix-payment-badge-popover-keyboard-links b/plugins/woocommerce/changelog/fix-payment-badge-popover-keyboard-links
new file mode 100644
index 00000000000..a5882f3eb89
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-payment-badge-popover-keyboard-links
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Fix an accessibility issue where the Enter key didn't open links in Payments settings badge popovers.
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/official-badge/official-badge.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/official-badge/official-badge.tsx
index 43e55f62679..356a530f28b 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/official-badge/official-badge.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/official-badge/official-badge.tsx
@@ -72,7 +72,10 @@ export const OfficialBadge = ( {
 			event.stopPropagation();
 			setPopoverVisible( false );
 			buttonRef.current?.focus();
-		} else if ( event.key === 'Enter' || event.key === ' ' ) {
+		} else if (
+			( event.key === 'Enter' || event.key === ' ' ) &&
+			event.target === buttonRef.current
+		) {
 			event.preventDefault();
 			handleClick( event );
 		}
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/official-badge/test/official-badge.test.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/official-badge/test/official-badge.test.tsx
new file mode 100644
index 00000000000..579c17ee0fa
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/official-badge/test/official-badge.test.tsx
@@ -0,0 +1,74 @@
+/**
+ * External dependencies
+ */
+import { fireEvent, render, screen } from '@testing-library/react';
+
+/**
+ * Internal dependencies
+ */
+import { OfficialBadge } from '../official-badge';
+
+jest.mock( '~/utils/admin-settings', () => ( {
+	WC_ASSET_URL: 'https://example.test/',
+} ) );
+
+jest.mock( '~/settings-payments/utils', () => ( {
+	recordPaymentsEvent: jest.fn(),
+} ) );
+
+describe( 'OfficialBadge component', () => {
+	it.each( [
+		[ 'Enter', 'Enter' ],
+		[ 'Space', ' ' ],
+	] )(
+		'allows %s on a popover link without preventing its default action',
+		async ( _name, key ) => {
+			render(
+				<OfficialBadge variant="expanded" suggestionId="test_gateway" />
+			);
+			fireEvent.click(
+				screen.getByRole( 'button', {
+					name: /Official WooCommerce extension badge/,
+				} )
+			);
+			const link = await screen.findByRole( 'link', {
+				name: /Learn more/,
+			} );
+			link.focus();
+			const event = new window.KeyboardEvent( 'keydown', {
+				key,
+				bubbles: true,
+				cancelable: true,
+			} );
+
+			fireEvent( link, event );
+
+			expect( event.defaultPrevented ).toBe( false );
+			expect( link ).toBeInTheDocument();
+		}
+	);
+
+	it( 'opens with Enter and Space and returns focus to the trigger on Escape', async () => {
+		render(
+			<OfficialBadge variant="expanded" suggestionId="test_gateway" />
+		);
+		const trigger = screen.getByRole( 'button', {
+			name: /Official WooCommerce extension badge/,
+		} );
+		trigger.focus();
+		fireEvent.keyDown( trigger, { key: 'Enter' } );
+		const link = await screen.findByRole( 'link', { name: /Learn more/ } );
+		link.focus();
+
+		fireEvent.keyDown( link, { key: 'Escape' } );
+
+		expect(
+			screen.queryByRole( 'link', { name: /Learn more/ } )
+		).not.toBeInTheDocument();
+		expect( trigger ).toHaveFocus();
+		fireEvent.keyDown( trigger, { key: ' ' } );
+		expect(
+			await screen.findByRole( 'link', { name: /Learn more/ } )
+		).toBeInTheDocument();
+	} );
+} );
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/status-badge/status-badge.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/status-badge/status-badge.tsx
index e16c51ad431..e2302115589 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/status-badge/status-badge.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/status-badge/status-badge.tsx
@@ -84,7 +84,10 @@ export const StatusBadge = ( {
 			event.stopPropagation();
 			setPopoverVisible( false );
 			buttonRef.current?.focus();
-		} else if ( event.key === 'Enter' || event.key === ' ' ) {
+		} else if (
+			( event.key === 'Enter' || event.key === ' ' ) &&
+			event.target === buttonRef.current
+		) {
 			event.preventDefault();
 			handleClick( event );
 		}
diff --git a/plugins/woocommerce/client/admin/client/settings-payments/components/status-badge/test/status-badge.test.tsx b/plugins/woocommerce/client/admin/client/settings-payments/components/status-badge/test/status-badge.test.tsx
index 33b9e4eff81..d18a6e2513c 100644
--- a/plugins/woocommerce/client/admin/client/settings-payments/components/status-badge/test/status-badge.test.tsx
+++ b/plugins/woocommerce/client/admin/client/settings-payments/components/status-badge/test/status-badge.test.tsx
@@ -1,7 +1,7 @@
 /**
  * External dependencies
  */
-import { render } from '@testing-library/react';
+import { fireEvent, render, screen } from '@testing-library/react';

 /**
  * Internal dependencies
@@ -9,6 +9,69 @@ import { render } from '@testing-library/react';
 import { StatusBadge } from '../status-badge';

 describe( 'StatusBadge component', () => {
+	it.each( [
+		[ 'Enter', 'Enter' ],
+		[ 'Space', ' ' ],
+	] )(
+		'allows %s on a popover link without preventing its default action',
+		async ( _name, key ) => {
+			render(
+				<StatusBadge
+					status="not_supported"
+					popoverContent={
+						<a href="https://example.test/">Learn more</a>
+					}
+				/>
+			);
+			fireEvent.click(
+				screen.getByRole( 'button', { name: 'More information' } )
+			);
+			const link = await screen.findByRole( 'link', {
+				name: 'Learn more',
+			} );
+			link.focus();
+			const event = new window.KeyboardEvent( 'keydown', {
+				key,
+				bubbles: true,
+				cancelable: true,
+			} );
+
+			fireEvent( link, event );
+
+			expect( event.defaultPrevented ).toBe( false );
+			expect( link ).toBeInTheDocument();
+		}
+	);
+
+	it( 'opens with Enter and Space and returns focus to the trigger on Escape', async () => {
+		render(
+			<StatusBadge
+				status="not_supported"
+				popoverContent={
+					<a href="https://example.test/">Learn more</a>
+				}
+			/>
+		);
+		const trigger = screen.getByRole( 'button', {
+			name: 'More information',
+		} );
+		trigger.focus();
+		fireEvent.keyDown( trigger, { key: 'Enter' } );
+		const link = await screen.findByRole( 'link', { name: 'Learn more' } );
+		link.focus();
+
+		fireEvent.keyDown( link, { key: 'Escape' } );
+
+		expect(
+			screen.queryByRole( 'link', { name: 'Learn more' } )
+		).not.toBeInTheDocument();
+		expect( trigger ).toHaveFocus();
+		fireEvent.keyDown( trigger, { key: ' ' } );
+		expect(
+			await screen.findByRole( 'link', { name: 'Learn more' } )
+		).toBeInTheDocument();
+	} );
+
 	it( 'renders the correct message for active status', () => {
 		const { getByText } = render( <StatusBadge status="active" /> );
 		expect( getByText( 'Active' ) ).toBeInTheDocument();