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();