From 4d21eaddfb7305642839127f9bd338aaa1bf5849 Mon Sep 17 00:00:00 2001 From: Andrea Fercia Date: Tue, 25 Sep 2018 20:31:59 +0200 Subject: [PATCH 1/2] Handle edge cases in with-constrained-tabbing. --- .../src/higher-order/with-constrained-tabbing/index.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/components/src/higher-order/with-constrained-tabbing/index.js b/packages/components/src/higher-order/with-constrained-tabbing/index.js index 8d1574e0a20197..75745cc9d02429 100644 --- a/packages/components/src/higher-order/with-constrained-tabbing/index.js +++ b/packages/components/src/higher-order/with-constrained-tabbing/index.js @@ -33,6 +33,13 @@ const withConstrainedTabbing = createHigherOrderComponent( } else if ( ! event.shiftKey && event.target === lastTabbable ) { event.preventDefault(); firstTabbable.focus(); + /* + * When pressing Tab and none of the tabbables has focus, the keydown + * event happens on the wrapper div: move focus on the first tabbable. + */ + } else if ( ! tabbables.includes( event.target ) ) { + event.preventDefault(); + firstTabbable.focus(); } } @@ -44,6 +51,7 @@ const withConstrainedTabbing = createHigherOrderComponent(
From 2ee25cc75a97fc6bd70ad290861a3d461b398eed Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Mon, 1 Oct 2018 10:57:18 -0400 Subject: [PATCH 2/2] E2E Tests: a11y: Test focus retention in modals Checks edge cases in withConstrainedTabbing. --- test/e2e/specs/a11y.test.js | 53 +++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/test/e2e/specs/a11y.test.js b/test/e2e/specs/a11y.test.js index 4a15f7ccf8668e..704ae68bba49e4 100644 --- a/test/e2e/specs/a11y.test.js +++ b/test/e2e/specs/a11y.test.js @@ -1,10 +1,20 @@ /** * Internal dependencies */ -import { newPost, pressWithModifier } from '../support/utils'; +import { + ACCESS_MODIFIER_KEYS, + newPost, + pressWithModifier, +} from '../support/utils'; + +function isCloseButtonFocused() { + return page.$eval( ':focus', ( focusedElement ) => { + return focusedElement.getAttribute( 'aria-label' ) === 'Close dialog'; + } ); +} describe( 'a11y', () => { - beforeAll( async () => { + beforeEach( async () => { await newPost(); } ); @@ -19,4 +29,43 @@ describe( 'a11y', () => { expect( isFocusedToggle ).toBe( true ); } ); + + it( 'constrains focus to a modal when tabbing', async () => { + // Open help modal + await pressWithModifier( ACCESS_MODIFIER_KEYS, 'h' ); + + // Test that the Close button of the modal is focused when the + // latter is opened. + expect( await isCloseButtonFocused() ).toBe( true ); + + await page.keyboard.press( 'Tab' ); + + // Test that the Close button of the modal is focused when the + // latter is opened. + expect( await isCloseButtonFocused() ).toBe( true ); + } ); + + it( 'returns focus to the first tabbable in a modal after blurring a tabbable', async () => { + await pressWithModifier( ACCESS_MODIFIER_KEYS, 'h' ); + + // Click to move focus to an element after the last tabbable within the + // modal. + await page.click( '.components-modal__content' ); + + await page.keyboard.press( 'Tab' ); + + expect( await isCloseButtonFocused() ).toBe( true ); + } ); + + it( 'returns focus to the last tabbable in a modal after blurring a tabbable and tabbing in reverse direction', async () => { + await pressWithModifier( ACCESS_MODIFIER_KEYS, 'h' ); + + // Click to move focus to an element before the first tabbable within + // the modal. + await page.click( '.components-modal__header-heading' ); + + await pressWithModifier( 'Shift', 'Tab' ); + + expect( await isCloseButtonFocused() ).toBe( true ); + } ); } );