From 863b2cbc53e449de01e8e92a4038dc424e42eb97 Mon Sep 17 00:00:00 2001 From: Mario Santos Date: Tue, 10 Oct 2023 11:55:27 +0200 Subject: [PATCH 01/12] Focus element manually when open submenu on click --- packages/block-library/src/navigation/view.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index c0853b2814e2b3..f9bd3ac75d796c 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -21,6 +21,14 @@ const openMenu = ( store, menuOpenedOn ) => { // Add a `has-modal-open` class to the root. document.documentElement.classList.add( 'has-modal-open' ); } + // Focus the clicked button manually because Safari + // doesn't place focus on it. + if ( + menuOpenedOn === 'click' && + context.core.navigation.type === 'submenu' + ) { + ref.focus(); + } }; const closeMenu = ( store, menuClosedOn ) => { From c9ca86ab07a5eb8749f8b0afcbaf114f72e20f9a Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Wed, 11 Oct 2023 11:34:38 +0200 Subject: [PATCH 02/12] Try using `tabindex="-1"` --- packages/block-library/src/navigation/index.php | 1 + packages/block-library/src/navigation/view.js | 8 -------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index 40b22757528551..4078a687ecd6f7 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -90,6 +90,7 @@ function block_core_navigation_add_directives_to_submenu( $w, $block_attributes $w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initMenu' ); $w->set_attribute( 'data-wp-on--focusout', 'actions.core.navigation.handleMenuFocusout' ); $w->set_attribute( 'data-wp-on--keydown', 'actions.core.navigation.handleMenuKeydown' ); + $w->set_attribute( 'tabindex', '-1' ); if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) { $w->set_attribute( 'data-wp-on--mouseenter', 'actions.core.navigation.openMenuOnHover' ); $w->set_attribute( 'data-wp-on--mouseleave', 'actions.core.navigation.closeMenuOnHover' ); diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index f9bd3ac75d796c..c0853b2814e2b3 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -21,14 +21,6 @@ const openMenu = ( store, menuOpenedOn ) => { // Add a `has-modal-open` class to the root. document.documentElement.classList.add( 'has-modal-open' ); } - // Focus the clicked button manually because Safari - // doesn't place focus on it. - if ( - menuOpenedOn === 'click' && - context.core.navigation.type === 'submenu' - ) { - ref.focus(); - } }; const closeMenu = ( store, menuClosedOn ) => { From a93abd96bb40cbebb5e61f76583be182aadd22e8 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Wed, 11 Oct 2023 16:15:25 +0200 Subject: [PATCH 03/12] Use `tabindex="-1"` also in body when a submenu is opened --- packages/block-library/src/navigation/view.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index c0853b2814e2b3..fe7dee92861be6 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -20,6 +20,9 @@ const openMenu = ( store, menuOpenedOn ) => { if ( context.core.navigation.type === 'overlay' ) { // Add a `has-modal-open` class to the root. document.documentElement.classList.add( 'has-modal-open' ); + } else { + // Ensure that Safari on iOS/iPadOS trigger the mouseleave events. + document.body.setAttribute( 'tabindex', '-1' ); } }; @@ -39,6 +42,8 @@ const closeMenu = ( store, menuClosedOn ) => { context.core.navigation.previousFocus = null; if ( context.core.navigation.type === 'overlay' ) { document.documentElement.classList.remove( 'has-modal-open' ); + } else { + document.body.removeAttribute( 'tabindex' ); } } }; From a6dfcf5b40e315f8d10b178e00fe6a74dca1abe2 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Tue, 17 Oct 2023 10:32:03 +0200 Subject: [PATCH 04/12] Replace tabindex with event listener --- packages/block-library/src/navigation/view.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index fe7dee92861be6..90a870dca58628 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -20,12 +20,11 @@ const openMenu = ( store, menuOpenedOn ) => { if ( context.core.navigation.type === 'overlay' ) { // Add a `has-modal-open` class to the root. document.documentElement.classList.add( 'has-modal-open' ); - } else { - // Ensure that Safari on iOS/iPadOS trigger the mouseleave events. - document.body.setAttribute( 'tabindex', '-1' ); } }; +document.addEventListener( 'click', () => {} ); + const closeMenu = ( store, menuClosedOn ) => { const { context, selectors } = store; selectors.core.navigation.menuOpenedBy( store )[ menuClosedOn ] = false; @@ -42,8 +41,6 @@ const closeMenu = ( store, menuClosedOn ) => { context.core.navigation.previousFocus = null; if ( context.core.navigation.type === 'overlay' ) { document.documentElement.classList.remove( 'has-modal-open' ); - } else { - document.body.removeAttribute( 'tabindex' ); } } }; From b9af57a4c75099c57baa0a1b1924ac9b9dbd6572 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Tue, 17 Oct 2023 18:37:56 +0200 Subject: [PATCH 05/12] Explain the tabindex on
  • --- packages/block-library/src/navigation/index.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index 4078a687ecd6f7..1066691b31094e 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -90,7 +90,11 @@ function block_core_navigation_add_directives_to_submenu( $w, $block_attributes $w->set_attribute( 'data-wp-effect', 'effects.core.navigation.initMenu' ); $w->set_attribute( 'data-wp-on--focusout', 'actions.core.navigation.handleMenuFocusout' ); $w->set_attribute( 'data-wp-on--keydown', 'actions.core.navigation.handleMenuKeydown' ); + + // This is a fix for Safari. It can be removed once we add an overlay to + // capture the clicks, instead of relying on the focusout event. $w->set_attribute( 'tabindex', '-1' ); + if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) { $w->set_attribute( 'data-wp-on--mouseenter', 'actions.core.navigation.openMenuOnHover' ); $w->set_attribute( 'data-wp-on--mouseleave', 'actions.core.navigation.closeMenuOnHover' ); From 74525781a549b20fa04634ac0df10aa21926e15f Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Tue, 17 Oct 2023 18:39:27 +0200 Subject: [PATCH 06/12] Don't store the element on hover to restore the focus later --- packages/block-library/src/navigation/view.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index 90a870dca58628..c9d23aa66ebd4c 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -14,9 +14,8 @@ const focusableSelectors = [ ]; const openMenu = ( store, menuOpenedOn ) => { - const { context, ref, selectors } = store; + const { context, selectors } = store; selectors.core.navigation.menuOpenedBy( store )[ menuOpenedOn ] = true; - context.core.navigation.previousFocus = ref; if ( context.core.navigation.type === 'overlay' ) { // Add a `has-modal-open` class to the root. document.documentElement.classList.add( 'has-modal-open' ); @@ -35,7 +34,7 @@ const closeMenu = ( store, menuClosedOn ) => { window.document.activeElement ) ) { - context.core.navigation.previousFocus.focus(); + context.core.navigation.previousFocus?.focus(); } context.core.navigation.modal = null; context.core.navigation.previousFocus = null; @@ -132,6 +131,8 @@ wpStore( { closeMenu( store, 'hover' ); }, openMenuOnClick( store ) { + const { context, ref } = store; + context.core.navigation.previousFocus = ref; openMenu( store, 'click' ); }, closeMenuOnClick( store ) { @@ -142,13 +143,14 @@ wpStore( { openMenu( store, 'focus' ); }, toggleMenuOnClick: ( store ) => { - const { selectors } = store; + const { selectors, context, ref } = store; const menuOpenedBy = selectors.core.navigation.menuOpenedBy( store ); if ( menuOpenedBy.click || menuOpenedBy.focus ) { closeMenu( store, 'click' ); closeMenu( store, 'focus' ); } else { + context.core.navigation.previousFocus = ref; openMenu( store, 'click' ); } }, From 91d6c5c95f4e8f0997622b4993c0d33a70152729 Mon Sep 17 00:00:00 2001 From: Luis Herranz Date: Tue, 17 Oct 2023 18:43:52 +0200 Subject: [PATCH 07/12] Improve explanations --- packages/block-library/src/navigation/index.php | 6 ++++-- packages/block-library/src/navigation/view.js | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/block-library/src/navigation/index.php b/packages/block-library/src/navigation/index.php index 1066691b31094e..592d188e22ae7c 100644 --- a/packages/block-library/src/navigation/index.php +++ b/packages/block-library/src/navigation/index.php @@ -91,8 +91,10 @@ function block_core_navigation_add_directives_to_submenu( $w, $block_attributes $w->set_attribute( 'data-wp-on--focusout', 'actions.core.navigation.handleMenuFocusout' ); $w->set_attribute( 'data-wp-on--keydown', 'actions.core.navigation.handleMenuKeydown' ); - // This is a fix for Safari. It can be removed once we add an overlay to - // capture the clicks, instead of relying on the focusout event. + // This is a fix for Safari. Without it, Safari doesn't change the active + // element when the user clicks on a button. It can be removed once we add + // an overlay to capture the clicks, instead of relying on the focusout + // event. $w->set_attribute( 'tabindex', '-1' ); if ( ! isset( $block_attributes['openSubmenusOnClick'] ) || false === $block_attributes['openSubmenusOnClick'] ) { diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index c9d23aa66ebd4c..13675fad6f3108 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -22,6 +22,9 @@ const openMenu = ( store, menuOpenedOn ) => { } }; +// This is a fix for Safari in iOS/iPadOS. Without it, Safari doesn't focus out +// when the user taps in the body. It can be removed once we add an overlay to +// capture the clicks, instead of relying on the focusout event. document.addEventListener( 'click', () => {} ); const closeMenu = ( store, menuClosedOn ) => { From a0665a149179a75e6914a60bea926b4a8fcfa7f3 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Tue, 17 Oct 2023 15:17:13 -0500 Subject: [PATCH 08/12] Add tests to cover webkit frontend menu interactions Safari doesn't place focus on a clicked button as expected. These tests verify that when a submenu chevron button is clicked, focus is correctly placed on that button. It also verifies that the click on the body correctly closes any open submenus, which was failing in Safari. --- .../navigation-frontend-interactivity.spec.js | 48 +++++++++++++++++-- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js b/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js index 7e761f1861856f..983a74276c8bf3 100644 --- a/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js +++ b/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js @@ -3,7 +3,7 @@ */ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); -test.describe( 'Navigation block - Frontend interactivity', () => { +test.describe( 'Navigation block - Frontend interactivity (@firefox, @webkit)', () => { test.beforeAll( async ( { requestUtils } ) => { await requestUtils.activateTheme( 'emptytheme' ); await requestUtils.deleteAllTemplates( 'wp_template_part' ); @@ -133,10 +133,14 @@ test.describe( 'Navigation block - Frontend interactivity', () => { const secondLevelElement = page.getByRole( 'link', { name: 'Nested Submenu Link 1', } ); + const lastFirstLevelElement = page.getByRole( 'link', { + name: 'Complex Submenu Link 2', + } ); // Test: submenu opens on click await expect( innerElement ).toBeHidden(); await simpleSubmenuButton.click(); + await expect( simpleSubmenuButton ).toBeFocused(); await expect( innerElement ).toBeVisible(); // Test: submenu closes on click outside submenu @@ -145,10 +149,12 @@ test.describe( 'Navigation block - Frontend interactivity', () => { // Test: nested submenu opens on click await complexSubmenuButton.click(); + await expect( complexSubmenuButton ).toBeFocused(); await expect( firstLevelElement ).toBeVisible(); await expect( secondLevelElement ).toBeHidden(); await nestedSubmenuButton.click(); + await expect( nestedSubmenuButton ).toBeFocused(); await expect( firstLevelElement ).toBeVisible(); await expect( secondLevelElement ).toBeVisible(); @@ -160,6 +166,7 @@ test.describe( 'Navigation block - Frontend interactivity', () => { // Test: submenu opens on Enter keypress await simpleSubmenuButton.focus(); await pageUtils.pressKeys( 'Enter' ); + await expect( simpleSubmenuButton ).toBeFocused(); await expect( innerElement ).toBeVisible(); // Test: submenu closes on ESC key and focuses parent link @@ -168,27 +175,29 @@ test.describe( 'Navigation block - Frontend interactivity', () => { await expect( simpleSubmenuButton ).toBeFocused(); // Test: submenu closes on tab outside submenu - await simpleSubmenuButton.focus(); await pageUtils.pressKeys( 'Enter' ); + await expect( simpleSubmenuButton ).toBeFocused(); await expect( innerElement ).toBeVisible(); // Tab to first element, then tab outside the submenu. await pageUtils.pressKeys( 'Tab', { times: 2, delay: 50 } ); - await expect( innerElement ).toBeHidden(); await expect( complexSubmenuButton ).toBeFocused(); + await expect( innerElement ).toBeHidden(); // Test: only nested submenu closes on tab outside - await complexSubmenuButton.focus(); await pageUtils.pressKeys( 'Enter' ); + await expect( complexSubmenuButton ).toBeFocused(); await expect( firstLevelElement ).toBeVisible(); await expect( secondLevelElement ).toBeHidden(); await nestedSubmenuButton.click(); + await expect( nestedSubmenuButton ).toBeFocused(); await expect( firstLevelElement ).toBeVisible(); await expect( secondLevelElement ).toBeVisible(); // Tab to nested submenu first element, then tab outside the nested // submenu. await pageUtils.pressKeys( 'Tab', { times: 2, delay: 50 } ); + await expect( lastFirstLevelElement ).toBeFocused(); await expect( firstLevelElement ).toBeVisible(); await expect( secondLevelElement ).toBeHidden(); // Tab outside the complex submenu. @@ -222,7 +231,7 @@ test.describe( 'Navigation block - Frontend interactivity', () => { await editor.saveSiteEditorEntities(); } ); - test( 'submenu opens on click in the arrow', async ( { page } ) => { + test( 'submenu click on the arrow interactions', async ( { page } ) => { await page.goto( '/' ); const arrowButton = page.getByRole( 'button', { name: 'Submenu submenu', @@ -239,12 +248,41 @@ test.describe( 'Navigation block - Frontend interactivity', () => { await expect( firstLevelElement ).toBeHidden(); await expect( secondLevelElement ).toBeHidden(); + // Open first submenu level await arrowButton.click(); + await expect( arrowButton ).toBeFocused(); await expect( firstLevelElement ).toBeVisible(); await expect( secondLevelElement ).toBeHidden(); + + // Close first submenu level, check that it closes and focus is on the arrow button + await arrowButton.click(); + await expect( arrowButton ).toBeFocused(); + // Move the mouse so the hover on the button doesn't keep the menu open + await page.mouse.move( 400, 400 ); + await expect( firstLevelElement ).toBeHidden(); + await expect( secondLevelElement ).toBeHidden(); + + // Open first submenu level one more time so we can test the nested submenu + await arrowButton.click(); + await expect( arrowButton ).toBeFocused(); + await expect( firstLevelElement ).toBeVisible(); + await expect( secondLevelElement ).toBeHidden(); + + // Nested submenu open await nestedSubmenuArrowButton.click(); + await expect( nestedSubmenuArrowButton ).toBeFocused(); await expect( firstLevelElement ).toBeVisible(); await expect( secondLevelElement ).toBeVisible(); + + // Nested submenu close + await nestedSubmenuArrowButton.click(); + await expect( nestedSubmenuArrowButton ).toBeFocused(); + // Move the mouse so the hover on the button doesn't keep the menu open + await page.mouse.move( 400, 400 ); + await expect( firstLevelElement ).toBeVisible(); + await expect( secondLevelElement ).toBeHidden(); + + // Close menu via click on the body await page.click( 'body' ); await expect( firstLevelElement ).toBeHidden(); await expect( secondLevelElement ).toBeHidden(); From 57e3bd2cc0b7bbd40885357b0d36fb6048ee29b4 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Tue, 17 Oct 2023 15:50:44 -0500 Subject: [PATCH 09/12] Focus clicked button on Safari Combining the tabindex -1 on the parent li and focusing the button on Safari, and also checking that the relatedTarget is null inside the handleMenuFocusout seems to contain the focus within the menu to not fire the handleMenuFocusout as often, and still works to click on the body to close the menu. --- packages/block-library/src/navigation/view.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index 13675fad6f3108..4372bbb866109f 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -22,11 +22,6 @@ const openMenu = ( store, menuOpenedOn ) => { } }; -// This is a fix for Safari in iOS/iPadOS. Without it, Safari doesn't focus out -// when the user taps in the body. It can be removed once we add an overlay to -// capture the clicks, instead of relying on the focusout event. -document.addEventListener( 'click', () => {} ); - const closeMenu = ( store, menuClosedOn ) => { const { context, selectors } = store; selectors.core.navigation.menuOpenedBy( store )[ menuClosedOn ] = false; @@ -147,6 +142,8 @@ wpStore( { }, toggleMenuOnClick: ( store ) => { const { selectors, context, ref } = store; + // Safari won't send focus to the clicked element, so we need to manually place it: https://bugs.webkit.org/show_bug.cgi?id=22261 + if ( window.document.activeElement !== ref ) ref.focus(); const menuOpenedBy = selectors.core.navigation.menuOpenedBy( store ); if ( menuOpenedBy.click || menuOpenedBy.focus ) { @@ -201,11 +198,14 @@ wpStore( { // event.relatedTarget === The element receiving focus (if any) // When focusout is outsite the document, // `window.document.activeElement` doesn't change. + + // The event.relatedTarget is null when something outside the navigation menu is clicked. This is only necessary for Safari. if ( - ! context.core.navigation.modal?.contains( + event.relatedTarget === null || + ( ! context.core.navigation.modal?.contains( event.relatedTarget ) && - event.target !== window.document.activeElement + event.target !== window.document.activeElement ) ) { closeMenu( store, 'click' ); closeMenu( store, 'focus' ); From f6d50ac817c02f3bed395215e906d980183717e1 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 18 Oct 2023 08:03:28 -0500 Subject: [PATCH 10/12] Added the document.addEventListener body click back in Authored by Luis Herranz . I'm just re-applying the change. --- packages/block-library/src/navigation/view.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/block-library/src/navigation/view.js b/packages/block-library/src/navigation/view.js index 4372bbb866109f..bad36f6240134f 100644 --- a/packages/block-library/src/navigation/view.js +++ b/packages/block-library/src/navigation/view.js @@ -13,6 +13,11 @@ const focusableSelectors = [ '[tabindex]:not([tabindex^="-"])', ]; +// This is a fix for Safari in iOS/iPadOS. Without it, Safari doesn't focus out +// when the user taps in the body. It can be removed once we add an overlay to +// capture the clicks, instead of relying on the focusout event. +document.addEventListener( 'click', () => {} ); + const openMenu = ( store, menuOpenedOn ) => { const { context, selectors } = store; selectors.core.navigation.menuOpenedBy( store )[ menuOpenedOn ] = true; From b92bf041768cbbf6cbf418e7a469b2809b779c94 Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 18 Oct 2023 09:06:28 -0500 Subject: [PATCH 11/12] Remove tab keypresses from webkit menu interaction tests Tab keypreses on webkit playwright are really flakey (or it's something in our code that we haven't isolated) so I've split out the webkit tests to test everything I can without using a tab keypress. --- .../navigation-frontend-interactivity.spec.js | 140 +++++++++++++++++- 1 file changed, 137 insertions(+), 3 deletions(-) diff --git a/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js b/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js index 983a74276c8bf3..c3d8078aea0921 100644 --- a/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js +++ b/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js @@ -3,7 +3,7 @@ */ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); -test.describe( 'Navigation block - Frontend interactivity (@firefox, @webkit)', () => { +test.describe( 'Navigation block - Frontend interactivity', () => { test.beforeAll( async ( { requestUtils } ) => { await requestUtils.activateTheme( 'emptytheme' ); await requestUtils.deleteAllTemplates( 'wp_template_part' ); @@ -80,6 +80,51 @@ test.describe( 'Navigation block - Frontend interactivity (@firefox, @webkit)', await expect( overlayMenuFirstElement ).toBeHidden(); await expect( openMenuButton ).toBeFocused(); } ); + + /** + * These are already tested within the Overlay Interactions test above, but Safari is flakey on the Tab + * keypresses (passes 50 - 70% of the time). Tab keypresses are testing fine manually in Safari, but not + * in the test. nce we figure out why the Tab keypresses are flakey in the test, we can + * remove this test and only rely on the Overlay Interactions test above and add a (@firefox, @webkit) + * directive to the describe() statement. https://github.com/WordPress/gutenberg/pull/55198 + */ + test( 'Overlay menu interactions in Safari (@webkit)', async ( { + page, + pageUtils, + } ) => { + await page.goto( '/' ); + const overlayMenuFirstElement = page.getByRole( 'link', { + name: 'Item 1', + } ); + const openMenuButton = page.getByRole( 'button', { + name: 'Open menu', + } ); + + const closeMenuButton = page.getByRole( 'button', { + name: 'Close menu', + } ); + + // Test: overlay menu opens on click on open menu button + await expect( overlayMenuFirstElement ).toBeHidden(); + await openMenuButton.click(); + await expect( overlayMenuFirstElement ).toBeVisible(); + + // Test: overlay menu focuses on first element after opening + await expect( overlayMenuFirstElement ).toBeFocused(); + + // Not Tested: overlay menu traps focus + + // Test: overlay menu closes on click on close menu button + await closeMenuButton.click(); + await expect( overlayMenuFirstElement ).toBeHidden(); + + // Test: overlay menu closes on ESC key + await openMenuButton.click(); + await expect( overlayMenuFirstElement ).toBeVisible(); + await pageUtils.pressKeys( 'Escape' ); + await expect( overlayMenuFirstElement ).toBeHidden(); + await expect( openMenuButton ).toBeFocused(); + } ); } ); test.describe( 'Submenu mouse and keyboard interactions', () => { @@ -204,9 +249,98 @@ test.describe( 'Navigation block - Frontend interactivity (@firefox, @webkit)', await page.keyboard.press( 'Tab' ); await expect( firstLevelElement ).toBeHidden(); } ); + + /** + * These are already tested within the Submenu Interactions test above, but Safari is flakey on the + * Tab keypresses (passes 50 - 70% of the time). Tab keypresses are testing fine manually in Safari, + * but not in the test. Once we figure out why the Tab keypresses are flakey in the test, we can + * remove this test and only rely on the Submenu interactions test above and add a (@firefox, @webkit) + * directive to the describe() statement. https://github.com/WordPress/gutenberg/pull/55198 + */ + test( 'Submenu interactions on Safari (@webkit)', async ( { + page, + pageUtils, + } ) => { + await page.goto( '/' ); + const simpleSubmenuButton = page.getByRole( 'button', { + name: 'Simple Submenu', + } ); + const innerElement = page.getByRole( 'link', { + name: 'Simple Submenu Link 1', + } ); + const complexSubmenuButton = page.getByRole( 'button', { + name: 'Complex Submenu', + } ); + const nestedSubmenuButton = page.getByRole( 'button', { + name: 'Nested Submenu', + } ); + const firstLevelElement = page.getByRole( 'link', { + name: 'Complex Submenu Link 1', + } ); + const secondLevelElement = page.getByRole( 'link', { + name: 'Nested Submenu Link 1', + } ); + + // Test: submenu opens on click and focuses the button + await expect( innerElement ).toBeHidden(); + await simpleSubmenuButton.click(); + await expect( simpleSubmenuButton ).toBeFocused(); + await expect( innerElement ).toBeVisible(); + + // Test: a second click closes the submenu + await simpleSubmenuButton.click(); + await expect( simpleSubmenuButton ).toBeFocused(); + await expect( innerElement ).toBeHidden(); + + // Test: submenu opens on Enter keypress + await simpleSubmenuButton.focus(); + await pageUtils.pressKeys( 'Enter' ); + await expect( simpleSubmenuButton ).toBeFocused(); + await expect( innerElement ).toBeVisible(); + + // Test: submenu closes on second Enter keypress + await pageUtils.pressKeys( 'Enter' ); + await expect( innerElement ).toBeHidden(); + await expect( simpleSubmenuButton ).toBeFocused(); + + // Test: inner submenu opens on click and focuses the button + await complexSubmenuButton.click(); + await expect( complexSubmenuButton ).toBeFocused(); + await expect( firstLevelElement ).toBeVisible(); + await expect( secondLevelElement ).toBeHidden(); + // Click the inner menu button and check it opens the third level menu + await nestedSubmenuButton.click(); + await expect( nestedSubmenuButton ).toBeFocused(); + await expect( firstLevelElement ).toBeVisible(); + await expect( secondLevelElement ).toBeVisible(); + + // Click the inner menu button and check it closes the third level menu + await nestedSubmenuButton.click(); + await expect( nestedSubmenuButton ).toBeFocused(); + await expect( firstLevelElement ).toBeVisible(); + await expect( secondLevelElement ).toBeHidden(); + + // Do the same with Enter keypresses: open the third level menu + await pageUtils.pressKeys( 'Enter' ); + await expect( nestedSubmenuButton ).toBeFocused(); + await expect( firstLevelElement ).toBeVisible(); + await expect( secondLevelElement ).toBeVisible(); + + // Close the third level menu + await pageUtils.pressKeys( 'Enter' ); + await expect( nestedSubmenuButton ).toBeFocused(); + await expect( firstLevelElement ).toBeVisible(); + await expect( secondLevelElement ).toBeHidden(); + + // Close all the menus with click on a non-body element outside the menu + await page.getByText( 'Just another Gutenberg site' ).click(); + await expect( firstLevelElement ).toBeHidden(); + + // Tests not covered: Tabbing to close menus + } ); } ); - test.describe( 'Submenus (Arrow setting)', () => { + test.describe( 'Submenus (Arrow setting) (@firefox, @webkit)', () => { test.beforeEach( async ( { admin, editor, requestUtils } ) => { await admin.visitSiteEditor( { postId: 'emptytheme//header', @@ -289,7 +423,7 @@ test.describe( 'Navigation block - Frontend interactivity (@firefox, @webkit)', } ); } ); - test.describe( 'Page list block', () => { + test.describe( 'Page list block (@firefox, @webkit)', () => { test.beforeEach( async ( { admin, editor, requestUtils } ) => { const parentPage = await requestUtils.createPage( { title: 'Parent Page', From 6d719ba247abffc888b278faa1c5ec246b57b55b Mon Sep 17 00:00:00 2001 From: Jerry Jones Date: Wed, 18 Oct 2023 09:58:22 -0500 Subject: [PATCH 12/12] Use body click instead for consistency across environments --- .../editor/blocks/navigation-frontend-interactivity.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js b/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js index c3d8078aea0921..ac6094c3d3eac3 100644 --- a/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js +++ b/test/e2e/specs/editor/blocks/navigation-frontend-interactivity.spec.js @@ -332,8 +332,8 @@ test.describe( 'Navigation block - Frontend interactivity', () => { await expect( firstLevelElement ).toBeVisible(); await expect( secondLevelElement ).toBeHidden(); - // Close all the menus with click on a non-body element outside the menu - await page.getByText( 'Just another Gutenberg site' ).click(); + // Close the menu via click on the body + await page.click( 'body' ); await expect( firstLevelElement ).toBeHidden(); // Tests not covered: Tabbing to close menus