', array( 'HTML', 'BODY', 'SPAN', 'MAIN', 'MAIN' ), 1 ),
+ 'MAIN next to unclosed P' => array( '', array( 'HTML', 'BODY', 'MAIN' ), 1 ),
);
}
diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php b/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php
index 01bb41ba844f1..dcaa39fbcdfed 100644
--- a/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php
+++ b/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php
@@ -16,6 +16,105 @@ class Tests_HtmlApi_WpHtmlProcessorSemanticRules extends WP_UnitTestCase {
* RULES FOR "IN BODY" MODE
*******************************************************************/
+ /**
+ * Verifies that tags in the container group, including the ARTICLE element,
+ * close out an open P element if one exists.
+ *
+ * @covers WP_HTML_Processor::step_in_body
+ *
+ * @ticket {TICKET_NUMBER}
+ *
+ * @dataProvider data_article_container_group
+ *
+ * @param string $tag_name Name of tag in group under test.
+ */
+ public function test_in_body_article_group_closes_open_p_element( $tag_name ) {
+ $processor = WP_HTML_Processor::create_fragment( "<{$tag_name} target>" );
+
+ while ( $processor->next_tag() && null === $processor->get_attribute( 'target' ) ) {
+ continue;
+ }
+
+ $this->assertEquals(
+ $tag_name,
+ $processor->get_tag(),
+ "Expected to find {$tag_name} but found {$processor->get_tag()} instead."
+ );
+
+ $this->assertSame(
+ array( 'HTML', 'BODY', $tag_name ),
+ $processor->get_breadcrumbs(),
+ "Expected to find {$tag_name} as direct child of BODY as a result of implicitly closing an open P element."
+ );
+ }
+
+ /**
+ * Verifies that tags in the container group, including the ARTICLE element,
+ * nest inside each other despite being invalid in most cases.
+ *
+ * @covers WP_HTML_Processor::step_in_body
+ *
+ * @ticket {TICKET_NUMBER}
+ *
+ * @dataProvider data_article_container_group
+ *
+ * @param string $tag_name Name of tag in group under test.
+ */
+ public function test_in_body_article_group_can_nest_inside_itself( $tag_name ) {
+ $processor = WP_HTML_Processor::create_fragment( "
<{$tag_name}><{$tag_name}>{$tag_name}><{$tag_name}>
<{$tag_name} target>" );
+
+ while ( $processor->next_tag() && null === $processor->get_attribute( 'target' ) ) {
+ continue;
+ }
+
+ $this->assertSame(
+ array( 'HTML', 'BODY', 'DIV', $tag_name, $tag_name, 'SPAN', $tag_name ),
+ $processor->get_breadcrumbs(),
+ "Expected to find {$tag_name} deeply nested inside itself."
+ );
+ }
+
+ /**
+ * Data provider.
+ *
+ * @return array[].
+ */
+ public function data_article_container_group() {
+ $group = array();
+
+ foreach (
+ array(
+ 'ADDRESS',
+ 'ARTICLE',
+ 'ASIDE',
+ 'BLOCKQUOTE',
+ 'CENTER',
+ 'DETAILS',
+ 'DIALOG',
+ 'DIR',
+ 'DL',
+ 'DIV',
+ 'FIELDSET',
+ 'FIGCAPTION',
+ 'FIGURE',
+ 'FOOTER',
+ 'HEADER',
+ 'HGROUP',
+ 'MAIN',
+ 'MENU',
+ 'NAV',
+ 'SEARCH',
+ 'SECTION',
+ 'SUMMARY',
+ )
+ as $tag_name
+ ) {
+ $group[ $tag_name ] = array( $tag_name );
+ }
+
+ return $group;
+ }
+
/**
* Verifies that when encountering an end tag for which there is no corresponding
* element in scope, that it skips the tag entirely.
@@ -142,11 +241,11 @@ public function test_in_body_button_with_button_in_scope_as_ancestor() {
* that the HTML processor ignores the end tag if there's a special
* element on the stack of open elements before the matching opening.
*
+ * @covers WP_HTML_Processor::step_in_body
+ *
* @ticket 58907
*
* @since 6.4.0
- *
- * @covers WP_HTML_Processor::step_in_body
*/
public function test_in_body_any_other_end_tag_with_unclosed_special_element() {
$p = WP_HTML_Processor::create_fragment( '' );
@@ -165,11 +264,11 @@ public function test_in_body_any_other_end_tag_with_unclosed_special_element() {
* that the HTML processor closes appropriate elements on the stack of
* open elements up to the matching opening.
*
+ * @covers WP_HTML_Processor::step_in_body
+ *
* @ticket 58907
*
* @since 6.4.0
- *
- * @covers WP_HTML_Processor::step_in_body
*/
public function test_in_body_any_other_end_tag_with_unclosed_non_special_element() {
$p = WP_HTML_Processor::create_fragment( '