diff --git a/tests/phpunit/tests/blocks/wpApplyBlockContentFilters.php b/tests/phpunit/tests/blocks/wpApplyBlockContentFilters.php new file mode 100644 index 0000000000000..56ee579dab8ff --- /dev/null +++ b/tests/phpunit/tests/blocks/wpApplyBlockContentFilters.php @@ -0,0 +1,262 @@ +original_use_smilies = get_option( 'use_smilies' ); + $this->filtered_image_context = null; + + update_option( 'use_smilies', 1 ); + smilies_init(); + + register_block_type( + self::TEST_BLOCK_NAME, + array( + 'render_callback' => static function ( $attributes ) { + return sprintf( + '
%s
', + esc_html( $attributes['label'] ) + ); + }, + ) + ); + + register_block_type( + self::TEST_EXCEPTION_BLOCK_NAME, + array( + 'render_callback' => static function () { + throw new Exception( 'Block rendering failed.' ); + }, + ) + ); + } + + public function tear_down() { + remove_shortcode( self::TEST_SHORTCODE ); + wp_embed_unregister_handler( self::TEST_EMBED_HANDLER ); + remove_filter( 'wp_content_img_tag', array( $this, 'filter_content_image_tag' ) ); + + if ( WP_Block_Type_Registry::get_instance()->is_registered( self::TEST_BLOCK_NAME ) ) { + unregister_block_type( self::TEST_BLOCK_NAME ); + } + + if ( WP_Block_Type_Registry::get_instance()->is_registered( self::TEST_EXCEPTION_BLOCK_NAME ) ) { + unregister_block_type( self::TEST_EXCEPTION_BLOCK_NAME ); + } + + update_option( 'use_smilies', $this->original_use_smilies ); + + parent::tear_down(); + } + + /** + * Filters content image tags. + * + * @param string $filtered_image Full img tag with attributes. + * @param string $context Context for the image tag. + * @return string Filtered image tag. + */ + public function filter_content_image_tag( $filtered_image, $context ) { + $this->filtered_image_context = $context; + + if ( str_contains( $filtered_image, 'class="content-image"' ) ) { + return str_replace( 'Embedded content'; + } + + /** + * Tests that each expected content filter is applied. + * + * @ticket 65586 + */ + public function test_applies_content_filters() { + add_filter( 'wp_content_img_tag', array( $this, 'filter_content_image_tag' ), 10, 2 ); + + wp_embed_register_handler( + self::TEST_EMBED_HANDLER, + '#https?://example\.com/apply-block-content-filters#i', + array( $this, 'render_test_embed' ) + ); + + add_shortcode( + self::TEST_SHORTCODE, + static function () { + return ''; + } + ); + + $content = sprintf( + "This shouldn't use a straight apostrophe, and this should become a smilie: :mrgreen:\n\n

[%s]

\n\n\n\n\"\"\n\nhttps://example.com/apply-block-content-filters", + self::TEST_SHORTCODE + ); + + $output = _wp_apply_block_content_filters( $content, 'test-context' ); + + $this->assertStringContainsString( + 'This shouldn’t use a straight apostrophe', + $output, + 'wptexturize() should convert straight apostrophes to curly apostrophe entities.' + ); + $this->assertStringContainsString( + 'class="wp-smiley"', + $output, + 'convert_smilies() should process smilies in the filtered content.' + ); + $this->assertStringContainsString( + '
Shortcode block output
', + $output, + 'do_shortcode() should run before do_blocks() so block markup returned by a shortcode is rendered.' + ); + $this->assertStringNotContainsString( + '

Shortcode block output

', + $output, + 'shortcode_unautop() should remove paragraph wrappers around standalone shortcodes.' + ); + $this->assertStringContainsString( + '
Direct block output
', + $output, + 'do_blocks() should render block markup in the filtered content.' + ); + $this->assertStringNotContainsString( + '', + 'test-context', + $seen_ids, + 'test-id' + ); + + $this->assertStringContainsString( + 'Nested block rendered while recursion guard was active', + $output, + 'The test block should render while the ID is marked as seen.' + ); + $this->assertSame( + array( 'test-id' => true ), + $seen_ids_during_render, + 'The ID should be marked as seen while do_blocks() renders nested blocks.' + ); + $this->assertSame( + array(), + $seen_ids, + 'The seen ID should be cleared after do_blocks() completes.' + ); + } + + /** + * Tests that seen IDs are cleared when block rendering throws an exception. + * + * @ticket 65586 + */ + public function test_clears_seen_id_when_block_rendering_throws() { + $seen_ids = array(); + + try { + _wp_apply_block_content_filters( + '', + 'test-context', + $seen_ids, + 'test-id' + ); + $this->fail( 'Expected block rendering to throw an exception.' ); + } catch ( Exception $exception ) { + $this->assertSame( + 'Block rendering failed.', + $exception->getMessage(), + 'The exception from the nested block render callback should be rethrown.' + ); + } + + $this->assertSame( + array(), + $seen_ids, + 'The seen ID should be cleared when do_blocks() throws.' + ); + } +}