diff --git a/backport-changelog/7.0/11410.md b/backport-changelog/7.0/11410.md new file mode 100644 index 00000000000000..1fba27b3cf3612 --- /dev/null +++ b/backport-changelog/7.0/11410.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/11410 + +* https://github.com/WordPress/gutenberg/pull/77344 diff --git a/lib/compat/wordpress-7.0/blocks.php b/lib/compat/wordpress-7.0/blocks.php index df7d2849166b4f..66d77564d3a991 100644 --- a/lib/compat/wordpress-7.0/blocks.php +++ b/lib/compat/wordpress-7.0/blocks.php @@ -182,3 +182,22 @@ function gutenberg_update_tax_query_of_query_loop_block( $query, $block ) { } add_filter( 'query_loop_block_query_vars', 'gutenberg_update_tax_query_of_query_loop_block', 10, 2 ); + +/** + * Add the `_wp_ignored_hooked_blocks` post meta to the REST API response for content-like post types. + * + * @param WP_REST_Response $response The REST API response. + * @param WP_Post $post The post object. + * @return WP_REST_Response The modified REST API response. + */ +function gutenberg_set_wp_ignored_hooked_blocks_post_meta_in_rest_response( $response, $post ) { + $updated_post = update_ignored_hooked_blocks_postmeta( (object) $post ); + if ( ! empty( $updated_post->meta_input['_wp_ignored_hooked_blocks'] ) ) { + $response->data['meta']['_wp_ignored_hooked_blocks'] = $updated_post->meta_input['_wp_ignored_hooked_blocks']; + } + return $response; +} +add_filter( 'rest_prepare_page', 'gutenberg_set_wp_ignored_hooked_blocks_post_meta_in_rest_response', 11, 2 ); +add_filter( 'rest_prepare_post', 'gutenberg_set_wp_ignored_hooked_blocks_post_meta_in_rest_response', 11, 2 ); +add_filter( 'rest_prepare_wp_block', 'gutenberg_set_wp_ignored_hooked_blocks_post_meta_in_rest_response', 11, 2 ); +add_filter( 'rest_prepare_wp_navigation', 'gutenberg_set_wp_ignored_hooked_blocks_post_meta_in_rest_response', 11, 2 ); diff --git a/test/e2e/specs/editor/plugins/block-hooks.spec.js b/test/e2e/specs/editor/plugins/block-hooks.spec.js index f4251888fb4801..0fb9cda47c3764 100644 --- a/test/e2e/specs/editor/plugins/block-hooks.spec.js +++ b/test/e2e/specs/editor/plugins/block-hooks.spec.js @@ -3,13 +3,6 @@ */ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); -/** - * Internal dependencies - */ -const { - setCollaboration, -} = require( '../../editor/collaboration/fixtures/collaboration-utils' ); - const dummyBlocksContent = `

This is a dummy heading

@@ -73,12 +66,6 @@ test.describe( 'Block Hooks API', () => { } else { containerPost = postObject; } - - /** - * Since the Block Hooks API relies on server-side rendering to insert - * the hooked blocks, there is a fundamental incompatibility with RTC. - */ - await setCollaboration( requestUtils, false ); } ); test.afterAll( async ( { requestUtils } ) => { @@ -88,7 +75,6 @@ test.describe( 'Block Hooks API', () => { await requestUtils.deleteAllPosts(); await requestUtils.deleteAllBlocks(); - await setCollaboration( requestUtils, true ); } ); test( `should insert hooked blocks into ${ name } on frontend`, async ( { @@ -132,55 +118,73 @@ test.describe( 'Block Hooks API', () => { }, }; - await admin.editPost( postObject.id ); - await expect - .poll( editor.getBlocks ) - .toMatchObject( [ - { name: 'core/heading' }, - expectedHookedBlockAfterHeading, - { name: 'core/paragraph' }, - expectedHookedBlockLastChild, - ] ); + await test.step( 'Editor contains the hooked blocks in the expected positions', async () => { + await admin.editPost( postObject.id ); + await expect + .poll( editor.getBlocks ) + .toMatchObject( [ + { name: 'core/heading' }, + expectedHookedBlockAfterHeading, + { name: 'core/paragraph' }, + expectedHookedBlockLastChild, + ] ); + } ); - const hookedBlock = editor.canvas.getByText( - getHookedBlockContent( 'last_child', blockType ) - ); - await editor.selectBlocks( hookedBlock ); - await editor.clickBlockToolbarButton( 'Move up' ); - - // Save updated post. - const saveButton = page - .getByRole( 'region', { name: 'Editor top bar' } ) - .getByRole( 'button', { name: 'Save', exact: true } ); - await saveButton.click(); - await page - .getByRole( 'button', { name: 'Dismiss this notice' } ) - .filter( { hasText: 'updated' } ) - .waitFor(); - - // Reload and verify that the new position of the hooked block has been persisted. - await page.reload(); - await expect - .poll( editor.getBlocks ) - .toMatchObject( [ - { name: 'core/heading' }, - expectedHookedBlockAfterHeading, - expectedHookedBlockLastChild, - { name: 'core/paragraph' }, - ] ); + await test.step( "Hooked blocks aren't duplicated in the editor after a reload", async () => { + await page.reload(); + await expect + .poll( editor.getBlocks ) + .not.toMatchObject( [ + { name: 'core/heading' }, + expectedHookedBlockAfterHeading, + { name: 'core/paragraph' }, + expectedHookedBlockLastChild, + expectedHookedBlockLastChild, + ] ); + } ); - // Verify that the frontend reflects the changes made in the editor. - await page.goto( `/?p=${ containerPost.id }` ); - await expect( - page.locator( '.entry-content > *' ) - ).toHaveClass( [ - 'wp-block-heading', - getHookedBlockClassName( 'after', 'core/heading' ) + - ' wp-block-paragraph', - getHookedBlockClassName( 'last_child', blockType ) + - ' wp-block-paragraph', - 'dummy-paragraph wp-block-paragraph', - ] ); + await test.step( 'Moving the last hooked block is persisted upon save', async () => { + const hookedBlock = editor.canvas.getByText( + getHookedBlockContent( 'last_child', blockType ) + ); + await editor.selectBlocks( hookedBlock ); + await editor.clickBlockToolbarButton( 'Move up' ); + + // Save updated post. + const saveButton = page + .getByRole( 'region', { name: 'Editor top bar' } ) + .getByRole( 'button', { name: 'Save', exact: true } ); + await saveButton.click(); + await page + .getByRole( 'button', { name: 'Dismiss this notice' } ) + .filter( { hasText: 'updated' } ) + .waitFor(); + + // Reload and verify that the new position of the hooked block has been persisted. + await page.reload(); + await expect + .poll( editor.getBlocks ) + .toMatchObject( [ + { name: 'core/heading' }, + expectedHookedBlockAfterHeading, + expectedHookedBlockLastChild, + { name: 'core/paragraph' }, + ] ); + } ); + + await test.step( 'Frontend reflects the changes made in the editor', async () => { + await page.goto( `/?p=${ containerPost.id }` ); + await expect( + page.locator( '.entry-content > *' ) + ).toHaveClass( [ + 'wp-block-heading', + getHookedBlockClassName( 'after', 'core/heading' ) + + ' wp-block-paragraph', + getHookedBlockClassName( 'last_child', blockType ) + + ' wp-block-paragraph', + 'dummy-paragraph wp-block-paragraph', + ] ); + } ); } ); } ); @@ -214,12 +218,6 @@ test.describe( 'Block Hooks API', () => { } else { containerPost = postObject; } - - /** - * Since the Block Hooks API relies on server-side rendering to insert - * the hooked blocks, there is a fundamental incompatibility with RTC. - */ - await setCollaboration( requestUtils, false ); } ); test.afterAll( async ( { requestUtils } ) => { @@ -229,7 +227,6 @@ test.describe( 'Block Hooks API', () => { await requestUtils.deleteAllPosts(); await requestUtils.deleteAllBlocks(); - await setCollaboration( requestUtils, true ); } ); test( `should insert hooked blocks into ${ name } on frontend`, async ( { @@ -261,49 +258,66 @@ test.describe( 'Block Hooks API', () => { }, }; - await admin.editPost( postObject.id ); - await expect - .poll( editor.getBlocks ) - .toMatchObject( [ - { name: 'core/freeform' }, - expectedHookedBlockLastChild, - ] ); + await test.step( 'Editor contains the hooked block in the expected position', async () => { + await admin.editPost( postObject.id ); + await expect + .poll( editor.getBlocks ) + .toMatchObject( [ + { name: 'core/freeform' }, + expectedHookedBlockLastChild, + ] ); + } ); - const hookedBlock = editor.canvas.getByText( - getHookedBlockContent( 'last_child', blockType ) - ); - await editor.selectBlocks( hookedBlock ); - await editor.clickBlockToolbarButton( 'Move up' ); - - // Save updated post. - const saveButton = page - .getByRole( 'region', { name: 'Editor top bar' } ) - .getByRole( 'button', { name: 'Save', exact: true } ); - await saveButton.click(); - await page - .getByRole( 'button', { name: 'Dismiss this notice' } ) - .filter( { hasText: 'updated' } ) - .waitFor(); - - // Reload and verify that the new position of the hooked block has been persisted. - await page.reload(); - await expect - .poll( editor.getBlocks ) - .toMatchObject( [ - expectedHookedBlockLastChild, - { name: 'core/freeform' }, - ] ); + await test.step( "Hooked block isn't duplicated in the editor after a reload", async () => { + await page.reload(); + await expect + .poll( editor.getBlocks ) + .not.toMatchObject( [ + { name: 'core/freeform' }, + expectedHookedBlockLastChild, + expectedHookedBlockLastChild, + ] ); + } ); - // Verify that the frontend reflects the changes made in the editor. - await page.goto( `/?p=${ containerPost.id }` ); - await expect( - page.locator( '.entry-content > *' ) - ).toHaveClass( [ - getHookedBlockClassName( 'last_child', blockType ) + - ' wp-block-paragraph', - 'dummy-classic-heading', - 'dummy-classic-paragraph', - ] ); + await test.step( 'Moving the last hooked block is persisted upon save', async () => { + const hookedBlock = editor.canvas.getByText( + getHookedBlockContent( 'last_child', blockType ) + ); + await editor.selectBlocks( hookedBlock ); + await editor.clickBlockToolbarButton( 'Move up' ); + + // Save updated post. + const saveButton = page + .getByRole( 'region', { name: 'Editor top bar' } ) + .getByRole( 'button', { name: 'Save', exact: true } ); + await saveButton.click(); + await page + .getByRole( 'button', { name: 'Dismiss this notice' } ) + .filter( { hasText: 'updated' } ) + .waitFor(); + + // Reload and verify that the new position of the hooked block has been persisted. + await page.reload(); + await expect + .poll( editor.getBlocks ) + .toMatchObject( [ + expectedHookedBlockLastChild, + { name: 'core/freeform' }, + ] ); + } ); + + await test.step( 'Frontend reflects the changes made in the editor', async () => { + // Verify that the frontend reflects the changes made in the editor. + await page.goto( `/?p=${ containerPost.id }` ); + await expect( + page.locator( '.entry-content > *' ) + ).toHaveClass( [ + getHookedBlockClassName( 'last_child', blockType ) + + ' wp-block-paragraph', + 'dummy-classic-heading', + 'dummy-classic-paragraph', + ] ); + } ); } ); } ); } );