diff --git a/includes/blocks/simplified-summary/block.json b/includes/blocks/simplified-summary/block.json
new file mode 100644
index 000000000..0dc3fc6d7
--- /dev/null
+++ b/includes/blocks/simplified-summary/block.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://schemas.wp.org/trunk/block.json",
+ "apiVersion": 3,
+ "name": "edac/simplified-summary",
+ "title": "Simplified Summary",
+ "description": "Displays this post's simplified summary from Accessibility Checker.",
+ "category": "accessibility-checker",
+ "icon": "universal-access-alt",
+ "keywords": [ "accessibility", "summary", "readability" ],
+ "textdomain": "accessibility-checker",
+ "usesContext": [ "postId" ],
+ "supports": {
+ "html": false,
+ "multiple": false,
+ "spacing": {
+ "margin": true
+ }
+ },
+ "editorScript": "edac-simplified-summary-block",
+ "editorStyle": "edac-simplified-summary-block-editor"
+}
diff --git a/includes/classes/Blocks/SimplifiedSummaryBlock.php b/includes/classes/Blocks/SimplifiedSummaryBlock.php
new file mode 100644
index 000000000..f35b34ec1
--- /dev/null
+++ b/includes/classes/Blocks/SimplifiedSummaryBlock.php
@@ -0,0 +1,182 @@
+ self::CATEGORY,
+ 'title' => esc_html__( 'Accessibility Checker', 'accessibility-checker' ),
+ 'icon' => null,
+ ];
+
+ return $categories;
+ }
+
+ /**
+ * Register the block and its editor assets.
+ *
+ * The build does not generate *.asset.php files, so the editor script is
+ * registered here with an explicit dependency list and block.json
+ * references the handle rather than a file.
+ *
+ * @since 1.xx.x
+ *
+ * @return void
+ */
+ public function register() {
+ if ( \WP_Block_Type_Registry::get_instance()->is_registered( self::BLOCK_NAME ) ) {
+ return;
+ }
+
+ wp_register_script(
+ self::SCRIPT_HANDLE,
+ plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/simplifiedSummaryBlock.bundle.js',
+ [ 'wp-blocks', 'wp-element', 'wp-i18n', 'wp-block-editor' ],
+ EDAC_VERSION,
+ true
+ );
+
+ wp_set_script_translations(
+ self::SCRIPT_HANDLE,
+ 'accessibility-checker',
+ plugin_dir_path( EDAC_PLUGIN_FILE ) . 'languages'
+ );
+
+ wp_localize_script(
+ self::SCRIPT_HANDLE,
+ 'edacSimplifiedSummaryBlock',
+ [
+ /** This filter is documented in includes/classes/class-simplified-summary.php */
+ 'heading' => apply_filters(
+ 'edac_filter_simplified_summary_heading',
+ esc_html__( 'Simplified Summary', 'accessibility-checker' )
+ ),
+ ]
+ );
+
+ wp_register_style(
+ self::STYLE_HANDLE,
+ plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/css/simplifiedSummaryBlock.css',
+ [],
+ EDAC_VERSION
+ );
+
+ register_block_type(
+ EDAC_PLUGIN_DIR . 'includes/blocks/simplified-summary',
+ [ 'render_callback' => [ $this, 'render' ] ]
+ );
+ }
+
+ /**
+ * Render the block on the front end.
+ *
+ * Uses the postId block context when available (FSE templates, Query
+ * Loop) and falls back to the global post. Renders regardless of the
+ * edac_simplified_summary_prompt option because manual placement is a
+ * deliberate act, matching edac_get_simplified_summary().
+ *
+ * @since 1.xx.x
+ *
+ * @param array $attributes The block attributes.
+ * @param string $content The block content.
+ * @param WP_Block|null $block The block instance.
+ * @return string
+ */
+ public function render( $attributes, $content, $block = null ) {
+ $post_id = ( $block instanceof WP_Block && isset( $block->context['postId'] ) )
+ ? (int) $block->context['postId']
+ : (int) get_the_ID();
+
+ if ( ! $post_id ) {
+ return '';
+ }
+
+ $markup = ( new Simplified_Summary() )->simplified_summary_markup( $post_id );
+
+ if ( ! $markup ) {
+ return '';
+ }
+
+ // The wrapper carries the block supports output (margin classes/styles).
+ // get_block_wrapper_attributes() requires an active block render; guard
+ // against direct calls to this callback outside of one.
+ $wrapper_attributes = null !== \WP_Block_Supports::$block_to_render
+ ? get_block_wrapper_attributes()
+ : 'class="wp-block-edac-simplified-summary"';
+
+ return sprintf( '
%s
', $wrapper_attributes, $markup );
+ }
+}
diff --git a/includes/classes/Shortcodes/SimplifiedSummaryShortcode.php b/includes/classes/Shortcodes/SimplifiedSummaryShortcode.php
new file mode 100644
index 000000000..5a94525c1
--- /dev/null
+++ b/includes/classes/Shortcodes/SimplifiedSummaryShortcode.php
@@ -0,0 +1,75 @@
+ 0 ],
+ $atts,
+ self::SHORTCODE
+ );
+
+ $explicit_post_id = absint( $atts['post_id'] );
+
+ if ( $explicit_post_id && ! is_post_publicly_viewable( $explicit_post_id ) ) {
+ return '';
+ }
+
+ $post_id = $explicit_post_id ? $explicit_post_id : (int) get_the_ID();
+
+ if ( ! $post_id ) {
+ return '';
+ }
+
+ return ( new Simplified_Summary() )->simplified_summary_markup( $post_id );
+ }
+}
diff --git a/includes/classes/class-plugin.php b/includes/classes/class-plugin.php
index ee40dc785..303dedeb9 100644
--- a/includes/classes/class-plugin.php
+++ b/includes/classes/class-plugin.php
@@ -11,7 +11,9 @@
use EDAC\Admin\Meta_Boxes;
use EDAC\Admin\Orphaned_Issues_Cleanup;
use EqualizeDigital\AccessibilityChecker\Admin\AdminPage\AccessibilityReportsPage;
+use EqualizeDigital\AccessibilityChecker\Blocks\SimplifiedSummaryBlock;
use EqualizeDigital\AccessibilityChecker\MyDot\Connector;
+use EqualizeDigital\AccessibilityChecker\Shortcodes\SimplifiedSummaryShortcode;
use EqualizeDigital\AccessibilityChecker\WPCLI\BootstrapCLI;
use EqualizeDigital\AccessibilityChecker\Fixes\FixesManager;
@@ -47,6 +49,13 @@ public function __construct() {
$cleanup = new Orphaned_Issues_Cleanup();
$cleanup->init_hooks();
+ // The block and shortcode must register in admin (for the editor) and on the front end.
+ $simplified_summary_block = new SimplifiedSummaryBlock();
+ $simplified_summary_block->init_hooks();
+
+ $simplified_summary_shortcode = new SimplifiedSummaryShortcode();
+ $simplified_summary_shortcode->init_hooks();
+
$this->register_fixes_manager();
$this->register_sr_only_meta_hooks();
diff --git a/includes/classes/class-simplified-summary.php b/includes/classes/class-simplified-summary.php
index ca3fc6e5b..93ddd1c60 100644
--- a/includes/classes/class-simplified-summary.php
+++ b/includes/classes/class-simplified-summary.php
@@ -40,6 +40,9 @@ public function output_simplified_summary( $content ) {
if ( 'none' === $simplified_summary_prompt ) {
return $content;
}
+ if ( $this->is_manually_placed( get_the_ID() ) ) {
+ return $content;
+ }
$simplified_summary = $this->simplified_summary_markup( get_the_ID() );
$simplified_summary_position = get_option( 'edac_simplified_summary_position', $default = false );
@@ -54,6 +57,58 @@ public function output_simplified_summary( $content ) {
return $content;
}
+ /**
+ * Check whether the simplified summary has been manually placed for a post.
+ *
+ * Detects the edac/simplified-summary block or [edac_simplified_summary]
+ * shortcode in the post content, and the block in the current block theme
+ * template. Blocks nested inside template parts or synced patterns cannot
+ * be detected by has_block(); the filter below is the escape hatch for
+ * those cases.
+ *
+ * @since 1.xx.x
+ *
+ * @param int|\WP_Post|null $post Post ID or post object.
+ * @return bool
+ */
+ public function is_manually_placed( $post = null ): bool {
+ $manually_placed = false;
+
+ $post = get_post( $post );
+ if ( $post instanceof \WP_Post ) {
+ if (
+ has_block( 'edac/simplified-summary', $post ) ||
+ has_shortcode( (string) $post->post_content, 'edac_simplified_summary' )
+ ) {
+ $manually_placed = true;
+ }
+ }
+
+ // Set by WordPress when rendering a block theme template.
+ global $_wp_current_template_content;
+ if (
+ ! $manually_placed &&
+ ! empty( $_wp_current_template_content ) &&
+ (
+ has_block( 'edac/simplified-summary', $_wp_current_template_content ) ||
+ has_shortcode( $_wp_current_template_content, 'edac_simplified_summary' )
+ )
+ ) {
+ $manually_placed = true;
+ }
+
+ /**
+ * Filter whether the simplified summary is manually placed, which
+ * suppresses the automatic insertion on the_content.
+ *
+ * @since 1.xx.x
+ *
+ * @param bool $manually_placed Whether the summary is manually placed.
+ * @param \WP_Post|null $post The post being checked.
+ */
+ return apply_filters( 'edac_filter_simplified_summary_is_manually_placed', $manually_placed, $post );
+ }
+
/**
* Simplified summary markup
*
diff --git a/readme.txt b/readme.txt
index 8854adaec..8f148306b 100644
--- a/readme.txt
+++ b/readme.txt
@@ -168,6 +168,7 @@ Current settings in the free plugin include:
* Control if you want scans to run on both pages and posts.
* Control when the plugin prompts for a simplified summary.
* Choose the position of the simplified summary above content, below content, or manually in a template.
+* Place the simplified summary anywhere with the Simplified Summary block or the `[edac_simplified_summary]` shortcode (accepts an optional `post_id` attribute); manual placement automatically disables the automatic insertion for that post.
* Add footer accessibility statement.
* Choose positioning for the front-end Accessibility Checker.
* Show or hide the Accessibility Checker metabox in the block editor.
diff --git a/src/simplifiedSummaryBlock/edit.js b/src/simplifiedSummaryBlock/edit.js
new file mode 100644
index 000000000..e4def03de
--- /dev/null
+++ b/src/simplifiedSummaryBlock/edit.js
@@ -0,0 +1,37 @@
+import { useBlockProps } from '@wordpress/block-editor';
+import { __ } from '@wordpress/i18n';
+
+/**
+ * Editor view for the simplified summary block.
+ *
+ * The summary is authored in the Accessibility Checker sidebar panel, not in
+ * the block, so the editor shows a static placeholder mirroring the front end
+ * markup shape. The heading is localized server-side with the
+ * edac_filter_simplified_summary_heading filter applied, so a custom heading
+ * (a pro setting) previews exactly as it renders on the front end.
+ *
+ * @return {Object} The block edit component.
+ */
+const Edit = () => {
+ const blockProps = useBlockProps( {
+ className: 'edac-simplified-summary',
+ } );
+
+ const heading =
+ window.edacSimplifiedSummaryBlock?.heading ||
+ __( 'Simplified Summary', 'accessibility-checker' );
+
+ return (
+
+
{ heading }
+
+ { __(
+ 'The simplified summary for this post will display here. Write the summary in the Readability section of the Accessibility Checker sidebar or meta box.',
+ 'accessibility-checker'
+ ) }
+
+
+ );
+};
+
+export default Edit;
diff --git a/src/simplifiedSummaryBlock/index.js b/src/simplifiedSummaryBlock/index.js
new file mode 100644
index 000000000..07064e8eb
--- /dev/null
+++ b/src/simplifiedSummaryBlock/index.js
@@ -0,0 +1,11 @@
+import { registerBlockType } from '@wordpress/blocks';
+import Edit from './edit';
+
+/**
+ * The block is dynamic: metadata comes from block.json server-side and the
+ * front end output is produced by a PHP render callback, so save returns null.
+ */
+registerBlockType( 'edac/simplified-summary', {
+ edit: Edit,
+ save: () => null,
+} );
diff --git a/src/simplifiedSummaryBlock/sass/simplified-summary-block.scss b/src/simplifiedSummaryBlock/sass/simplified-summary-block.scss
new file mode 100644
index 000000000..c356b7361
--- /dev/null
+++ b/src/simplifiedSummaryBlock/sass/simplified-summary-block.scss
@@ -0,0 +1,9 @@
+// Editor-only styles for the simplified summary block placeholder.
+.edac-simplified-summary {
+ .edac-simplified-summary-block__placeholder {
+ border: 1px dashed currentcolor;
+ border-radius: 2px;
+ opacity: 0.62;
+ padding: 0.75em 1em;
+ }
+}
diff --git a/tests/jest/simplifiedSummaryBlock/index.test.js b/tests/jest/simplifiedSummaryBlock/index.test.js
new file mode 100644
index 000000000..65340285a
--- /dev/null
+++ b/tests/jest/simplifiedSummaryBlock/index.test.js
@@ -0,0 +1,65 @@
+/**
+ * Tests for the simplified summary block registration and edit component.
+ *
+ * @wordpress/blocks and @wordpress/block-editor are webpack externals and not
+ * installed as packages, so they are mocked here.
+ */
+import { renderReact } from '../helpers/renderReact';
+
+jest.mock( '@wordpress/blocks', () => ( {
+ registerBlockType: jest.fn(),
+} ), { virtual: true } );
+
+jest.mock( '@wordpress/block-editor', () => ( {
+ useBlockProps: jest.fn( ( props ) => props ),
+} ), { virtual: true } );
+
+import { registerBlockType } from '@wordpress/blocks';
+import Edit from '../../../src/simplifiedSummaryBlock/edit';
+import '../../../src/simplifiedSummaryBlock/index';
+
+describe( 'simplified summary block registration', () => {
+ test( 'registers the edac/simplified-summary block', () => {
+ expect( registerBlockType ).toHaveBeenCalledTimes( 1 );
+ expect( registerBlockType ).toHaveBeenCalledWith(
+ 'edac/simplified-summary',
+ expect.objectContaining( {
+ edit: Edit,
+ save: expect.any( Function ),
+ } ),
+ );
+ } );
+
+ test( 'save returns null so the block is dynamic', () => {
+ const { save } = registerBlockType.mock.calls[ 0 ][ 1 ];
+ expect( save() ).toBeNull();
+ } );
+} );
+
+describe( 'Edit', () => {
+ afterEach( () => {
+ delete window.edacSimplifiedSummaryBlock;
+ } );
+
+ test( 'renders the default heading and placeholder text', () => {
+ const { container, unmount } = renderReact( );
+ expect( container.querySelector( 'h2' ).textContent ).toBe( 'Simplified Summary' );
+ expect(
+ container.querySelector( '.edac-simplified-summary-block__placeholder' ).textContent,
+ ).toContain( 'will display here' );
+ unmount();
+ } );
+
+ test( 'renders the localized heading when provided', () => {
+ window.edacSimplifiedSummaryBlock = { heading: 'TL;DR' };
+ const { container, unmount } = renderReact( );
+ expect( container.querySelector( 'h2' ).textContent ).toBe( 'TL;DR' );
+ unmount();
+ } );
+
+ test( 'applies the frontend wrapper class via block props', () => {
+ const { container, unmount } = renderReact( );
+ expect( container.querySelector( '.edac-simplified-summary' ) ).not.toBeNull();
+ unmount();
+ } );
+} );
diff --git a/tests/phpunit/includes/classes/Blocks/SimplifiedSummaryBlockTest.php b/tests/phpunit/includes/classes/Blocks/SimplifiedSummaryBlockTest.php
new file mode 100644
index 000000000..8b871779f
--- /dev/null
+++ b/tests/phpunit/includes/classes/Blocks/SimplifiedSummaryBlockTest.php
@@ -0,0 +1,175 @@
+block = new SimplifiedSummaryBlock();
+
+ // The registry persists across tests while the scripts registry resets,
+ // so unregister first to exercise a full registration every time.
+ if ( WP_Block_Type_Registry::get_instance()->is_registered( SimplifiedSummaryBlock::BLOCK_NAME ) ) {
+ unregister_block_type( SimplifiedSummaryBlock::BLOCK_NAME );
+ }
+ $this->block->register();
+ }
+
+ /**
+ * Tests that the block and its editor script are registered.
+ *
+ * @return void
+ */
+ public function test_register_registers_block_and_script() {
+ $this->assertTrue( WP_Block_Type_Registry::get_instance()->is_registered( SimplifiedSummaryBlock::BLOCK_NAME ) );
+ $this->assertTrue( wp_script_is( SimplifiedSummaryBlock::SCRIPT_HANDLE, 'registered' ) );
+ $this->assertTrue( wp_style_is( SimplifiedSummaryBlock::STYLE_HANDLE, 'registered' ) );
+ }
+
+ /**
+ * Tests that the block category is registered once and the block uses it.
+ *
+ * @return void
+ */
+ public function test_register_block_category() {
+ $categories = $this->block->register_block_category( [] );
+ $this->assertContains( SimplifiedSummaryBlock::CATEGORY, wp_list_pluck( $categories, 'slug' ) );
+
+ // Running the filter again must not duplicate the category.
+ $categories = $this->block->register_block_category( $categories );
+ $this->assertCount( 1, $categories );
+
+ $block_type = WP_Block_Type_Registry::get_instance()->get_registered( SimplifiedSummaryBlock::BLOCK_NAME );
+ $this->assertSame( SimplifiedSummaryBlock::CATEGORY, $block_type->category );
+ }
+
+ /**
+ * Tests that render uses the postId block context.
+ *
+ * @return void
+ */
+ public function test_render_uses_post_id_context() {
+ $post_id = self::factory()->post->create();
+ update_post_meta( $post_id, '_edac_simplified_summary', 'Context summary.' );
+
+ $block = new WP_Block(
+ [ 'blockName' => SimplifiedSummaryBlock::BLOCK_NAME ],
+ [ 'postId' => $post_id ]
+ );
+
+ $output = $this->block->render( [], '', $block );
+ $this->assertStringContainsString( 'Context summary.', $output );
+ $this->assertStringContainsString( 'edac-simplified-summary', $output );
+ }
+
+ /**
+ * Tests that render falls back to the global post without context.
+ *
+ * @return void
+ */
+ public function test_render_falls_back_to_global_post() {
+ $post_id = self::factory()->post->create();
+ update_post_meta( $post_id, '_edac_simplified_summary', 'Global post summary.' );
+
+ global $post;
+ $post = get_post( $post_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Setting up the loop for the test.
+ setup_postdata( $post );
+
+ $block = new WP_Block( [ 'blockName' => SimplifiedSummaryBlock::BLOCK_NAME ] );
+
+ $output = $this->block->render( [], '', $block );
+ $this->assertStringContainsString( 'Global post summary.', $output );
+ }
+
+ /**
+ * Tests that render returns an empty string when there is no summary.
+ *
+ * @return void
+ */
+ public function test_render_returns_empty_string_without_summary() {
+ $post_id = self::factory()->post->create();
+
+ $block = new WP_Block(
+ [ 'blockName' => SimplifiedSummaryBlock::BLOCK_NAME ],
+ [ 'postId' => $post_id ]
+ );
+
+ $this->assertSame( '', $this->block->render( [], '', $block ) );
+ }
+
+ /**
+ * Tests that render returns an empty string when there is no post at all.
+ *
+ * @return void
+ */
+ public function test_render_returns_empty_string_without_post() {
+ global $post;
+ $post = null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Clearing the loop for the test.
+
+ $block = new WP_Block( [ 'blockName' => SimplifiedSummaryBlock::BLOCK_NAME ] );
+
+ $this->assertSame( '', $this->block->render( [], '', $block ) );
+ }
+
+ /**
+ * Tests that the block renders end-to-end through do_blocks.
+ *
+ * @return void
+ */
+ public function test_block_renders_through_do_blocks() {
+ $post_id = self::factory()->post->create();
+ update_post_meta( $post_id, '_edac_simplified_summary', 'Do blocks summary.' );
+
+ global $post;
+ $post = get_post( $post_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Setting up the loop for the test.
+ setup_postdata( $post );
+
+ $output = do_blocks( '' );
+ $this->assertStringContainsString( 'Do blocks summary.', $output );
+ $this->assertStringContainsString( 'wp-block-edac-simplified-summary', $output );
+ }
+
+ /**
+ * Tests that the block renders even when the prompt option is set to none.
+ *
+ * Manual placement is a deliberate act and is not gated by the
+ * edac_simplified_summary_prompt option.
+ *
+ * @return void
+ */
+ public function test_render_ignores_prompt_none_option() {
+ update_option( 'edac_simplified_summary_prompt', 'none' );
+
+ $post_id = self::factory()->post->create();
+ update_post_meta( $post_id, '_edac_simplified_summary', 'Prompt none summary.' );
+
+ $block = new WP_Block(
+ [ 'blockName' => SimplifiedSummaryBlock::BLOCK_NAME ],
+ [ 'postId' => $post_id ]
+ );
+
+ $output = $this->block->render( [], '', $block );
+ $this->assertStringContainsString( 'Prompt none summary.', $output );
+ }
+}
diff --git a/tests/phpunit/includes/classes/Shortcodes/SimplifiedSummaryShortcodeTest.php b/tests/phpunit/includes/classes/Shortcodes/SimplifiedSummaryShortcodeTest.php
new file mode 100644
index 000000000..5563d60aa
--- /dev/null
+++ b/tests/phpunit/includes/classes/Shortcodes/SimplifiedSummaryShortcodeTest.php
@@ -0,0 +1,99 @@
+init_hooks();
+ }
+ }
+
+ /**
+ * Tests that the shortcode renders the summary for the current post.
+ *
+ * @return void
+ */
+ public function test_shortcode_renders_summary_for_current_post() {
+ $post_id = self::factory()->post->create();
+ update_post_meta( $post_id, '_edac_simplified_summary', 'Shortcode summary.' );
+
+ global $post;
+ $post = get_post( $post_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Setting up the loop for the test.
+ setup_postdata( $post );
+
+ $output = do_shortcode( '[edac_simplified_summary]' );
+ $this->assertStringContainsString( 'Shortcode summary.', $output );
+ $this->assertStringContainsString( 'edac-simplified-summary', $output );
+ }
+
+ /**
+ * Tests that the shortcode accepts an explicit post_id attribute.
+ *
+ * @return void
+ */
+ public function test_shortcode_accepts_post_id_attribute() {
+ $post_id = self::factory()->post->create();
+ update_post_meta( $post_id, '_edac_simplified_summary', 'Attribute summary.' );
+
+ $output = do_shortcode( '[edac_simplified_summary post_id="' . $post_id . '"]' );
+ $this->assertStringContainsString( 'Attribute summary.', $output );
+ }
+
+ /**
+ * Tests that the shortcode returns an empty string when the post has no summary.
+ *
+ * @return void
+ */
+ public function test_shortcode_returns_empty_string_without_summary() {
+ $post_id = self::factory()->post->create();
+
+ $output = do_shortcode( '[edac_simplified_summary post_id="' . $post_id . '"]' );
+ $this->assertSame( '', $output );
+ }
+
+ /**
+ * Tests that the shortcode does not expose summaries of non-public posts.
+ *
+ * @return void
+ */
+ public function test_shortcode_does_not_render_non_public_posts() {
+ $draft_id = self::factory()->post->create( [ 'post_status' => 'draft' ] );
+ update_post_meta( $draft_id, '_edac_simplified_summary', 'Draft summary.' );
+
+ $private_id = self::factory()->post->create( [ 'post_status' => 'private' ] );
+ update_post_meta( $private_id, '_edac_simplified_summary', 'Private summary.' );
+
+ $this->assertSame( '', do_shortcode( '[edac_simplified_summary post_id="' . $draft_id . '"]' ) );
+ $this->assertSame( '', do_shortcode( '[edac_simplified_summary post_id="' . $private_id . '"]' ) );
+ }
+
+ /**
+ * Tests that the shortcode returns an empty string outside the loop with no post_id.
+ *
+ * @return void
+ */
+ public function test_shortcode_returns_empty_string_without_post() {
+ global $post;
+ $post = null; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Clearing the loop for the test.
+
+ $output = do_shortcode( '[edac_simplified_summary]' );
+ $this->assertSame( '', $output );
+ }
+}
diff --git a/tests/phpunit/includes/classes/SimplifiedSummaryTest.php b/tests/phpunit/includes/classes/SimplifiedSummaryTest.php
index 5044cf4e2..f52f137de 100644
--- a/tests/phpunit/includes/classes/SimplifiedSummaryTest.php
+++ b/tests/phpunit/includes/classes/SimplifiedSummaryTest.php
@@ -34,6 +34,19 @@ public function setUp(): void {
$this->simplified_summary = new Simplified_Summary();
}
+ /**
+ * Clean up the test fixture.
+ *
+ * The core test framework does not reset the block template global.
+ *
+ * @return void
+ */
+ public function tearDown(): void {
+ global $_wp_current_template_content;
+ $_wp_current_template_content = null;
+ parent::tearDown();
+ }
+
/**
* Tests output of simplified_summary_markup with a summary.
*
@@ -67,4 +80,108 @@ public function test_simplified_summary_markup_without_summary() {
$output = $this->simplified_summary->simplified_summary_markup( $post_id );
$this->assertEmpty( $output );
}
+
+ /**
+ * Creates a post with a summary and sets it up as the current global post.
+ *
+ * @param string $content The post content.
+ * @return int The post ID.
+ */
+ private function create_post_in_loop( $content = 'Post content.' ) {
+ $post_id = self::factory()->post->create( [ 'post_content' => $content ] );
+ update_post_meta( $post_id, '_edac_simplified_summary', 'This is a simplified summary.' );
+ update_option( 'edac_simplified_summary_prompt', 'always' );
+ update_option( 'edac_simplified_summary_position', 'after' );
+
+ global $post;
+ $post = get_post( $post_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Setting up the loop for the test.
+ setup_postdata( $post );
+
+ return $post_id;
+ }
+
+ /**
+ * Tests that the summary is auto-inserted when not manually placed.
+ *
+ * @return void
+ */
+ public function test_output_simplified_summary_auto_inserts_when_not_manually_placed() {
+ $this->create_post_in_loop();
+
+ $output = $this->simplified_summary->output_simplified_summary( 'Post content.' );
+ $this->assertStringContainsString( 'edac-simplified-summary', $output );
+ }
+
+ /**
+ * Tests that auto-insertion is suppressed when the block is in the post content.
+ *
+ * @return void
+ */
+ public function test_output_simplified_summary_suppressed_when_block_in_content() {
+ $this->create_post_in_loop( 'Post content.
' );
+
+ $output = $this->simplified_summary->output_simplified_summary( 'Post content.' );
+ $this->assertStringNotContainsString( 'edac-simplified-summary', $output );
+ }
+
+ /**
+ * Tests that auto-insertion is suppressed when the shortcode is in the post content.
+ *
+ * @return void
+ */
+ public function test_output_simplified_summary_suppressed_when_shortcode_in_content() {
+ $this->create_post_in_loop( 'Post content. [edac_simplified_summary]' );
+
+ $output = $this->simplified_summary->output_simplified_summary( 'Post content.' );
+ $this->assertStringNotContainsString( 'edac-simplified-summary', $output );
+ }
+
+ /**
+ * Tests that auto-insertion is suppressed when the block is in the current block theme template.
+ *
+ * @return void
+ */
+ public function test_output_simplified_summary_suppressed_when_block_in_template() {
+ $this->create_post_in_loop();
+
+ global $_wp_current_template_content;
+ $_wp_current_template_content = '';
+
+ $output = $this->simplified_summary->output_simplified_summary( 'Post content.' );
+
+ $this->assertStringNotContainsString( 'edac-simplified-summary', $output );
+ }
+
+ /**
+ * Tests that auto-insertion is suppressed when the shortcode is in the current block theme template.
+ *
+ * Covers a core Shortcode block containing [edac_simplified_summary] in an FSE template.
+ *
+ * @return void
+ */
+ public function test_output_simplified_summary_suppressed_when_shortcode_in_template() {
+ $this->create_post_in_loop();
+
+ global $_wp_current_template_content;
+ $_wp_current_template_content = '[edac_simplified_summary]';
+
+ $output = $this->simplified_summary->output_simplified_summary( 'Post content.' );
+
+ $this->assertStringNotContainsString( 'edac-simplified-summary', $output );
+ }
+
+ /**
+ * Tests that the manually placed filter can suppress auto-insertion.
+ *
+ * @return void
+ */
+ public function test_output_simplified_summary_suppressed_by_filter() {
+ $this->create_post_in_loop();
+
+ add_filter( 'edac_filter_simplified_summary_is_manually_placed', '__return_true' );
+ $output = $this->simplified_summary->output_simplified_summary( 'Post content.' );
+ remove_filter( 'edac_filter_simplified_summary_is_manually_placed', '__return_true' );
+
+ $this->assertStringNotContainsString( 'edac-simplified-summary', $output );
+ }
}
diff --git a/webpack.config.js b/webpack.config.js
index 82902f4cb..c294ecc88 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -43,6 +43,10 @@ module.exports = {
'./src/srOnlyFormat/index.js',
'./src/srOnlyFormat/sass/sr-only-format.scss',
],
+ simplifiedSummaryBlock: [
+ './src/simplifiedSummaryBlock/index.js',
+ './src/simplifiedSummaryBlock/sass/simplified-summary-block.scss',
+ ],
sharedComponents: {
import: './src/sharedComponents/index.js',
library: {
@@ -126,6 +130,7 @@ module.exports = {
externals: {
// Exclude WordPress core scripts and styles from the build.
'@wordpress/i18n': [ 'wp', 'i18n' ],
+ '@wordpress/blocks': [ 'wp', 'blocks' ],
'@wordpress/plugins': [ 'wp', 'plugins' ],
'@wordpress/editor': [ 'wp', 'editor' ],
'@wordpress/edit-post': [ 'wp', 'editPost' ],