From ccc90db551f85ff23923352cd56e324dc2ccaf6f Mon Sep 17 00:00:00 2001
From: salvatorecorsi <86927009+salvatorecorsi@users.noreply.github.com>
Date: Wed, 21 Oct 2026 09:20:42 +0200
Subject: [PATCH] Blocks: Preserve pre-rendered content when generating preset
styles.
---
src/wp-includes/block-supports/settings.php | 8 +-
.../phpunit/tests/block-supports/settings.php | 172 ++++++++++++++++++
2 files changed, 176 insertions(+), 4 deletions(-)
create mode 100644 tests/phpunit/tests/block-supports/settings.php
diff --git a/src/wp-includes/block-supports/settings.php b/src/wp-includes/block-supports/settings.php
index 6b1eac6defc7f..b4ab5dfdc1d18 100644
--- a/src/wp-includes/block-supports/settings.php
+++ b/src/wp-includes/block-supports/settings.php
@@ -71,19 +71,19 @@ function _wp_add_block_level_presets_class( $block_content, $block ) {
*
* @param string|null $pre_render The pre-rendered content. Default null.
* @param array $block The block being rendered.
- * @return null
+ * @return string|null The pre-rendered content.
*/
function _wp_add_block_level_preset_styles( $pre_render, $block ) {
// Return early if the block has not support for descendent block styles.
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
if ( ! block_has_support( $block_type, '__experimentalSettings', false ) ) {
- return null;
+ return $pre_render;
}
// return early if no settings are found on the block attributes.
$block_settings = $block['attrs']['settings'] ?? null;
if ( empty( $block_settings ) ) {
- return null;
+ return $pre_render;
}
$class_name = '.' . _wp_get_presets_class_name( $block );
@@ -144,7 +144,7 @@ function _wp_add_block_level_preset_styles( $pre_render, $block ) {
wp_enqueue_block_support_styles( $styles );
}
- return null;
+ return $pre_render;
}
add_filter( 'render_block', '_wp_add_block_level_presets_class', 10, 2 );
diff --git a/tests/phpunit/tests/block-supports/settings.php b/tests/phpunit/tests/block-supports/settings.php
new file mode 100644
index 0000000000000..08e8175012675
--- /dev/null
+++ b/tests/phpunit/tests/block-supports/settings.php
@@ -0,0 +1,172 @@
+test_block_name ) {
+ unregister_block_type( $this->test_block_name );
+ }
+ $this->test_block_name = null;
+ parent::tear_down();
+ }
+
+ /**
+ * Preserves earlier render results without losing the block's preset styles.
+ *
+ * @ticket 59551
+ * @dataProvider data_preserve_pre_rendered_content
+ *
+ * @param bool $supports_settings Whether the block supports preset settings.
+ * @param array $settings The block's preset settings.
+ * @param string|null $pre_render The earlier filter's render result.
+ */
+ public function test_preserve_pre_rendered_content( $supports_settings, $settings, $pre_render ) {
+ $this->test_block_name = 'test/settings-pre-render';
+ $render_calls = 0;
+ $filter_calls = 0;
+
+ register_block_type(
+ $this->test_block_name,
+ array(
+ 'api_version' => 3,
+ 'attributes' => array( 'settings' => array( 'type' => 'object' ) ),
+ 'supports' => array( '__experimentalSettings' => $supports_settings ),
+ 'render_callback' => static function () use ( &$render_calls ) {
+ ++$render_calls;
+ return '
Rendered settings block
';
+ },
+ )
+ );
+
+ $block = array(
+ 'blockName' => $this->test_block_name,
+ 'attrs' => $settings ? array( 'settings' => $settings ) : array(),
+ 'innerBlocks' => array(),
+ 'innerHTML' => '',
+ 'innerContent' => array(),
+ );
+
+ $pre_render_filter = function ( $content, $parsed_block ) use ( $pre_render, &$filter_calls ) {
+ if ( $this->test_block_name !== $parsed_block['blockName'] ) {
+ return $content;
+ }
+ ++$filter_calls;
+ $this->assertNull( $content );
+ return $pre_render;
+ };
+ add_filter( 'pre_render_block', $pre_render_filter, 9, 2 );
+
+ // Resolve the theme before measuring the block's newly enqueued styles.
+ wp_is_block_theme();
+ $style_hooks = array();
+ foreach ( array( 'wp_head', 'wp_footer' ) as $hook_name ) {
+ $style_hooks[ $hook_name ] = isset( $GLOBALS['wp_filter'][ $hook_name ] )
+ ? clone $GLOBALS['wp_filter'][ $hook_name ]
+ : null;
+ }
+
+ try {
+ $actual = do_blocks( serialize_block( $block ) );
+
+ if ( null === $pre_render ) {
+ $this->assertSame( 1, $render_calls );
+ $this->assertStringContainsString( 'Rendered settings block', $actual );
+ } else {
+ $this->assertSame( $pre_render, $actual );
+ $this->assertSame( 0, $render_calls );
+ }
+ $this->assertSame( 1, $filter_calls );
+
+ $styles = $this->get_added_style_output( $style_hooks );
+ if ( $supports_settings && $settings ) {
+ $this->assertStringContainsString( '--wp--preset--color--test-settings-pre-render', $styles );
+ $this->assertStringContainsString( '#123abc', $styles );
+ $this->assertStringContainsString( '.has-test-settings-pre-render-color', $styles );
+ } else {
+ $this->assertStringNotContainsString( 'test-settings-pre-render', $styles );
+ }
+ } finally {
+ remove_filter( 'pre_render_block', $pre_render_filter, 9 );
+ foreach ( $style_hooks as $hook_name => $original_hook ) {
+ if ( null === $original_hook ) {
+ unset( $GLOBALS['wp_filter'][ $hook_name ] );
+ } else {
+ $GLOBALS['wp_filter'][ $hook_name ] = $original_hook;
+ }
+ }
+ }
+ }
+
+ /**
+ * Captures the real style callbacks added by the rendered block only.
+ *
+ * @param array $original_hooks Style hooks before rendering.
+ * @return string Newly enqueued stylesheet output.
+ */
+ private function get_added_style_output( $original_hooks ) {
+ ob_start();
+ try {
+ foreach ( $original_hooks as $hook_name => $original_hook ) {
+ if ( ! isset( $GLOBALS['wp_filter'][ $hook_name ] ) ) {
+ continue;
+ }
+ foreach ( $GLOBALS['wp_filter'][ $hook_name ]->callbacks as $priority => $callbacks ) {
+ $original_callbacks = null === $original_hook ? array() : ( $original_hook->callbacks[ $priority ] ?? array() );
+ foreach ( array_diff_key( $callbacks, $original_callbacks ) as $callback ) {
+ call_user_func( $callback['function'] );
+ }
+ }
+ }
+ return ob_get_contents();
+ } finally {
+ ob_end_clean();
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public static function data_preserve_pre_rendered_content() {
+ $settings = array(
+ 'color' => array(
+ 'palette' => array(
+ 'custom' => array(
+ array(
+ 'name' => 'Test settings pre-render',
+ 'slug' => 'test-settings-pre-render',
+ 'color' => '#123abc',
+ ),
+ ),
+ ),
+ ),
+ );
+ $branches = array(
+ 'unsupported block' => array( false, $settings ),
+ 'no settings' => array( true, array() ),
+ 'preset settings' => array( true, $settings ),
+ );
+ $results = array(
+ 'empty string' => '',
+ 'zero string' => '0',
+ 'cached markup' => 'Cached settings block
',
+ 'normal render' => null,
+ );
+ $cases = array();
+ foreach ( $branches as $branch_name => $branch ) {
+ foreach ( $results as $result_name => $pre_render ) {
+ $cases[ $branch_name . ', ' . $result_name ] = array( $branch[0], $branch[1], $pre_render );
+ }
+ }
+ return $cases;
+ }
+}