diff --git a/projects/plugins/jetpack/changelog/add-featured-image-duplicate-notice b/projects/plugins/jetpack/changelog/add-featured-image-duplicate-notice
new file mode 100644
index 000000000000..57f987327f91
--- /dev/null
+++ b/projects/plugins/jetpack/changelog/add-featured-image-duplicate-notice
@@ -0,0 +1,4 @@
+Significance: minor
+Type: enhancement
+
+Editor: Let writers know when the featured image is also in the post, and hide it on the post where the theme supports that.
diff --git a/projects/plugins/jetpack/extensions/index.json b/projects/plugins/jetpack/extensions/index.json
index 7c68ef9c4ddc..2d6c1941d078 100644
--- a/projects/plugins/jetpack/extensions/index.json
+++ b/projects/plugins/jetpack/extensions/index.json
@@ -13,6 +13,7 @@
"contact-info",
"donations",
"eventbrite",
+ "featured-image-duplicate",
"field-file",
"gif",
"google-calendar",
diff --git a/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/components.tsx b/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/components.tsx
new file mode 100644
index 000000000000..039971c85f95
--- /dev/null
+++ b/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/components.tsx
@@ -0,0 +1,142 @@
+import { CheckboxControl, Notice } from '@wordpress/components';
+import { createHigherOrderComponent } from '@wordpress/compose';
+import { useSelect } from '@wordpress/data';
+import { PostPreviewButton, store as editorStore } from '@wordpress/editor';
+import { useEffect } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { HIDE_META_KEY, useFeaturedImageDuplicate } from '.';
+import type { ComponentType } from 'react';
+import './editor.scss';
+
+/**
+ * Notice and checkbox shown under the featured image.
+ *
+ * @return Content, or null when there's nothing to show.
+ */
+function FeaturedImageDuplicate() {
+ const { postType, isHidden, showNotice, showCheckbox, dismiss, setHidden } =
+ useFeaturedImageDuplicate();
+
+ if ( ! showNotice && ! showCheckbox ) {
+ return null;
+ }
+
+ // Separate objects, so minification can't merge the __() calls and break string extraction.
+ const copy =
+ postType === 'page'
+ ? {
+ notice: __( 'This image is also in your page, so it may appear twice.', 'jetpack' ),
+ label: __( 'Hide featured image on this page', 'jetpack' ),
+ help: __( 'It still shows as the thumbnail and when the page is shared.', 'jetpack' ),
+ }
+ : {
+ notice: __( 'This image is also in your post, so it may appear twice.', 'jetpack' ),
+ label: __( 'Hide featured image on this post', 'jetpack' ),
+ help: __( 'It still shows as the thumbnail and when the post is shared.', 'jetpack' ),
+ };
+
+ return (
+
+ { showNotice && (
+
+
{ copy.notice }
+ { /* @ts-expect-error -- Its JSDoc types mark the optional props as required. */ }
+
+
+ ) }
+ { showCheckbox && (
+
+ ) }
+
+ );
+}
+
+/**
+ * Adds the notice under core's featured image control.
+ */
+export const withDuplicateNotice = createHigherOrderComponent(
+ OriginalComponent => props => (
+ <>
+
+
+ >
+ ),
+ 'withDuplicateNotice' // Name for React DevTools.
+);
+
+/**
+ * Stops hiding the featured image once it's no longer in the content.
+ *
+ * Registered as an editor plugin so it runs even when the sidebar is closed.
+ *
+ * @return Nothing.
+ */
+export function HiddenSync() {
+ const { isHidden, isDuplicate, isResolved, setHidden } = useFeaturedImageDuplicate();
+
+ useEffect( () => {
+ if ( isHidden && isResolved && ! isDuplicate ) {
+ setHidden( false );
+ }
+ }, [ isHidden, isResolved, isDuplicate, setHidden ] );
+
+ return null;
+}
+
+type FeaturedImageBlockProps = {
+ context?: { postId?: number; queryId?: number };
+};
+
+/**
+ * The template's Featured Image block, hidden when this post hides its featured image.
+ *
+ * @param props - Props.
+ * @param props.BlockEdit - Original block edit component.
+ * @return The block, or null when hidden.
+ */
+function FeaturedImageBlock( {
+ BlockEdit,
+ ...props
+}: FeaturedImageBlockProps & { BlockEdit: ComponentType< FeaturedImageBlockProps > } ) {
+ const { postId, queryId } = props.context ?? {};
+ const isHidden = useSelect(
+ select => {
+ // Inside a Query Loop it's a list of posts, not this post's header.
+ if ( queryId !== undefined ) {
+ return false;
+ }
+ const editor = select( editorStore );
+ const meta = ( editor.getEditedPostAttribute( 'meta' ) ?? {} ) as Record< string, unknown >;
+ return postId === editor.getCurrentPostId() && !! meta[ HIDE_META_KEY ];
+ },
+ [ postId, queryId ]
+ );
+
+ return isHidden ? null : ;
+}
+
+/**
+ * Hides the template's Featured Image block in the editor too, matching the site.
+ */
+export const withHiddenFeaturedImageBlock = createHigherOrderComponent(
+ BlockEdit => props =>
+ props.name === 'core/post-featured-image' ? (
+
+ ) : (
+
+ ),
+ 'withHiddenFeaturedImageBlock'
+);
diff --git a/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/editor.js b/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/editor.js
new file mode 100644
index 000000000000..165857d89131
--- /dev/null
+++ b/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/editor.js
@@ -0,0 +1,14 @@
+import {
+ getJetpackExtensionAvailability,
+ registerJetpackPlugin,
+} from '@automattic/jetpack-shared-extension-utils';
+import { addFilter } from '@wordpress/hooks';
+import { HiddenSync, withDuplicateNotice, withHiddenFeaturedImageBlock } from './components';
+import { FEATURE_NAME } from '.';
+
+if ( getJetpackExtensionAvailability( FEATURE_NAME ).available ) {
+ addFilter( 'editor.PostFeaturedImage', `jetpack/${ FEATURE_NAME }`, withDuplicateNotice ); // Notice and checkbox under the featured image.
+ addFilter( 'editor.BlockEdit', `jetpack/${ FEATURE_NAME }`, withHiddenFeaturedImageBlock ); // Hide the template's Featured Image block when ticked.
+}
+
+registerJetpackPlugin( FEATURE_NAME, { render: HiddenSync } ); // Untick checkbox once the duplicate is gone.
diff --git a/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/editor.scss b/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/editor.scss
new file mode 100644
index 000000000000..f4016da6a9de
--- /dev/null
+++ b/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/editor.scss
@@ -0,0 +1,19 @@
+.jetpack-featured-image-duplicate {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.jetpack-featured-image-duplicate__notice {
+ margin: 0;
+
+ p {
+ margin-block: 0 4px;
+ margin-inline: 0;
+ }
+
+ // Core greys out the Preview link while it autosaves; keep it blue.
+ .components-button.is-link:is(:active, :disabled, [aria-disabled="true"]) {
+ color: var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9));
+ }
+}
diff --git a/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/featured-image-duplicate.php b/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/featured-image-duplicate.php
new file mode 100644
index 000000000000..eae1d09f0693
--- /dev/null
+++ b/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/featured-image-duplicate.php
@@ -0,0 +1,263 @@
+ 'boolean',
+ 'description' => __( 'Whether to hide the featured image on this post’s own page.', 'jetpack' ),
+ 'single' => true,
+ 'default' => false,
+ 'show_in_rest' => true,
+ 'revisions_enabled' => true, // Saved with autosaves, so Preview of a published post matches the checkbox.
+ 'auth_callback' => __NAMESPACE__ . '\can_edit_post_meta',
+ )
+ );
+ register_post_meta(
+ $post_type,
+ DISMISSED_META_KEY,
+ array(
+ 'type' => 'integer',
+ 'description' => __( 'Featured image ID whose duplicate notice was dismissed.', 'jetpack' ),
+ 'single' => true,
+ 'default' => 0,
+ 'show_in_rest' => true,
+ 'sanitize_callback' => 'absint',
+ 'auth_callback' => __NAMESPACE__ . '\can_edit_post_meta',
+ )
+ );
+ }
+}
+add_action( 'init', __NAMESPACE__ . '\register_meta_keys' );
+
+/**
+ * Whether hiding the featured image on this post's own page is known to work on the active theme.
+ *
+ * @param WP_Post|int $post Post or post ID.
+ * @return bool
+ */
+function is_hide_supported( $post ) {
+ $post = get_post( $post );
+ if ( ! $post instanceof WP_Post || ! in_array( $post->post_type, POST_TYPES, true ) ) {
+ return false;
+ }
+
+ if ( ! wp_is_block_theme() ) {
+ return classic_theme_supports_hiding( $post->post_type );
+ }
+
+ // Block themes: check the template hierarchy to see if the Featured Image block is present in the template WordPress renders for this post.
+ $hierarchy = get_template_hierarchy_for_post( $post );
+ $contents = wp_list_pluck( get_block_templates( array( 'slug__in' => $hierarchy ), 'wp_template' ), 'content', 'slug' ); // Slug => content.
+
+ // Several templates may match; walk the hierarchy in priority order to find the one WordPress renders.
+ foreach ( $hierarchy as $slug ) {
+ if ( isset( $contents[ $slug ] ) ) {
+ return blocks_contain_featured_image( parse_blocks( $contents[ $slug ] ) );
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Whether the classic theme declares Content Options featured image support for this post type.
+ *
+ * @param string $post_type Post type.
+ * @return bool
+ */
+function classic_theme_supports_hiding( $post_type ) {
+ $options = get_theme_support( 'jetpack-content-options' ); // Declared by the theme or Jetpack's compat file; no site owner action needed.
+ return ! empty( $options[0]['featured-images'][ $post_type ] );
+}
+
+/**
+ * Template slugs WordPress tries for this post, most specific first.
+ *
+ * @param WP_Post $post Post.
+ * @return string[]
+ */
+function get_template_hierarchy_for_post( WP_Post $post ): array {
+ if ( 'page' === $post->post_type ) {
+ $hierarchy = array( "page-{$post->post_name}", "page-{$post->ID}", 'page', 'singular', 'index' );
+ } else {
+ $hierarchy = array( "single-{$post->post_type}-{$post->post_name}", "single-{$post->post_type}", 'single', 'singular', 'index' );
+ }
+
+ $custom = get_page_template_slug( $post );
+ if ( $custom ) {
+ array_unshift( $hierarchy, $custom );
+ }
+
+ return $hierarchy;
+}
+
+/**
+ * Whether the blocks render the current post's featured image.
+ *
+ * Follows patterns and template parts; skips Query Loops, which show other posts.
+ *
+ * @param array $blocks Parsed blocks.
+ * @param int $depth Recursion depth.
+ * @return bool
+ */
+function blocks_contain_featured_image( array $blocks, $depth = 0 ): bool {
+ if ( $depth > 5 ) {
+ return false;
+ }
+
+ foreach ( resolve_pattern_blocks( $blocks ) as $block ) {
+ $name = $block['blockName'] ?? '';
+ $inner = $block['innerBlocks'] ?? array();
+
+ if ( 'core/post-featured-image' === $name ) {
+ return true;
+ }
+
+ if ( 'core/query' === $name ) {
+ continue;
+ }
+
+ if ( 'core/template-part' === $name && ! empty( $block['attrs']['slug'] ) ) {
+ $part = get_block_template( ( $block['attrs']['theme'] ?? get_stylesheet() ) . '//' . $block['attrs']['slug'], 'wp_template_part' );
+ $inner = $part ? parse_blocks( $part->content ) : array();
+ }
+
+ if ( $inner && blocks_contain_featured_image( $inner, $depth + 1 ) ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Tell the editor whether hiding works for the post being edited.
+ *
+ * @param array $flags Editor feature flags.
+ * @return array
+ */
+function add_editor_feature_flag( $flags ): array {
+ $flags['featured-image-hide-supported'] = is_hide_supported( get_post() );
+ return $flags;
+}
+add_filter( 'jetpack_block_editor_feature_flags', __NAMESPACE__ . '\add_editor_feature_flag' );
+
+/**
+ * Only filter thumbnail reads on pages that hide the featured image.
+ *
+ * @return void
+ */
+function maybe_filter_thumbnail_id() {
+ // Block themes hide the Featured Image block instead; see hide_featured_image_block().
+ if ( ! wp_is_block_theme() && should_hide_for_post( get_queried_object_id() ) ) {
+ // Late, since core overrides the thumbnail ID in _wp_preview_post_thumbnail_filter.
+ add_filter( 'get_post_metadata', __NAMESPACE__ . '\filter_thumbnail_id', PHP_INT_MAX, 3 );
+ }
+}
+add_action( 'template_redirect', __NAMESPACE__ . '\maybe_filter_thumbnail_id' ); // Fires on every front-end page load.
+
+/**
+ * Whether we're on this post's own page and the writer chose to hide its featured image there.
+ *
+ * @param int $post_id Post ID.
+ * @return bool
+ */
+function should_hide_for_post( $post_id ): bool {
+ return is_singular( POST_TYPES )
+ && get_queried_object_id() === $post_id
+ && (bool) get_post_meta( $post_id, HIDE_META_KEY, true );
+}
+
+/**
+ * Filter the post thumbnail ID to hide the featured image on post's own page.
+ *
+ * Outside the loop (Open Graph, schema) and inside blocks (Latest Posts) it's still returned.
+ *
+ * @param mixed $value Short-circuit value.
+ * @param int $object_id Post ID.
+ * @param string $meta_key Meta key.
+ * @return mixed
+ */
+function filter_thumbnail_id( $value, $object_id, $meta_key ) {
+ if (
+ '_thumbnail_id' !== $meta_key
+ || get_queried_object_id() !== (int) $object_id
+ || ! in_the_loop() // Not in the main loop.
+ || null !== WP_Block_Supports::$block_to_render // Inside a block, don't hide the thumbnail.
+ ) {
+ return $value;
+ }
+
+ return false;
+}
+
+/**
+ * Block themes: skip the template's Featured Image block on the post's own page.
+ *
+ * @param string $block_content Rendered block.
+ * @param array $block Parsed block.
+ * @param WP_Block $instance Block instance.
+ * @return string
+ */
+function hide_featured_image_block( $block_content, $block, $instance ): string {
+ $context = $instance->context; // Set from the template and parent blocks.
+
+ // Inside a Query Loop it's a list of posts, not this post's header.
+ if ( array_key_exists( 'queryId', $context ) || empty( $context['postId'] ) ) {
+ return $block_content;
+ }
+
+ return should_hide_for_post( (int) $context['postId'] ) ? '' : $block_content;
+}
+add_filter( 'render_block_core/post-featured-image', __NAMESPACE__ . '\hide_featured_image_block', 10, 3 );
diff --git a/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/index.ts b/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/index.ts
new file mode 100644
index 000000000000..3865eb9b1cee
--- /dev/null
+++ b/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/index.ts
@@ -0,0 +1,168 @@
+import { hasFeatureFlag } from '@automattic/jetpack-shared-extension-utils';
+import { store as coreStore } from '@wordpress/core-data';
+import { useDispatch, useSelect } from '@wordpress/data';
+import { store as editorStore } from '@wordpress/editor';
+import { useCallback } from '@wordpress/element';
+
+export const FEATURE_NAME = 'featured-image-duplicate';
+export const HIDE_META_KEY = '_jetpack_hide_featured_image';
+const DISMISSED_META_KEY = '_jetpack_featured_image_duplicate_dismissed';
+const POST_TYPES = [ 'post', 'page' ];
+
+const SIZE_SUFFIX = /-(\d+x\d+|scaled)(?=\.[a-z0-9]+$)/i; // Matches "-300x200" or "-scaled" before the file extension.
+const UPLOADS = '/wp-content/uploads'; // Matches the uploads path, which is the same for all sites.
+const PHOTON_HOST = /^i\d\.wp\.com$/; // Matches the Photon CDN host, which is the same for all sites.
+
+type EditorBlock = {
+ name: string;
+ attributes?: Record< string, unknown >;
+ innerBlocks?: EditorBlock[];
+};
+
+type MediaRecord = {
+ source_url?: string;
+ media_details?: { sizes?: Record< string, { source_url?: string } | undefined > };
+};
+
+type PostType = 'post' | 'page';
+
+/**
+ * Reduce an image URL to its upload path, without size suffix, CDN host or query.
+ *
+ * @param url - Image URL.
+ * @return Normalized path, or null.
+ */
+function normalizeImageUrl( url: unknown ): string | null {
+ if ( ! url || typeof url !== 'string' ) {
+ return null;
+ }
+
+ let parsed: URL;
+ try {
+ parsed = new URL( url, 'https://localhost' );
+ } catch {
+ return null;
+ }
+
+ let path = parsed.pathname;
+ if ( PHOTON_HOST.test( parsed.hostname ) ) {
+ path = path.replace( /^\/[^/]+/, '' );
+ }
+
+ const uploadsIndex = path.indexOf( UPLOADS + '/' );
+ if ( uploadsIndex !== -1 ) {
+ path = path.slice( uploadsIndex + UPLOADS.length );
+ }
+
+ try {
+ path = decodeURIComponent( path );
+ } catch {
+ // Keep the encoded path.
+ }
+
+ return path.replace( SIZE_SUFFIX, '' ).toLowerCase();
+}
+
+/**
+ * Normalized URLs of an attachment and all its sizes.
+ *
+ * @param media - Media REST record.
+ * @return Normalized paths.
+ */
+function getImageUrls( media?: MediaRecord ): Set< string > {
+ const urls = [
+ media?.source_url,
+ ...Object.values( media?.media_details?.sizes ?? {} ).map( size => size?.source_url ),
+ ];
+ return new Set( urls.map( normalizeImageUrl ).filter( ( url ): url is string => !! url ) );
+}
+
+/**
+ * Whether an Image block in the content (outside galleries) shows the featured image, by ID or URL.
+ *
+ * @param blocks - Editor blocks.
+ * @param id - Featured image ID.
+ * @param getUrls - Normalized featured image URLs, built on first use.
+ * @return Whether a copy was found.
+ */
+function blocksContainImage(
+ blocks: EditorBlock[] | undefined,
+ id: number,
+ getUrls: () => Set< string >
+): boolean {
+ for ( const { name: blockName, attributes = {}, innerBlocks } of blocks ?? [] ) {
+ // Images with an ID are matched by ID; only ID-less ones (e.g. pasted URLs) need a URL compare.
+ if (
+ blockName === 'core/image' &&
+ ( attributes.id
+ ? Number( attributes.id ) === id
+ : getUrls().has( normalizeImageUrl( attributes.url ) ?? '' ) )
+ ) {
+ return true;
+ }
+
+ // Galleries are a deliberate choice, so an image in one isn't treated as a duplicate.
+ if ( blockName !== 'core/gallery' && blocksContainImage( innerBlocks, id, getUrls ) ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * What to show about a duplicated featured image, plus actions to change it.
+ *
+ * Dismissal stores the image ID, so a new featured image brings the notice back.
+ *
+ * @return State and actions.
+ */
+export function useFeaturedImageDuplicate() {
+ const state = useSelect( select => {
+ const editor = select( editorStore );
+ const postType = editor.getCurrentPostType() as PostType;
+ const featuredId = editor.getEditedPostAttribute( 'featured_media' ) as number;
+ const meta = ( editor.getEditedPostAttribute( 'meta' ) ?? {} ) as Record< string, unknown >;
+ const isHidden = !! meta[ HIDE_META_KEY ];
+ const isApplicable = POST_TYPES.includes( postType ) && !! featuredId;
+
+ const query = { context: 'view' }; // Get the media record as it appears on the front end, not the editor.
+ let urls: Set< string > | undefined;
+ const getUrls = () =>
+ ( urls ??= getImageUrls( select( coreStore ).getMedia( featuredId, query ) as MediaRecord ) );
+
+ const isDuplicate =
+ isApplicable &&
+ blocksContainImage( editor.getEditorBlocks() as EditorBlock[], featuredId, getUrls );
+ const isDismissed = meta[ DISMISSED_META_KEY ] === featuredId;
+
+ return {
+ postType,
+ featuredId,
+ isHidden,
+ isDuplicate,
+ // URL matches need the media record, so don't trust a miss before it loads.
+ isResolved:
+ ! urls || select( coreStore ).hasFinishedResolution( 'getMedia', [ featuredId, query ] ),
+ showNotice: isDuplicate && ! isHidden && ! isDismissed,
+ showCheckbox:
+ hasFeatureFlag( 'featured-image-hide-supported' ) &&
+ isDuplicate &&
+ ( ! isDismissed || isHidden ),
+ };
+ }, [] );
+
+ const { editPost } = useDispatch( editorStore );
+ const { featuredId } = state;
+
+ const dismiss = useCallback(
+ () => editPost( { meta: { [ DISMISSED_META_KEY ]: featuredId } } ),
+ [ editPost, featuredId ]
+ );
+ const setHidden = useCallback(
+ ( hidden: boolean ) => editPost( { meta: { [ HIDE_META_KEY ]: hidden } } ),
+ [ editPost ]
+ );
+
+ return { ...state, dismiss, setHidden };
+}
diff --git a/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/test/components.test.tsx b/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/test/components.test.tsx
new file mode 100644
index 000000000000..8886270367fb
--- /dev/null
+++ b/projects/plugins/jetpack/extensions/plugins/featured-image-duplicate/test/components.test.tsx
@@ -0,0 +1,72 @@
+import { render, screen } from '@testing-library/react';
+import { HiddenSync, withHiddenFeaturedImageBlock } from '../components';
+
+let mockDuplicate: Record< string, unknown >;
+const mockSetHidden = jest.fn();
+let mockEditor: { postId: number; hidden: boolean };
+
+jest.mock( '..', () => ( {
+ HIDE_META_KEY: '_jetpack_hide_featured_image',
+ useFeaturedImageDuplicate: () => ( { ...mockDuplicate, setHidden: mockSetHidden } ),
+} ) );
+jest.mock( '@wordpress/editor', () => ( { store: 'core/editor', PostPreviewButton: () => null } ) );
+jest.mock( '@wordpress/data', () => {
+ const mocks = {
+ useSelect: selector =>
+ selector( () => ( {
+ getCurrentPostId: () => mockEditor.postId,
+ getEditedPostAttribute: () => ( { _jetpack_hide_featured_image: mockEditor.hidden } ),
+ } ) ),
+ };
+ // Keep the real registry so @wordpress/components' own stores still work.
+ return new Proxy( jest.requireActual( '@wordpress/data' ), {
+ get: ( target, property ) => mocks[ property ] ?? target[ property ],
+ } );
+} );
+
+describe( 'HiddenSync', () => {
+ beforeEach( () => jest.clearAllMocks() );
+
+ it.each( [
+ [
+ 'unticks once the duplicate is gone',
+ { isHidden: true, isResolved: true, isDuplicate: false },
+ true,
+ ],
+ [
+ 'keeps it while still duplicated',
+ { isHidden: true, isResolved: true, isDuplicate: true },
+ false,
+ ],
+ [
+ 'waits for the media record',
+ { isHidden: true, isResolved: false, isDuplicate: false },
+ false,
+ ],
+ ] )( '%s', ( _, state, expected ) => {
+ mockDuplicate = state;
+ render( );
+ expect( mockSetHidden.mock.calls ).toEqual( expected ? [ [ false ] ] : [] );
+ } );
+} );
+
+describe( 'withHiddenFeaturedImageBlock', () => {
+ const Block = withHiddenFeaturedImageBlock( () =>