diff --git a/lib/experimental/html/class-wp-html-tag-processor.php b/lib/experimental/html/class-wp-html-tag-processor.php
index 0dfa57f30f2aab..e50cb3d43f76ea 100644
--- a/lib/experimental/html/class-wp-html-tag-processor.php
+++ b/lib/experimental/html/class-wp-html-tag-processor.php
@@ -1412,6 +1412,31 @@ public function get_attribute( $name ) {
return html_entity_decode( $raw_value );
}
+ /**
+ * Returns the names of all attributes in the currently-opened tag.
+ *
+ * Example:
+ *
+ * $p = new WP_HTML_Tag_Processor( 'Test
' );
+ * $p->next_tag( [ 'class_name' => 'test' ] ) === true;
+ * $p->get_attribute_names() === array( 'enabled', 'class', 'data-test-id' );
+ *
+ * $p->next_tag( [] ) === false;
+ * $p->get_attribute_names() === null;
+ *
+ *
+ * @since 6.2.0
+ *
+ * @return array|null List of attribute names, or `null` if not at a tag.
+ */
+ function get_attribute_names() {
+ if ( $this->is_closing_tag || null === $this->tag_name_starts_at ) {
+ return null;
+ }
+
+ return array_keys( $this->attributes );
+ }
+
/**
* Returns the lowercase name of the currently-opened tag.
*
diff --git a/phpunit/html/wp-html-tag-processor-test.php b/phpunit/html/wp-html-tag-processor-test.php
index e6b3d4d8071af5..03bd4914102025 100644
--- a/phpunit/html/wp-html-tag-processor-test.php
+++ b/phpunit/html/wp-html-tag-processor-test.php
@@ -74,6 +74,19 @@ public function test_get_attribute_returns_null_when_not_in_open_tag() {
$this->assertNull( $p->get_attribute( 'class' ), 'Accessing an attribute of a non-existing tag did not return null' );
}
+ /**
+ * @ticket 56299
+ *
+ * @covers next_tag
+ * @covers get_attribute
+ */
+ public function test_get_attribute_returns_null_when_in_closing_tag() {
+ $p = new WP_HTML_Tag_Processor( '
Test
' );
+ $this->assertTrue( $p->next_tag( 'div' ), 'Querying an existing tag did not return true' );
+ $this->assertTrue( $p->next_tag( array( 'tag_closers' => 'visit' ) ), 'Querying an existing closing tag did not return true' );
+ $this->assertNull( $p->get_attribute( 'class' ), 'Accessing an attribute of a closing tag did not return null' );
+ }
+
/**
* @ticket 56299
*
@@ -195,6 +208,87 @@ public function test_set_attribute_is_case_insensitive() {
$this->assertEquals( 'Test
', $p->get_updated_html(), 'A case-insensitive set_attribute call did not update the existing attribute.' );
}
+ /**
+ * @ticket 56299
+ *
+ * @covers get_attribute_names
+ */
+ public function test_get_attribute_names_returns_null_before_finding_tags() {
+ $p = new WP_HTML_Tag_Processor( 'Test
' );
+ $this->assertNull( $p->get_attribute_names() );
+ }
+
+ /**
+ * @ticket 56299
+ *
+ * @covers get_attribute_names
+ */
+ public function test_get_attribute_names_returns_null_when_not_in_open_tag() {
+ $p = new WP_HTML_Tag_Processor( 'Test
' );
+ $p->next_tag( 'p' );
+ $this->assertNull( $p->get_attribute_names(), 'Accessing attributes of a non-existing tag did not return null' );
+ }
+
+ /**
+ * @ticket 56299
+ *
+ * @covers get_attribute_names
+ */
+ public function test_get_attribute_names_returns_null_when_in_closing_tag() {
+ $p = new WP_HTML_Tag_Processor( 'Test
' );
+ $p->next_tag( 'div' );
+ $p->next_tag( array( 'tag_closers' => 'visit' ) );
+ $this->assertNull( $p->get_attribute_names(), 'Accessing attributes of a closing tag did not return null' );
+ }
+
+ /**
+ * @ticket 56299
+ *
+ * @covers get_attribute_names
+ */
+ public function test_get_attribute_names_returns_empty_array_when_no_attributes_present() {
+ $p = new WP_HTML_Tag_Processor( 'Test
' );
+ $p->next_tag( 'div' );
+ $this->assertSame( array(), $p->get_attribute_names(), 'Accessing the attributes on a tag without any did not return an empty array' );
+ }
+
+ /**
+ * @ticket 56299
+ *
+ * @covers get_attribute_names
+ */
+ public function test_get_attribute_names_returns_attribute_names() {
+ $p = new WP_HTML_Tag_Processor( 'Test
' );
+ $p->next_tag();
+ $this->assertSame(
+ array( 'enabled', 'class', 'data-test-id' ),
+ $p->get_attribute_names()
+ );
+ }
+
+ /**
+ * @ticket 56299
+ *
+ * @covers set_attribute
+ * @covers get_updated_html
+ * @covers get_attribute_names
+ */
+ public function test_get_attribute_names_returns_attribute_added_by_set_attribute() {
+ $p = new WP_HTML_Tag_Processor( 'Test
' );
+ $p->next_tag();
+ $p->set_attribute( 'test-attribute', 'test-value' );
+ $this->assertSame(
+ 'Test
',
+ $p->get_updated_html(),
+ "Updated HTML doesn't include attribute added via set_attribute"
+ );
+ $this->assertSame(
+ array( 'test-attribute', 'class' ),
+ $p->get_attribute_names(),
+ "Accessing attribute names doesn't find attribute added via set_attribute"
+ );
+ }
+
/**
* @ticket 56299
*