diff --git a/packages/e2e-test-utils/README.md b/packages/e2e-test-utils/README.md index d8b3263c2e6e9..3b11399259294 100644 --- a/packages/e2e-test-utils/README.md +++ b/packages/e2e-test-utils/README.md @@ -181,6 +181,18 @@ _Returns_ - `Promise`: Promise resolving once the edit post sidebar is opened. +# **externalWrapperHasFocus** + +Asserts that the element with keyboard focus is a block's external wrapper + +_Parameters_ + +- _blockType_ `string`: The expected value of the data-type attribute of the block's external wrapper + +_Returns_ + +- `Promise`: A promise that's resolved when the active element is evaluated and asserted against the expected result. + # **findSidebarPanelToggleButtonWithTitle** Finds a sidebar panel with the provided title. @@ -259,6 +271,19 @@ _Returns_ - `Promise`: Promise resolving with a boolean. +# **insertAndPopulateBlock** + +Inserts a content block and then, if it has text content areas, fills them with text + +_Parameters_ + +- _blockName_ `string`: The type of block to insert +- _content_ `string`: The text to enter into each contenteditable area + +_Returns_ + +- `Promise`: A promise that resolves when all the blocks are inserted and filled with content. + # **insertBlock** Opens the inserter, searches for the given term, then selects the first @@ -327,6 +352,14 @@ _Returns_ - `Promise`: Promise that uses `mockCheck` to see if a request should be mocked with `mock`, and optionally transforms the response with `responseObjectTransform`. +# **navigateToContentEditorTop** + +Navigates to the top of the content editor using the keyboard. + +_Returns_ + +- `Promise`: A promise that's resolved when the browser has finished emulating the keyboard shortcut for focusing the top of the editor, and tabbed to the next focusable element. + # **observeFocusLoss** Binds to the document on page load which throws an error if a `focusout` @@ -477,6 +510,91 @@ running the test is not already the admin user). Switches the current user to whichever user we should be running the tests as (if we're not already that user). +# **tabThroughBlockControls** + +Tabs through a content block and asserts that the external wrapper, inserter toggle, mover controls, and toolbar buttons all receive keyboard focus. + +_Parameters_ + +- _blockType_ `string`: The expected value of the data-type attribute of the block's external wrapper + +_Returns_ + +- `Promise`: A promise that's resolved when the browser has finished tabbing through the major components of a common block. + +# **tabThroughBlockMovers** + +Navigates through the block mover control using the keyboard. Asserts that the 'move up' and 'move down' controls receive focus. + +_Returns_ + +- `Promise`: A promise that's resolved when the browser has finished tabbing throught the block mover controls. + +# **tabThroughBlockToolbar** + +Navigate through a block's toolbar using the keyboard. Asserts that each button receives focus. + +_Returns_ + +- `Promise`: A promise that resolves when it's finished tabbing through the buttons in a block's toolbar, asserting that each one received focus. + +# **tabThroughFileBlock** + +Tabs through a content block with file upload buttons, such as an Image, Gallery, Audio, or Cover block + +_Parameters_ + +- _blockType_ `string`: The expected value of the data-type attribute of the block's external wrapper + +_Returns_ + +- `Promise`: A promise that resolves when the browser has completed tabbing through the common block components, and the placeholder buttons that are unique to blocks with file-upload features. + +# **tabThroughPlaceholderButtons** + +Tabs through the file upload buttons that appear in a file content block's placeholder area + +_Returns_ + +- `Promise`: A promise that resolves when the browser has completed tabbing through the placeholder buttons that are unique to blocks with file-upload features. + +# **tabThroughTextBlock** + +Tabs through a content block with text content areas, such as a Heading, Quote, or Paragraph block. Asserts that the text content areas all receive focus. + +_Parameters_ + +- _blockType_ `string`: The expected value of the data-type attribute of the block's external wrapper +- _content_ `string`: The expected title of the block + +_Returns_ + +- `Promise`: A promise that resolves when the browser has completed tabbing through the focusable elements of a common block, and through the contenteditbable areas unique to text blocks. + +# **tabThroughTextContentAreas** + +Tabs through the text content areas of a block and asserts the expected values + +_Parameters_ + +- _content_ `string`: The expected value of the block's contenteditable elements + +_Returns_ + +- `Promise`: A promise that's resolved when the browser has finished tabbing throught the contenteditable areas of a block, and asserting they have keyboard focus and the expected content. + +# **textContentAreas** + +Returns a list of a block's contenteditable elements. + +_Parameters_ + +- _empty_ `boolean`: When true, restricts the list to contenteditable elements with no value + +_Returns_ + +- `Promise`: A promise that resolves when it's returned an array of classes representing the contenteditable areas of a block with keyboard focus. + # **toggleMoreMenu** Toggles the More Menu. diff --git a/packages/e2e-test-utils/src/external-wrapper-has-focus.js b/packages/e2e-test-utils/src/external-wrapper-has-focus.js new file mode 100644 index 0000000000000..9660b3affc3f9 --- /dev/null +++ b/packages/e2e-test-utils/src/external-wrapper-has-focus.js @@ -0,0 +1,11 @@ +/** + * Asserts that the element with keyboard focus is a block's external wrapper + * + * @param {string} blockType The expected value of the data-type attribute of the block's external wrapper + * @return {Promise} A promise that's resolved when the active element is evaluated and asserted against the expected result. + */ + +export async function externalWrapperHasFocus( blockType ) { + const activeElementDataType = await page.evaluate( () => document.activeElement.dataset.type ); + expect( activeElementDataType ).toEqual( blockType ); +} diff --git a/packages/e2e-test-utils/src/index.js b/packages/e2e-test-utils/src/index.js index 8fcf2b400c585..51c7dd88e02dc 100644 --- a/packages/e2e-test-utils/src/index.js +++ b/packages/e2e-test-utils/src/index.js @@ -15,28 +15,39 @@ export { enableExperimentalFeatures } from './enable-experimental-features'; export { enablePageDialogAccept } from './enable-page-dialog-accept'; export { enablePrePublishChecks } from './enable-pre-publish-checks'; export { ensureSidebarOpened } from './ensure-sidebar-opened'; -export { findSidebarPanelToggleButtonWithTitle } from './find-sidebar-panel-toggle-button-with-title'; +export { externalWrapperHasFocus } from './external-wrapper-has-focus'; +export { + findSidebarPanelToggleButtonWithTitle, +} from './find-sidebar-panel-toggle-button-with-title'; export { findSidebarPanelWithTitle } from './find-sidebar-panel-with-title'; -export { getAllBlockInserterItemTitles } from './get-all-block-inserter-item-titles'; +export { + getAllBlockInserterItemTitles, +} from './get-all-block-inserter-item-titles'; export { getAllBlocks } from './get-all-blocks'; export { getAvailableBlockTransforms } from './get-available-block-transforms'; export { getBlockSetting } from './get-block-setting'; export { getEditedPostContent } from './get-edited-post-content'; export { hasBlockSwitcher } from './has-block-switcher'; +export { insertAndPopulateBlock } from './insert-and-populate-block'; export { insertBlock } from './insert-block'; export { installPlugin } from './install-plugin'; export { isCurrentURL } from './is-current-url'; export { isInDefaultBlock } from './is-in-default-block'; export { loginUser } from './login-user'; +export { navigateToContentEditorTop } from './navigate-to-content-editor-top'; export { observeFocusLoss } from './observe-focus-loss'; -export { openAllBlockInserterCategories } from './open-all-block-inserter-categories'; +export { + openAllBlockInserterCategories, +} from './open-all-block-inserter-categories'; export { openDocumentSettingsSidebar } from './open-document-settings-sidebar'; export { openGlobalBlockInserter } from './open-global-block-inserter'; export { openPublishPanel } from './open-publish-panel'; export { pressKeyTimes } from './press-key-times'; export { pressKeyWithModifier } from './press-key-with-modifier'; export { publishPost } from './publish-post'; -export { publishPostWithPrePublishChecksDisabled } from './publish-post-with-pre-publish-checks-disabled'; +export { + publishPostWithPrePublishChecksDisabled, +} from './publish-post-with-pre-publish-checks-disabled'; export { saveDraft } from './save-draft'; export { searchForBlock } from './search-for-block'; export { selectBlockByClientId } from './select-block-by-client-id'; @@ -46,6 +57,16 @@ export { switchEditorModeTo } from './switch-editor-mode-to'; export { disableNavigationMode } from './keyboard-mode'; export { switchUserToAdmin } from './switch-user-to-admin'; export { switchUserToTest } from './switch-user-to-test'; +export { tabThroughBlockControls } from './tab-through-block-controls'; +export { tabThroughBlockMovers } from './tab-through-block-movers'; +export { tabThroughBlockToolbar } from './tab-through-block-toolbar'; +export { tabThroughFileBlock } from './tab-through-file-block'; +export { + tabThroughPlaceholderButtons, +} from './tab-through-placeholder-buttons'; +export { tabThroughTextBlock } from './tab-through-text-block'; +export { tabThroughTextContentAreas } from './tab-through-text-content-areas'; +export { textContentAreas } from './text-content-areas'; export { toggleMoreMenu } from './toggle-more-menu'; export { toggleOfflineMode, isOfflineMode } from './offline-mode'; export { toggleScreenOption } from './toggle-screen-option'; diff --git a/packages/e2e-test-utils/src/insert-and-populate-block.js b/packages/e2e-test-utils/src/insert-and-populate-block.js new file mode 100644 index 0000000000000..606544c4fb8d1 --- /dev/null +++ b/packages/e2e-test-utils/src/insert-and-populate-block.js @@ -0,0 +1,27 @@ +/** + * Internal dependencies + */ +import { insertBlock } from './insert-block'; +import { textContentAreas } from './text-content-areas'; + +/** + * Inserts a content block and then, if it has text content areas, fills them with text + * + * @param {string} blockName The type of block to insert + * @param {string} content The text to enter into each contenteditable area + * @return {Promise} A promise that resolves when all the blocks are inserted and filled with content. + */ +export async function insertAndPopulateBlock( blockName, content ) { + await insertBlock( blockName ); + // typing populates the first content area + await page.keyboard.type( content ); + + // if there are more contenteditable elements, select and populate them too: + const blockContentAreas = await textContentAreas( { empty: true } ); + + for ( let i = 0; i < blockContentAreas.length; i++ ) { + await page.keyboard.press( 'Tab' ); + await page.keyboard.type( content ); + } + await page.keyboard.press( 'Enter' ); +} diff --git a/packages/e2e-test-utils/src/navigate-to-content-editor-top.js b/packages/e2e-test-utils/src/navigate-to-content-editor-top.js new file mode 100644 index 0000000000000..e5c94707c9c16 --- /dev/null +++ b/packages/e2e-test-utils/src/navigate-to-content-editor-top.js @@ -0,0 +1,18 @@ +/** + * Internal dependencies + */ +import { pressKeyWithModifier } from './press-key-with-modifier'; + +/** + * Navigates to the top of the content editor using the keyboard. + * + * @return {Promise} A promise that's resolved when the browser has finished emulating the keyboard shortcut for focusing the top of the editor, and tabbed to the next focusable element. + */ +export async function navigateToContentEditorTop() { + // Use 'Ctrl+`' to return to the top of the editor + await pressKeyWithModifier( 'ctrl', '`' ); + await pressKeyWithModifier( 'ctrl', '`' ); + + // Tab into the Title block + await page.keyboard.press( 'Tab' ); +} diff --git a/packages/e2e-test-utils/src/tab-through-block-controls.js b/packages/e2e-test-utils/src/tab-through-block-controls.js new file mode 100644 index 0000000000000..6117c764e7f4d --- /dev/null +++ b/packages/e2e-test-utils/src/tab-through-block-controls.js @@ -0,0 +1,37 @@ +/** + * Internal dependencies + */ +import { externalWrapperHasFocus } from './external-wrapper-has-focus'; +import { tabThroughBlockMovers } from './tab-through-block-movers'; +import { tabThroughBlockToolbar } from './tab-through-block-toolbar'; + +/** + * Asserts that a content block's inserter toggle has keyboard focus + * + * @return {Promise} A promise that's resolved when the active element is evaluated and asserted to have the inserter toggle's classname. + */ + +const inserterToggleHasFocus = async function() { + const isFocusedInserterToggle = await page.evaluate( () => document.activeElement.classList.contains( 'block-editor-inserter__toggle' ) ); + expect( isFocusedInserterToggle ).toBe( true ); +}; + +/** + * Tabs through a content block and asserts that the external wrapper, inserter toggle, mover controls, and toolbar buttons all receive keyboard focus. + * + * @param {string} blockType The expected value of the data-type attribute of the block's external wrapper + * @return {Promise} A promise that's resolved when the browser has finished tabbing through the major components of a common block. + */ + +export async function tabThroughBlockControls( blockType ) { + // Tab to the next block + await page.keyboard.press( 'Tab' ); + await externalWrapperHasFocus( blockType ); + + // Tab causes 'add block' button to receive focus + await page.keyboard.press( 'Tab' ); + await inserterToggleHasFocus(); + + await tabThroughBlockMovers(); + await tabThroughBlockToolbar(); +} diff --git a/packages/e2e-test-utils/src/tab-through-block-movers.js b/packages/e2e-test-utils/src/tab-through-block-movers.js new file mode 100644 index 0000000000000..1000dc372a94b --- /dev/null +++ b/packages/e2e-test-utils/src/tab-through-block-movers.js @@ -0,0 +1,21 @@ +/** + * Navigates through the block mover control using the keyboard. Asserts that the 'move up' and 'move down' controls receive focus. + * + * @return {Promise} A promise that's resolved when the browser has finished tabbing throught the block mover controls. + */ + +export async function tabThroughBlockMovers() { + // Tab to focus on the 'move up' control + await page.keyboard.press( 'Tab' ); + const isFocusedMoveUpControl = await page.evaluate( () => + document.activeElement.classList.contains( 'block-editor-block-mover__control' ) + ); + expect( isFocusedMoveUpControl ).toBe( true ); + + // Tab to focus on the 'move down' control + await page.keyboard.press( 'Tab' ); + const isFocusedMoveDownControl = await page.evaluate( () => + document.activeElement.classList.contains( 'block-editor-block-mover__control' ) + ); + expect( isFocusedMoveDownControl ).toBe( true ); +} diff --git a/packages/e2e-test-utils/src/tab-through-block-toolbar.js b/packages/e2e-test-utils/src/tab-through-block-toolbar.js new file mode 100644 index 0000000000000..85a811745a5ff --- /dev/null +++ b/packages/e2e-test-utils/src/tab-through-block-toolbar.js @@ -0,0 +1,18 @@ +/** + * Navigate through a block's toolbar using the keyboard. Asserts that each button receives focus. + * + * @return {Promise} A promise that resolves when it's finished tabbing through the buttons in a block's toolbar, asserting that each one received focus. + */ + +export async function tabThroughBlockToolbar() { + const blockToolbarButtons = await page.$$eval( '.block-editor-block-contextual-toolbar button:not([disabled])', + ( elements ) => elements.map( ( elem ) => elem.className ) ); + + for ( const buttonClassName of blockToolbarButtons ) { + await page.keyboard.press( 'Tab' ); + const focusedBlockToolBarButton = await page.evaluate( () => + document.activeElement.className + ); + expect( focusedBlockToolBarButton ).toEqual( buttonClassName ); + } +} diff --git a/packages/e2e-test-utils/src/tab-through-file-block.js b/packages/e2e-test-utils/src/tab-through-file-block.js new file mode 100644 index 0000000000000..1f309e019643d --- /dev/null +++ b/packages/e2e-test-utils/src/tab-through-file-block.js @@ -0,0 +1,18 @@ + +/** + * Internal dependencies + */ + +import { tabThroughBlockControls } from './tab-through-block-controls'; +import { tabThroughPlaceholderButtons } from './tab-through-placeholder-buttons'; + +/** + * Tabs through a content block with file upload buttons, such as an Image, Gallery, Audio, or Cover block + * + * @param {string} blockType The expected value of the data-type attribute of the block's external wrapper + * @return {Promise} A promise that resolves when the browser has completed tabbing through the common block components, and the placeholder buttons that are unique to blocks with file-upload features. + */ +export async function tabThroughFileBlock( blockType ) { + await tabThroughBlockControls( blockType ); + await tabThroughPlaceholderButtons(); +} diff --git a/packages/e2e-test-utils/src/tab-through-placeholder-buttons.js b/packages/e2e-test-utils/src/tab-through-placeholder-buttons.js new file mode 100644 index 0000000000000..59894cd3a7523 --- /dev/null +++ b/packages/e2e-test-utils/src/tab-through-placeholder-buttons.js @@ -0,0 +1,17 @@ +/** + * Tabs through the file upload buttons that appear in a file content block's placeholder area + * + * @return {Promise} A promise that resolves when the browser has completed tabbing through the placeholder buttons that are unique to blocks with file-upload features. + */ +export const tabThroughPlaceholderButtons = async () => { + const placeholderButtons = await page.$$eval( '.wp-block.is-selected .block-editor-media-placeholder button:not([disabled])', + ( elements ) => elements.map( ( elem ) => elem.className ) ); + + for ( const buttonClassName of placeholderButtons ) { + await page.keyboard.press( 'Tab' ); + const focusedPlaceholderButton = await page.evaluate( () => + document.activeElement.className + ); + expect( focusedPlaceholderButton ).toEqual( buttonClassName ); + } +}; diff --git a/packages/e2e-test-utils/src/tab-through-text-block.js b/packages/e2e-test-utils/src/tab-through-text-block.js new file mode 100644 index 0000000000000..97f7ab84bc584 --- /dev/null +++ b/packages/e2e-test-utils/src/tab-through-text-block.js @@ -0,0 +1,21 @@ +/** + * Internal dependencies + */ +import { tabThroughBlockControls } from './tab-through-block-controls'; +import { tabThroughTextContentAreas } from './tab-through-text-content-areas'; + +/** + * Tabs through a content block with text content areas, such as a Heading, Quote, or Paragraph block. Asserts that the text content areas all receive focus. + * + * @param {string} blockType The expected value of the data-type attribute of the block's external wrapper + * @param {string} content The expected title of the block + * @return {Promise} A promise that resolves when the browser has completed tabbing through the focusable elements of a common block, and through the contenteditbable areas unique to text blocks. + */ + +export async function tabThroughTextBlock( blockType, content ) { + await tabThroughBlockControls( blockType ); + + // Tab causes the block text content to receive focus + await page.keyboard.press( 'Tab' ); + await tabThroughTextContentAreas( content ); +} diff --git a/packages/e2e-test-utils/src/tab-through-text-content-areas.js b/packages/e2e-test-utils/src/tab-through-text-content-areas.js new file mode 100644 index 0000000000000..b7c59a8ea6e3a --- /dev/null +++ b/packages/e2e-test-utils/src/tab-through-text-content-areas.js @@ -0,0 +1,27 @@ +/** + * Internal dependencies + */ +import { textContentAreas } from './text-content-areas'; + +/** + * Tabs through the text content areas of a block and asserts the expected values + * + * @param {string} content The expected value of the block's contenteditable elements + * @return {Promise} A promise that's resolved when the browser has finished tabbing throught the contenteditable areas of a block, and asserting they have keyboard focus and the expected content. + */ + +export async function tabThroughTextContentAreas( content ) { + const blocks = await textContentAreas( { empty: false } ); + + for ( let i = 0; i < blocks.length; i++ ) { + if ( i > 0 ) { + await page.keyboard.press( 'Tab' ); + } + const isFocusedTextContentArea = await page.evaluate( () => document.activeElement.contentEditable ); + const textContentAreaContent = await page.evaluate( () => document.activeElement.innerHTML ); + + // The value of 'contentEditable' should be the string 'true' + expect( isFocusedTextContentArea ).toBe( 'true' ); + expect( textContentAreaContent ).toContain( content ); + } +} diff --git a/packages/e2e-test-utils/src/text-content-areas.js b/packages/e2e-test-utils/src/text-content-areas.js new file mode 100644 index 0000000000000..0be764ca3226f --- /dev/null +++ b/packages/e2e-test-utils/src/text-content-areas.js @@ -0,0 +1,17 @@ +/** + * Returns a list of a block's contenteditable elements. + * + * @param {boolean} empty When true, restricts the list to contenteditable elements with no value + * @return {Promise} A promise that resolves when it's returned an array of classes representing the contenteditable areas of a block with keyboard focus. + */ +export async function textContentAreas( { empty = false } ) { + const selectors = [ + '.wp-block.is-selected [contenteditable]', + '.wp-block.is-typing [contenteditable]', + ].map( ( selector ) => { + return empty ? selector + '[data-is-placeholder-visible="true"]' : selector; + }, empty ).join( ',' ); + + return await page.$$eval( selectors, + ( elements ) => elements.map( ( elem ) => elem.className ) ); +} diff --git a/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js index a8ebe96c34fcd..01fb5f19a0cd3 100644 --- a/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/keyboard-navigable-blocks.test.js @@ -3,121 +3,102 @@ */ import { createNewPost, - insertBlock, - pressKeyWithModifier, + insertAndPopulateBlock, + navigateToContentEditorTop, + tabThroughTextBlock, + tabThroughFileBlock, } from '@wordpress/e2e-test-utils'; -const navigateToContentEditorTop = async () => { - // Use 'Ctrl+`' to return to the top of the editor - await pressKeyWithModifier( 'ctrl', '`' ); - await pressKeyWithModifier( 'ctrl', '`' ); - - // Tab into the Title block - await page.keyboard.press( 'Tab' ); -}; - -const tabThroughParagraphBlock = async ( paragraphText ) => { - // Tab to the next paragraph block - await page.keyboard.press( 'Tab' ); - - // The block external focusable wrapper has focus - const isFocusedParagraphBlock = await page.evaluate( - () => document.activeElement.dataset.type - ); - await expect( isFocusedParagraphBlock ).toEqual( 'core/paragraph' ); - - // Tab causes 'add block' button to receive focus - await page.keyboard.press( 'Tab' ); - const isFocusedParagraphInserterToggle = await page.evaluate( () => - document.activeElement.classList.contains( 'block-editor-inserter__toggle' ) - ); - await expect( isFocusedParagraphInserterToggle ).toBe( true ); - - await tabThroughBlockMoverControl(); - await tabThroughBlockToolbar(); - - // Tab causes the paragraph content to receive focus - await page.keyboard.press( 'Tab' ); - const isFocusedParagraphContent = await page.evaluate( - () => document.activeElement.contentEditable - ); - // The value of 'contentEditable' should be the string 'true' - await expect( isFocusedParagraphContent ).toBe( 'true' ); - - const paragraphEditableContent = await page.evaluate( - () => document.activeElement.innerHTML - ); - await expect( paragraphEditableContent ).toBe( paragraphText ); -}; - -const tabThroughBlockMoverControl = async () => { - // Tab to focus on the 'move up' control - await page.keyboard.press( 'Tab' ); - const isFocusedMoveUpControl = await page.evaluate( () => - document.activeElement.classList.contains( 'block-editor-block-mover__control' ) - ); - await expect( isFocusedMoveUpControl ).toBe( true ); - - // Tab to focus on the 'move down' control - await page.keyboard.press( 'Tab' ); - const isFocusedMoveDownControl = await page.evaluate( () => - document.activeElement.classList.contains( 'block-editor-block-mover__control' ) - ); - await expect( isFocusedMoveDownControl ).toBe( true ); -}; - -const tabThroughBlockToolbar = async () => { - // Tab to focus on the 'block switcher' control - await page.keyboard.press( 'Tab' ); - const isFocusedBlockSwitcherControl = await page.evaluate( () => - document.activeElement.classList.contains( - 'block-editor-block-switcher__toggle' - ) - ); - await expect( isFocusedBlockSwitcherControl ).toBe( true ); - - // Tab to focus on the 'Change text alignment' dropdown control - await page.keyboard.press( 'Tab' ); - const isFocusedChangeTextAlignmentControl = await page.evaluate( () => - document.activeElement.classList.contains( 'components-dropdown-menu__toggle' ) - ); - await expect( isFocusedChangeTextAlignmentControl ).toBe( true ); - - // Tab to focus on the 'More formatting' dropdown toggle - await page.keyboard.press( 'Tab' ); - const isFocusedBlockSettingsDropdown = await page.evaluate( () => - document.activeElement.classList.contains( 'components-dropdown-menu__toggle' ) - ); - await expect( isFocusedBlockSettingsDropdown ).toBe( true ); -}; - describe( 'Order of block keyboard navigation', () => { beforeEach( async () => { await createNewPost(); } ); + it( 'permits tabbing through heading blocks in the expected order', async () => { + await insertAndPopulateBlock( 'Heading', 'Heading Block Content' ); + + await navigateToContentEditorTop(); + + await tabThroughTextBlock( 'core/heading', 'Heading Block Content' ); + } ); + + it( 'permits tabbing through quote blocks in the expected order', async () => { + await insertAndPopulateBlock( 'Quote', 'Quote Block Content' ); + // in order to see the block mover controls, we must insert more than one of these blocks: + await insertAndPopulateBlock( 'Quote', 'Quote Block 2 Content' ); + + await navigateToContentEditorTop(); + + await tabThroughTextBlock( 'core/quote', 'Quote Block Content' ); + } ); + + it( 'permits tabbing through list blocks in the expected order', async () => { + await insertAndPopulateBlock( 'List', 'List Block Content' ); + // in order to see the block mover controls, we must insert more than one of these blocks: + await insertAndPopulateBlock( 'List', 'List Block 2 Content' ); + + await navigateToContentEditorTop(); + + await tabThroughTextBlock( 'core/list', 'List Block Content' ); + } ); + it( 'permits tabbing through paragraph blocks in the expected order', async () => { - const paragraphBlocks = [ 'Paragraph 0', 'Paragraph 1', 'Paragraph 2' ]; + await insertAndPopulateBlock( 'Paragraph', 'Paragraph Block Content' ); + // in order to see the block mover controls, we must insert more than one of these blocks: + await insertAndPopulateBlock( 'Paragraph', 'Paragraph Block 2 Content' ); + + await navigateToContentEditorTop(); - // create 3 paragraphs blocks with some content - for ( const paragraphBlock of paragraphBlocks ) { - await insertBlock( 'Paragraph' ); - await page.keyboard.type( paragraphBlock ); - await page.keyboard.press( 'Enter' ); - } + await tabThroughTextBlock( 'core/paragraph', 'Paragraph Block Content' ); + } ); + + it( 'permits tabbing through image blocks in the expected order', async () => { + await insertAndPopulateBlock( 'Image', 'Image Block Content' ); + // in order to see the block mover controls, we must insert more than one of these blocks: + await insertAndPopulateBlock( 'Image', 'Image Block 2 Content' ); + + await navigateToContentEditorTop(); + + await tabThroughFileBlock( 'core/image' ); + } ); + + it( 'permits tabbing through gallery blocks in the expected order', async () => { + await insertAndPopulateBlock( 'Gallery', 'Gallery Block Content' ); + // in order to see the block mover controls, we must insert more than one of these blocks: + await insertAndPopulateBlock( 'Gallery', 'Gallery Block 2 Content' ); + + await navigateToContentEditorTop(); + + await tabThroughFileBlock( 'core/gallery' ); + } ); + + it( 'permits tabbing through audio blocks in the expected order', async () => { + await insertAndPopulateBlock( 'Audio', 'Audio Block Content' ); + // in order to see the block mover controls, we must insert more than one of these blocks: + await insertAndPopulateBlock( 'Audio', 'Audio Block 2 Content' ); await navigateToContentEditorTop(); - for ( const paragraphBlock of paragraphBlocks ) { - await tabThroughParagraphBlock( paragraphBlock ); - } + await tabThroughFileBlock( 'core/audio' ); + } ); + + it( 'permits tabbing through cover blocks in the expected order', async () => { + await insertAndPopulateBlock( 'Cover', 'Cover Block 2 Content' ); + // in order to see the block mover controls, we must insert more than one of these blocks: + await insertAndPopulateBlock( 'Cover', 'Cover Block Content' ); + + await navigateToContentEditorTop(); + + await tabThroughFileBlock( 'core/cover' ); + } ); + + it( 'permits tabbing through file blocks in the expected order', async () => { + await insertAndPopulateBlock( 'File', 'File Block Content' ); + // in order to see the block mover controls, we must insert more than one of these blocks: + await insertAndPopulateBlock( 'File', 'File Block 2 Content' ); - // Repeat the same steps to ensure that there is no change introduced in how the focus is handled. - // This prevents the previous regression explained in: https://github.com/WordPress/gutenberg/issues/11773. await navigateToContentEditorTop(); - for ( const paragraphBlock of paragraphBlocks ) { - await tabThroughParagraphBlock( paragraphBlock ); - } + await tabThroughFileBlock( 'core/file' ); } ); } ); diff --git a/packages/e2e-tests/specs/keyboard-navigable-content-editor.test.js b/packages/e2e-tests/specs/keyboard-navigable-content-editor.test.js new file mode 100644 index 0000000000000..d1003634417b0 --- /dev/null +++ b/packages/e2e-tests/specs/keyboard-navigable-content-editor.test.js @@ -0,0 +1,42 @@ +/** + * WordPress dependencies + */ +import { + createNewPost, + insertAndPopulateBlock, + navigateToContentEditorTop, + tabThroughTextBlock, +} from '@wordpress/e2e-test-utils'; + +/** + * External dependencies + */ + +describe( 'Order of the block editor keyboard navigation', () => { + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'permits tabbing through paragraph blocks and the top of the content in the expected order', async () => { + const paragraphBlocks = [ 'Paragraph 0', 'Paragraph 1', 'Paragraph 2' ]; + + // create 3 paragraphs blocks with some content + for ( const paragraphBlock of paragraphBlocks ) { + await insertAndPopulateBlock( 'Paragraph', paragraphBlock ); + } + + await navigateToContentEditorTop(); + + for ( const paragraphBlock of paragraphBlocks ) { + await tabThroughTextBlock( 'core/paragraph', paragraphBlock ); + } + + // Repeat the same steps to ensure that there is no change introduced in how the focus is handled. + // This prevents the previous regression explained in: https://github.com/WordPress/gutenberg/issues/11773. + await navigateToContentEditorTop(); + + for ( const paragraphBlock of paragraphBlocks ) { + await tabThroughTextBlock( 'core/paragraph', paragraphBlock ); + } + } ); +} );