Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Multiple use" block validation logic improvement. #40901

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,53 @@
/**
* WordPress dependencies
*/
import { createNewPost, insertBlock } from '@wordpress/e2e-test-utils';

describe( 'Validate multiple use', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move this test to use Playwright?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @getdave, thanks for your review. I'm away from work for the next few weeks, so It would be nice if anyone were interested in taking this issue.

beforeEach( async () => {
await createNewPost();
} );

// Regression of: https://github.com/WordPress/gutenberg/pull/39813
it( 'should display correct amount of warning message', async () => {
const OPTIONS_SELECTOR =
'//div[contains(@class, "block-editor-block-settings-menu")]//button[contains(@aria-label, "Options")]';
const DUPLICATE_BUTTON_SELECTOR =
'//button[contains(@class, "components-menu-item__button")][contains(., "Duplicate")]';

// Insert a block with `multiple` feature enabled, such as `core/more`
await insertBlock( 'More' );

// Block toolbar options dropdown button
let optionButton = await page.waitForXPath( OPTIONS_SELECTOR );
await optionButton.click();

const groupButton = await page.waitForXPath(
'//button[contains(@class, "components-menu-item__button")][contains(., "Group")]'
);

await groupButton.click();

// Block toolbar options dropdown button
optionButton = await page.waitForXPath( OPTIONS_SELECTOR );
await optionButton.click();

// Duplicate block twice
let duplicateButton = await page.waitForXPath(
DUPLICATE_BUTTON_SELECTOR
);
await duplicateButton.click();

optionButton = await page.waitForXPath( OPTIONS_SELECTOR );
await optionButton.click();
duplicateButton = await page.waitForXPath( DUPLICATE_BUTTON_SELECTOR );
await duplicateButton.click();

// Check if there are correct amount of warnings.
expect(
await page.$x(
'//p[contains(@class, "block-editor-warning__message")][contains(., "More: This block can only be used once.")]'
)
).toHaveLength( 2 );
} );
} );
37 changes: 28 additions & 9 deletions packages/edit-post/src/hooks/validate-multiple-use/index.js
@@ -1,8 +1,3 @@
/**
* External dependencies
*/
import { find } from 'lodash';

/**
* WordPress dependencies
*/
Expand All @@ -20,6 +15,33 @@ import { addFilter } from '@wordpress/hooks';
import { __ } from '@wordpress/i18n';
import { compose, createHigherOrderComponent } from '@wordpress/compose';

/**
* Recursively find very first block of an specific block type.
*
* @param {Object[]} blocks List of blocks.
* @param {string} name Block name to search.
*
* @return {Object|undefined} Return block object or undefined.
*/
function findFirstOfSameType( blocks, name ) {
if ( ! Array.isArray( blocks ) || ! blocks.length ) {
return;
}

for ( const block of blocks ) {
if ( block.name === name ) {
return block;
}

// Search inside innerBlocks.
const firstBlock = findFirstOfSameType( block.innerBlocks, name );

if ( firstBlock ) {
return firstBlock;
}
}
}

const enhance = compose(
/**
* For blocks whose block type doesn't support `multiple`, provides the
Expand All @@ -44,10 +66,7 @@ const enhance = compose(
// Otherwise, only pass `originalBlockClientId` if it refers to a different
// block from the current one.
const blocks = select( blockEditorStore ).getBlocks();
const firstOfSameType = find(
blocks,
( { name } ) => block.name === name
);
const firstOfSameType = findFirstOfSameType( blocks, block.name );
const isInvalid =
firstOfSameType && firstOfSameType.clientId !== block.clientId;
return {
Expand Down