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',
+ ] );
+ } );
} );
} );
} );