From dfe3871d95e67c282963215ea31af7d749e6a86a Mon Sep 17 00:00:00 2001 From: Gerardo Date: Tue, 4 Jul 2023 18:40:48 +0200 Subject: [PATCH 01/11] Remove BlockMobileToolbar --- .../block-mobile-toolbar/index.native.js | 127 ------------------ .../block-mobile-toolbar/style.native.scss | 16 --- 2 files changed, 143 deletions(-) delete mode 100644 packages/block-editor/src/components/block-mobile-toolbar/index.native.js delete mode 100644 packages/block-editor/src/components/block-mobile-toolbar/style.native.scss diff --git a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js b/packages/block-editor/src/components/block-mobile-toolbar/index.native.js deleted file mode 100644 index d4dd69551715f..0000000000000 --- a/packages/block-editor/src/components/block-mobile-toolbar/index.native.js +++ /dev/null @@ -1,127 +0,0 @@ -/** - * External dependencies - */ -import { View } from 'react-native'; - -/** - * WordPress dependencies - */ -import { withDispatch, withSelect } from '@wordpress/data'; -import { compose } from '@wordpress/compose'; -import { useState, useEffect } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import styles from './style.scss'; -import BlockMover from '../block-mover'; -import BlockDraggable from '../block-draggable'; -import BlockActionsMenu from './block-actions-menu'; -import { BlockSettingsButton } from '../block-settings'; -import { store as blockEditorStore } from '../../store'; - -// Defined breakpoints are used to get a point when -// `settings` and `mover` controls should be wrapped into `BlockActionsMenu` -// and accessed through `BottomSheet`(Android)/`ActionSheet`(iOS). -const BREAKPOINTS = { - wrapSettings: 65, - wrapMover: 150, -}; -const BlockMobileToolbar = ( { - clientId, - onDelete, - isStackedHorizontally, - blockWidth, - anchorNodeRef, - isFullWidth, - draggingClientId, -} ) => { - const [ fillsLength, setFillsLength ] = useState( null ); - const [ appenderWidth, setAppenderWidth ] = useState( 0 ); - const spacingValue = styles.toolbar.marginLeft * 2; - - function onLayout( { nativeEvent } ) { - const { layout } = nativeEvent; - const layoutWidth = Math.floor( layout.width ); - if ( layoutWidth !== appenderWidth ) { - setAppenderWidth( nativeEvent.layout.width ); - } - } - - const wrapBlockSettings = - blockWidth < BREAKPOINTS.wrapSettings || - appenderWidth - spacingValue < BREAKPOINTS.wrapSettings; - const wrapBlockMover = - blockWidth <= BREAKPOINTS.wrapMover || - appenderWidth - spacingValue <= BREAKPOINTS.wrapMover; - - const BlockSettingsButtonFill = ( fillProps ) => { - useEffect( - () => fillProps.onChangeFillsLength( fillProps.fillsLength ), - [ fillProps.fillsLength ] - ); - return fillProps.children ?? null; - }; - - return ( - - { ! wrapBlockMover && ( - - ) } - - - { () => } - - - - { /* Render only one settings icon even if we have more than one fill - need for hooks with controls. */ } - { ( fills = [ null ] ) => ( - // The purpose of BlockSettingsButtonFill component is only to provide a way - // to pass data upstream from the slot rendering. - - { wrapBlockSettings ? null : fills[ 0 ] } - - ) } - - - - - ); -}; - -export default compose( - withSelect( ( select, { clientId } ) => { - const { getBlockIndex } = select( blockEditorStore ); - - return { - order: getBlockIndex( clientId ), - }; - } ), - withDispatch( ( dispatch, { clientId, rootClientId, onDelete } ) => { - const { removeBlock } = dispatch( blockEditorStore ); - return { - onDelete: - onDelete || ( () => removeBlock( clientId, rootClientId ) ), - }; - } ) -)( BlockMobileToolbar ); diff --git a/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss b/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss deleted file mode 100644 index 9a95177048660..0000000000000 --- a/packages/block-editor/src/components/block-mobile-toolbar/style.native.scss +++ /dev/null @@ -1,16 +0,0 @@ -.toolbar { - flex-direction: row; - height: $mobile-block-toolbar-height; - align-items: flex-start; - margin-left: $block-selected-margin; - margin-right: $block-selected-margin; -} - -.toolbarFullWidth { - padding-left: $grid-unit-15; - padding-right: $grid-unit-15; -} - -.spacer { - flex-grow: 1; -} From 3a442fb1782ab09434ea511f0bc708d0d50a7aab Mon Sep 17 00:00:00 2001 From: Gerardo Date: Tue, 4 Jul 2023 18:41:40 +0200 Subject: [PATCH 02/11] Refactor BlockSettingsButton --- .../block-settings/button.native.js | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/packages/block-editor/src/components/block-settings/button.native.js b/packages/block-editor/src/components/block-settings/button.native.js index 9fa1a03749429..9db4c279a05d9 100644 --- a/packages/block-editor/src/components/block-settings/button.native.js +++ b/packages/block-editor/src/components/block-settings/button.native.js @@ -1,35 +1,20 @@ /** * WordPress dependencies */ -import { createSlotFill, ToolbarButton } from '@wordpress/components'; +import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { withDispatch } from '@wordpress/data'; import { cog } from '@wordpress/icons'; -const { Fill, Slot } = createSlotFill( 'SettingsToolbarButton' ); +const SettingsButton = ( { onOpenBlockSettings } ) => { + return ( + + + + ); +}; -const SettingsButton = ( { openGeneralSidebar } ) => ( - -); - -const SettingsButtonFill = ( props ) => ( - - - -); - -const SettingsToolbarButton = withDispatch( ( dispatch ) => { - const { openGeneralSidebar } = dispatch( 'core/edit-post' ); - - return { - openGeneralSidebar: () => openGeneralSidebar( 'edit-post/block' ), - }; -} )( SettingsButtonFill ); - -SettingsToolbarButton.Slot = Slot; - -export default SettingsToolbarButton; +export default SettingsButton; From 7d6341ffc402aa263974072c597cfbda21604bcc Mon Sep 17 00:00:00 2001 From: Gerardo Date: Tue, 4 Jul 2023 18:43:09 +0200 Subject: [PATCH 03/11] Update BlockToolbar with new order for buttons --- .../block-toolbar-menu.native.js | 483 ++++++++++++++++++ .../components/block-toolbar/index.native.js | 87 +++- .../block-toolbar-menu.native.js.snap | 125 +++++ .../test/block-toolbar-menu.native.js | 405 +++++++++++++++ .../header/header-toolbar/index.native.js | 14 +- 5 files changed, 1107 insertions(+), 7 deletions(-) create mode 100644 packages/block-editor/src/components/block-toolbar/block-toolbar-menu.native.js create mode 100644 packages/block-editor/src/components/block-toolbar/test/__snapshots__/block-toolbar-menu.native.js.snap create mode 100644 packages/block-editor/src/components/block-toolbar/test/block-toolbar-menu.native.js diff --git a/packages/block-editor/src/components/block-toolbar/block-toolbar-menu.native.js b/packages/block-editor/src/components/block-toolbar/block-toolbar-menu.native.js new file mode 100644 index 0000000000000..bff035173b290 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar/block-toolbar-menu.native.js @@ -0,0 +1,483 @@ +/** + * External dependencies + */ +import { Platform, findNodeHandle } from 'react-native'; + +/** + * WordPress dependencies + */ +import { + getClipboard, + setClipboard, + ToolbarGroup, + ToolbarButton, + Picker, +} from '@wordpress/components'; +import { + getBlockType, + getDefaultBlockName, + hasBlockSupport, + serialize, + rawHandler, + createBlock, + isUnmodifiedDefaultBlock, + isReusableBlock, +} from '@wordpress/blocks'; +import { __, sprintf } from '@wordpress/i18n'; +import { withDispatch, withSelect, useSelect } from '@wordpress/data'; +import { withInstanceId, compose } from '@wordpress/compose'; +import { moreHorizontalMobile } from '@wordpress/icons'; +import { useRef, useState } from '@wordpress/element'; +import { store as noticesStore } from '@wordpress/notices'; +import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; +// Disable Reason: Needs to be refactored. +// eslint-disable-next-line no-restricted-imports +import { store as coreStore } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import { getMoversSetup } from '../block-mover/mover-description'; +import { store as blockEditorStore } from '../../store'; +import BlockTransformationsMenu from '../block-switcher/block-transformations-menu'; +import { + useConvertToGroupButtons, + useConvertToGroupButtonProps, +} from '../convert-to-group-buttons'; + +const BlockActionsMenu = ( { + // Select. + blockTitle, + canInsertBlockType, + getBlocksByClientId, + isEmptyDefaultBlock, + isLocked, + canDuplicate, + isFirst, + isLast, + isReusableBlockType, + reusableBlock, + rootClientId, + selectedBlockClientId, + selectedBlockPossibleTransformations, + canRemove, + // Dispatch. + createSuccessNotice, + convertToRegularBlocks, + duplicateBlock, + onMoveDown, + onMoveUp, + openGeneralSidebar, + pasteBlock, + removeBlocks, + // Passed in. + anchorNodeRef, + isStackedHorizontally, + onDelete, + wrapBlockMover, + wrapBlockSettings, +} ) => { + const [ clipboard, setCurrentClipboard ] = useState( getClipboard() ); + const blockActionsMenuPickerRef = useRef(); + const blockTransformationMenuPickerRef = useRef(); + const moversOptions = { keys: [ 'icon', 'actionTitle' ] }; + const clipboardBlock = clipboard && rawHandler( { HTML: clipboard } )[ 0 ]; + const isPasteEnabled = + clipboardBlock && + canInsertBlockType( clipboardBlock.name, rootClientId ); + + const innerBlockCount = useSelect( + ( select ) => + select( blockEditorStore ).getBlockCount( selectedBlockClientId ), + [ selectedBlockClientId ] + ); + + const { + actionTitle: { + backward: backwardButtonTitle, + forward: forwardButtonTitle, + }, + } = getMoversSetup( isStackedHorizontally, moversOptions ); + + // Check if selected block is Groupable and/or Ungroupable. + const convertToGroupButtonProps = useConvertToGroupButtonProps( [ + selectedBlockClientId, + ] ); + const { isGroupable, isUngroupable } = convertToGroupButtonProps; + const showConvertToGroupButton = + ( isGroupable || isUngroupable ) && canRemove; + const convertToGroupButtons = useConvertToGroupButtons( { + ...convertToGroupButtonProps, + } ); + + const allOptions = { + settings: { + id: 'settingsOption', + label: __( 'Block settings' ), + value: 'settingsOption', + onSelect: openGeneralSidebar, + }, + backwardButton: { + id: 'backwardButtonOption', + label: backwardButtonTitle, + value: 'backwardButtonOption', + disabled: isFirst, + onSelect: onMoveUp, + }, + forwardButton: { + id: 'forwardButtonOption', + label: forwardButtonTitle, + value: 'forwardButtonOption', + disabled: isLast, + onSelect: onMoveDown, + }, + delete: { + id: 'deleteOption', + label: __( 'Remove block' ), + value: 'deleteOption', + separated: true, + disabled: isEmptyDefaultBlock, + onSelect: () => { + onDelete(); + createSuccessNotice( + // translators: displayed right after the block is removed. + __( 'Block removed' ) + ); + }, + }, + transformButton: { + id: 'transformButtonOption', + label: __( 'Transform block…' ), + value: 'transformButtonOption', + onSelect: () => { + if ( blockTransformationMenuPickerRef.current ) { + blockTransformationMenuPickerRef.current.presentPicker(); + } + }, + }, + copyButton: { + id: 'copyButtonOption', + label: __( 'Copy' ), + value: 'copyButtonOption', + onSelect: () => { + const serializedBlock = serialize( + getBlocksByClientId( selectedBlockClientId ) + ); + setCurrentClipboard( serializedBlock ); + setClipboard( serializedBlock ); + createSuccessNotice( + // translators: displayed right after the block is copied. + __( 'Block copied' ) + ); + }, + }, + cutButton: { + id: 'cutButtonOption', + label: __( 'Cut block' ), + value: 'cutButtonOption', + onSelect: () => { + setClipboard( + serialize( getBlocksByClientId( selectedBlockClientId ) ) + ); + removeBlocks( selectedBlockClientId ); + createSuccessNotice( + // translators: displayed right after the block is cut. + __( 'Block cut' ) + ); + }, + }, + pasteButton: { + id: 'pasteButtonOption', + label: __( 'Paste block after' ), + value: 'pasteButtonOption', + onSelect: () => { + onPasteBlock(); + createSuccessNotice( + // translators: displayed right after the block is pasted. + __( 'Block pasted' ) + ); + }, + }, + duplicateButton: { + id: 'duplicateButtonOption', + label: __( 'Duplicate block' ), + value: 'duplicateButtonOption', + onSelect: () => { + duplicateBlock(); + createSuccessNotice( + // translators: displayed right after the block is duplicated. + __( 'Block duplicated' ) + ); + }, + }, + convertToRegularBlocks: { + id: 'convertToRegularBlocksOption', + label: + innerBlockCount > 1 + ? __( 'Detach patterns' ) + : __( 'Detach pattern' ), + value: 'convertToRegularBlocksOption', + onSelect: () => { + /* translators: %s: name of the synced block */ + const successNotice = __( '%s detached' ); + createSuccessNotice( + sprintf( + successNotice, + reusableBlock?.title?.raw || blockTitle + ) + ); + convertToRegularBlocks(); + }, + }, + }; + + const options = [ + wrapBlockMover && allOptions.backwardButton, + wrapBlockMover && allOptions.forwardButton, + wrapBlockSettings && allOptions.settings, + ! isLocked && + selectedBlockPossibleTransformations.length && + allOptions.transformButton, + canDuplicate && allOptions.copyButton, + canDuplicate && allOptions.cutButton, + canDuplicate && isPasteEnabled && allOptions.pasteButton, + canDuplicate && allOptions.duplicateButton, + showConvertToGroupButton && isGroupable && convertToGroupButtons.group, + showConvertToGroupButton && + isUngroupable && + convertToGroupButtons.ungroup, + isReusableBlockType && + innerBlockCount > 0 && + allOptions.convertToRegularBlocks, + ! isLocked && allOptions.delete, + ].filter( Boolean ); + + // End early if there are no options to show. + if ( ! options.length ) { + return ( + + + + ); + } + + function onPasteBlock() { + if ( ! clipboard ) { + return; + } + + pasteBlock( rawHandler( { HTML: clipboard } )[ 0 ] ); + } + + function onPickerSelect( value ) { + const selectedItem = options.find( ( item ) => item.value === value ); + selectedItem.onSelect(); + } + + function onPickerPresent() { + if ( blockActionsMenuPickerRef.current ) { + blockActionsMenuPickerRef.current.presentPicker(); + } + } + + const disabledButtonIndices = options + .map( ( option, index ) => option.disabled && index + 1 ) + .filter( Boolean ); + + const accessibilityHint = + Platform.OS === 'ios' + ? __( 'Double tap to open Action Sheet with available options' ) + : __( 'Double tap to open Bottom Sheet with available options' ); + + const getAnchor = () => + anchorNodeRef ? findNodeHandle( anchorNodeRef ) : undefined; + + return ( + + + + + + ); +}; + +const EMPTY_BLOCK_LIST = []; + +export default compose( + withSelect( ( select, { clientId } ) => { + const { + getBlockIndex, + getBlockRootClientId, + getBlockOrder, + getBlockName, + getBlockTransformItems, + getBlock, + getBlocksByClientId, + getSelectedBlockClientIds, + canInsertBlockType, + getTemplateLock, + canRemoveBlock, + } = select( blockEditorStore ); + const block = getBlock( clientId ); + const blockName = getBlockName( clientId ); + const blockType = getBlockType( blockName ); + const blockTitle = blockType?.title; + const rootClientId = getBlockRootClientId( clientId ); + const blockOrder = getBlockOrder( rootClientId ); + + const currentBlockIndex = getBlockIndex( clientId ); + const isFirst = currentBlockIndex === 0; + const isLast = currentBlockIndex === blockOrder.length - 1; + + const innerBlocks = getBlocksByClientId( clientId ); + + const canDuplicate = innerBlocks.every( ( innerBlock ) => { + return ( + !! innerBlock && + hasBlockSupport( innerBlock.name, 'multiple', true ) && + canInsertBlockType( innerBlock.name, rootClientId ) + ); + } ); + + const isDefaultBlock = blockName === getDefaultBlockName(); + const isEmptyContent = block?.attributes.content === ''; + const isExactlyOneBlock = blockOrder.length === 1; + const isEmptyDefaultBlock = + isExactlyOneBlock && isDefaultBlock && isEmptyContent; + const isLocked = !! getTemplateLock( rootClientId ); + + const selectedBlockClientId = getSelectedBlockClientIds()[ 0 ]; + const selectedBlock = selectedBlockClientId + ? getBlocksByClientId( selectedBlockClientId )[ 0 ] + : undefined; + const selectedBlockPossibleTransformations = selectedBlock + ? getBlockTransformItems( selectedBlock, rootClientId ) + : EMPTY_BLOCK_LIST; + const canRemove = canRemoveBlock( selectedBlockClientId ); + + const isReusableBlockType = block ? isReusableBlock( block ) : false; + const reusableBlock = isReusableBlockType + ? select( coreStore ).getEntityRecord( + 'postType', + 'wp_block', + block?.attributes.ref + ) + : undefined; + + return { + blockTitle, + canInsertBlockType, + currentIndex: currentBlockIndex, + getBlocksByClientId, + isEmptyDefaultBlock, + isLocked, + canDuplicate, + isFirst, + isLast, + isReusableBlockType, + reusableBlock, + rootClientId, + selectedBlockClientId, + selectedBlockPossibleTransformations, + canRemove, + }; + } ), + withDispatch( + ( + dispatch, + { clientId, rootClientId, currentIndex, selectedBlockClientId }, + { select } + ) => { + const { + moveBlocksDown, + moveBlocksUp, + duplicateBlocks, + removeBlocks, + insertBlock, + replaceBlock, + clearSelectedBlock, + } = dispatch( blockEditorStore ); + const { openGeneralSidebar } = dispatch( 'core/edit-post' ); + const { getBlockSelectionEnd, getBlock } = + select( blockEditorStore ); + const { createSuccessNotice } = dispatch( noticesStore ); + + const { __experimentalConvertBlockToStatic: convertBlockToStatic } = + dispatch( reusableBlocksStore ); + + return { + createSuccessNotice, + convertToRegularBlocks() { + clearSelectedBlock(); + // Convert action is executed at the end of the current JavaScript execution block + // to prevent issues related to undo/redo actions. + setImmediate( () => + convertBlockToStatic( selectedBlockClientId ) + ); + }, + duplicateBlock() { + return duplicateBlocks( [ clientId ] ); + }, + onMoveDown: ( ...args ) => + moveBlocksDown( [ clientId ], rootClientId, ...args ), + onMoveUp: ( ...args ) => + moveBlocksUp( [ clientId ], rootClientId, ...args ), + openGeneralSidebar: () => + openGeneralSidebar( 'edit-post/block' ), + pasteBlock: ( clipboardBlock ) => { + const canReplaceBlock = isUnmodifiedDefaultBlock( + getBlock( getBlockSelectionEnd() ) + ); + + if ( ! canReplaceBlock ) { + const insertedBlock = createBlock( + clipboardBlock.name, + clipboardBlock.attributes, + clipboardBlock.innerBlocks + ); + + insertBlock( + insertedBlock, + currentIndex + 1, + rootClientId + ); + } else { + replaceBlock( clientId, clipboardBlock ); + } + }, + removeBlocks, + }; + } + ), + withInstanceId +)( BlockActionsMenu ); diff --git a/packages/block-editor/src/components/block-toolbar/index.native.js b/packages/block-editor/src/components/block-toolbar/index.native.js index a1726441031b1..32233fa54a1c1 100644 --- a/packages/block-editor/src/components/block-toolbar/index.native.js +++ b/packages/block-editor/src/components/block-toolbar/index.native.js @@ -1,23 +1,60 @@ /** * WordPress dependencies */ -import { useSelect } from '@wordpress/data'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { useCallback } from '@wordpress/element'; /** * Internal dependencies */ +import BlockActionsMenu from './block-toolbar-menu'; import BlockControls from '../block-controls'; +import BlockMover from '../block-mover'; import UngroupButton from '../ungroup-button'; +import { BlockSettingsButton } from '../block-settings'; import { store as blockEditorStore } from '../../store'; -export default function BlockToolbar() { - const { isSelected, isValidAndVisual } = useSelect( ( select ) => { - const { getBlockMode, getSelectedBlockClientIds, isBlockValid } = - select( blockEditorStore ); +const REMOVE_EMPY_PARENT_BLOCKS = [ + 'core/buttons', + 'core/columns', + 'core/social-links', +]; + +export default function BlockToolbar( { anchorNodeRef, onOpenBlockSettings } ) { + const { + rootClientId, + blockClientId, + isSelected, + isValidAndVisual, + isStackedHorizontally, + parentBlockName, + parentNumberOfInnerBlocks, + } = useSelect( ( select ) => { + const { + getBlockListSettings, + getBlockMode, + getBlockName, + getBlockCount, + getBlockRootClientId, + getSelectedBlockClientIds, + isBlockValid, + } = select( blockEditorStore ); const selectedBlockClientIds = getSelectedBlockClientIds(); + const selectedBlockClientId = selectedBlockClientIds[ 0 ]; + const blockRootClientId = getBlockRootClientId( selectedBlockClientId ); + const blockListSettings = getBlockListSettings( blockRootClientId ); + const orientation = blockListSettings?.orientation; + const isBlockStackedHorizontally = orientation === 'horizontal'; + const parentName = getBlockName( blockRootClientId ); + const numberOfInnerBlocks = getBlockCount( blockRootClientId ); return { + rootClientId: blockRootClientId, + blockClientId: selectedBlockClientId, isSelected: selectedBlockClientIds.length > 0, + isStackedHorizontally: isBlockStackedHorizontally, + parentBlockName: parentName, + parentNumberOfInnerBlocks: numberOfInnerBlocks, isValidAndVisual: selectedBlockClientIds.length === 1 ? isBlockValid( selectedBlockClientIds[ 0 ] ) && @@ -26,6 +63,28 @@ export default function BlockToolbar() { }; }, [] ); + const { removeBlock } = useDispatch( blockEditorStore ); + const onRemove = useCallback( () => { + // Temp: remove parent block for specific cases where they don't + // have inner blocks, ideally we should match the behavior as in + // the Web editor and show a placeholder instead of removing the parent. + if ( + REMOVE_EMPY_PARENT_BLOCKS.includes( parentBlockName ) && + parentNumberOfInnerBlocks === 1 + ) { + removeBlock( rootClientId ); + return; + } + + removeBlock( blockClientId ); + }, [ + blockClientId, + parentBlockName, + parentNumberOfInnerBlocks, + removeBlock, + rootClientId, + ] ); + if ( ! isSelected ) { return null; } @@ -34,11 +93,27 @@ export default function BlockToolbar() { <> { isValidAndVisual && ( <> - + + + + + + + ) } diff --git a/packages/block-editor/src/components/block-toolbar/test/__snapshots__/block-toolbar-menu.native.js.snap b/packages/block-editor/src/components/block-toolbar/test/__snapshots__/block-toolbar-menu.native.js.snap new file mode 100644 index 0000000000000..6e6c832d5bd52 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar/test/__snapshots__/block-toolbar-menu.native.js.snap @@ -0,0 +1,125 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Block Actions Menu block options copies and pastes a block 1`] = ` +" +

Hello!

+ + + +

+ + + + + + + +

+" +`; + +exports[`Block Actions Menu block options cuts and pastes a block 1`] = ` +" + + + + +

+ + + +

Hello!

+" +`; + +exports[`Block Actions Menu block options does not replace a non empty Paragraph block when pasting another block 1`] = ` +" +

Hello!

+ + + +

+ + + + + + + +

+" +`; + +exports[`Block Actions Menu block options duplicates a block 1`] = ` +" +

Hello!

+ + + + + + + + + + + +

+" +`; + +exports[`Block Actions Menu block options transforms a Paragraph block into a Pullquote block 1`] = ` +" +

Hello!

+ + + + + + + +

+" +`; + +exports[`Block Actions Menu moving blocks disables the Move Down button for the last block 1`] = ` +" +

Hello!

+ + + + + + + +

+" +`; + +exports[`Block Actions Menu moving blocks disables the Move Up button for the first block 1`] = ` +" +

Hello!

+ + + + + + + +

+" +`; + +exports[`Block Actions Menu moving blocks moves blocks up and down 1`] = ` +" +

+ + + +

Hello!

+ + + + +" +`; diff --git a/packages/block-editor/src/components/block-toolbar/test/block-toolbar-menu.native.js b/packages/block-editor/src/components/block-toolbar/test/block-toolbar-menu.native.js new file mode 100644 index 0000000000000..6e26b906d68c9 --- /dev/null +++ b/packages/block-editor/src/components/block-toolbar/test/block-toolbar-menu.native.js @@ -0,0 +1,405 @@ +/** + * External dependencies + */ +import { + addBlock, + fireEvent, + initializeEditor, + getBlock, + within, + getEditorHtml, + typeInRichText, + openBlockActionsMenu, +} from 'test/helpers'; + +/** + * WordPress dependencies + */ +import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; +import { registerCoreBlocks } from '@wordpress/block-library'; + +beforeAll( () => { + // Register all core blocks + registerCoreBlocks(); +} ); + +afterAll( () => { + // Clean up registered blocks + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); +} ); + +describe( 'Block Actions Menu', () => { + it( "renders the block name in the Picker's header", async () => { + const screen = await initializeEditor( { + initialHtml: ` +

+ `, + } ); + const { getByRole } = screen; + + // Get block + const paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + + // Open block actions menu + await openBlockActionsMenu( screen ); + + // Get Picker title + const pickerHeader = getByRole( 'header' ); + const headerTitle = within( pickerHeader ).getByText( + /Paragraph block options/ + ); + expect( headerTitle ).toBeVisible(); + } ); + + describe( 'moving blocks', () => { + it( 'moves blocks up and down', async () => { + const screen = await initializeEditor(); + const { getByLabelText } = screen; + + // Add Paragraph block + await addBlock( screen, 'Paragraph' ); + + // Get Paragraph block + const paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphField = + within( paragraphBlock ).getByPlaceholderText( + 'Start writing…' + ); + typeInRichText( paragraphField, 'Hello!' ); + + // Add Spacer block + await addBlock( screen, 'Spacer' ); + + // Add Heading block + await addBlock( screen, 'Heading' ); + + // Get Spacer block + const spacerBlock = await getBlock( screen, 'Spacer', { + rowIndex: 2, + } ); + fireEvent.press( spacerBlock ); + + // Tap on the Move Down button + fireEvent.press( getByLabelText( /Move block down/ ) ); + + // Get Heading block + const headingBlock = await getBlock( screen, 'Heading', { + rowIndex: 2, + } ); + fireEvent.press( headingBlock ); + + // Tap on the Move Up button + fireEvent.press( getByLabelText( /Move block up/ ) ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'disables the Move Up button for the first block', async () => { + const screen = await initializeEditor(); + const { getByLabelText } = screen; + + // Add Paragraph block + await addBlock( screen, 'Paragraph' ); + + // Get Paragraph block + let paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphField = + within( paragraphBlock ).getByPlaceholderText( + 'Start writing…' + ); + typeInRichText( paragraphField, 'Hello!' ); + + // Add Spacer block + await addBlock( screen, 'Spacer' ); + + // Add Heading block + await addBlock( screen, 'Heading' ); + + // Get Paragraph block + paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + + // Get the Move Up button + const upButton = getByLabelText( /Move block up/ ); + const isUpButtonDisabled = + upButton.props.accessibilityState?.disabled; + expect( isUpButtonDisabled ).toBe( true ); + + // Press the button to make sure the block doesn't move + fireEvent.press( upButton ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'disables the Move Down button for the last block', async () => { + const screen = await initializeEditor( { + screenWidth: 100, + } ); + const { getByLabelText } = screen; + + // Add Paragraph block + await addBlock( screen, 'Paragraph' ); + + // Get Paragraph block + const paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphField = + within( paragraphBlock ).getByPlaceholderText( + 'Start writing…' + ); + typeInRichText( paragraphField, 'Hello!' ); + + // Add Spacer block + await addBlock( screen, 'Spacer' ); + + // Add Heading block + await addBlock( screen, 'Heading' ); + + // Get Heading block + const headingBlock = await getBlock( screen, 'Heading', { + rowIndex: 3, + } ); + fireEvent.press( headingBlock ); + + // Get the Move Down button + const downButton = getByLabelText( /Move block down/ ); + const isDownButtonDisabled = + downButton.props.accessibilityState?.disabled; + expect( isDownButtonDisabled ).toBe( true ); + + // Press the button to make sure the block doesn't move + fireEvent.press( downButton ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + } ); + + describe( 'block options', () => { + it( 'copies and pastes a block', async () => { + const screen = await initializeEditor(); + const { getByLabelText } = screen; + + // Add Paragraph block + await addBlock( screen, 'Paragraph' ); + + // Get Paragraph block + let paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphField = + within( paragraphBlock ).getByPlaceholderText( + 'Start writing…' + ); + typeInRichText( paragraphField, 'Hello!' ); + + // Add Spacer block + await addBlock( screen, 'Spacer' ); + + // Add Heading block + await addBlock( screen, 'Heading' ); + + // Get Heading block + const headingBlock = await getBlock( screen, 'Heading', { + rowIndex: 3, + } ); + fireEvent.press( headingBlock ); + + // Open block actions menu + await openBlockActionsMenu( screen ); + + // Tap on the Copy button + fireEvent.press( getByLabelText( /Copy/ ) ); + + // Get Paragraph block + paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + + // Open block actions menu + await openBlockActionsMenu( screen ); + + // Tap on the Paste block after button + fireEvent.press( getByLabelText( /Paste block after/ ) ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'does not replace a non empty Paragraph block when pasting another block', async () => { + const screen = await initializeEditor(); + const { getByLabelText } = screen; + + // Add Paragraph block + await addBlock( screen, 'Paragraph' ); + + // Get Paragraph block + let paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphField = + within( paragraphBlock ).getByPlaceholderText( + 'Start writing…' + ); + typeInRichText( paragraphField, 'Hello!' ); + + // Add Spacer block + await addBlock( screen, 'Spacer' ); + + // Add Heading block + await addBlock( screen, 'Heading' ); + + // Get Heading block + const headingBlock = await getBlock( screen, 'Heading', { + rowIndex: 3, + } ); + fireEvent.press( headingBlock ); + + // Open block actions menu + await openBlockActionsMenu( screen ); + + // Tap on the Copy button + fireEvent.press( getByLabelText( /Copy/ ) ); + + // Get Paragraph block + paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + + // Open block actions menu + await openBlockActionsMenu( screen ); + + // Tap on the Past block after button + fireEvent.press( getByLabelText( /Paste block after/ ) ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'cuts and pastes a block', async () => { + const screen = await initializeEditor(); + const { getByLabelText } = screen; + + // Add Paragraph block + await addBlock( screen, 'Paragraph' ); + + // Get Paragraph block + let paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphField = + within( paragraphBlock ).getByPlaceholderText( + 'Start writing…' + ); + typeInRichText( paragraphField, 'Hello!' ); + + // Add Spacer block + await addBlock( screen, 'Spacer' ); + + // Add Heading block + await addBlock( screen, 'Heading' ); + + // Get Paragraph block + paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + + // Open block actions menu + await openBlockActionsMenu( screen ); + + // Tap on the Cut button + fireEvent.press( getByLabelText( /Cut block/ ) ); + + const headingBlock = await getBlock( screen, 'Heading', { + rowIndex: 2, + } ); + fireEvent.press( headingBlock ); + + // Open block actions menu + await openBlockActionsMenu( screen ); + + // Tap on the Cut button + fireEvent.press( getByLabelText( /Paste block after/ ) ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'duplicates a block', async () => { + const screen = await initializeEditor(); + const { getByLabelText } = screen; + + // Add Paragraph block + await addBlock( screen, 'Paragraph' ); + + // Get Paragraph block + const paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphField = + within( paragraphBlock ).getByPlaceholderText( + 'Start writing…' + ); + typeInRichText( paragraphField, 'Hello!' ); + + // Add Spacer block + await addBlock( screen, 'Spacer' ); + + // Add Heading block + await addBlock( screen, 'Heading' ); + + // Get Spacer block + const spacerBlock = await getBlock( screen, 'Spacer', { + rowIndex: 2, + } ); + fireEvent.press( spacerBlock ); + + // Open block actions menu + await openBlockActionsMenu( screen ); + + // Tap on the Duplicate button + fireEvent.press( getByLabelText( /Duplicate block/ ) ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + + it( 'transforms a Paragraph block into a Pullquote block', async () => { + const screen = await initializeEditor(); + const { getByLabelText, getByRole } = screen; + + // Add Paragraph block + await addBlock( screen, 'Paragraph' ); + + // Get Paragraph block + let paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + const paragraphField = + within( paragraphBlock ).getByPlaceholderText( + 'Start writing…' + ); + typeInRichText( paragraphField, 'Hello!' ); + + // Add Spacer block + await addBlock( screen, 'Spacer' ); + + // Add Heading block + await addBlock( screen, 'Heading' ); + + // Get Paragraph block + paragraphBlock = await getBlock( screen, 'Paragraph' ); + fireEvent.press( paragraphBlock ); + + // Open block actions menu + await openBlockActionsMenu( screen ); + + // Tap on the Transform block button + fireEvent.press( getByLabelText( /Transform block…/ ) ); + + // Get Picker title + const pickerHeader = getByRole( 'header' ); + const headerTitle = within( pickerHeader ).getByText( + /Transform Paragraph to/ + ); + expect( headerTitle ).toBeVisible(); + + // Tap on the Transform block button + fireEvent.press( getByLabelText( /Pullquote/ ) ); + + expect( getEditorHtml() ).toMatchSnapshot(); + } ); + } ); +} ); diff --git a/packages/edit-post/src/components/header/header-toolbar/index.native.js b/packages/edit-post/src/components/header/header-toolbar/index.native.js index b9b91c8d4f558..9552ed55edd7b 100644 --- a/packages/edit-post/src/components/header/header-toolbar/index.native.js +++ b/packages/edit-post/src/components/header/header-toolbar/index.native.js @@ -39,9 +39,11 @@ function HeaderToolbar( { showKeyboardHideButton, getStylesFromColorScheme, onHideKeyboard, + onOpenBlockSettings, isRTL, noContentSelected, } ) { + const anchorNodeRef = useRef(); const wasNoContentSelected = useRef( noContentSelected ); const [ isInserterOpen, setIsInserterOpen ] = useState( false ); @@ -55,6 +57,7 @@ function HeaderToolbar( { scrollViewRef.current.scrollTo( { x: 0 } ); } }; + const renderHistoryButtons = () => { const buttons = [ /* TODO: replace with EditorHistoryRedo and EditorHistoryUndo. */ @@ -104,6 +107,7 @@ function HeaderToolbar( { return ( + { renderHistoryButtons() } - + { showKeyboardHideButton && ( { const { clearSelectedBlock } = dispatch( blockEditorStore ); + const { openGeneralSidebar } = dispatch( editPostStore ); const { togglePostTitleSelection } = dispatch( editorStore ); return { @@ -191,6 +200,9 @@ export default compose( [ clearSelectedBlock(); togglePostTitleSelection( false ); }, + onOpenBlockSettings() { + openGeneralSidebar( 'edit-post/block' ); + }, }; } ), withViewportMatch( { isLargeViewport: 'medium' } ), From f7d33324aabdcd7581d6fe790b393d41adcb097f Mon Sep 17 00:00:00 2001 From: Gerardo Date: Tue, 4 Jul 2023 18:44:16 +0200 Subject: [PATCH 04/11] Clean up BlockMobileToolbar refactor --- .../block-actions-menu.native.js | 480 ------------------ .../block-actions-menu.native.js.snap | 125 ----- .../test/block-actions-menu.native.js | 439 ---------------- 3 files changed, 1044 deletions(-) delete mode 100644 packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js delete mode 100644 packages/block-editor/src/components/block-mobile-toolbar/test/__snapshots__/block-actions-menu.native.js.snap delete mode 100644 packages/block-editor/src/components/block-mobile-toolbar/test/block-actions-menu.native.js diff --git a/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js b/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js deleted file mode 100644 index 48be5cb8a9c00..0000000000000 --- a/packages/block-editor/src/components/block-mobile-toolbar/block-actions-menu.native.js +++ /dev/null @@ -1,480 +0,0 @@ -/** - * External dependencies - */ -import { Platform, findNodeHandle } from 'react-native'; - -/** - * WordPress dependencies - */ -import { - getClipboard, - setClipboard, - ToolbarButton, - Picker, -} from '@wordpress/components'; -import { - getBlockType, - getDefaultBlockName, - hasBlockSupport, - serialize, - rawHandler, - createBlock, - isUnmodifiedDefaultBlock, - isReusableBlock, -} from '@wordpress/blocks'; -import { __, sprintf } from '@wordpress/i18n'; -import { withDispatch, withSelect, useSelect } from '@wordpress/data'; -import { withInstanceId, compose } from '@wordpress/compose'; -import { moreHorizontalMobile } from '@wordpress/icons'; -import { useRef, useState } from '@wordpress/element'; -import { store as noticesStore } from '@wordpress/notices'; -import { store as reusableBlocksStore } from '@wordpress/reusable-blocks'; -// Disable Reason: Needs to be refactored. -// eslint-disable-next-line no-restricted-imports -import { store as coreStore } from '@wordpress/core-data'; - -/** - * Internal dependencies - */ -import { getMoversSetup } from '../block-mover/mover-description'; -import { store as blockEditorStore } from '../../store'; -import BlockTransformationsMenu from '../block-switcher/block-transformations-menu'; -import { - useConvertToGroupButtons, - useConvertToGroupButtonProps, -} from '../convert-to-group-buttons'; - -const BlockActionsMenu = ( { - // Select. - blockTitle, - canInsertBlockType, - getBlocksByClientId, - isEmptyDefaultBlock, - isLocked, - canDuplicate, - isFirst, - isLast, - isReusableBlockType, - reusableBlock, - rootClientId, - selectedBlockClientId, - selectedBlockPossibleTransformations, - canRemove, - // Dispatch. - createSuccessNotice, - convertToRegularBlocks, - duplicateBlock, - onMoveDown, - onMoveUp, - openGeneralSidebar, - pasteBlock, - removeBlocks, - // Passed in. - anchorNodeRef, - isStackedHorizontally, - onDelete, - wrapBlockMover, - wrapBlockSettings, -} ) => { - const [ clipboard, setCurrentClipboard ] = useState( getClipboard() ); - const blockActionsMenuPickerRef = useRef(); - const blockTransformationMenuPickerRef = useRef(); - const moversOptions = { keys: [ 'icon', 'actionTitle' ] }; - const clipboardBlock = clipboard && rawHandler( { HTML: clipboard } )[ 0 ]; - const isPasteEnabled = - clipboardBlock && - canInsertBlockType( clipboardBlock.name, rootClientId ); - - const innerBlockCount = useSelect( - ( select ) => - select( blockEditorStore ).getBlockCount( selectedBlockClientId ), - [ selectedBlockClientId ] - ); - - const { - actionTitle: { - backward: backwardButtonTitle, - forward: forwardButtonTitle, - }, - } = getMoversSetup( isStackedHorizontally, moversOptions ); - - // Check if selected block is Groupable and/or Ungroupable. - const convertToGroupButtonProps = useConvertToGroupButtonProps( [ - selectedBlockClientId, - ] ); - const { isGroupable, isUngroupable } = convertToGroupButtonProps; - const showConvertToGroupButton = - ( isGroupable || isUngroupable ) && canRemove; - const convertToGroupButtons = useConvertToGroupButtons( { - ...convertToGroupButtonProps, - } ); - - const allOptions = { - settings: { - id: 'settingsOption', - label: __( 'Block settings' ), - value: 'settingsOption', - onSelect: openGeneralSidebar, - }, - backwardButton: { - id: 'backwardButtonOption', - label: backwardButtonTitle, - value: 'backwardButtonOption', - disabled: isFirst, - onSelect: onMoveUp, - }, - forwardButton: { - id: 'forwardButtonOption', - label: forwardButtonTitle, - value: 'forwardButtonOption', - disabled: isLast, - onSelect: onMoveDown, - }, - delete: { - id: 'deleteOption', - label: __( 'Remove block' ), - value: 'deleteOption', - separated: true, - disabled: isEmptyDefaultBlock, - onSelect: () => { - onDelete(); - createSuccessNotice( - // translators: displayed right after the block is removed. - __( 'Block removed' ) - ); - }, - }, - transformButton: { - id: 'transformButtonOption', - label: __( 'Transform block…' ), - value: 'transformButtonOption', - onSelect: () => { - if ( blockTransformationMenuPickerRef.current ) { - blockTransformationMenuPickerRef.current.presentPicker(); - } - }, - }, - copyButton: { - id: 'copyButtonOption', - label: __( 'Copy' ), - value: 'copyButtonOption', - onSelect: () => { - const serializedBlock = serialize( - getBlocksByClientId( selectedBlockClientId ) - ); - setCurrentClipboard( serializedBlock ); - setClipboard( serializedBlock ); - createSuccessNotice( - // translators: displayed right after the block is copied. - __( 'Block copied' ) - ); - }, - }, - cutButton: { - id: 'cutButtonOption', - label: __( 'Cut block' ), - value: 'cutButtonOption', - onSelect: () => { - setClipboard( - serialize( getBlocksByClientId( selectedBlockClientId ) ) - ); - removeBlocks( selectedBlockClientId ); - createSuccessNotice( - // translators: displayed right after the block is cut. - __( 'Block cut' ) - ); - }, - }, - pasteButton: { - id: 'pasteButtonOption', - label: __( 'Paste block after' ), - value: 'pasteButtonOption', - onSelect: () => { - onPasteBlock(); - createSuccessNotice( - // translators: displayed right after the block is pasted. - __( 'Block pasted' ) - ); - }, - }, - duplicateButton: { - id: 'duplicateButtonOption', - label: __( 'Duplicate block' ), - value: 'duplicateButtonOption', - onSelect: () => { - duplicateBlock(); - createSuccessNotice( - // translators: displayed right after the block is duplicated. - __( 'Block duplicated' ) - ); - }, - }, - convertToRegularBlocks: { - id: 'convertToRegularBlocksOption', - label: - innerBlockCount > 1 - ? __( 'Detach patterns' ) - : __( 'Detach pattern' ), - value: 'convertToRegularBlocksOption', - onSelect: () => { - /* translators: %s: name of the synced block */ - const successNotice = __( '%s detached' ); - createSuccessNotice( - sprintf( - successNotice, - reusableBlock?.title?.raw || blockTitle - ) - ); - convertToRegularBlocks(); - }, - }, - }; - - const options = [ - wrapBlockMover && allOptions.backwardButton, - wrapBlockMover && allOptions.forwardButton, - wrapBlockSettings && allOptions.settings, - ! isLocked && - selectedBlockPossibleTransformations.length && - allOptions.transformButton, - canDuplicate && allOptions.copyButton, - canDuplicate && allOptions.cutButton, - canDuplicate && isPasteEnabled && allOptions.pasteButton, - canDuplicate && allOptions.duplicateButton, - showConvertToGroupButton && isGroupable && convertToGroupButtons.group, - showConvertToGroupButton && - isUngroupable && - convertToGroupButtons.ungroup, - isReusableBlockType && - innerBlockCount > 0 && - allOptions.convertToRegularBlocks, - ! isLocked && allOptions.delete, - ].filter( Boolean ); - - // End early if there are no options to show. - if ( ! options.length ) { - return ( - - ); - } - - function onPasteBlock() { - if ( ! clipboard ) { - return; - } - - pasteBlock( rawHandler( { HTML: clipboard } )[ 0 ] ); - } - - function onPickerSelect( value ) { - const selectedItem = options.find( ( item ) => item.value === value ); - selectedItem.onSelect(); - } - - function onPickerPresent() { - if ( blockActionsMenuPickerRef.current ) { - blockActionsMenuPickerRef.current.presentPicker(); - } - } - - const disabledButtonIndices = options - .map( ( option, index ) => option.disabled && index + 1 ) - .filter( Boolean ); - - const accessibilityHint = - Platform.OS === 'ios' - ? __( 'Double tap to open Action Sheet with available options' ) - : __( 'Double tap to open Bottom Sheet with available options' ); - - const getAnchor = () => - anchorNodeRef ? findNodeHandle( anchorNodeRef ) : undefined; - - return ( - <> - - - - - ); -}; - -const EMPTY_BLOCK_LIST = []; - -export default compose( - withSelect( ( select, { clientId } ) => { - const { - getBlockIndex, - getBlockRootClientId, - getBlockOrder, - getBlockName, - getBlockTransformItems, - getBlock, - getBlocksByClientId, - getSelectedBlockClientIds, - canInsertBlockType, - getTemplateLock, - canRemoveBlock, - } = select( blockEditorStore ); - const block = getBlock( clientId ); - const blockName = getBlockName( clientId ); - const blockType = getBlockType( blockName ); - const blockTitle = blockType?.title; - const rootClientId = getBlockRootClientId( clientId ); - const blockOrder = getBlockOrder( rootClientId ); - - const currentBlockIndex = getBlockIndex( clientId ); - const isFirst = currentBlockIndex === 0; - const isLast = currentBlockIndex === blockOrder.length - 1; - - const innerBlocks = getBlocksByClientId( clientId ); - - const canDuplicate = innerBlocks.every( ( innerBlock ) => { - return ( - !! innerBlock && - hasBlockSupport( innerBlock.name, 'multiple', true ) && - canInsertBlockType( innerBlock.name, rootClientId ) - ); - } ); - - const isDefaultBlock = blockName === getDefaultBlockName(); - const isEmptyContent = block?.attributes.content === ''; - const isExactlyOneBlock = blockOrder.length === 1; - const isEmptyDefaultBlock = - isExactlyOneBlock && isDefaultBlock && isEmptyContent; - const isLocked = !! getTemplateLock( rootClientId ); - - const selectedBlockClientId = getSelectedBlockClientIds()[ 0 ]; - const selectedBlock = selectedBlockClientId - ? getBlocksByClientId( selectedBlockClientId )[ 0 ] - : undefined; - const selectedBlockPossibleTransformations = selectedBlock - ? getBlockTransformItems( selectedBlock, rootClientId ) - : EMPTY_BLOCK_LIST; - const canRemove = canRemoveBlock( selectedBlockClientId ); - - const isReusableBlockType = block ? isReusableBlock( block ) : false; - const reusableBlock = isReusableBlockType - ? select( coreStore ).getEntityRecord( - 'postType', - 'wp_block', - block?.attributes.ref - ) - : undefined; - - return { - blockTitle, - canInsertBlockType, - currentIndex: currentBlockIndex, - getBlocksByClientId, - isEmptyDefaultBlock, - isLocked, - canDuplicate, - isFirst, - isLast, - isReusableBlockType, - reusableBlock, - rootClientId, - selectedBlockClientId, - selectedBlockPossibleTransformations, - canRemove, - }; - } ), - withDispatch( - ( - dispatch, - { clientId, rootClientId, currentIndex, selectedBlockClientId }, - { select } - ) => { - const { - moveBlocksDown, - moveBlocksUp, - duplicateBlocks, - removeBlocks, - insertBlock, - replaceBlock, - clearSelectedBlock, - } = dispatch( blockEditorStore ); - const { openGeneralSidebar } = dispatch( 'core/edit-post' ); - const { getBlockSelectionEnd, getBlock } = - select( blockEditorStore ); - const { createSuccessNotice } = dispatch( noticesStore ); - - const { __experimentalConvertBlockToStatic: convertBlockToStatic } = - dispatch( reusableBlocksStore ); - - return { - createSuccessNotice, - convertToRegularBlocks() { - clearSelectedBlock(); - // Convert action is executed at the end of the current JavaScript execution block - // to prevent issues related to undo/redo actions. - setImmediate( () => - convertBlockToStatic( selectedBlockClientId ) - ); - }, - duplicateBlock() { - return duplicateBlocks( [ clientId ] ); - }, - onMoveDown: ( ...args ) => - moveBlocksDown( [ clientId ], rootClientId, ...args ), - onMoveUp: ( ...args ) => - moveBlocksUp( [ clientId ], rootClientId, ...args ), - openGeneralSidebar: () => - openGeneralSidebar( 'edit-post/block' ), - pasteBlock: ( clipboardBlock ) => { - const canReplaceBlock = isUnmodifiedDefaultBlock( - getBlock( getBlockSelectionEnd() ) - ); - - if ( ! canReplaceBlock ) { - const insertedBlock = createBlock( - clipboardBlock.name, - clipboardBlock.attributes, - clipboardBlock.innerBlocks - ); - - insertBlock( - insertedBlock, - currentIndex + 1, - rootClientId - ); - } else { - replaceBlock( clientId, clipboardBlock ); - } - }, - removeBlocks, - }; - } - ), - withInstanceId -)( BlockActionsMenu ); diff --git a/packages/block-editor/src/components/block-mobile-toolbar/test/__snapshots__/block-actions-menu.native.js.snap b/packages/block-editor/src/components/block-mobile-toolbar/test/__snapshots__/block-actions-menu.native.js.snap deleted file mode 100644 index 6e6c832d5bd52..0000000000000 --- a/packages/block-editor/src/components/block-mobile-toolbar/test/__snapshots__/block-actions-menu.native.js.snap +++ /dev/null @@ -1,125 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Block Actions Menu block options copies and pastes a block 1`] = ` -" -

Hello!

- - - -

- - - - - - - -

-" -`; - -exports[`Block Actions Menu block options cuts and pastes a block 1`] = ` -" - - - - -

- - - -

Hello!

-" -`; - -exports[`Block Actions Menu block options does not replace a non empty Paragraph block when pasting another block 1`] = ` -" -

Hello!

- - - -

- - - - - - - -

-" -`; - -exports[`Block Actions Menu block options duplicates a block 1`] = ` -" -

Hello!

- - - - - - - - - - - -

-" -`; - -exports[`Block Actions Menu block options transforms a Paragraph block into a Pullquote block 1`] = ` -" -

Hello!

- - - - - - - -

-" -`; - -exports[`Block Actions Menu moving blocks disables the Move Down button for the last block 1`] = ` -" -

Hello!

- - - - - - - -

-" -`; - -exports[`Block Actions Menu moving blocks disables the Move Up button for the first block 1`] = ` -" -

Hello!

- - - - - - - -

-" -`; - -exports[`Block Actions Menu moving blocks moves blocks up and down 1`] = ` -" -

- - - -

Hello!

- - - - -" -`; diff --git a/packages/block-editor/src/components/block-mobile-toolbar/test/block-actions-menu.native.js b/packages/block-editor/src/components/block-mobile-toolbar/test/block-actions-menu.native.js deleted file mode 100644 index 940d95d274942..0000000000000 --- a/packages/block-editor/src/components/block-mobile-toolbar/test/block-actions-menu.native.js +++ /dev/null @@ -1,439 +0,0 @@ -/** - * External dependencies - */ -import { - addBlock, - fireEvent, - initializeEditor, - getBlock, - within, - getEditorHtml, - typeInRichText, -} from 'test/helpers'; - -/** - * WordPress dependencies - */ -import { getBlockTypes, unregisterBlockType } from '@wordpress/blocks'; -import { registerCoreBlocks } from '@wordpress/block-library'; - -beforeAll( () => { - // Register all core blocks - registerCoreBlocks(); -} ); - -afterAll( () => { - // Clean up registered blocks - getBlockTypes().forEach( ( block ) => { - unregisterBlockType( block.name ); - } ); -} ); - -describe( 'Block Actions Menu', () => { - it( "renders the block name in the Picker's header", async () => { - const screen = await initializeEditor( { - initialHtml: ` -

- `, - } ); - const { getByLabelText, getByRole } = screen; - - // Get block - const paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - - // Open block actions menu - const blockActionsButton = getByLabelText( /Open Block Actions Menu/ ); - fireEvent.press( blockActionsButton ); - - // Get Picker title - const pickerHeader = getByRole( 'header' ); - const headerTitle = within( pickerHeader ).getByText( - /Paragraph block options/ - ); - expect( headerTitle ).toBeVisible(); - } ); - - describe( 'moving blocks', () => { - it( 'moves blocks up and down', async () => { - const screen = await initializeEditor( { - screenWidth: 100, // To collapse the up/arrow buttons bellow blocks - } ); - const { getByLabelText, getByTestId } = screen; - - // Add Paragraph block - await addBlock( screen, 'Paragraph' ); - - // Get Paragraph block - const paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - const paragraphField = - within( paragraphBlock ).getByPlaceholderText( - 'Start writing…' - ); - typeInRichText( paragraphField, 'Hello!' ); - - // Add Spacer block - await addBlock( screen, 'Spacer' ); - - // Add Heading block - await addBlock( screen, 'Heading' ); - - // Get Spacer block - const spacerBlock = await getBlock( screen, 'Spacer', { - rowIndex: 2, - } ); - fireEvent.press( spacerBlock ); - - // Open block actions menu - fireEvent.press( getByLabelText( /Open Block Actions Menu/ ) ); - - // Get block actions modal - let blockActionsMenu = await getByTestId( 'block-actions-menu' ); - - // Tap on the Move Down button - fireEvent.press( - within( blockActionsMenu ).getByLabelText( 'Move block down' ) - ); - - // Get Heading block - const headingBlock = await getBlock( screen, 'Heading', { - rowIndex: 2, - } ); - fireEvent.press( headingBlock ); - - // Open block actions menu - fireEvent.press( getByLabelText( /Open Block Actions Menu/ ) ); - - // Get block actions modal - blockActionsMenu = await getByTestId( 'block-actions-menu' ); - - // Tap on the Move Up button - fireEvent.press( - within( blockActionsMenu ).getByLabelText( 'Move block up' ) - ); - - expect( getEditorHtml() ).toMatchSnapshot(); - } ); - - it( 'disables the Move Up button for the first block', async () => { - const screen = await initializeEditor( { - screenWidth: 100, // To collapse the up/arrow buttons bellow blocks - } ); - const { getByLabelText, getByTestId } = screen; - - // Add Paragraph block - await addBlock( screen, 'Paragraph' ); - - // Get Paragraph block - let paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - const paragraphField = - within( paragraphBlock ).getByPlaceholderText( - 'Start writing…' - ); - typeInRichText( paragraphField, 'Hello!' ); - - // Add Spacer block - await addBlock( screen, 'Spacer' ); - - // Add Heading block - await addBlock( screen, 'Heading' ); - - // Get Paragraph block - paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - - // Open block actions menu - fireEvent.press( getByLabelText( /Open Block Actions Menu/ ) ); - - // Get block actions modal - const blockActionsMenu = await getByTestId( 'block-actions-menu' ); - - // Get the Move Up button - const upButton = - within( blockActionsMenu ).getByLabelText( 'Move block up' ); - const isUpButtonDisabled = - upButton.props.accessibilityState?.disabled; - expect( isUpButtonDisabled ).toBe( true ); - - // Press the button to make sure the block doesn't move - fireEvent.press( upButton ); - - expect( getEditorHtml() ).toMatchSnapshot(); - } ); - - it( 'disables the Move Down button for the last block', async () => { - const screen = await initializeEditor( { - screenWidth: 100, - } ); - const { getByLabelText, getByTestId } = screen; - - // Add Paragraph block - await addBlock( screen, 'Paragraph' ); - - // Get Paragraph block - const paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - const paragraphField = - within( paragraphBlock ).getByPlaceholderText( - 'Start writing…' - ); - typeInRichText( paragraphField, 'Hello!' ); - - // Add Spacer block - await addBlock( screen, 'Spacer' ); - - // Add Heading block - await addBlock( screen, 'Heading' ); - - // Get Heading block - const headingBlock = await getBlock( screen, 'Heading', { - rowIndex: 3, - } ); - fireEvent.press( headingBlock ); - - // Open block actions menu - fireEvent.press( getByLabelText( /Open Block Actions Menu/ ) ); - - // Get block actions modal - const blockActionsMenu = await getByTestId( 'block-actions-menu' ); - - // Get the Move Down button - const downButton = - within( blockActionsMenu ).getByLabelText( 'Move block down' ); - const isDownButtonDisabled = - downButton.props.accessibilityState?.disabled; - expect( isDownButtonDisabled ).toBe( true ); - - // Press the button to make sure the block doesn't move - fireEvent.press( downButton ); - - expect( getEditorHtml() ).toMatchSnapshot(); - } ); - } ); - - describe( 'block options', () => { - it( 'copies and pastes a block', async () => { - const screen = await initializeEditor(); - const { getByLabelText } = screen; - - // Add Paragraph block - await addBlock( screen, 'Paragraph' ); - - // Get Paragraph block - let paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - const paragraphField = - within( paragraphBlock ).getByPlaceholderText( - 'Start writing…' - ); - typeInRichText( paragraphField, 'Hello!' ); - - // Add Spacer block - await addBlock( screen, 'Spacer' ); - - // Add Heading block - await addBlock( screen, 'Heading' ); - - // Get Heading block - const headingBlock = await getBlock( screen, 'Heading', { - rowIndex: 3, - } ); - fireEvent.press( headingBlock ); - - // Open block actions menu - fireEvent.press( getByLabelText( /Open Block Actions Menu/ ) ); - - // Tap on the Copy button - fireEvent.press( getByLabelText( /Copy/ ) ); - - // Get Paragraph block - paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - - // Open block actions menu - fireEvent.press( getByLabelText( /Open Block Actions Menu/ ) ); - - // Tap on the Paste block after button - fireEvent.press( getByLabelText( /Paste block after/ ) ); - - expect( getEditorHtml() ).toMatchSnapshot(); - } ); - - it( 'does not replace a non empty Paragraph block when pasting another block', async () => { - const screen = await initializeEditor(); - const { getByLabelText } = screen; - - // Add Paragraph block - await addBlock( screen, 'Paragraph' ); - - // Get Paragraph block - let paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - const paragraphField = - within( paragraphBlock ).getByPlaceholderText( - 'Start writing…' - ); - typeInRichText( paragraphField, 'Hello!' ); - - // Add Spacer block - await addBlock( screen, 'Spacer' ); - - // Add Heading block - await addBlock( screen, 'Heading' ); - - // Get Heading block - const headingBlock = await getBlock( screen, 'Heading', { - rowIndex: 3, - } ); - fireEvent.press( headingBlock ); - - // Open block actions menu - fireEvent.press( getByLabelText( /Open Block Actions Menu/ ) ); - - // Tap on the Copy button - fireEvent.press( getByLabelText( /Copy/ ) ); - - // Get Paragraph block - paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - - // Open block actions menu - fireEvent.press( getByLabelText( /Open Block Actions Menu/ ) ); - - // Tap on the Past block after button - fireEvent.press( getByLabelText( /Paste block after/ ) ); - - expect( getEditorHtml() ).toMatchSnapshot(); - } ); - - it( 'cuts and pastes a block', async () => { - const screen = await initializeEditor(); - const { getByLabelText } = screen; - - // Add Paragraph block - await addBlock( screen, 'Paragraph' ); - - // Get Paragraph block - let paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - const paragraphField = - within( paragraphBlock ).getByPlaceholderText( - 'Start writing…' - ); - typeInRichText( paragraphField, 'Hello!' ); - - // Add Spacer block - await addBlock( screen, 'Spacer' ); - - // Add Heading block - await addBlock( screen, 'Heading' ); - - // Get Paragraph block - paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - - // Open block actions menu - fireEvent.press( getByLabelText( /Open Block Actions Menu/ ) ); - - // Tap on the Cut button - fireEvent.press( getByLabelText( /Cut block/ ) ); - - const headingBlock = await getBlock( screen, 'Heading', { - rowIndex: 2, - } ); - fireEvent.press( headingBlock ); - - // Open block actions menu - fireEvent.press( getByLabelText( /Open Block Actions Menu/ ) ); - - // Tap on the Cut button - fireEvent.press( getByLabelText( /Paste block after/ ) ); - - expect( getEditorHtml() ).toMatchSnapshot(); - } ); - - it( 'duplicates a block', async () => { - const screen = await initializeEditor(); - const { getByLabelText } = screen; - - // Add Paragraph block - await addBlock( screen, 'Paragraph' ); - - // Get Paragraph block - const paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - const paragraphField = - within( paragraphBlock ).getByPlaceholderText( - 'Start writing…' - ); - typeInRichText( paragraphField, 'Hello!' ); - - // Add Spacer block - await addBlock( screen, 'Spacer' ); - - // Add Heading block - await addBlock( screen, 'Heading' ); - - // Get Spacer block - const spacerBlock = await getBlock( screen, 'Spacer', { - rowIndex: 2, - } ); - fireEvent.press( spacerBlock ); - - // Open block actions menu - fireEvent.press( getByLabelText( /Open Block Actions Menu/ ) ); - - // Tap on the Duplicate button - fireEvent.press( getByLabelText( /Duplicate block/ ) ); - - expect( getEditorHtml() ).toMatchSnapshot(); - } ); - - it( 'transforms a Paragraph block into a Pullquote block', async () => { - const screen = await initializeEditor(); - const { getByLabelText, getByRole } = screen; - - // Add Paragraph block - await addBlock( screen, 'Paragraph' ); - - // Get Paragraph block - let paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - const paragraphField = - within( paragraphBlock ).getByPlaceholderText( - 'Start writing…' - ); - typeInRichText( paragraphField, 'Hello!' ); - - // Add Spacer block - await addBlock( screen, 'Spacer' ); - - // Add Heading block - await addBlock( screen, 'Heading' ); - - // Get Paragraph block - paragraphBlock = await getBlock( screen, 'Paragraph' ); - fireEvent.press( paragraphBlock ); - - // Open block actions menu - fireEvent.press( getByLabelText( /Open Block Actions Menu/ ) ); - - // Tap on the Transform block button - fireEvent.press( getByLabelText( /Transform block…/ ) ); - - // Get Picker title - const pickerHeader = getByRole( 'header' ); - const headerTitle = within( pickerHeader ).getByText( - /Transform Paragraph to/ - ); - expect( headerTitle ).toBeVisible(); - - // Tap on the Transform block button - fireEvent.press( getByLabelText( /Pullquote/ ) ); - - expect( getEditorHtml() ).toMatchSnapshot(); - } ); - } ); -} ); From bd11ce290c82e919e5afa19971ca303e16dd90d9 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Tue, 4 Jul 2023 18:49:58 +0200 Subject: [PATCH 05/11] Update EditorPage with new swipeToolbarToElement and updates removeBlock, toggleFormatting and addBlock helpers --- .../__device-tests__/pages/editor-page.js | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/packages/react-native-editor/__device-tests__/pages/editor-page.js b/packages/react-native-editor/__device-tests__/pages/editor-page.js index 3b06187482a6f..252bb61c971b9 100644 --- a/packages/react-native-editor/__device-tests__/pages/editor-page.js +++ b/packages/react-native-editor/__device-tests__/pages/editor-page.js @@ -341,6 +341,41 @@ class EditorPage { } } + async swipeToolbarToElement( elementSelector, options ) { + const toolbar = await this.getToolbar(); + const toolbarLocation = await toolbar.getLocation(); + const toolbarSize = await toolbar.getSize(); + const maxLocatorAttempts = 5; + const { byId } = options || {}; + let locatorAttempts = 0; + const offset = isAndroid() ? 300 : 50; + let element; + + while ( locatorAttempts < maxLocatorAttempts ) { + element = byId + ? await this.driver.elementsByAccessibilityId( elementSelector ) + : await this.driver.elementsByXPath( elementSelector ); + if ( await element[ 0 ]?.isDisplayed() ) { + break; + } + + swipeFromTo( + this.driver, + { + x: toolbarSize.width - offset, + y: toolbarLocation.y + toolbarSize.height / 2, + }, + { + x: toolbarSize.width / 2, + y: toolbarLocation.y + toolbarSize.height / 2, + }, + 1000 + ); + locatorAttempts++; + } + return element; + } + async openBlockSettings() { const settingsButtonElement = isAndroid() ? '//android.widget.Button[@content-desc="Open Settings"]/android.view.ViewGroup' @@ -356,10 +391,10 @@ class EditorPage { const blockActionsButtonElement = isAndroid() ? '//android.widget.Button[contains(@content-desc, "Open Block Actions Menu")]' : '//XCUIElementTypeButton[@name="Open Block Actions Menu"]'; - const blockActionsMenu = await this.waitForElementToBeDisplayedByXPath( + const blockActionsMenu = await this.swipeToolbarToElement( blockActionsButtonElement ); - await blockActionsMenu.click(); + await blockActionsMenu[ 0 ].click(); const removeElement = 'Remove block'; const removeBlockButton = await this.waitForElementToBeDisplayedById( @@ -378,13 +413,15 @@ class EditorPage { // ========================= async getToolbar() { - return await this.driver.elementsByAccessibilityId( 'Document tools' ); + return this.waitForElementToBeDisplayedById( 'Document tools', 4000 ); } async addNewBlock( blockName, { skipInserterOpen = false } = {} ) { if ( ! skipInserterOpen ) { - const addButton = await this.getAddBlockButton(); - await addButton.click(); + const addButton = await this.swipeToolbarToElement( ADD_BLOCK_ID, { + byId: true, + } ); + await addButton[ 0 ].click(); } // Click on block of choice. @@ -599,10 +636,8 @@ class EditorPage { const identifier = isAndroid() ? `//android.widget.Button[@content-desc="${ formatting }"]/android.view.ViewGroup` : `//XCUIElementTypeButton[@name="${ formatting }"]`; - const toggleElement = await this.waitForElementToBeDisplayedByXPath( - identifier - ); - return await toggleElement.click(); + const toggleElement = await this.swipeToolbarToElement( identifier ); + return await toggleElement[ 0 ].click(); } async openLinkToSettings() { From d3684ce4f076a0958f6bb1af3bb0338538763574 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Tue, 4 Jul 2023 18:50:45 +0200 Subject: [PATCH 06/11] Update E2E Tests --- .../gutenberg-editor-block-insertion-@canary.test.js | 8 ++++---- .../gutenberg-editor-device-actions.test.js | 4 ++-- .../gutenberg-editor-media-blocks-@canary.test.js | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion-@canary.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion-@canary.test.js index b413f3b6a42ce..05f7c6bfcd077 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion-@canary.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-block-insertion-@canary.test.js @@ -104,7 +104,7 @@ describe( 'Gutenberg Editor Slash Inserter tests', () => { ); expect( await editorPage.assertSlashInserterPresent() ).toBe( true ); - await editorPage.removeBlockAtPosition( blockNames.paragraph ); + await editorPage.removeBlock(); } ); it( 'should hide the menu after deleting the / character', async () => { @@ -139,7 +139,7 @@ describe( 'Gutenberg Editor Slash Inserter tests', () => { // Check if the slash inserter UI no longer exists. expect( await editorPage.assertSlashInserterPresent() ).toBe( false ); - await editorPage.removeBlockAtPosition( blockNames.paragraph ); + await editorPage.removeBlock(); } ); it( 'should add an Image block after tying /image and tapping on the Image block button', async () => { @@ -172,7 +172,7 @@ describe( 'Gutenberg Editor Slash Inserter tests', () => { expect( await editorPage.assertSlashInserterPresent() ).toBe( false ); // Remove image block. - await editorPage.removeBlockAtPosition( blockNames.image ); + await editorPage.removeBlock(); } ); it( 'should insert an embed image block with "/img" + enter', async () => { @@ -190,6 +190,6 @@ describe( 'Gutenberg Editor Slash Inserter tests', () => { await editorPage.hasBlockAtPosition( 1, blockNames.embed ) ).toBe( true ); - await editorPage.removeBlockAtPosition( blockNames.embed ); + await editorPage.removeBlock(); } ); } ); diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-device-actions.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-device-actions.test.js index 3a64ca3750898..e5e7b5c829f8e 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-device-actions.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-device-actions.test.js @@ -108,8 +108,8 @@ describe( 'Gutenberg Editor Paste tests', () => { const text = await editorPage.getTextForParagraphBlockAtPosition( 2 ); expect( text ).toBe( testData.pastePlainText ); - await editorPage.removeBlockAtPosition( blockNames.paragraph, 2 ); - await editorPage.removeBlockAtPosition( blockNames.paragraph, 1 ); + await editorPage.removeBlock(); + await editorPage.removeBlock(); } ); it.skip( 'copies styled text from one paragraph block and pastes in another', async () => { diff --git a/packages/react-native-editor/__device-tests__/gutenberg-editor-media-blocks-@canary.test.js b/packages/react-native-editor/__device-tests__/gutenberg-editor-media-blocks-@canary.test.js index 6fd68a7a4aff3..e17e39bd8357f 100644 --- a/packages/react-native-editor/__device-tests__/gutenberg-editor-media-blocks-@canary.test.js +++ b/packages/react-native-editor/__device-tests__/gutenberg-editor-media-blocks-@canary.test.js @@ -134,7 +134,7 @@ onlyOniOS( 'Gutenberg Editor Cover Block test', () => { // Navigate upwards to select parent block await editorPage.moveBlockSelectionUp(); - await editorPage.removeBlockAtPosition( blockNames.cover ); + await editorPage.removeBlock(); } ); // Testing this for iOS on a device is valuable to ensure that it properly @@ -165,6 +165,6 @@ onlyOniOS( 'Gutenberg Editor Cover Block test', () => { await editorPage.chooseMediaLibrary(); expect( coverBlock ).toBeTruthy(); - await editorPage.removeBlockAtPosition( blockNames.cover ); + await editorPage.removeBlock(); } ); } ); From 7ef6a937de4cd1cbd79c0cefc5f1620899fcfb99 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Tue, 4 Jul 2023 18:56:32 +0200 Subject: [PATCH 07/11] Update BlockList to remove inline block settings options --- .../src/components/block-list/block.native.js | 30 +------------------ .../components/block-list/block.native.scss | 7 +---- .../inspector-controls/fill.native.js | 3 -- 3 files changed, 2 insertions(+), 38 deletions(-) diff --git a/packages/block-editor/src/components/block-list/block.native.js b/packages/block-editor/src/components/block-list/block.native.js index bb537f22d0fb0..89f30572fe331 100644 --- a/packages/block-editor/src/components/block-list/block.native.js +++ b/packages/block-editor/src/components/block-list/block.native.js @@ -6,12 +6,11 @@ import { Pressable, useWindowDimensions, View } from 'react-native'; /** * WordPress dependencies */ -import { useCallback, useMemo, useRef, useState } from '@wordpress/element'; +import { useCallback, useMemo, useState } from '@wordpress/element'; import { GlobalStylesContext, getMergedGlobalStyles, useMobileGlobalStylesColors, - alignmentHelpers, useGlobalStyles, } from '@wordpress/components'; import { @@ -36,9 +35,7 @@ import { compose, ifCondition, pure } from '@wordpress/compose'; import BlockEdit from '../block-edit'; import BlockDraggable from '../block-draggable'; import BlockInvalidWarning from './block-invalid-warning'; -import BlockMobileToolbar from '../block-mobile-toolbar'; import BlockOutline from './block-outline'; -import styles from './block.scss'; import { store as blockEditorStore } from '../../store'; import { useLayout } from './layout'; import useSetting from '../use-setting'; @@ -63,8 +60,6 @@ function getWrapperProps( value, getWrapperPropsFunction ) { function BlockWrapper( { accessibilityLabel, - align, - blockWidth, children, clientId, draggingClientId, @@ -72,18 +67,12 @@ function BlockWrapper( { isDescendentBlockSelected, isParentSelected, isSelected, - isStackedHorizontally, isTouchable, marginHorizontal, marginVertical, - onDeleteBlock, onFocus, } ) { const { width: screenWidth } = useWindowDimensions(); - const anchorNodeRef = useRef(); - const { isFullWidth } = alignmentHelpers; - const isScreenWidthEqual = blockWidth === screenWidth; - const isFullWidthToolbar = isFullWidth( align ) || isScreenWidthEqual; const blockWrapperStyles = { flex: 1 }; const blockWrapperStyle = [ blockWrapperStyles, @@ -116,19 +105,6 @@ function BlockWrapper( { > { children } - - { isSelected && ( - - ) } - ); } @@ -295,7 +271,6 @@ function BlockListBlock( { ), ] ); - const { align } = attributes; const isFocused = isSelected || isDescendentBlockSelected; const isTouchable = isSelected || @@ -312,8 +287,6 @@ function BlockListBlock( { return ( { () => diff --git a/packages/block-editor/src/components/block-list/block.native.scss b/packages/block-editor/src/components/block-list/block.native.scss index 78905de5bd733..0576f97f5a3a9 100644 --- a/packages/block-editor/src/components/block-list/block.native.scss +++ b/packages/block-editor/src/components/block-list/block.native.scss @@ -51,15 +51,10 @@ min-height: 50px; } -.neutralToolbar { - margin-left: -$block-edge-to-content; - margin-right: -$block-edge-to-content; -} - .solidBorder { position: absolute; top: -$solid-border-space; - bottom: 0; + bottom: -$solid-border-space; left: -$solid-border-space; right: -$solid-border-space; border-width: $block-selected-border-width; diff --git a/packages/block-editor/src/components/inspector-controls/fill.native.js b/packages/block-editor/src/components/inspector-controls/fill.native.js index d38d865cd15cc..f4cd288c6913c 100644 --- a/packages/block-editor/src/components/inspector-controls/fill.native.js +++ b/packages/block-editor/src/components/inspector-controls/fill.native.js @@ -6,7 +6,6 @@ import { View } from 'react-native'; /** * WordPress dependencies */ -import { Children } from '@wordpress/element'; import { BottomSheetConsumer } from '@wordpress/components'; import warning from '@wordpress/warning'; import deprecated from '@wordpress/deprecated'; @@ -16,7 +15,6 @@ import deprecated from '@wordpress/deprecated'; */ import groups from './groups'; import useDisplayBlockControls from '../use-display-block-controls'; -import { BlockSettingsButton } from '../block-settings'; export default function InspectorControlsFill( { children, @@ -55,7 +53,6 @@ export default function InspectorControlsFill( { } - { Children.count( children ) > 0 && } ); } From 77c5d59c5bd1384a1ec92313d2e18208feb34f20 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Tue, 4 Jul 2023 18:57:37 +0200 Subject: [PATCH 08/11] Update Gallery test --- .../block-library/src/gallery/test/index.native.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/block-library/src/gallery/test/index.native.js b/packages/block-library/src/gallery/test/index.native.js index a64b8bf403281..75bf796b4a70b 100644 --- a/packages/block-library/src/gallery/test/index.native.js +++ b/packages/block-library/src/gallery/test/index.native.js @@ -511,10 +511,11 @@ describe( 'Gallery block', () => { // Reference: https://github.com/wordpress-mobile/test-cases/blob/trunk/test-cases/gutenberg/gallery.md#tc010 it( 'rearranges gallery items', async () => { // Initialize with a gallery that contains three items - const { galleryBlock } = await initializeWithGalleryBlock( { - numberOfItems: 3, - media, - } ); + const { getByLabelText, galleryBlock } = + await initializeWithGalleryBlock( { + numberOfItems: 3, + media, + } ); // Rearrange items (final disposition will be: Image 3 - Image 1 - Image 2) const galleryItem1 = getGalleryItem( galleryBlock, 1 ); @@ -523,7 +524,7 @@ describe( 'Gallery block', () => { fireEvent.press( galleryItem3 ); await act( () => fireEvent.press( - within( galleryItem3 ).getByLabelText( + getByLabelText( /Move block left from position 3 to position 2/ ) ) @@ -532,7 +533,7 @@ describe( 'Gallery block', () => { fireEvent.press( galleryItem1 ); await act( () => fireEvent.press( - within( galleryItem1 ).getByLabelText( + getByLabelText( /Move block right from position 1 to position 2/ ) ) From e076e8cf4fe39c79c4a607ac1d49fce8f47b21a6 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Tue, 4 Jul 2023 18:59:17 +0200 Subject: [PATCH 09/11] Update BlockDraggable test --- .../block-draggable/test/index.native.js | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/packages/block-editor/src/components/block-draggable/test/index.native.js b/packages/block-editor/src/components/block-draggable/test/index.native.js index 5b670970406f5..47b3bc829345f 100644 --- a/packages/block-editor/src/components/block-draggable/test/index.native.js +++ b/packages/block-editor/src/components/block-draggable/test/index.native.js @@ -133,16 +133,6 @@ describe( 'BlockDraggable', () => { // "firePanGesture" finishes the dragging gesture firePanGesture( blockDraggableWrapper ); expect( getDraggableChip( screen ) ).not.toBeDefined(); - - // Start dragging from block's mobile toolbar - fireLongPress( - paragraphBlock, - 'draggable-trigger-mobile-toolbar' - ); - expect( getDraggableChip( screen ) ).toBeVisible(); - // "firePanGesture" finishes the dragging gesture - firePanGesture( blockDraggableWrapper ); - expect( getDraggableChip( screen ) ).not.toBeDefined(); } ) ); it( 'does not enable drag mode when selected and editing text', async () => @@ -243,16 +233,6 @@ describe( 'BlockDraggable', () => { // "firePanGesture" finishes the dragging gesture firePanGesture( blockDraggableWrapper ); expect( getDraggableChip( screen ) ).not.toBeDefined(); - - // Start dragging from block's mobile toolbar - fireLongPress( - imageBlock, - 'draggable-trigger-mobile-toolbar' - ); - expect( getDraggableChip( screen ) ).toBeVisible(); - // "firePanGesture" finishes the dragging gesture - firePanGesture( blockDraggableWrapper ); - expect( getDraggableChip( screen ) ).not.toBeDefined(); } ) ); } ); @@ -301,16 +281,6 @@ describe( 'BlockDraggable', () => { // "firePanGesture" finishes the dragging gesture firePanGesture( blockDraggableWrapper ); expect( getDraggableChip( screen ) ).not.toBeDefined(); - - // Start dragging from block's mobile toolbar - fireLongPress( - galleryBlock, - 'draggable-trigger-mobile-toolbar' - ); - expect( getDraggableChip( screen ) ).toBeVisible(); - // "firePanGesture" finishes the dragging gesture - firePanGesture( blockDraggableWrapper ); - expect( getDraggableChip( screen ) ).not.toBeDefined(); } ) ); it( 'enables drag mode when nested block is selected', async () => @@ -336,20 +306,6 @@ describe( 'BlockDraggable', () => { // "firePanGesture" finishes the dragging gesture firePanGesture( blockDraggableWrapper ); expect( getDraggableChip( screen ) ).not.toBeDefined(); - - // After dropping the block, the gallery item gets automatically selected. - // Hence, we have to select the gallery item again. - fireEvent.press( galleryItem ); - - // Start dragging from nested block's mobile toolbar - fireLongPress( - galleryItem, - 'draggable-trigger-mobile-toolbar' - ); - expect( getDraggableChip( screen ) ).toBeVisible(); - // "firePanGesture" finishes the dragging gesture - firePanGesture( blockDraggableWrapper ); - expect( getDraggableChip( screen ) ).not.toBeDefined(); } ) ); } ); @@ -390,16 +346,6 @@ describe( 'BlockDraggable', () => { // "firePanGesture" finishes the dragging gesture firePanGesture( blockDraggableWrapper ); expect( getDraggableChip( screen ) ).not.toBeDefined(); - - // Start dragging from block's mobile toolbar - fireLongPress( - spacerBlock, - 'draggable-trigger-mobile-toolbar' - ); - expect( getDraggableChip( screen ) ).toBeVisible(); - // "firePanGesture" finishes the dragging gesture - firePanGesture( blockDraggableWrapper ); - expect( getDraggableChip( screen ) ).not.toBeDefined(); } ) ); } ); } ); From c7b7dd926816c853ad24372b83d28c47b9b1588d Mon Sep 17 00:00:00 2001 From: Gerardo Date: Tue, 4 Jul 2023 19:03:53 +0200 Subject: [PATCH 10/11] Update BlockMover --- .../src/components/block-mover/index.native.js | 6 +++--- .../test/__snapshots__/index.native.js.snap | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/block-editor/src/components/block-mover/index.native.js b/packages/block-editor/src/components/block-mover/index.native.js index 852515a343821..06a1a881d14f8 100644 --- a/packages/block-editor/src/components/block-mover/index.native.js +++ b/packages/block-editor/src/components/block-mover/index.native.js @@ -7,7 +7,7 @@ import { Platform } from 'react-native'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { Picker, ToolbarButton } from '@wordpress/components'; +import { Picker, ToolbarButton, ToolbarGroup } from '@wordpress/components'; import { withInstanceId, compose } from '@wordpress/compose'; import { withSelect, withDispatch } from '@wordpress/data'; import { useCallback, useEffect, useRef, useState } from '@wordpress/element'; @@ -107,7 +107,7 @@ export const BlockMover = ( { } return ( - <> + - + ); }; diff --git a/packages/block-editor/src/components/block-mover/test/__snapshots__/index.native.js.snap b/packages/block-editor/src/components/block-mover/test/__snapshots__/index.native.js.snap index ea30939ef9d52..db322ec2af840 100644 --- a/packages/block-editor/src/components/block-mover/test/__snapshots__/index.native.js.snap +++ b/packages/block-editor/src/components/block-mover/test/__snapshots__/index.native.js.snap @@ -43,7 +43,16 @@ exports[`Block Mover Picker moving blocks moves blocks up and down 1`] = ` `; exports[`Block Mover Picker should render without crashing and match snapshot 1`] = ` -[ + - , +
- , -] + + `; From 6c46130bf36686b73cc28d1a5ac9d677c92adf27 Mon Sep 17 00:00:00 2001 From: Gerardo Date: Wed, 5 Jul 2023 19:49:26 +0200 Subject: [PATCH 11/11] EditorPage utils - Update swipeToolbarToElement to take into account swiping to the right --- .../__device-tests__/pages/editor-page.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/react-native-editor/__device-tests__/pages/editor-page.js b/packages/react-native-editor/__device-tests__/pages/editor-page.js index 252bb61c971b9..f03dae92c4174 100644 --- a/packages/react-native-editor/__device-tests__/pages/editor-page.js +++ b/packages/react-native-editor/__device-tests__/pages/editor-page.js @@ -342,15 +342,16 @@ class EditorPage { } async swipeToolbarToElement( elementSelector, options ) { - const toolbar = await this.getToolbar(); - const toolbarLocation = await toolbar.getLocation(); - const toolbarSize = await toolbar.getSize(); + const { byId, swipeRight } = options || {}; + const offset = isAndroid() ? 300 : 50; const maxLocatorAttempts = 5; - const { byId } = options || {}; let locatorAttempts = 0; - const offset = isAndroid() ? 300 : 50; let element; + const toolbar = await this.getToolbar(); + const toolbarLocation = await toolbar.getLocation(); + const toolbarSize = await toolbar.getSize(); + while ( locatorAttempts < maxLocatorAttempts ) { element = byId ? await this.driver.elementsByAccessibilityId( elementSelector ) @@ -362,11 +363,15 @@ class EditorPage { swipeFromTo( this.driver, { - x: toolbarSize.width - offset, + x: ! swipeRight + ? toolbarSize.width - offset + : toolbarSize.width / 2, y: toolbarLocation.y + toolbarSize.height / 2, }, { - x: toolbarSize.width / 2, + x: ! swipeRight + ? toolbarSize.width / 2 + : toolbarSize.width - offset, y: toolbarLocation.y + toolbarSize.height / 2, }, 1000 @@ -420,6 +425,7 @@ class EditorPage { if ( ! skipInserterOpen ) { const addButton = await this.swipeToolbarToElement( ADD_BLOCK_ID, { byId: true, + swipeRight: true, } ); await addButton[ 0 ].click(); }