diff --git a/src/wp-includes/html-api/class-wp-html-tag-processor.php b/src/wp-includes/html-api/class-wp-html-tag-processor.php
index fcf418e856d5e..0ff2cdc4dd10d 100644
--- a/src/wp-includes/html-api/class-wp-html-tag-processor.php
+++ b/src/wp-includes/html-api/class-wp-html-tag-processor.php
@@ -614,7 +614,7 @@ class WP_HTML_Tag_Processor {
*
* @since 6.5.0
*
- * @var string
+ * @var int
*/
private $text_length;
@@ -2894,11 +2894,13 @@ public function get_comment_type(): ?string {
* @return string
*/
public function get_modifiable_text(): string {
- if ( null === $this->text_starts_at || 0 === $this->text_length ) {
+ $has_enqueued_update = isset( $this->lexical_updates['modifiable text'] );
+
+ if ( ! $has_enqueued_update && ( null === $this->text_starts_at || 0 === $this->text_length ) ) {
return '';
}
- $text = isset( $this->lexical_updates['modifiable text'] )
+ $text = $has_enqueued_update
? $this->lexical_updates['modifiable text']->text
: substr( $this->html, $this->text_starts_at, $this->text_length );
diff --git a/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php b/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php
index 717d061016a2d..03c65321e34bd 100644
--- a/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php
+++ b/tests/phpunit/tests/html-api/wpHtmlTagProcessorModifiableText.php
@@ -39,6 +39,83 @@ public function test_get_modifiable_text_is_idempotent() {
}
}
+ /**
+ * Ensures that `get_modifiable_text()` reads enqueued updates when read
+ * from after writing; guarantees consistency through writes.
+ *
+ * @ticket 61617
+ */
+ public function test_get_modifiable_text_is_consistent_after_writes() {
+ $before = 'just some text';
+ $after = 'different text';
+ $processor = new WP_HTML_Tag_Processor( $before );
+ $processor->next_token();
+
+ $this->assertSame(
+ '#text',
+ $processor->get_token_name(),
+ "Should have found text node but found '{$processor->get_token_name()}' instead: check test setup."
+ );
+
+ $this->assertSame(
+ $before,
+ $processor->get_modifiable_text(),
+ 'Should have found initial test text: check test setup.'
+ );
+
+ $processor->set_modifiable_text( $after );
+ $this->assertSame(
+ $after,
+ $processor->get_modifiable_text(),
+ 'Should have found enqueued updated text.'
+ );
+
+ $processor->get_updated_html();
+ $this->assertSame(
+ $after,
+ $processor->get_modifiable_text(),
+ 'Should have found updated text.'
+ );
+ }
+
+ /**
+ * Ensures that `get_modifiable_text()` reads enqueued updates when read from after
+ * writing when starting from an empty text; guarantees consistency through writes.
+ *
+ * @ticket 61617
+ */
+ public function test_get_modifiable_text_is_consistent_after_writes_to_empty_text() {
+ $after = 'different text';
+ $processor = new WP_HTML_Tag_Processor( '' );
+ $processor->next_token();
+
+ $this->assertSame(
+ 'SCRIPT',
+ $processor->get_token_name(),
+ "Should have found text node but found '{$processor->get_token_name()}' instead: check test setup."
+ );
+
+ $this->assertSame(
+ '',
+ $processor->get_modifiable_text(),
+ 'Should have found initial test text: check test setup.'
+ );
+
+ $processor->set_modifiable_text( $after );
+ $this->assertSame(
+ $after,
+ $processor->get_modifiable_text(),
+ 'Should have found enqueued updated text.'
+ );
+
+ $processor->get_updated_html();
+ $this->assertSame(
+ $after,
+ $processor->get_modifiable_text(),
+ 'Should have found updated text.'
+ );
+ }
+
/**
* Ensures that updates to modifiable text that are shorter than the
* original text do not cause the parser to lose its orientation.