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 68c227f221066..3a0bb2d4f4a62 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 @@ -2970,8 +2970,11 @@ public function get_attribute_names_with_prefix( $prefix ): ?array { /* * For the `class` attribute, ensure that enqueued class changes from * `add_class` and `remove_class` are flushed into attribute updates. + * An enqueued `class` attribute update takes precedence over the + * input document in determining whether the attribute exists. */ - $has_class = isset( $this->attributes['class'] ); + $enqueued_class = $this->get_enqueued_attribute_value( 'class' ); + $has_class = false === $enqueued_class ? isset( $this->attributes['class'] ) : null !== $enqueued_class; if ( '' === $comparable || str_starts_with( 'class', $comparable ) ) { foreach ( $this->classname_updates as $update ) { if ( diff --git a/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php b/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php index 1bfaa01bf0453..a206ce1465e53 100644 --- a/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php +++ b/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php @@ -672,6 +672,112 @@ public function test_get_attribute_names_with_prefix_immediately_agrees_with_get } } + /** + * Ensures that get_attribute_names_with_prefix() reflects class name updates + * which follow an enqueued update to the `class` attribute. + * + * @ticket 65967 + * + * @covers WP_HTML_Tag_Processor::get_attribute_names_with_prefix + * + * @dataProvider data_class_name_updates_after_class_attribute_updates + * + * @param string $html HTML containing a DIV tag. + * @param Closure $update Enqueues updates to the DIV tag. + * @param string[] $expected_names Attribute names on the DIV tag after the updates. + * @param string $expected_html HTML after the updates. + */ + public function test_get_attribute_names_with_prefix_reflects_class_name_updates_after_class_attribute_updates( string $html, Closure $update, array $expected_names, string $expected_html ): void { + $processor = new WP_HTML_Tag_Processor( $html ); + $processor->next_tag(); + $update( $processor ); + + $this->assertSame( + $expected_names, + $processor->get_attribute_names_with_prefix( '' ), + 'Failed to report the attribute names resulting from the enqueued updates.' + ); + + $this->assertSame( + $expected_html, + $processor->get_updated_html(), + 'Failed to apply the enqueued updates.' + ); + } + + /** + * Data provider. + * + * @return array, 3: string}> + */ + public static function data_class_name_updates_after_class_attribute_updates(): array { + return array( + 'Class added after removing class attribute' => array( + '
', + static function ( WP_HTML_Tag_Processor $processor ): void { + $processor->remove_attribute( 'class' ); + $processor->add_class( 'b' ); + }, + array( 'class' ), + '
', + ), + 'Class added after setting class attribute false' => array( + '
', + static function ( WP_HTML_Tag_Processor $processor ): void { + $processor->set_attribute( 'class', false ); + $processor->add_class( 'b' ); + }, + array( 'class' ), + '
', + ), + 'Class added after removing empty class attribute' => array( + '
', + static function ( WP_HTML_Tag_Processor $processor ): void { + $processor->remove_attribute( 'class' ); + $processor->add_class( 'b' ); + }, + array( 'class' ), + '
', + ), + 'Class added after removing boolean class attribute' => array( + '
', + static function ( WP_HTML_Tag_Processor $processor ): void { + $processor->remove_attribute( 'class' ); + $processor->add_class( 'b' ); + }, + array( 'class' ), + '
', + ), + 'Class added after removing duplicated class attribute' => array( + '
', + static function ( WP_HTML_Tag_Processor $processor ): void { + $processor->remove_attribute( 'class' ); + $processor->add_class( 'c' ); + }, + array( 'class' ), + '
', + ), + 'Class added after removing class attribute, with other attributes' => array( + '
', + static function ( WP_HTML_Tag_Processor $processor ): void { + $processor->remove_attribute( 'class' ); + $processor->add_class( 'b' ); + }, + array( 'id', 'class', 'title' ), + '
', + ), + 'Only class removed after adding class attribute' => array( + '
', + static function ( WP_HTML_Tag_Processor $processor ): void { + $processor->set_attribute( 'class', 'a' ); + $processor->remove_class( 'a' ); + }, + array(), + '
', + ), + ); + } + /** * @ticket 56299 *