Skip to content

Conversation

@sirreal
Copy link
Member

@sirreal sirreal commented Jan 20, 2026

Ensure that leading text whitespace is included in assertEqualHTML comparisons. See the Trac ticket description:

Some leading whitespace is trimmed from text nodes resulting in incorrect assertions like the following (passes, but should not):

$this->assertEqualHTML(
  "<p> x</p>",
  "<p>\nx</p>"
);

The following tree is constructed by build_visual_html_tree() in both cases:

<p>
  "x"

The expected trees are:

<p>
  " x"
<p>
  "
x"

Trac ticket: https://core.trac.wordpress.org/ticket/64531


This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.

@github-actions
Copy link

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • The Plugin and Theme Directories cannot be accessed within Playground.
  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@sirreal
Copy link
Member Author

sirreal commented Jan 20, 2026

I've pushed commit 9f672ba that updates a test to add the whitespace inside block delimiters.

This is a good example for discussion, I'd like to hear other opinions: @ockham @dmsnell

case '#text':
$text_content = $processor->get_modifiable_text();
if ( '' === trim( $text_content, " \f\t\r\n" ) ) {
if ( '' === $text_content ) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was the main problem, it effectively eliminated whitespace text completely, and leading whitespace from existing text nodes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this added in the first place? The trim() here occurs after decoding, meaning that any raw markup would have already been processed and whitespace normalized.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was introduced as part of block delimiters appearing in the HTML tree. That change is not open source, this was largely developed in another project before being proposed for Core.

@sirreal
Copy link
Member Author

sirreal commented Jan 20, 2026

I was surprised when whitespace wasn't detected recently but didn't think to look further. This explains it and the changes in the scripts tests are important.

@sirreal
Copy link
Member Author

sirreal commented Jan 20, 2026

If we consider block delimiters as analogous to HTML tags, then it makes complete sense to preserve their inner whitespace. I'm more and more convinced that it should be preserved.

Comment on lines +1500 to 1501
$expected = "<script src='/main-script-d4.js' id='main-script-d4-js' data-wp-strategy='defer'></script>";
$this->assertEqualHTMLScriptTagById( $expected, $output, 'Scripts registered as defer but that have all dependents with no strategy, should become blocking (no strategy).' );
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may seem surprising that this assertion removes a trailing newline. This is using assertEqualHTMLScriptTagById() which matches the script tag, so a trailing newline will never be expected.

assertEqualHTMLScriptTagById() could probably trim its input, but it seems fine to just clean things up.

@sirreal sirreal marked this pull request as ready for review January 20, 2026 14:49
@github-actions
Copy link

github-actions bot commented Jan 20, 2026

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Core Committers: Use this line as a base for the props when committing in SVN:

Props jonsurrell, dmsnell.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@sirreal
Copy link
Member Author

sirreal commented Jan 20, 2026

Commit 951307e is informative.

It's a bit surprising because HTML is being compared with rendered block markup. This includes odd whitespace combinations like <div>\n\t\n\t<div> which is caused by whitespace around block-delimiter contents (which are removed) and rendered block contents. Many code editors (mine included) are configured to remove trailing whitespace, which makes it tricky to actually print \t\n inside the block markup (and I used the character reference &#x9;).

diff --git a/tests/phpunit/tests/blocks/wpBlock.php b/tests/phpunit/tests/blocks/wpBlock.php
index b3c11ad084386..85fa6e7151d46 100644
--- a/tests/phpunit/tests/blocks/wpBlock.php
+++ b/tests/phpunit/tests/blocks/wpBlock.php
@@ -387,13 +387,20 @@ public function data_provider_test_render_enqueues_scripts_and_styles(): array {
 			'all_printed'                             => array(
 				'set_up'                  => null,
 				'block_markup'            => $block_markup,
-				'expected_rendered_block' => '
-					<div class="static">
-						<div class="static-child">First child</div>
-						<p class="dynamic">Hello World!</p>
-						<div class="static-child">Last child</div>
-					</div>
-				',
+				'expected_rendered_block' => <<<'HTML'
+
+<div class="static">
+&#x9;
+	<div class="static-child">First child</div>
+&#x9;
+	<p class="dynamic">Hello World!</p>
+&#x9;
+	<div class="static-child">Last child</div>
+&#x9;
+</div>
+
+HTML
+				,
 				'expected_styles'         => array( 'static-view-style', 'static-child-view-style', 'dynamic-view-style' ),
 				'expected_scripts'        => array( 'static-view-script', 'static-child-view-script', 'dynamic-view-script' ),
 				'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module', 'dynamic-view-script-module' ),
@@ -414,13 +421,20 @@ static function ( $content ) {
 					);
 				},
 				'block_markup'            => $block_markup,
-				'expected_rendered_block' => '
-					<div class="static">
-						<div class="static-child">First child</div>
-						<p class="dynamic filtered">Hello World!</p>
-						<div class="static-child">Last child</div>
-					</div>
-				',
+				'expected_rendered_block' => <<<'HTML'
+
+<div class="static">
+&#x9;
+	<div class="static-child">First child</div>
+&#x9;
+	<p class="dynamic filtered">Hello World!</p>
+&#x9;
+	<div class="static-child">Last child</div>
+&#x9;
+</div>
+
+HTML
+				,
 				'expected_styles'         => array( 'static-view-style', 'dynamic-extra', 'static-child-view-style', 'dynamic-view-style' ),
 				'expected_scripts'        => array( 'static-view-script', 'static-child-view-script', 'dynamic-view-script' ),
 				'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module', 'dynamic-view-script-module' ),
@@ -430,12 +444,20 @@ static function ( $content ) {
 					add_filter( 'render_block_core/dynamic', '__return_empty_string' );
 				},
 				'block_markup'            => $block_markup,
-				'expected_rendered_block' => '
-					<div class="static">
-						<div class="static-child">First child</div>
-						<div class="static-child">Last child</div>
-					</div>
-				',
+				'expected_rendered_block' => <<<'HTML'
+
+<div class="static">
+&#x9;
+	<div class="static-child">First child</div>
+&#x9;
+&#x9;
+&#x9;
+	<div class="static-child">Last child</div>
+&#x9;
+</div>
+
+HTML
+				,
 				'expected_styles'         => array( 'static-view-style', 'static-child-view-style' ),
 				'expected_scripts'        => array( 'static-view-script', 'static-child-view-script' ),
 				'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module' ),
@@ -456,12 +478,20 @@ static function ( $enqueue, $block_name ) {
 					);
 				},
 				'block_markup'            => $block_markup,
-				'expected_rendered_block' => '
-					<div class="static">
-						<div class="static-child">First child</div>
-						<div class="static-child">Last child</div>
-					</div>
-				',
+				'expected_rendered_block' => <<<'HTML'
+
+<div class="static">
+&#x9;
+	<div class="static-child">First child</div>
+&#x9;
+&#x9;
+&#x9;
+	<div class="static-child">Last child</div>
+&#x9;
+</div>
+
+HTML
+				,
 				'expected_styles'         => array( 'static-view-style', 'static-child-view-style', 'dynamic-view-style' ),
 				'expected_scripts'        => array( 'static-view-script', 'static-child-view-script', 'dynamic-view-script' ),
 				'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module', 'dynamic-view-script-module' ),
@@ -488,11 +518,16 @@ static function ( $content ) {
 					add_filter( 'render_block_core/static-child', '__return_empty_string' );
 				},
 				'block_markup'            => $block_markup,
-				'expected_rendered_block' => '
-					<div class="static">
-						<p class="dynamic">Hello World!</p>
-					</div>
-				',
+				'expected_rendered_block' => <<<'HTML'
+
+<div class="static">
+&#x9;
+	<p class="dynamic">Hello World!</p>
+&#x9;
+</div>
+
+HTML
+				,
 				'expected_styles'         => array( 'static-view-style', 'dynamic-view-style' ),
 				'expected_scripts'        => array( 'static-view-script', 'dynamic-view-script' ),
 				'expected_script_modules' => array( 'static-view-script-module', 'dynamic-view-script-module' ),
@@ -512,12 +547,18 @@ static function ( $content ) {
 					);
 				},
 				'block_markup'            => $block_markup,
-				'expected_rendered_block' => '
-					<div class="static">
-						<div class="static-child">First child</div>
-						<p class="dynamic">Hello World!</p>
-					</div>
-				',
+				'expected_rendered_block' => <<<'HTML'
+
+<div class="static">
+&#x9;
+	<div class="static-child">First child</div>
+&#x9;
+	<p class="dynamic">Hello World!</p>
+&#x9;
+</div>
+
+HTML
+				,
 				'expected_styles'         => array( 'static-view-style', 'static-child-view-style', 'dynamic-view-style' ),
 				'expected_scripts'        => array( 'static-view-script', 'static-child-view-script', 'dynamic-view-script' ),
 				'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module', 'dynamic-view-script-module' ),

A simple alternative in this case is to remove the tabs from the block markup in the test.

@sirreal
Copy link
Member Author

sirreal commented Jan 20, 2026

Here's a similar diff of the test fix without the block indentation (commit 29faaf5):

diff --git a/tests/phpunit/tests/blocks/wpBlock.php b/tests/phpunit/tests/blocks/wpBlock.php
index b3c11ad084..ae067e7d79 100644
--- a/tests/phpunit/tests/blocks/wpBlock.php
+++ b/tests/phpunit/tests/blocks/wpBlock.php
@@ -371,13 +371,13 @@ class Tests_Blocks_wpBlock extends WP_UnitTestCase {
 		$block_markup = <<<'HTML'
 <!-- wp:static -->
 <div class="static">
-	<!-- wp:static-child -->
-	<div class="static-child">First child</div>
-	<!-- /wp:static-child -->
-	<!-- wp:dynamic /-->
-	<!-- wp:static-child -->
-	<div class="static-child">Last child</div>
-	<!-- /wp:static-child -->
+<!-- wp:static-child -->
+<div class="static-child">First child</div>
+<!-- /wp:static-child -->
+<!-- wp:dynamic /-->
+<!-- wp:static-child -->
+<div class="static-child">Last child</div>
+<!-- /wp:static-child -->
 </div>
 <!-- /wp:static -->
 HTML;
@@ -387,13 +387,20 @@ HTML;
 			'all_printed'                             => array(
 				'set_up'                  => null,
 				'block_markup'            => $block_markup,
-				'expected_rendered_block' => '
-					<div class="static">
-						<div class="static-child">First child</div>
-						<p class="dynamic">Hello World!</p>
-						<div class="static-child">Last child</div>
-					</div>
-				',
+				'expected_rendered_block' => <<<'HTML'
+
+<div class="static">
+
+<div class="static-child">First child</div>
+
+<p class="dynamic">Hello World!</p>
+
+<div class="static-child">Last child</div>
+
+</div>
+
+HTML
+				,
 				'expected_styles'         => array( 'static-view-style', 'static-child-view-style', 'dynamic-view-style' ),
 				'expected_scripts'        => array( 'static-view-script', 'static-child-view-script', 'dynamic-view-script' ),
 				'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module', 'dynamic-view-script-module' ),
@@ -414,13 +421,20 @@ HTML;
 					);
 				},
 				'block_markup'            => $block_markup,
-				'expected_rendered_block' => '
-					<div class="static">
-						<div class="static-child">First child</div>
-						<p class="dynamic filtered">Hello World!</p>
-						<div class="static-child">Last child</div>
-					</div>
-				',
+				'expected_rendered_block' => <<<'HTML'
+
+<div class="static">
+
+<div class="static-child">First child</div>
+
+<p class="dynamic filtered">Hello World!</p>
+
+<div class="static-child">Last child</div>
+
+</div>
+
+HTML
+				,
 				'expected_styles'         => array( 'static-view-style', 'dynamic-extra', 'static-child-view-style', 'dynamic-view-style' ),
 				'expected_scripts'        => array( 'static-view-script', 'static-child-view-script', 'dynamic-view-script' ),
 				'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module', 'dynamic-view-script-module' ),
@@ -430,12 +444,20 @@ HTML;
 					add_filter( 'render_block_core/dynamic', '__return_empty_string' );
 				},
 				'block_markup'            => $block_markup,
-				'expected_rendered_block' => '
-					<div class="static">
-						<div class="static-child">First child</div>
-						<div class="static-child">Last child</div>
-					</div>
-				',
+				'expected_rendered_block' => <<<'HTML'
+
+<div class="static">
+
+<div class="static-child">First child</div>
+
+
+
+<div class="static-child">Last child</div>
+
+</div>
+
+HTML
+				,
 				'expected_styles'         => array( 'static-view-style', 'static-child-view-style' ),
 				'expected_scripts'        => array( 'static-view-script', 'static-child-view-script' ),
 				'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module' ),
@@ -456,12 +478,20 @@ HTML;
 					);
 				},
 				'block_markup'            => $block_markup,
-				'expected_rendered_block' => '
-					<div class="static">
-						<div class="static-child">First child</div>
-						<div class="static-child">Last child</div>
-					</div>
-				',
+				'expected_rendered_block' => <<<'HTML'
+
+<div class="static">
+
+<div class="static-child">First child</div>
+
+
+
+<div class="static-child">Last child</div>
+
+</div>
+
+HTML
+				,
 				'expected_styles'         => array( 'static-view-style', 'static-child-view-style', 'dynamic-view-style' ),
 				'expected_scripts'        => array( 'static-view-script', 'static-child-view-script', 'dynamic-view-script' ),
 				'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module', 'dynamic-view-script-module' ),
@@ -488,11 +518,16 @@ HTML;
 					add_filter( 'render_block_core/static-child', '__return_empty_string' );
 				},
 				'block_markup'            => $block_markup,
-				'expected_rendered_block' => '
-					<div class="static">
-						<p class="dynamic">Hello World!</p>
-					</div>
-				',
+				'expected_rendered_block' => <<<'HTML'
+
+<div class="static">
+
+<p class="dynamic">Hello World!</p>
+
+</div>
+
+HTML
+				,
 				'expected_styles'         => array( 'static-view-style', 'dynamic-view-style' ),
 				'expected_scripts'        => array( 'static-view-script', 'dynamic-view-script' ),
 				'expected_script_modules' => array( 'static-view-script-module', 'dynamic-view-script-module' ),
@@ -512,12 +547,18 @@ HTML;
 					);
 				},
 				'block_markup'            => $block_markup,
-				'expected_rendered_block' => '
-					<div class="static">
-						<div class="static-child">First child</div>
-						<p class="dynamic">Hello World!</p>
-					</div>
-				',
+				'expected_rendered_block' => <<<'HTML'
+
+<div class="static">
+
+<div class="static-child">First child</div>
+
+<p class="dynamic">Hello World!</p>
+
+</div>
+
+HTML
+				,
 				'expected_styles'         => array( 'static-view-style', 'static-child-view-style', 'dynamic-view-style' ),
 				'expected_scripts'        => array( 'static-view-script', 'static-child-view-script', 'dynamic-view-script' ),
 				'expected_script_modules' => array( 'static-view-script-module', 'static-child-view-script-module', 'dynamic-view-script-module' ),

case '#text':
$text_content = $processor->get_modifiable_text();
if ( '' === trim( $text_content, " \f\t\r\n" ) ) {
if ( '' === $text_content ) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this added in the first place? The trim() here occurs after decoding, meaning that any raw markup would have already been processed and whitespace normalized.

'expected_rendered_block' => <<<'HTML'
<div class="static">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not sure how to think through this, and I think you are alluding to it in how we split blocks from the HTML, but is this right? It seems to be producing two newlines which I wouldn’t have expected.

Also, the test code itself performs an initial trim() on the supplied test data. Could that be making these more confusing than they need to be. That @todo looks like a good candidate for WP_Block_Processor::extract_full_block_and_advance()

// test_render_enqueues_scripts_and_styles:L670

// TODO: Why not use do_blocks() instead?
$parsed_blocks  = parse_blocks( trim( $block_markup ) );

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not sure how to think through this, and I think you are alluding to it in how we split blocks from the HTML, but is this right? It seems to be producing two newlines which I wouldn’t have expected.

The block markup is this (after removing leading whitespace in this PR):

<!-- wp:static -->
<div class="static">
<!-- wp:static-child -->
<div class="static-child">First child</div>
<!-- /wp:static-child -->
<!-- wp:dynamic /-->
<!-- wp:static-child -->
<div class="static-child">Last child</div>
<!-- /wp:static-child -->
</div>
<!-- /wp:static -->

Rendering removes the block delimiters (and replaces the registered dynamic block), but preserves everything else. The result is this:

<div class="static">

<div class="static-child">First child</div>

<p class="dynamic">Hello World!</p>

<div class="static-child">Last child</div>

</div>

Before removing the indentation, this is what caused the \n\t\n sequences (whitespace characters added for clarity):

<!-- wp:static -->␊
␉	<div class="static">␊
␉	<!-- wp:static-child -->␊
␉	<div class="static-child">First child</div>␊
␉	<!-- /wp:static-child -->␊
␉	<!-- wp:dynamic /-->␊
␉	<!-- wp:static-child -->␊
␉	<div class="static-child">Last child</div>␊
␉	<!-- /wp:static-child -->␊
␉	</div><!-- /wp:static -->

Becomes:

␊
␉	<div class="static">␊
␉	␊
␉	<div class="static-child">First child</div>␊
␉	␊
␉	<p class="dynamic">Hello World!</p>␊
␉	␊
␉	<div class="static-child">Last child</div>␊
␉	␊
␉	</div>

I've noticed here that the indentation was inconsistent, with the static block's contents being indented, but static-child block's content remains at the same indentation:

$block_markup = '
<!-- wp:static -->
<div class="static">
<!-- wp:static-child -->
<div class="static-child">First child</div>
<!-- /wp:static-child -->
<!-- wp:dynamic /-->
<!-- wp:static-child -->
<div class="static-child">Last child</div>
<!-- /wp:static-child -->
</div>
<!-- /wp:static -->
';

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants