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 545af0650386b..e62a9131eb4ab 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..6fbdf91a36bf1 100644 --- a/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php +++ b/tests/phpunit/tests/html-api/wpHtmlTagProcessor.php @@ -672,6 +672,89 @@ 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 array[] $updates Method name and arguments of each update to the DIV tag, in order. + * @param string[] $expected_names Attribute names on the DIV tag after the updates. + */ + public function test_get_attribute_names_with_prefix_reflects_class_name_updates_after_class_attribute_updates( string $html, array $updates, array $expected_names ): void { + $processor = new WP_HTML_Tag_Processor( $html ); + $processor->next_tag(); + + foreach ( $updates as $update ) { + $processor->{$update[0]}( ...array_slice( $update, 1 ) ); + } + + $this->assertSame( + $expected_names, + $processor->get_attribute_names_with_prefix( '' ), + 'Failed to report the attribute names resulting from the enqueued updates.' + ); + + $updated = new WP_HTML_Tag_Processor( $processor->get_updated_html() ); + $updated->next_tag(); + + $this->assertSame( + $expected_names, + $updated->get_attribute_names_with_prefix( '' ), + 'Found different attribute names in the updated HTML: check test setup.' + ); + } + + /** + * Data provider. + * + * @return array>, 2: list}> + */ + public static function data_class_name_updates_after_class_attribute_updates(): array { + return array( + 'Class added after removing class attribute' => array( + '
', + array( array( 'remove_attribute', 'class' ), array( 'add_class', 'b' ) ), + array( 'class' ), + ), + 'Class added after setting class attribute false' => array( + '
', + array( array( 'set_attribute', 'class', false ), array( 'add_class', 'b' ) ), + array( 'class' ), + ), + 'Class added after removing empty class attribute' => array( + '
', + array( array( 'remove_attribute', 'class' ), array( 'add_class', 'b' ) ), + array( 'class' ), + ), + 'Class added after removing boolean class attribute' => array( + '
', + array( array( 'remove_attribute', 'class' ), array( 'add_class', 'b' ) ), + array( 'class' ), + ), + 'Class added after removing duplicated class attribute' => array( + '
', + array( array( 'remove_attribute', 'class' ), array( 'add_class', 'b' ) ), + array( 'class' ), + ), + 'Class added after removing class attribute, with other attributes' => array( + '
', + array( array( 'remove_attribute', 'class' ), array( 'add_class', 'b' ) ), + array( 'id', 'class', 'title' ), + ), + 'Only class removed after adding class attribute' => array( + '
', + array( array( 'set_attribute', 'class', 'a' ), array( 'remove_class', 'a' ) ), + array(), + ), + ); + } + /** * @ticket 56299 *