From e0516acce0582e8a4adc2c2d4f0b4eea675583f1 Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Mon, 5 Jul 2021 17:01:20 +0200 Subject: [PATCH] Fix "Select all" behavior in the editor (#33167) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Make isEntirelySelected consider "deep" first and last children in case the selection does not belong to the top-level children directly * Rollback unit tests – jsdom does not support contentEditable https://github.com/jsdom/jsdom/issues/1670 * Add an e2e test for multi-block selection * Adjust e2e tests * Add a separate test for gradual select-all from within the list block * Cosmetic adjustments to the e2e test * Update the snapshot --- packages/dom/src/dom/is-entirely-selected.js | 36 +++++++++++++++---- .../multi-block-selection.test.js.snap | 10 ++++++ .../various/multi-block-selection.test.js | 21 +++++++++++ 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/packages/dom/src/dom/is-entirely-selected.js b/packages/dom/src/dom/is-entirely-selected.js index 3445f90facb31..2a2ddc6308c16 100644 --- a/packages/dom/src/dom/is-entirely-selected.js +++ b/packages/dom/src/dom/is-entirely-selected.js @@ -48,15 +48,37 @@ export default function isEntirelySelected( element ) { const lastChild = element.lastChild; assertIsDefined( lastChild, 'lastChild' ); - const lastChildContentLength = - lastChild.nodeType === lastChild.TEXT_NODE - ? /** @type {Text} */ ( lastChild ).data.length - : lastChild.childNodes.length; + const endContainerContentLength = + endContainer.nodeType === endContainer.TEXT_NODE + ? /** @type {Text} */ ( endContainer ).data.length + : endContainer.childNodes.length; return ( - startContainer === element.firstChild && - endContainer === element.lastChild && + isDeepChild( startContainer, element, 'firstChild' ) && + isDeepChild( endContainer, element, 'lastChild' ) && startOffset === 0 && - endOffset === lastChildContentLength + endOffset === endContainerContentLength ); } + +/** + * Check whether the contents of the element have been entirely selected. + * Returns true if there is no possibility of selection. + * + * @param {HTMLElement|Node} query The element to check. + * @param {HTMLElement} container The container that we suspect "query" may be a first or last child of. + * @param {"firstChild"|"lastChild"} propName "firstChild" or "lastChild" + * + * @return {boolean} True if query is a deep first/last child of container, false otherwise. + */ +function isDeepChild( query, container, propName ) { + /** @type {HTMLElement | ChildNode | null} */ + let candidate = container; + do { + if ( query === candidate ) { + return true; + } + candidate = candidate[ propName ]; + } while ( candidate ); + return false; +} diff --git a/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap b/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap index 3964a7ccae760..2ba023db79f50 100644 --- a/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap +++ b/packages/e2e-tests/specs/editor/various/__snapshots__/multi-block-selection.test.js.snap @@ -94,6 +94,16 @@ exports[`Multi-block selection should gradually multi-select 2`] = ` " `; +exports[`Multi-block selection should multi-select from within the list block 1`] = ` +" +

1

+ + + + +" +`; + exports[`Multi-block selection should not multi select single block 1`] = ` "

diff --git a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js index 6a8a1b63b1164..7464fe989b0d1 100644 --- a/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js +++ b/packages/e2e-tests/specs/editor/various/multi-block-selection.test.js @@ -655,4 +655,25 @@ describe( 'Multi-block selection', () => { // Expect both columns to be deleted. expect( await getEditedPostContent() ).toMatchSnapshot(); } ); + + it( 'should multi-select from within the list block', async () => { + await clickBlockAppender(); + // Select a paragraph. + await page.keyboard.type( '1' ); + await page.keyboard.press( 'Enter' ); + // Add a list + await page.keyboard.type( '/list' ); + await page.keyboard.press( 'Enter' ); + await page.keyboard.type( '1' ); + + // Confirm correct setup: a paragraph and a list + expect( await getEditedPostContent() ).toMatchSnapshot(); + + await pressKeyWithModifier( 'primary', 'a' ); + await pressKeyWithModifier( 'primary', 'a' ); + + await page.waitForSelector( + '[data-type="core/paragraph"].is-multi-selected' + ); + } ); } );