diff --git a/packages/block-editor/src/components/block-list/block-wrapper.js b/packages/block-editor/src/components/block-list/block-wrapper.js new file mode 100644 index 00000000000000..bcd5d4c38929f5 --- /dev/null +++ b/packages/block-editor/src/components/block-list/block-wrapper.js @@ -0,0 +1,238 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { first, last, omit } from 'lodash'; +import { animated } from 'react-spring/web.cjs'; + +/** + * WordPress dependencies + */ +import { + useRef, + useEffect, + useLayoutEffect, + useContext, + forwardRef, +} from '@wordpress/element'; +import { focus, isTextField, placeCaretAtHorizontalEdge } from '@wordpress/dom'; +import { BACKSPACE, DELETE, ENTER } from '@wordpress/keycodes'; +import { __, sprintf } from '@wordpress/i18n'; +import { useSelect, useDispatch } from '@wordpress/data'; + +/** + * Internal dependencies + */ +import { isInsideRootBlock } from '../../utils/dom'; +import useMovingAnimation from './moving-animation'; +import { Context, BlockNodes } from './root-container'; +import { BlockContext } from './block'; + +const BlockComponent = forwardRef( + ( { children, tagName = 'div', __unstableIsHtml, ...props }, wrapper ) => { + const onSelectionStart = useContext( Context ); + const [ , setBlockNodes ] = useContext( BlockNodes ); + const { + clientId, + rootClientId, + isSelected, + isFirstMultiSelected, + isLastMultiSelected, + isMultiSelecting, + isNavigationMode, + isPartOfMultiSelection, + enableAnimation, + index, + className, + isLocked, + name, + mode, + blockTitle, + } = useContext( BlockContext ); + const { initialPosition } = useSelect( + ( select ) => { + if ( ! isSelected ) { + return {}; + } + + return { + initialPosition: select( + 'core/block-editor' + ).getSelectedBlocksInitialCaretPosition(), + }; + }, + [ isSelected ] + ); + const { removeBlock, insertDefaultBlock } = useDispatch( + 'core/block-editor' + ); + const fallbackRef = useRef(); + + wrapper = wrapper || fallbackRef; + + // Provide the selected node, or the first and last nodes of a multi- + // selection, so it can be used to position the contextual block toolbar. + // We only provide what is necessary, and remove the nodes again when they + // are no longer selected. + useLayoutEffect( () => { + if ( isSelected || isFirstMultiSelected || isLastMultiSelected ) { + const node = wrapper.current; + setBlockNodes( ( nodes ) => ( { + ...nodes, + [ clientId ]: node, + } ) ); + return () => { + setBlockNodes( ( nodes ) => omit( nodes, clientId ) ); + }; + } + }, [ isSelected, isFirstMultiSelected, isLastMultiSelected ] ); + + // translators: %s: Type of block (i.e. Text, Image etc) + const blockLabel = sprintf( __( 'Block: %s' ), blockTitle ); + + // Handing the focus of the block on creation and update + + /** + * When a block becomes selected, transition focus to an inner tabbable. + * + * @param {boolean} ignoreInnerBlocks Should not focus inner blocks. + */ + const focusTabbable = ( ignoreInnerBlocks ) => { + // Focus is captured by the wrapper node, so while focus transition + // should only consider tabbables within editable display, since it + // may be the wrapper itself or a side control which triggered the + // focus event, don't unnecessary transition to an inner tabbable. + if ( wrapper.current.contains( document.activeElement ) ) { + return; + } + + // Find all tabbables within node. + const textInputs = focus.tabbable + .find( wrapper.current ) + .filter( isTextField ) + // Exclude inner blocks + .filter( + ( node ) => + ! ignoreInnerBlocks || + isInsideRootBlock( wrapper.current, node ) + ); + + // If reversed (e.g. merge via backspace), use the last in the set of + // tabbables. + const isReverse = -1 === initialPosition; + const target = + ( isReverse ? last : first )( textInputs ) || wrapper.current; + + placeCaretAtHorizontalEdge( target, isReverse ); + }; + + // Focus the selected block's wrapper or inner input on mount and update + const isMounting = useRef( true ); + + useEffect( () => { + if ( ! isMultiSelecting && ! isNavigationMode && isSelected ) { + focusTabbable( ! isMounting.current ); + } + + isMounting.current = false; + }, [ isSelected, isMultiSelecting, isNavigationMode ] ); + + // Block Reordering animation + const animationStyle = useMovingAnimation( + wrapper, + isSelected || isPartOfMultiSelection, + isSelected || isFirstMultiSelected, + enableAnimation, + index + ); + + /** + * Interprets keydown event intent to remove or insert after block if key + * event occurs on wrapper node. This can occur when the block has no text + * fields of its own, particularly after initial insertion, to allow for + * easy deletion and continuous writing flow to add additional content. + * + * @param {KeyboardEvent} event Keydown event. + */ + const onKeyDown = ( event ) => { + const { keyCode, target } = event; + + if ( props.onKeyDown ) { + props.onKeyDown( event ); + } + + if ( + keyCode !== ENTER && + keyCode !== BACKSPACE && + keyCode !== DELETE + ) { + return; + } + + if ( target !== wrapper.current || isTextField( target ) ) { + return; + } + + event.preventDefault(); + + if ( keyCode === ENTER ) { + insertDefaultBlock( {}, rootClientId, index + 1 ); + } else { + removeBlock( clientId ); + } + }; + + const onMouseLeave = ( { which, buttons } ) => { + // The primary button must be pressed to initiate selection. Fall back + // to `which` if the standard `buttons` property is falsy. There are + // cases where Firefox might always set `buttons` to `0`. + // See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons + // See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which + if ( ( buttons || which ) === 1 ) { + onSelectionStart( clientId ); + } + }; + + const htmlSuffix = + mode === 'html' && ! __unstableIsHtml ? '-visual' : ''; + const blockElementId = `block-${ clientId }${ htmlSuffix }`; + const Animated = animated[ tagName ]; + + return ( + + { children } + + ); + } +); + +const elements = [ 'p', 'div' ]; + +const ExtendedBlockComponent = elements.reduce( ( acc, element ) => { + acc[ element ] = forwardRef( ( props, ref ) => { + return ; + } ); + return acc; +}, BlockComponent ); + +export const Block = ExtendedBlockComponent; diff --git a/packages/block-editor/src/components/block-list/block.js b/packages/block-editor/src/components/block-list/block.js index fd273612ebca8f..a18d1b4e68b025 100644 --- a/packages/block-editor/src/components/block-list/block.js +++ b/packages/block-editor/src/components/block-list/block.js @@ -2,30 +2,20 @@ * External dependencies */ import classnames from 'classnames'; -import { first, last, omit } from 'lodash'; -import { animated } from 'react-spring/web.cjs'; /** * WordPress dependencies */ -import { - useRef, - useEffect, - useLayoutEffect, - useState, - useContext, -} from '@wordpress/element'; -import { focus, isTextField, placeCaretAtHorizontalEdge } from '@wordpress/dom'; -import { BACKSPACE, DELETE, ENTER } from '@wordpress/keycodes'; +import { useState, createContext, useMemo } from '@wordpress/element'; import { getBlockType, getSaveElement, isReusableBlock, isUnmodifiedDefaultBlock, getUnregisteredTypeHandlerName, + hasBlockSupport, } from '@wordpress/blocks'; import { withFilters } from '@wordpress/components'; -import { __, sprintf } from '@wordpress/i18n'; import { withDispatch, withSelect, useSelect } from '@wordpress/data'; import { withViewportMatch } from '@wordpress/viewport'; import { compose, pure, ifCondition } from '@wordpress/compose'; @@ -38,15 +28,16 @@ import BlockInvalidWarning from './block-invalid-warning'; import BlockCrashWarning from './block-crash-warning'; import BlockCrashBoundary from './block-crash-boundary'; import BlockHtml from './block-html'; -import { isInsideRootBlock } from '../../utils/dom'; -import useMovingAnimation from './moving-animation'; -import { Context, BlockNodes } from './root-container'; +import { Block } from './block-wrapper'; + +export const BlockContext = createContext(); function BlockListBlock( { mode, isFocusMode, isLocked, clientId, + rootClientId, isSelected, isMultiSelected, isPartOfMultiSelection, @@ -59,166 +50,43 @@ function BlockListBlock( { name, isValid, attributes, - initialPosition, wrapperProps, setAttributes, onReplace, onInsertBlocksAfter, onMerge, - onRemove, - onInsertDefaultBlockAfter, toggleSelection, - animateOnChange, + index, enableAnimation, isNavigationMode, isMultiSelecting, hasSelectedUI = true, } ) { - const onSelectionStart = useContext( Context ); - const [ , setBlockNodes ] = useContext( BlockNodes ); - // In addition to withSelect, we should favor using useSelect in this component going forward - // to avoid leaking new props to the public API (editor.BlockListBlock filter) + // In addition to withSelect, we should favor using useSelect in this + // component going forward to avoid leaking new props to the public API + // (editor.BlockListBlock filter) const { isDraggingBlocks } = useSelect( ( select ) => { return { isDraggingBlocks: select( 'core/block-editor' ).isDraggingBlocks(), }; }, [] ); - // Reference of the wrapper - const wrapper = useRef( null ); - - // Provide the selected node, or the first and last nodes of a multi- - // selection, so it can be used to position the contextual block toolbar. - // We only provide what is necessary, and remove the nodes again when they - // are no longer selected. - useLayoutEffect( () => { - if ( isSelected || isFirstMultiSelected || isLastMultiSelected ) { - const node = wrapper.current; - setBlockNodes( ( nodes ) => ( { ...nodes, [ clientId ]: node } ) ); - return () => { - setBlockNodes( ( nodes ) => omit( nodes, clientId ) ); - }; - } - }, [ isSelected, isFirstMultiSelected, isLastMultiSelected ] ); - // Handling the error state const [ hasError, setErrorState ] = useState( false ); const onBlockError = () => setErrorState( true ); const blockType = getBlockType( name ); - // translators: %s: Type of block (i.e. Text, Image etc) - const blockLabel = sprintf( __( 'Block: %s' ), blockType.title ); - - // Handing the focus of the block on creation and update - - /** - * When a block becomes selected, transition focus to an inner tabbable. - * - * @param {boolean} ignoreInnerBlocks Should not focus inner blocks. - */ - const focusTabbable = ( ignoreInnerBlocks ) => { - // Focus is captured by the wrapper node, so while focus transition - // should only consider tabbables within editable display, since it - // may be the wrapper itself or a side control which triggered the - // focus event, don't unnecessary transition to an inner tabbable. - if ( wrapper.current.contains( document.activeElement ) ) { - return; - } - - // Find all tabbables within node. - const textInputs = focus.tabbable - .find( wrapper.current ) - .filter( isTextField ) - // Exclude inner blocks - .filter( - ( node ) => - ! ignoreInnerBlocks || - isInsideRootBlock( wrapper.current, node ) - ); - - // If reversed (e.g. merge via backspace), use the last in the set of - // tabbables. - const isReverse = -1 === initialPosition; - const target = ( isReverse ? last : first )( textInputs ); - - if ( ! target ) { - wrapper.current.focus(); - return; - } - - placeCaretAtHorizontalEdge( target, isReverse ); - }; - - // Focus the selected block's wrapper or inner input on mount and update - const isMounting = useRef( true ); - - useEffect( () => { - if ( ! isMultiSelecting && ! isNavigationMode && isSelected ) { - focusTabbable( ! isMounting.current ); - } - - isMounting.current = false; - }, [ isSelected, isMultiSelecting, isNavigationMode ] ); - - // Block Reordering animation - const animationStyle = useMovingAnimation( - wrapper, - isSelected || isPartOfMultiSelection, - isSelected || isFirstMultiSelected, - enableAnimation, - animateOnChange + const lightBlockWrapper = hasBlockSupport( + blockType, + 'lightBlockWrapper', + false ); - - // Other event handlers - - /** - * Interprets keydown event intent to remove or insert after block if key - * event occurs on wrapper node. This can occur when the block has no text - * fields of its own, particularly after initial insertion, to allow for - * easy deletion and continuous writing flow to add additional content. - * - * @param {KeyboardEvent} event Keydown event. - */ - const onKeyDown = ( event ) => { - const { keyCode, target } = event; - - switch ( keyCode ) { - case ENTER: - if ( target === wrapper.current ) { - // Insert default block after current block if enter and event - // not already handled by descendant. - onInsertDefaultBlockAfter(); - event.preventDefault(); - } - break; - case BACKSPACE: - case DELETE: - if ( target === wrapper.current ) { - // Remove block on backspace. - onRemove( clientId ); - event.preventDefault(); - } - break; - } - }; - - const onMouseLeave = ( { which, buttons } ) => { - // The primary button must be pressed to initiate selection. Fall back - // to `which` if the standard `buttons` property is falsy. There are - // cases where Firefox might always set `buttons` to `0`. - // See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons - // See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/which - if ( ( buttons || which ) === 1 ) { - onSelectionStart( clientId ); - } - }; - const isUnregisteredBlock = name === getUnregisteredTypeHandlerName(); const isDragging = isDraggingBlocks && ( isSelected || isPartOfMultiSelection ); // Determine whether the block has props to apply to the wrapper. - if ( blockType.getEditWrapperProps ) { + if ( ! lightBlockWrapper && blockType.getEditWrapperProps ) { wrapperProps = { ...wrapperProps, ...blockType.getEditWrapperProps( attributes ), @@ -228,7 +96,8 @@ function BlockListBlock( { const isAligned = wrapperProps && wrapperProps[ 'data-align' ]; // The wp-block className is important for editor styles. - // Generate the wrapper class names handling the different states of the block. + // Generate the wrapper class names handling the different states of the + // block. const wrapperClassName = classnames( 'wp-block block-editor-block-list__block', { @@ -248,8 +117,6 @@ function BlockListBlock( { className ); - const blockElementId = `block-${ clientId }`; - // We wrap the BlockEdit component in a div that hides it when editing in // HTML mode. This allows us to render all of the ancillary pieces // (InspectorControls, etc.) which are inside `BlockEdit` but not @@ -280,47 +147,59 @@ function BlockListBlock( { blockEdit =
{ blockEdit }
; } + const value = { + clientId, + rootClientId, + isSelected, + isFirstMultiSelected, + isLastMultiSelected, + isMultiSelecting, + isNavigationMode, + isPartOfMultiSelection, + enableAnimation, + index, + className: wrapperClassName, + isLocked, + name, + mode, + blockTitle: blockType.title, + }; + const memoizedValue = useMemo( () => value, Object.values( value ) ); + return ( - + - { isValid && blockEdit } - { isValid && mode === 'html' && ( - + { isValid && lightBlockWrapper && ( + <> + { blockEdit } + { mode === 'html' && ( + + + + ) } + + ) } + { isValid && ! lightBlockWrapper && ( + + { blockEdit } + { mode === 'html' && ( + + ) } + + ) } + { ! isValid && ( + + +
{ getSaveElement( blockType, attributes ) }
+
) } - { ! isValid && [ - , -
- { getSaveElement( blockType, attributes ) } -
, - ] }
- { !! hasError && } -
+ { !! hasError && ( + + + + ) } + ); } @@ -335,7 +214,6 @@ const applyWithSelect = withSelect( isTyping, getBlockMode, isSelectionEnabled, - getSelectedBlocksInitialCaretPosition, getSettings, hasSelectedInnerBlock, getTemplateLock, @@ -355,8 +233,9 @@ const applyWithSelect = withSelect( ); // The fallback to `{}` is a temporary fix. - // This function should never be called when a block is not present in the state. - // It happens now because the order in withSelect rendering is not correct. + // This function should never be called when a block is not present in + // the state. It happens now because the order in withSelect rendering + // is not correct. const { name, attributes, isValid } = block || {}; return { @@ -369,21 +248,20 @@ const applyWithSelect = withSelect( getLastMultiSelectedBlockClientId() === clientId, // We only care about this prop when the block is selected - // Thus to avoid unnecessary rerenders we avoid updating the prop if the block is not selected. + // Thus to avoid unnecessary rerenders we avoid updating the prop if + // the block is not selected. isTypingWithinBlock: ( isSelected || isAncestorOfSelectedBlock ) && isTyping(), mode: getBlockMode( clientId ), isSelectionEnabled: isSelectionEnabled(), - initialPosition: isSelected - ? getSelectedBlocksInitialCaretPosition() - : null, isLocked: !! templateLock, isFocusMode: focusMode && isLargeViewport, isNavigationMode: isNavigationMode(), isRTL, - // Users of the editor.BlockListBlock filter used to be able to access the block prop + // Users of the editor.BlockListBlock filter used to be able to + // access the block prop. // Ideally these blocks would rely on the clientId prop only. // This is kept for backward compatibility reasons. block, @@ -401,8 +279,6 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { const { updateBlockAttributes, insertBlocks, - insertDefaultBlock, - removeBlock, mergeBlocks, replaceBlocks, toggleSelection, @@ -418,21 +294,12 @@ const applyWithDispatch = withDispatch( ( dispatch, ownProps, { select } ) => { const { rootClientId } = ownProps; insertBlocks( blocks, index, rootClientId ); }, - onInsertDefaultBlockAfter() { - const { clientId, rootClientId } = ownProps; - const { getBlockIndex } = select( 'core/block-editor' ); - const index = getBlockIndex( clientId, rootClientId ); - insertDefaultBlock( {}, rootClientId, index + 1 ); - }, onInsertBlocksAfter( blocks ) { const { clientId, rootClientId } = ownProps; const { getBlockIndex } = select( 'core/block-editor' ); const index = getBlockIndex( clientId, rootClientId ); insertBlocks( blocks, index + 1, rootClientId ); }, - onRemove( clientId ) { - removeBlock( clientId ); - }, onMerge( forward ) { const { clientId } = ownProps; const { getPreviousBlockClientId, getNextBlockClientId } = select( @@ -474,7 +341,8 @@ export default compose( applyWithSelect, applyWithDispatch, // block is sometimes not mounted at the right time, causing it be undefined - // see issue for more info https://github.com/WordPress/gutenberg/issues/17013 + // see issue for more info + // https://github.com/WordPress/gutenberg/issues/17013 ifCondition( ( { block } ) => !! block ), withFilters( 'editor.BlockListBlock' ) )( BlockListBlock ); diff --git a/packages/block-editor/src/components/block-list/index.js b/packages/block-editor/src/components/block-list/index.js index 8bf433d083fe45..02fc6d6806fb04 100644 --- a/packages/block-editor/src/components/block-list/index.js +++ b/packages/block-editor/src/components/block-list/index.js @@ -107,7 +107,7 @@ function BlockList( { // This prop is explicitely computed and passed down // to avoid being impacted by the async mode // otherwise there might be a small delay to trigger the animation. - animateOnChange={ index } + index={ index } enableAnimation={ enableAnimation } hasSelectedUI={ __experimentalUIParts.hasSelectedUI diff --git a/packages/block-editor/src/components/block-list/insertion-point.js b/packages/block-editor/src/components/block-list/insertion-point.js index 1ce81ac1199a24..7aa46530ad3878 100644 --- a/packages/block-editor/src/components/block-list/insertion-point.js +++ b/packages/block-editor/src/components/block-list/insertion-point.js @@ -127,12 +127,11 @@ export default function InsertionPoint( { const isReverse = clientY < targetRect.top + targetRect.height / 2; const blockNode = getBlockDOMNode( inserterClientId ); const container = isReverse ? containerRef.current : blockNode; - const closest = getClosestTabbable( blockNode, true, container ); + const closest = + getClosestTabbable( blockNode, true, container ) || blockNode; const rect = new window.DOMRect( clientX, clientY, 0, 16 ); - if ( closest ) { - placeCaretAtVerticalEdge( closest, isReverse, rect, false ); - } + placeCaretAtVerticalEdge( closest, isReverse, rect, false ); } // Hide the inserter above the selected block and during multi-selection. diff --git a/packages/block-editor/src/components/index.js b/packages/block-editor/src/components/index.js index 3bf65a4b0e4cb4..2a3ed6f0402ace 100644 --- a/packages/block-editor/src/components/index.js +++ b/packages/block-editor/src/components/index.js @@ -60,6 +60,7 @@ export { default as __experimentalBlockSettingsMenuFirstItem } from './block-set export { default as __experimentalInserterMenuExtension } from './inserter-menu-extension'; export { default as BlockInspector } from './block-inspector'; export { default as BlockList } from './block-list'; +export { Block as __experimentalBlock } from './block-list/block-wrapper'; export { default as BlockMover } from './block-mover'; export { default as BlockPreview } from './block-preview'; export { default as BlockSelectionClearer } from './block-selection-clearer'; diff --git a/packages/block-editor/src/components/observe-typing/index.js b/packages/block-editor/src/components/observe-typing/index.js index ae7cddfeb80aec..a4f948c13c3575 100644 --- a/packages/block-editor/src/components/observe-typing/index.js +++ b/packages/block-editor/src/components/observe-typing/index.js @@ -17,6 +17,7 @@ import { ENTER, BACKSPACE, ESCAPE, + TAB, } from '@wordpress/keycodes'; import { withSafeTimeout } from '@wordpress/compose'; @@ -111,7 +112,10 @@ function ObserveTyping( { children, setTimeout: setSafeTimeout } ) { * @param {KeyboardEvent} event Keypress or keydown event to interpret. */ function stopTypingOnEscapeKey( event ) { - if ( isTyping && event.keyCode === ESCAPE ) { + if ( + isTyping && + ( event.keyCode === ESCAPE || event.keyCode === TAB ) + ) { stopTyping(); } } diff --git a/packages/block-editor/src/utils/dom.js b/packages/block-editor/src/utils/dom.js index 3fb25425678ab8..a12ffc276b16eb 100644 --- a/packages/block-editor/src/utils/dom.js +++ b/packages/block-editor/src/utils/dom.js @@ -91,7 +91,7 @@ export function getBlockClientId( node ) { node = node.parentElement; } - const blockNode = node.closest( '.wp-block' ); + const blockNode = node.closest( '.block-editor-block-list__block' ); if ( ! blockNode ) { return; diff --git a/packages/block-library/src/paragraph/edit.js b/packages/block-library/src/paragraph/edit.js index b195980c746e6c..456c2e02820357 100644 --- a/packages/block-library/src/paragraph/edit.js +++ b/packages/block-library/src/paragraph/edit.js @@ -16,6 +16,7 @@ import { RichText, withFontSizes, __experimentalUseColors, + __experimentalBlock as Block, } from '@wordpress/block-editor'; import { createBlock } from '@wordpress/blocks'; import { compose } from '@wordpress/compose'; @@ -151,7 +152,7 @@ function ParagraphBlock( { { await insertBlock( blockTitle ); expect( await getInnerHTML( - `[data-type="${ blockName }"] [data-type="core/paragraph"] p` + `[data-type="${ blockName }"] [data-type="core/paragraph"]` ) ).toEqual( blockTitle ); } ); diff --git a/packages/e2e-tests/specs/editor/plugins/block-variations.js b/packages/e2e-tests/specs/editor/plugins/block-variations.js index e75414add5eb43..2c8eb7596397c1 100644 --- a/packages/e2e-tests/specs/editor/plugins/block-variations.js +++ b/packages/e2e-tests/specs/editor/plugins/block-variations.js @@ -74,10 +74,7 @@ describe( 'Block variations', () => { ); expect( successMessageBlock ).toBeDefined(); expect( - await successMessageBlock.$eval( - 'p.has-vivid-green-cyan-background-color', - ( node ) => node.innerText - ) + await successMessageBlock.evaluate( ( node ) => node.innerText ) ).toBe( 'This is a success message!' ); } ); test( 'Pick the additional variation in the inserted Columns block', async () => { diff --git a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js b/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js index 5026baf4fbe616..bd3dcbbdad25e1 100644 --- a/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js +++ b/packages/e2e-tests/specs/editor/plugins/cpt-locking.test.js @@ -76,7 +76,7 @@ describe( 'cpt locking', () => { it( 'should not error when deleting the cotents of a paragraph', async () => { await page.click( - '.block-editor-block-list__block[data-type="core/paragraph"] p' + '.block-editor-block-list__block[data-type="core/paragraph"]' ); const textToType = 'Paragraph'; await page.keyboard.type( 'Paragraph' ); diff --git a/packages/e2e-tests/specs/editor/plugins/hooks-api.test.js b/packages/e2e-tests/specs/editor/plugins/hooks-api.test.js index 07f018c528cf24..e015de70257c7a 100644 --- a/packages/e2e-tests/specs/editor/plugins/hooks-api.test.js +++ b/packages/e2e-tests/specs/editor/plugins/hooks-api.test.js @@ -34,7 +34,7 @@ describe( 'Using Hooks API', () => { await clickBlockAppender(); await page.keyboard.type( 'First paragraph' ); const paragraphContent = await page.$eval( - 'div[data-type="core/paragraph"] p', + 'p[data-type="core/paragraph"]', ( element ) => element.textContent ); expect( paragraphContent ).toEqual( 'First paragraph' ); diff --git a/packages/e2e-tests/specs/editor/various/block-mover.test.js b/packages/e2e-tests/specs/editor/various/block-mover.test.js index 09f6ba41a2a82a..3b936f212aba0b 100644 --- a/packages/e2e-tests/specs/editor/various/block-mover.test.js +++ b/packages/e2e-tests/specs/editor/various/block-mover.test.js @@ -18,6 +18,10 @@ describe( 'block mover', () => { // Select a block so the block mover is rendered. await page.focus( '.block-editor-block-list__block' ); + // Move the mouse to show the block toolbar + await page.mouse.move( 0, 0 ); + await page.mouse.move( 10, 10 ); + const blockMover = await page.$$( '.block-editor-block-mover' ); // There should be a block mover. expect( blockMover ).toHaveLength( 1 ); @@ -31,6 +35,10 @@ describe( 'block mover', () => { // Select a block so the block mover has the possibility of being rendered. await page.focus( '.block-editor-block-list__block' ); + // Move the mouse to show the block toolbar + await page.mouse.move( 0, 0 ); + await page.mouse.move( 10, 10 ); + // Ensure no block mover exists when only one block exists on the page. const blockMover = await page.$$( '.block-editor-block-mover' ); expect( blockMover ).toHaveLength( 0 ); diff --git a/packages/e2e-tests/specs/editor/various/editor-modes.test.js b/packages/e2e-tests/specs/editor/various/editor-modes.test.js index f6f6a9ae10997b..15d767cd6cc2c9 100644 --- a/packages/e2e-tests/specs/editor/various/editor-modes.test.js +++ b/packages/e2e-tests/specs/editor/various/editor-modes.test.js @@ -18,7 +18,7 @@ describe( 'Editing modes (visual/HTML)', () => { it( 'should switch between visual and HTML modes', async () => { // This block should be in "visual" mode by default. let visualBlock = await page.$$( - '.block-editor-block-list__layout .block-editor-block-list__block .rich-text' + '.block-editor-block-list__layout .block-editor-block-list__block.rich-text' ); expect( visualBlock ).toHaveLength( 1 ); @@ -52,7 +52,7 @@ describe( 'Editing modes (visual/HTML)', () => { // This block should be in "visual" mode by default. visualBlock = await page.$$( - '.block-editor-block-list__layout .block-editor-block-list__block .rich-text' + '.block-editor-block-list__layout .block-editor-block-list__block.rich-text' ); expect( visualBlock ).toHaveLength( 1 ); } ); 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 815528f8c0e7ba..7226e0750e5726 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 @@ -31,9 +31,6 @@ const tabThroughParagraphBlock = async ( paragraphText ) => { await tabThroughBlockMoverControl(); await tabThroughBlockToolbar(); - await page.keyboard.press( 'Tab' ); - await expect( await getActiveLabel() ).toBe( 'Block: Paragraph' ); - await page.keyboard.press( 'Tab' ); await expect( await getActiveLabel() ).toBe( 'Paragraph block' ); await expect( diff --git a/packages/e2e-tests/specs/editor/various/preview.test.js b/packages/e2e-tests/specs/editor/various/preview.test.js index 3a495a1189dfdc..e973c1b44f87d6 100644 --- a/packages/e2e-tests/specs/editor/various/preview.test.js +++ b/packages/e2e-tests/specs/editor/various/preview.test.js @@ -14,7 +14,6 @@ import { publishPost, saveDraft, clickOnMoreMenuItem, - pressKeyWithModifier, } from '@wordpress/e2e-test-utils'; /** @typedef {import('puppeteer').Page} Page */ @@ -278,13 +277,13 @@ describe( 'Preview with Custom Fields enabled', () => { // Return to editor and modify the title and content. await editorPage.bringToFront(); await editorPage.click( '.editor-post-title__input' ); - await pressKeyWithModifier( 'primary', 'a' ); - await editorPage.keyboard.press( 'Delete' ); - await editorPage.keyboard.type( 'title 2' ); + await editorPage.keyboard.press( 'End' ); + await editorPage.keyboard.press( 'Backspace' ); + await editorPage.keyboard.type( '2' ); await editorPage.keyboard.press( 'Tab' ); - await pressKeyWithModifier( 'primary', 'a' ); - await editorPage.keyboard.press( 'Delete' ); - await editorPage.keyboard.type( 'content 2' ); + await editorPage.keyboard.press( 'End' ); + await editorPage.keyboard.press( 'Backspace' ); + await editorPage.keyboard.type( '2' ); // Open the preview page. await waitForPreviewNavigation( previewPage ); diff --git a/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js b/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js index b453bbc0aeeeed..794e8018691f61 100644 --- a/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js +++ b/packages/e2e-tests/specs/editor/various/reusable-blocks.test.js @@ -181,7 +181,7 @@ describe( 'Reusable blocks', () => { // Check that its content is up to date const text = await page.$eval( - '.block-editor-block-list__block[data-type="core/paragraph"] p', + '.block-editor-block-list__block[data-type="core/paragraph"]', ( element ) => element.innerText ); expect( text ).toMatch( 'Oh! Hello there!' ); diff --git a/packages/e2e-tests/specs/editor/various/undo.test.js b/packages/e2e-tests/specs/editor/various/undo.test.js index ed70af98bdaa2a..c37cba5b95f750 100644 --- a/packages/e2e-tests/specs/editor/various/undo.test.js +++ b/packages/e2e-tests/specs/editor/various/undo.test.js @@ -22,9 +22,16 @@ const getSelection = async () => { return {}; } - const editables = Array.from( - selectedBlock.querySelectorAll( '[contenteditable]' ) - ); + let editables; + + if ( selectedBlock.getAttribute( 'contenteditable' ) ) { + editables = [ selectedBlock ]; + } else { + editables = Array.from( + selectedBlock.querySelectorAll( '[contenteditable]' ) + ); + } + const editableIndex = editables.indexOf( document.activeElement ); const selection = window.getSelection(); diff --git a/packages/e2e-tests/specs/experiments/navigation.test.js b/packages/e2e-tests/specs/experiments/navigation.test.js index aabda57d9d4087..fc11a057968fa2 100644 --- a/packages/e2e-tests/specs/experiments/navigation.test.js +++ b/packages/e2e-tests/specs/experiments/navigation.test.js @@ -76,6 +76,7 @@ async function mockCreatePageResponse( title, slug ) { /** * Interacts with the LinkControl to perform a search and select a returned suggestion + * * @param {string} url What will be typed in the search input * @param {string} label What the resulting label will be in the creating Navigation Link Block after the block is created. * @param {string} type What kind of suggestion should be clicked, ie. 'url', 'create', or 'entity'