From 6acc2cd63d2bafd034bd9cf00a1c87757684c43b Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Thu, 7 May 2020 19:06:02 +0100 Subject: [PATCH 1/9] Writing flow: Copy whole block if no text is selected --- .../src/components/copy-handler/index.js | 20 +++++++++++-- packages/dom/README.md | 12 ++++++-- packages/dom/src/dom.js | 28 +++++++++++-------- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/packages/block-editor/src/components/copy-handler/index.js b/packages/block-editor/src/components/copy-handler/index.js index 61c93fe36be1df..255e1bd37eaa6b 100644 --- a/packages/block-editor/src/components/copy-handler/index.js +++ b/packages/block-editor/src/components/copy-handler/index.js @@ -3,8 +3,9 @@ */ import { useRef } from '@wordpress/element'; import { serialize, pasteHandler } from '@wordpress/blocks'; -import { documentHasSelection } from '@wordpress/dom'; +import { documentHasTextSelection } from '@wordpress/dom'; import { useDispatch, useSelect } from '@wordpress/data'; +import { _n, sprintf } from '@wordpress/i18n'; /** * Internal dependencies @@ -22,6 +23,7 @@ function CopyHandler( { children } ) { } = useSelect( ( select ) => select( 'core/block-editor' ), [] ); const { removeBlocks, replaceBlocks } = useDispatch( 'core/block-editor' ); + const { createSuccessNotice } = useDispatch( 'core/notices' ); const { __experimentalCanUserUseUnfilteredHTML: canUserUseUnfilteredHTML, @@ -36,7 +38,7 @@ function CopyHandler( { children } ) { // Always handle multiple selected blocks. // Let native copy behaviour take over in input fields. - if ( ! hasMultiSelection() && documentHasSelection() ) { + if ( ! hasMultiSelection() && documentHasTextSelection() ) { return; } @@ -46,6 +48,20 @@ function CopyHandler( { children } ) { event.preventDefault(); if ( event.type === 'copy' || event.type === 'cut' ) { + createSuccessNotice( + sprintf( + // Translators: Number of blocks being copied + _n( + 'Copied %d block to clipboard', + 'Copied %d blocks to clipboard', + selectedBlockClientIds.length + ), + selectedBlockClientIds.length + ), + { + type: 'snackbar', + } + ); const blocks = getBlocksByClientId( selectedBlockClientIds ); const serialized = serialize( blocks ); diff --git a/packages/dom/README.md b/packages/dom/README.md index d45b8439915786..656445efb52b83 100644 --- a/packages/dom/README.md +++ b/packages/dom/README.md @@ -24,8 +24,16 @@ _Returns_ # **documentHasSelection** -Check wether the current document has a selection. -This checks both for focus in an input field and general text selection. +Check whether the current document has a selection. This checks for both +focus in an input field and general text selection. + +_Returns_ + +- `boolean`: True if there is selection, false if not. + +# **documentHasTextSelection** + +Check whether the current document has selected text. _Returns_ diff --git a/packages/dom/src/dom.js b/packages/dom/src/dom.js index 8271abe1efcd9f..476ee8134aba0a 100644 --- a/packages/dom/src/dom.js +++ b/packages/dom/src/dom.js @@ -484,26 +484,30 @@ export function isNumberInput( element ) { } /** - * Check wether the current document has a selection. - * This checks both for focus in an input field and general text selection. + * Check whether the current document has selected text. * * @return {boolean} True if there is selection, false if not. */ -export function documentHasSelection() { - if ( isTextField( document.activeElement ) ) { - return true; - } - - if ( isNumberInput( document.activeElement ) ) { - return true; - } - +export function documentHasTextSelection() { const selection = window.getSelection(); const range = selection.rangeCount ? selection.getRangeAt( 0 ) : null; - return range && ! range.collapsed; } +/** + * Check whether the current document has a selection. This checks for both + * focus in an input field and general text selection. + * + * @return {boolean} True if there is selection, false if not. + */ +export function documentHasSelection() { + return ( + isTextField( document.activeElement ) || + isNumberInput( document.activeElement ) || + documentHasTextSelection() + ); +} + /** * Check whether the contents of the element have been entirely selected. * Returns true if there is no possibility of selection. From 15e442d4441d5590da533620476952547b5a81ca Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Thu, 7 May 2020 19:16:48 +0100 Subject: [PATCH 2/9] Append change to package changelog --- packages/dom/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/dom/CHANGELOG.md b/packages/dom/CHANGELOG.md index 2ca460fc66312a..c548afcb01aeb5 100644 --- a/packages/dom/CHANGELOG.md +++ b/packages/dom/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### New Feature + +- Add `documentHasTextSelection` to inquire specifically about ranges of selected text, in addition to the existing `documentHasSelection`. + ## 2.1.0 (2019-03-06) ### Bug Fix From 5106b313bb27aa11f5349870ca4953054e31b885 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Thu, 7 May 2020 20:55:18 +0100 Subject: [PATCH 3/9] Consider full selection when not copying/cutting --- .../src/components/copy-handler/index.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/copy-handler/index.js b/packages/block-editor/src/components/copy-handler/index.js index 255e1bd37eaa6b..eb0264720f47df 100644 --- a/packages/block-editor/src/components/copy-handler/index.js +++ b/packages/block-editor/src/components/copy-handler/index.js @@ -3,7 +3,7 @@ */ import { useRef } from '@wordpress/element'; import { serialize, pasteHandler } from '@wordpress/blocks'; -import { documentHasTextSelection } from '@wordpress/dom'; +import { documentHasSelection, documentHasTextSelection } from '@wordpress/dom'; import { useDispatch, useSelect } from '@wordpress/data'; import { _n, sprintf } from '@wordpress/i18n'; @@ -37,9 +37,18 @@ function CopyHandler( { children } ) { } // Always handle multiple selected blocks. - // Let native copy behaviour take over in input fields. - if ( ! hasMultiSelection() && documentHasTextSelection() ) { - return; + if ( ! hasMultiSelection() ) { + // If copying, only consider actual text selection as selection. + // Otherwise, any focus on an input field is considered. + const hasSelection = + event.type === 'copy' || event.type === 'cut' + ? documentHasTextSelection() + : documentHasSelection(); + + // Let native copy behaviour take over in input fields. + if ( hasSelection ) { + return; + } } if ( ! containerRef.current.contains( event.target ) ) { From cba7b5f0044264590acf3fd6c87a57907515567c Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Fri, 8 May 2020 15:02:47 +0100 Subject: [PATCH 4/9] Copy notice: Reveal block type if only one block copied --- .../src/components/copy-handler/index.js | 36 ++++++++++++------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/block-editor/src/components/copy-handler/index.js b/packages/block-editor/src/components/copy-handler/index.js index eb0264720f47df..86e463aabd9e1f 100644 --- a/packages/block-editor/src/components/copy-handler/index.js +++ b/packages/block-editor/src/components/copy-handler/index.js @@ -5,7 +5,7 @@ import { useRef } from '@wordpress/element'; import { serialize, pasteHandler } from '@wordpress/blocks'; import { documentHasSelection, documentHasTextSelection } from '@wordpress/dom'; import { useDispatch, useSelect } from '@wordpress/data'; -import { _n, sprintf } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; /** * Internal dependencies @@ -16,11 +16,16 @@ function CopyHandler( { children } ) { const containerRef = useRef(); const { + getBlockName, getBlocksByClientId, getSelectedBlockClientIds, hasMultiSelection, getSettings, } = useSelect( ( select ) => select( 'core/block-editor' ), [] ); + const { getBlockType } = useSelect( + ( select ) => select( 'core/blocks' ), + [] + ); const { removeBlocks, replaceBlocks } = useDispatch( 'core/block-editor' ); const { createSuccessNotice } = useDispatch( 'core/notices' ); @@ -57,20 +62,25 @@ function CopyHandler( { children } ) { event.preventDefault(); if ( event.type === 'copy' || event.type === 'cut' ) { - createSuccessNotice( - sprintf( + let notice; + if ( selectedBlockClientIds.length === 1 ) { + const clientId = selectedBlockClientIds[ 0 ]; + const { title } = getBlockType( getBlockName( clientId ) ); + notice = sprintf( + // Translators: Name of the block being copied, e.g. "Paragraph" + __( 'Copied block "%s" to clipboard.' ), + title + ); + } else { + notice = sprintf( // Translators: Number of blocks being copied - _n( - 'Copied %d block to clipboard', - 'Copied %d blocks to clipboard', - selectedBlockClientIds.length - ), + __( 'Copied %d blocks to clipboard.' ), selectedBlockClientIds.length - ), - { - type: 'snackbar', - } - ); + ); + } + createSuccessNotice( notice, { + type: 'snackbar', + } ); const blocks = getBlocksByClientId( selectedBlockClientIds ); const serialized = serialize( blocks ); From 089f87083b51c14e6266d9fdd2d18373f60e8620 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Fri, 8 May 2020 15:08:32 +0100 Subject: [PATCH 5/9] Flash block outline in response to copy action --- .../src/components/copy-handler/index.js | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/block-editor/src/components/copy-handler/index.js b/packages/block-editor/src/components/copy-handler/index.js index 86e463aabd9e1f..f3f73b252c4493 100644 --- a/packages/block-editor/src/components/copy-handler/index.js +++ b/packages/block-editor/src/components/copy-handler/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useRef } from '@wordpress/element'; +import { useCallback, useEffect, useRef } from '@wordpress/element'; import { serialize, pasteHandler } from '@wordpress/blocks'; import { documentHasSelection, documentHasTextSelection } from '@wordpress/dom'; import { useDispatch, useSelect } from '@wordpress/data'; @@ -12,6 +12,29 @@ import { __, sprintf } from '@wordpress/i18n'; */ import { getPasteEventData } from '../../utils/get-paste-event-data'; +function useFlashBlock() { + const { toggleBlockHighlight } = useDispatch( 'core/block-editor' ); + const timeouts = useRef( [] ); + const flashBlock = useCallback( + ( clientId ) => { + toggleBlockHighlight( clientId, true ); + const timeout = setTimeout( () => { + toggleBlockHighlight( clientId, false ); + }, 1000 ); + timeouts.current.push( timeout ); + }, + [ toggleBlockHighlight ] + ); + useEffect( () => { + return () => { + timeouts.current.forEach( ( timeout ) => { + clearTimeout( timeout ); + } ); + }; + }, [] ); + return flashBlock; +} + function CopyHandler( { children } ) { const containerRef = useRef(); @@ -30,6 +53,8 @@ function CopyHandler( { children } ) { const { removeBlocks, replaceBlocks } = useDispatch( 'core/block-editor' ); const { createSuccessNotice } = useDispatch( 'core/notices' ); + const flashBlock = useFlashBlock(); + const { __experimentalCanUserUseUnfilteredHTML: canUserUseUnfilteredHTML, } = getSettings(); @@ -66,6 +91,7 @@ function CopyHandler( { children } ) { if ( selectedBlockClientIds.length === 1 ) { const clientId = selectedBlockClientIds[ 0 ]; const { title } = getBlockType( getBlockName( clientId ) ); + flashBlock( clientId ); notice = sprintf( // Translators: Name of the block being copied, e.g. "Paragraph" __( 'Copied block "%s" to clipboard.' ), From 5b4aad8acf3cc889eb4831cfe79809cb6946fb53 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Mon, 11 May 2020 11:22:30 +0100 Subject: [PATCH 6/9] Notify of copy and cut actions w/ useNotifyCopy hook --- .../src/components/copy-handler/index.js | 75 +++++++++++++------ 1 file changed, 51 insertions(+), 24 deletions(-) diff --git a/packages/block-editor/src/components/copy-handler/index.js b/packages/block-editor/src/components/copy-handler/index.js index f3f73b252c4493..4bded3038447d7 100644 --- a/packages/block-editor/src/components/copy-handler/index.js +++ b/packages/block-editor/src/components/copy-handler/index.js @@ -35,25 +35,68 @@ function useFlashBlock() { return flashBlock; } +function useNotifyCopy() { + const { getBlockName } = useSelect( + ( select ) => select( 'core/block-editor' ), + [] + ); + const { getBlockType } = useSelect( + ( select ) => select( 'core/blocks' ), + [] + ); + const { createSuccessNotice } = useDispatch( 'core/notices' ); + + return useCallback( ( eventType, selectedBlockClientIds ) => { + let notice = ''; + if ( selectedBlockClientIds.length === 1 ) { + const clientId = selectedBlockClientIds[ 0 ]; + const { title } = getBlockType( getBlockName( clientId ) ); + notice = + eventType === 'copy' + ? sprintf( + // Translators: Name of the block being copied, e.g. "Paragraph" + __( 'Copied block "%s" to clipboard.' ), + title + ) + : sprintf( + // Translators: Name of the block being cut, e.g. "Paragraph" + __( 'Cut block "%s" to clipboard.' ), + title + ); + } else { + notice = + eventType === 'copy' + ? sprintf( + // Translators: Number of blocks being copied + __( 'Copied %d blocks to clipboard.' ), + selectedBlockClientIds.length + ) + : sprintf( + // Translators: Number of blocks being cut + __( 'Cut %d blocks to clipboard.' ), + selectedBlockClientIds.length + ); + } + createSuccessNotice( notice, { + type: 'snackbar', + } ); + }, [] ); +} + function CopyHandler( { children } ) { const containerRef = useRef(); const { - getBlockName, getBlocksByClientId, getSelectedBlockClientIds, hasMultiSelection, getSettings, } = useSelect( ( select ) => select( 'core/block-editor' ), [] ); - const { getBlockType } = useSelect( - ( select ) => select( 'core/blocks' ), - [] - ); const { removeBlocks, replaceBlocks } = useDispatch( 'core/block-editor' ); - const { createSuccessNotice } = useDispatch( 'core/notices' ); const flashBlock = useFlashBlock(); + const notifyCopy = useNotifyCopy(); const { __experimentalCanUserUseUnfilteredHTML: canUserUseUnfilteredHTML, @@ -87,26 +130,10 @@ function CopyHandler( { children } ) { event.preventDefault(); if ( event.type === 'copy' || event.type === 'cut' ) { - let notice; if ( selectedBlockClientIds.length === 1 ) { - const clientId = selectedBlockClientIds[ 0 ]; - const { title } = getBlockType( getBlockName( clientId ) ); - flashBlock( clientId ); - notice = sprintf( - // Translators: Name of the block being copied, e.g. "Paragraph" - __( 'Copied block "%s" to clipboard.' ), - title - ); - } else { - notice = sprintf( - // Translators: Number of blocks being copied - __( 'Copied %d blocks to clipboard.' ), - selectedBlockClientIds.length - ); + flashBlock( selectedBlockClientIds[ 0 ] ); } - createSuccessNotice( notice, { - type: 'snackbar', - } ); + notifyCopy( event.type, selectedBlockClientIds ); const blocks = getBlocksByClientId( selectedBlockClientIds ); const serialized = serialize( blocks ); From 6ea321ef0ffcb9029d2c84d88ee8611a1929f970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C3=ADas=20Ventura?= Date: Tue, 12 May 2020 20:43:54 +0200 Subject: [PATCH 7/9] Reduce block highlight time. --- .../block-editor/src/components/copy-handler/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/block-editor/src/components/copy-handler/index.js b/packages/block-editor/src/components/copy-handler/index.js index 4bded3038447d7..9457bd91dd5d32 100644 --- a/packages/block-editor/src/components/copy-handler/index.js +++ b/packages/block-editor/src/components/copy-handler/index.js @@ -20,7 +20,7 @@ function useFlashBlock() { toggleBlockHighlight( clientId, true ); const timeout = setTimeout( () => { toggleBlockHighlight( clientId, false ); - }, 1000 ); + }, 150 ); timeouts.current.push( timeout ); }, [ toggleBlockHighlight ] @@ -55,12 +55,12 @@ function useNotifyCopy() { eventType === 'copy' ? sprintf( // Translators: Name of the block being copied, e.g. "Paragraph" - __( 'Copied block "%s" to clipboard.' ), + __( 'Copied "%s" to clipboard.' ), title ) : sprintf( // Translators: Name of the block being cut, e.g. "Paragraph" - __( 'Cut block "%s" to clipboard.' ), + __( 'Moved "%s" to clipboard.' ), title ); } else { @@ -73,7 +73,7 @@ function useNotifyCopy() { ) : sprintf( // Translators: Number of blocks being cut - __( 'Cut %d blocks to clipboard.' ), + __( 'Moved %d blocks to clipboard.' ), selectedBlockClientIds.length ); } From 059ccc95d141f43545b949cd17e8091b741228b1 Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Tue, 12 May 2020 21:11:16 +0100 Subject: [PATCH 8/9] Block Editor: Add action `flashBlock` --- .../developers/data/data-core-block-editor.md | 9 ++++++ .../src/components/copy-handler/index.js | 30 +++---------------- packages/block-editor/src/store/actions.js | 15 ++++++++++ packages/block-editor/src/store/controls.js | 5 ++++ 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/docs/designers-developers/developers/data/data-core-block-editor.md b/docs/designers-developers/developers/data/data-core-block-editor.md index 82e01275c5387a..23e48402fb1cbd 100644 --- a/docs/designers-developers/developers/data/data-core-block-editor.md +++ b/docs/designers-developers/developers/data/data-core-block-editor.md @@ -972,6 +972,15 @@ _Returns_ - `Object`: Action object. +# **flashBlock** + +Yields action objects used in signalling that the block corresponding to the +given clientId should appear to "flash" by rhythmically highlighting it. + +_Parameters_ + +- _clientId_ `string`: Target block client ID. + # **hideInsertionPoint** Returns an action object hiding the insertion point. diff --git a/packages/block-editor/src/components/copy-handler/index.js b/packages/block-editor/src/components/copy-handler/index.js index 9457bd91dd5d32..b69f75520c746f 100644 --- a/packages/block-editor/src/components/copy-handler/index.js +++ b/packages/block-editor/src/components/copy-handler/index.js @@ -1,7 +1,7 @@ /** * WordPress dependencies */ -import { useCallback, useEffect, useRef } from '@wordpress/element'; +import { useCallback, useRef } from '@wordpress/element'; import { serialize, pasteHandler } from '@wordpress/blocks'; import { documentHasSelection, documentHasTextSelection } from '@wordpress/dom'; import { useDispatch, useSelect } from '@wordpress/data'; @@ -12,29 +12,6 @@ import { __, sprintf } from '@wordpress/i18n'; */ import { getPasteEventData } from '../../utils/get-paste-event-data'; -function useFlashBlock() { - const { toggleBlockHighlight } = useDispatch( 'core/block-editor' ); - const timeouts = useRef( [] ); - const flashBlock = useCallback( - ( clientId ) => { - toggleBlockHighlight( clientId, true ); - const timeout = setTimeout( () => { - toggleBlockHighlight( clientId, false ); - }, 150 ); - timeouts.current.push( timeout ); - }, - [ toggleBlockHighlight ] - ); - useEffect( () => { - return () => { - timeouts.current.forEach( ( timeout ) => { - clearTimeout( timeout ); - } ); - }; - }, [] ); - return flashBlock; -} - function useNotifyCopy() { const { getBlockName } = useSelect( ( select ) => select( 'core/block-editor' ), @@ -93,9 +70,10 @@ function CopyHandler( { children } ) { getSettings, } = useSelect( ( select ) => select( 'core/block-editor' ), [] ); - const { removeBlocks, replaceBlocks } = useDispatch( 'core/block-editor' ); + const { flashBlock, removeBlocks, replaceBlocks } = useDispatch( + 'core/block-editor' + ); - const flashBlock = useFlashBlock(); const notifyCopy = useNotifyCopy(); const { diff --git a/packages/block-editor/src/store/actions.js b/packages/block-editor/src/store/actions.js index bd1d275b673aa7..5aef9e4ce2dd6c 100644 --- a/packages/block-editor/src/store/actions.js +++ b/packages/block-editor/src/store/actions.js @@ -1011,6 +1011,21 @@ export function toggleBlockHighlight( clientId, isHighlighted ) { }; } +/** + * Yields action objects used in signalling that the block corresponding to the + * given clientId should appear to "flash" by rhythmically highlighting it. + * + * @param {string} clientId Target block client ID. + */ +export function* flashBlock( clientId ) { + yield toggleBlockHighlight( clientId, true ); + yield { + type: 'SLEEP', + duration: 150, + }; + yield toggleBlockHighlight( clientId, false ); +} + /** * Returns an action object that sets whether the block has controlled innerblocks. * diff --git a/packages/block-editor/src/store/controls.js b/packages/block-editor/src/store/controls.js index 3a64e045f69666..f0cbed509f3131 100644 --- a/packages/block-editor/src/store/controls.js +++ b/packages/block-editor/src/store/controls.js @@ -27,6 +27,11 @@ const controls = { return registry.select( storeName )[ selectorName ]( ...args ); } ), + SLEEP( { duration } ) { + return new Promise( ( resolve ) => { + setTimeout( resolve, duration ); + } ); + }, }; export default controls; From f3052348629f482b3dd158e299a5b777eb4a2e8b Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Thu, 14 May 2020 16:32:41 +0100 Subject: [PATCH 9/9] E2E: Test whole-block copy, cut and paste --- .../copy-cut-paste-whole-blocks.test.js.snap | 73 +++++++++++++++++++ .../copy-cut-paste-whole-blocks.test.js | 66 +++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 packages/e2e-tests/specs/editor/various/__snapshots__/copy-cut-paste-whole-blocks.test.js.snap create mode 100644 packages/e2e-tests/specs/editor/various/copy-cut-paste-whole-blocks.test.js diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/copy-cut-paste-whole-blocks.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/copy-cut-paste-whole-blocks.test.js.snap new file mode 100644 index 00000000000000..519d50e98f504e --- /dev/null +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/copy-cut-paste-whole-blocks.test.js.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Multi-block selection should copy and paste individual blocks 1`] = ` +" +

Here is a unique string so we can test copying.

+ + + +

2

+" +`; + +exports[`Multi-block selection should copy and paste individual blocks 2`] = ` +" +

Here is a unique string so we can test copying.

+ + + +

2

+ + + +

Here is a unique string so we can test copying.

+ + + +

+" +`; + +exports[`Multi-block selection should cut and paste individual blocks 1`] = ` +" +

2

+" +`; + +exports[`Multi-block selection should cut and paste individual blocks 2`] = ` +" +

2

+ + + +

Yet another unique string.

+ + + +

+" +`; + +exports[`Multi-block selection should respect inline copy when text is selected 1`] = ` +" +

First block

+ + + +

Second block

+" +`; + +exports[`Multi-block selection should respect inline copy when text is selected 2`] = ` +" +

First block

+ + + +

ck

+ + + +

Second block

+" +`; diff --git a/packages/e2e-tests/specs/editor/various/copy-cut-paste-whole-blocks.test.js b/packages/e2e-tests/specs/editor/various/copy-cut-paste-whole-blocks.test.js new file mode 100644 index 00000000000000..28896cddceba7a --- /dev/null +++ b/packages/e2e-tests/specs/editor/various/copy-cut-paste-whole-blocks.test.js @@ -0,0 +1,66 @@ +/** + * WordPress dependencies + */ +import { + clickBlockAppender, + createNewPost, + pressKeyWithModifier, + getEditedPostContent, +} from '@wordpress/e2e-test-utils'; + +describe( 'Multi-block selection', () => { + beforeEach( async () => { + await createNewPost(); + } ); + + it( 'should copy and paste individual blocks', async () => { + await clickBlockAppender(); + await page.keyboard.type( + 'Here is a unique string so we can test copying.' + ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'ArrowUp' ); + + await pressKeyWithModifier( 'primary', 'c' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'ArrowDown' ); + await pressKeyWithModifier( 'primary', 'v' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should cut and paste individual blocks', async () => { + await clickBlockAppender(); + await page.keyboard.type( 'Yet another unique string.' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '2' ); + await page.keyboard.press( 'ArrowUp' ); + + await pressKeyWithModifier( 'primary', 'x' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'Tab' ); + await page.keyboard.press( 'ArrowDown' ); + await pressKeyWithModifier( 'primary', 'v' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); + + it( 'should respect inline copy when text is selected', async () => { + await clickBlockAppender(); + await page.keyboard.type( 'First block' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( 'Second block' ); + await page.keyboard.press( 'ArrowUp' ); + await pressKeyWithModifier( 'shift', 'ArrowLeft' ); + await pressKeyWithModifier( 'shift', 'ArrowLeft' ); + + await pressKeyWithModifier( 'primary', 'c' ); + await page.keyboard.press( 'ArrowRight' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await page.keyboard.press( 'Enter' ); + await pressKeyWithModifier( 'primary', 'v' ); + expect( await getEditedPostContent() ).toMatchSnapshot(); + } ); +} );