" );
+
+ $this->assertTrue( $processor->next_tag(), 'Should have found the tag.' );
+ $this->assertTrue( $processor->get_attribute( "da\u{FFFD}ta" ), 'Should have reported the boolean attribute by its replaced name.' );
+ }
+
+ /**
+ * Ensures attribute-name prefixes are matched verbatim against the
+ * replaced names: a prefix spelled with U+FFFD matches, and a prefix
+ * containing a raw NULL byte matches nothing.
+ *
+ * @ticket 65372
+ *
+ * @covers ::get_attribute_names_with_prefix
+ */
+ public function test_attribute_name_prefixes_match_replaced_names(): void {
+ $processor = new WP_HTML_Tag_Processor( "
" );
+
+ $this->assertTrue( $processor->next_tag(), 'Should have found the tag.' );
+ $this->assertSame( array( "da\u{FFFD}ta" ), $processor->get_attribute_names_with_prefix( "da\u{FFFD}" ), 'A replaced-name prefix should match.' );
+ $this->assertSame( array(), $processor->get_attribute_names_with_prefix( "da\x00" ), 'A raw NULL prefix should match nothing.' );
+ }
+
+ /**
+ * Ensures the replaced tag names flow through HTML Processor tree
+ * construction: an end tag spelled with U+FFFD closes an element
+ * whose start tag was spelled with a raw NULL byte, as in browsers,
+ * where both spellings tokenize to the same name.
+ *
+ * @ticket 65372
+ */
+ public function test_html_processor_matches_end_tags_across_null_byte_spellings(): void {
+ $this->assertSame(
+ "
xy",
+ WP_HTML_Processor::normalize( "
xy" ),
+ 'The U+FFFD-spelled end tag should have closed the NULL-spelled element.'
+ );
+
+ $processor = WP_HTML_Processor::create_full_parser( "
xy" );
+ $this->assertTrue( $processor->next_tag( array( 'tag_name' => "DI\u{FFFD}V" ) ), 'Should have found the element by its replaced name.' );
+ $this->assertSame( array( 'HTML', 'BODY', "DI\u{FFFD}V" ), $processor->get_breadcrumbs(), 'Should have built breadcrumbs from replaced names.' );
+ }
+
+ /**
+ * Ensures pending class updates are flushed for any case spelling of
+ * the "class" attribute name, since attribute names are matched
+ * ASCII-case-insensitively.
+ *
+ * @ticket 65372
+ *
+ * @covers ::get_attribute
+ */
+ public function test_get_attribute_flushes_class_updates_case_insensitively(): void {
+ $processor = new WP_HTML_Tag_Processor( '
' );
+
+ $this->assertTrue( $processor->next_tag(), 'Should have found the tag.' );
+ $this->assertTrue( $processor->add_class( 'b' ), 'Should have enqueued the class addition.' );
+ $this->assertSame( 'a b', $processor->get_attribute( 'CLASS' ), 'Should have included pending class updates for an uppercase lookup.' );
+ }
+
+ /**
+ * Ensures numeric character references for U+0000 decode to U+FFFD in text.
+ *
+ * @ticket 65372
+ *
+ * @covers ::get_modifiable_text
+ */
+ public function test_encoded_null_in_text_node_decodes_to_replacement_character(): void {
+ $processor = new WP_HTML_Tag_Processor( 'ab' );
+
+ $this->assertTrue( $processor->next_token(), 'Should have found the text node.' );
+ $this->assertSame( "a\u{FFFD}b", $processor->get_modifiable_text() );
+ }
+}