diff --git a/includes/amp-helper-functions.php b/includes/amp-helper-functions.php index 67cc7878c09..e75beaca5b9 100644 --- a/includes/amp-helper-functions.php +++ b/includes/amp-helper-functions.php @@ -915,6 +915,7 @@ function amp_get_content_sanitizers( $post = null ) { 'AMP_Style_Sanitizer' => [ 'include_manifest_comment' => ( defined( 'WP_DEBUG' ) && WP_DEBUG ) ? 'always' : 'when_excessive', ], + 'AMP_Meta_Sanitizer' => [], 'AMP_Tag_And_Attribute_Sanitizer' => [], // Note: This whitelist sanitizer must come at the end to clean up any remaining issues the other sanitizers didn't catch. ]; @@ -971,7 +972,7 @@ function amp_get_content_sanitizers( $post = null ) { } // Force style sanitizer and whitelist sanitizer to be at end. - foreach ( [ 'AMP_Style_Sanitizer', 'AMP_Tag_And_Attribute_Sanitizer' ] as $class_name ) { + foreach ( [ 'AMP_Style_Sanitizer', 'AMP_Meta_Sanitizer', 'AMP_Tag_And_Attribute_Sanitizer' ] as $class_name ) { if ( isset( $sanitizers[ $class_name ] ) ) { $sanitizer = $sanitizers[ $class_name ]; unset( $sanitizers[ $class_name ] ); diff --git a/includes/class-amp-autoloader.php b/includes/class-amp-autoloader.php index 80b0fe4e765..c6241e87671 100644 --- a/includes/class-amp-autoloader.php +++ b/includes/class-amp-autoloader.php @@ -78,6 +78,7 @@ class AMP_Autoloader { 'AMP_Iframe_Sanitizer' => 'includes/sanitizers/class-amp-iframe-sanitizer', 'AMP_Img_Sanitizer' => 'includes/sanitizers/class-amp-img-sanitizer', 'AMP_Link_Sanitizer' => 'includes/sanitizers/class-amp-link-sanitizer', + 'AMP_Meta_Sanitizer' => 'includes/sanitizers/class-amp-meta-sanitizer', 'AMP_Nav_Menu_Toggle_Sanitizer' => 'includes/sanitizers/class-amp-nav-menu-toggle-sanitizer', 'AMP_Nav_Menu_Dropdown_Sanitizer' => 'includes/sanitizers/class-amp-nav-menu-dropdown-sanitizer', 'AMP_Comments_Sanitizer' => 'includes/sanitizers/class-amp-comments-sanitizer', diff --git a/includes/class-amp-theme-support.php b/includes/class-amp-theme-support.php index 0df3b892bd4..b9fe763e303 100644 --- a/includes/class-amp-theme-support.php +++ b/includes/class-amp-theme-support.php @@ -5,6 +5,8 @@ * @package AMP */ +use Amp\AmpWP\Dom\Document; + /** * Class AMP_Theme_Support * @@ -1600,10 +1602,10 @@ public static function filter_admin_bar_script_loader_tag( $tag, $handle ) { * @link https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp/ * @todo All of this might be better placed inside of a sanitizer. * - * @param DOMDocument $dom Document. - * @param string[] $script_handles AMP script handles for components identified during output buffering. + * @param Document $dom Document. + * @param string[] $script_handles AMP script handles for components identified during output buffering. */ - public static function ensure_required_markup( DOMDocument $dom, $script_handles = [] ) { + public static function ensure_required_markup( Document $dom, $script_handles = [] ) { /** * Elements. * @@ -1614,23 +1616,14 @@ public static function ensure_required_markup( DOMDocument $dom, $script_handles * @var DOMElement $noscript */ - $xpath = new DOMXPath( $dom ); - - // Make sure the HEAD element is in the doc. - $head = $dom->getElementsByTagName( 'head' )->item( 0 ); - if ( ! $head ) { - $head = $dom->createElement( 'head' ); - $dom->documentElement->insertBefore( $head, $dom->documentElement->firstChild ); - } - // Ensure there is a schema.org script in the document. // @todo Consider applying the amp_schemaorg_metadata filter on the contents when a script is already present. - $schema_org_meta_script = $xpath->query( '//script[ @type = "application/ld+json" ][ contains( ./text(), "schema.org" ) ]' )->item( 0 ); + $schema_org_meta_script = $dom->xpath->query( '//script[ @type = "application/ld+json" ][ contains( ./text(), "schema.org" ) ]' )->item( 0 ); if ( ! $schema_org_meta_script ) { $script = $dom->createElement( 'script' ); $script->setAttribute( 'type', 'application/ld+json' ); $script->appendChild( $dom->createTextNode( wp_json_encode( amp_get_schemaorg_metadata(), JSON_UNESCAPED_UNICODE ) ) ); - $head->appendChild( $script ); + $dom->head->appendChild( $script ); } // Gather all links. @@ -1647,7 +1640,7 @@ public static function ensure_required_markup( DOMDocument $dom, $script_handles ), ], ]; - $link_elements = $head->getElementsByTagName( 'link' ); + $link_elements = $dom->head->getElementsByTagName( 'link' ); foreach ( $link_elements as $link ) { if ( $link->hasAttribute( 'rel' ) ) { $links[ $link->getAttribute( 'rel' ) ][] = $link; @@ -1665,97 +1658,18 @@ public static function ensure_required_markup( DOMDocument $dom, $script_handles 'href' => self::get_current_canonical_url(), ] ); - $head->appendChild( $rel_canonical ); - } - - /* - * Ensure meta charset and meta viewport are present. - * - * "AMP is already quite restrictive about which markup is allowed in the section. However, - * there are a few basic optimizations that you can apply. The key is to structure the section - * in a way so that all render-blocking scripts and custom fonts load as fast as possible." - * - * "1. The first tag should be the meta charset tag, followed by any remaining meta tags." - * - * {@link https://amp.dev/documentation/guides-and-tutorials/optimize-and-measure/optimize_amp/ Optimize the AMP Runtime loading} - */ - $meta_charset = null; - $meta_viewport = null; - $meta_amp_script_srcs = []; - $meta_elements = []; - foreach ( $head->getElementsByTagName( 'meta' ) as $meta ) { - if ( $meta->hasAttribute( 'charset' ) ) { // There will not be a meta[http-equiv] because the sanitizer removed it. - $meta_charset = $meta; - } elseif ( 'viewport' === $meta->getAttribute( 'name' ) ) { - $meta_viewport = $meta; - } elseif ( 'amp-script-src' === $meta->getAttribute( 'name' ) ) { - $meta_amp_script_srcs[] = $meta; - } else { - $meta_elements[] = $meta; - } - } - - // Handle meta charset. - if ( ! $meta_charset ) { - // Warning: This probably means the character encoding needs to be converted. - $meta_charset = AMP_DOM_Utils::create_node( - $dom, - 'meta', - [ - 'charset' => 'utf-8', - ] - ); - } else { - $head->removeChild( $meta_charset ); // So we can move it. - } - $head->insertBefore( $meta_charset, $head->firstChild ); - - // Handle meta viewport. - if ( ! $meta_viewport ) { - $meta_viewport = AMP_DOM_Utils::create_node( - $dom, - 'meta', - [ - 'name' => 'viewport', - 'content' => 'width=device-width', - ] - ); - } else { - $head->removeChild( $meta_viewport ); // So we can move it. + $dom->head->appendChild( $rel_canonical ); } - $head->insertBefore( $meta_viewport, $meta_charset->nextSibling ); - // Handle meta amp-script-src elements. - $first_meta_amp_script_src = array_shift( $meta_amp_script_srcs ); - if ( $first_meta_amp_script_src ) { - $meta_elements[] = $first_meta_amp_script_src; - - // Merge (and remove) any subsequent meta amp-script-src elements. - if ( ! empty( $meta_amp_script_srcs ) ) { - $content_values = [ $first_meta_amp_script_src->getAttribute( 'content' ) ]; - foreach ( $meta_amp_script_srcs as $meta_amp_script_src ) { - $meta_amp_script_src->parentNode->removeChild( $meta_amp_script_src ); - $content_values[] = $meta_amp_script_src->getAttribute( 'content' ); - } - $first_meta_amp_script_src->setAttribute( 'content', implode( ' ', $content_values ) ); - unset( $meta_amp_script_src, $content_values ); - } - } - unset( $meta_amp_script_srcs, $first_meta_amp_script_src ); - - // Insert all the the meta elements next in the head. - $previous_node = $meta_viewport; - foreach ( $meta_elements as $meta_element ) { - $meta_element->parentNode->removeChild( $meta_element ); - $head->insertBefore( $meta_element, $previous_node->nextSibling ); - $previous_node = $meta_element; - } + // Store the last meta tag as the previous node to append to. + $meta_tags = $dom->head->getElementsByTagName( 'meta' ); + $previous_node = $meta_tags->length > 0 ? $meta_tags->item( $meta_tags->length - 1 ) : $dom->head->firstChild; // Handle the title. - $title = $head->getElementsByTagName( 'title' )->item( 0 ); + $title = $dom->head->getElementsByTagName( 'title' )->item( 0 ); if ( $title ) { $title->parentNode->removeChild( $title ); // So we can move it. - $head->insertBefore( $title, $previous_node->nextSibling ); + $dom->head->insertBefore( $title, $previous_node->nextSibling ); $previous_node = $title; } @@ -1771,7 +1685,7 @@ public static function ensure_required_markup( DOMDocument $dom, $script_handles $ordered_scripts = []; $head_scripts = []; $runtime_src = wp_scripts()->registered['amp-runtime']->src; - foreach ( $head->getElementsByTagName( 'script' ) as $script ) { // Note that prepare_response() already moved body scripts to head. + foreach ( $dom->head->getElementsByTagName( 'script' ) as $script ) { // Note that prepare_response() already moved body scripts to head. $head_scripts[] = $script; } foreach ( $head_scripts as $script ) { @@ -1879,7 +1793,7 @@ public static function ensure_required_markup( DOMDocument $dom, $script_handles if ( $link->parentNode ) { $link->parentNode->removeChild( $link ); // So we can move it. } - $head->insertBefore( $link, $previous_node->nextSibling ); + $dom->head->insertBefore( $link, $previous_node->nextSibling ); $previous_node = $link; } } @@ -1913,25 +1827,25 @@ public static function ensure_required_markup( DOMDocument $dom, $script_handles */ $ordered_scripts = array_merge( $ordered_scripts, $amp_scripts ); foreach ( $ordered_scripts as $ordered_script ) { - $head->insertBefore( $ordered_script, $previous_node->nextSibling ); + $dom->head->insertBefore( $ordered_script, $previous_node->nextSibling ); $previous_node = $ordered_script; } /* * "8. Specify any custom styles by using the ', - '', + '', ], 'head_noscript_span' => [ '', - 'No script', + 'No script', ], 'test_with_dev_mode' => [ - '', + '', null, ], ]; @@ -54,13 +56,13 @@ public function test_noscript_promotion( $source, $expected = null ) { if ( null === $expected ) { $expected = $source; } - $dom = AMP_DOM_Utils::get_dom( $source ); + $dom = Document::from_html( $source ); $this->assertSame( 1, $dom->getElementsByTagName( 'noscript' )->length ); $sanitizer = new AMP_Script_Sanitizer( $dom ); $sanitizer->sanitize(); $whitelist_sanitizer = new AMP_Tag_And_Attribute_Sanitizer( $dom ); $whitelist_sanitizer->sanitize(); - $content = AMP_DOM_Utils::get_content_from_dom_node( $dom, $dom->documentElement ); + $content = $dom->saveHTML( $dom->documentElement ); $this->assertEquals( $expected, $content ); } @@ -103,10 +105,10 @@ public function test_boilerplate_preservation() { 'use_document_element' => true, ]; - $dom = AMP_DOM_Utils::get_dom( $html ); + $dom = Document::from_html( $html ); AMP_Content_Sanitizer::sanitize_document( $dom, amp_get_content_sanitizers(), $args ); - $content = AMP_DOM_Utils::get_content_from_dom_node( $dom, $dom->documentElement ); + $content = $dom->saveHTML( $dom->documentElement ); $this->assertRegExp( '/\s*/', $content ); $this->assertContains( '', $content ); diff --git a/tests/php/test-amp-style-sanitizer.php b/tests/php/test-amp-style-sanitizer.php index b8441e8ee62..b22274e443d 100644 --- a/tests/php/test-amp-style-sanitizer.php +++ b/tests/php/test-amp-style-sanitizer.php @@ -7,6 +7,7 @@ // phpcs:disable WordPress.Arrays.MultipleStatementAlignment.DoubleArrowNotAligned +use Amp\AmpWP\Dom\Document; use Amp\AmpWP\Tests\PrivateAccess; /** @@ -573,7 +574,7 @@ static function( $preempt, $request, $url ) { 10, 3 ); - $dom = AMP_DOM_Utils::get_dom( $source ); + $dom = Document::from_html( $source ); $error_codes = []; $args = [ @@ -589,7 +590,7 @@ static function( $preempt, $request, $url ) { $whitelist_sanitizer = new AMP_Tag_And_Attribute_Sanitizer( $dom, $args ); $whitelist_sanitizer->sanitize(); - $sanitized_html = AMP_DOM_Utils::get_content_from_dom_node( $dom, $dom->documentElement ); + $sanitized_html = $dom->saveHTML( $dom->documentElement ); $actual_stylesheets = array_values( $sanitizer->get_stylesheets() ); $this->assertEquals( $expected_errors, $error_codes ); $this->assertCount( count( $expected_stylesheets ), $actual_stylesheets ); @@ -724,7 +725,7 @@ public function get_amp_selector_data() { */ public function test_amp_selector_conversion( $markup, $input, $output ) { $html = "$markup"; - $dom = AMP_DOM_Utils::get_dom( $html ); + $dom = Document::from_html( $html ); $sanitizer_classes = amp_get_content_sanitizers(); $sanitized = AMP_Content_Sanitizer::sanitize_document( @@ -870,7 +871,7 @@ static function ( $selector ) { ); $html = "$markup"; - $dom = AMP_DOM_Utils::get_dom( $html ); + $dom = Document::from_html( $html ); $sanitizer_classes = amp_get_content_sanitizers(); @@ -1063,7 +1064,7 @@ public function get_amp_css_hacks_data() { */ public function test_browser_css_hacks( $input ) { $html = ""; - $dom = AMP_DOM_Utils::get_dom( $html ); + $dom = Document::from_html( $html ); $error_codes = []; $sanitizer = new AMP_Style_Sanitizer( @@ -1093,7 +1094,7 @@ public function test_font_data_url_handling() { $html .= ''; // Test with tree-shaking. - $dom = AMP_DOM_Utils::get_dom( $html ); + $dom = Document::from_html( $html ); $error_codes = []; $sanitizer = new AMP_Style_Sanitizer( $dom, @@ -1136,7 +1137,7 @@ public function test_font_data_url_handling_without_file_sources() { $html .= ''; $html .= ''; - $dom = AMP_DOM_Utils::get_dom( $html ); + $dom = Document::from_html( $html ); $error_codes = []; $sanitizer = new AMP_Style_Sanitizer( $dom, @@ -1203,7 +1204,7 @@ public function test_class_amp_bind_preservation() { '; $html .= '...'; - $dom = AMP_DOM_Utils::get_dom( $html ); + $dom = Document::from_html( $html ); $error_codes = []; $sanitizer = new AMP_Style_Sanitizer( @@ -1344,7 +1345,7 @@ public function test_css_manifest() { $html = ob_get_clean(); $error_codes = []; - $dom = AMP_DOM_Utils::get_dom( $html ); + $dom = Document::from_html( $html ); $sanitizer = new AMP_Style_Sanitizer( $dom, array_merge( @@ -1358,8 +1359,7 @@ public function test_css_manifest() { ) ); $sanitizer->sanitize(); - $xpath = new DOMXPath( $dom ); - $style = $xpath->query( '//style[ @amp-custom ]' )->item( 0 ); + $style = $dom->xpath->query( '//style[ @amp-custom ]' )->item( 0 ); return [ $style, $error_codes ]; }; @@ -1433,7 +1433,7 @@ public function test_css_manifest() { */ public function test_relative_background_url_handling() { $html = ''; // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet - $dom = AMP_DOM_Utils::get_dom( $html ); + $dom = Document::from_html( $html ); $sanitizer = new AMP_Style_Sanitizer( $dom, @@ -1442,7 +1442,7 @@ public function test_relative_background_url_handling() { ] ); $sanitizer->sanitize(); - AMP_DOM_Utils::get_content_from_dom_node( $dom, $dom->documentElement ); + $dom->saveHTML( $dom->documentElement ); $actual_stylesheets = array_values( $sanitizer->get_stylesheets() ); $this->assertCount( 1, $actual_stylesheets ); $stylesheet = $actual_stylesheets[0]; @@ -1519,7 +1519,7 @@ static function( $preempt, $request, $url ) use ( $href, &$request_count, $conte $sanitize_and_get_stylesheets = static function() use ( $href ) { $html = sprintf( '', esc_url( $href ) ); // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet - $dom = AMP_DOM_Utils::get_dom( $html ); + $dom = Document::from_html( $html ); $found_error_codes = []; @@ -1533,7 +1533,7 @@ static function( $preempt, $request, $url ) use ( $href, &$request_count, $conte ] ); $sanitizer->sanitize(); - AMP_DOM_Utils::get_content_from_dom_node( $dom, $dom->documentElement ); + $dom->saveHTML( $dom->documentElement ); return [ $found_error_codes, array_values( $sanitizer->get_stylesheets() ) ]; }; @@ -1748,7 +1748,7 @@ public function get_stylesheet_urls() { * @param string $error_code Error code. Optional. */ public function test_get_validated_url_file_path( $source, $expected, $error_code = null ) { - $dom = AMP_DOM_Utils::get_dom( '' ); + $dom = Document::from_html( '' ); $sanitizer = new AMP_Style_Sanitizer( $dom ); $actual = $sanitizer->get_validated_url_file_path( $source, [ 'css' ] ); @@ -1809,7 +1809,7 @@ public function test_remove_spaces_from_url_values( $source, $expected ) { $html .= $source; $html .= ''; - $dom = AMP_DOM_Utils::get_dom( $html ); + $dom = Document::from_html( $html ); $sanitizer = new AMP_Style_Sanitizer( $dom ); $sanitizer->sanitize(); @@ -1865,7 +1865,7 @@ public function test_font_urls( $url, $error_codes ) { $tag = sprintf( '', esc_url( $url ) ); // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet $tag = amp_filter_font_style_loader_tag_with_crossorigin_anonymous( $tag, 'font', $url ); - $dom = AMP_DOM_Utils::get_dom( sprintf( '%s', $tag ) ); + $dom = Document::from_html( sprintf( '%s', $tag ) ); $validation_errors = []; @@ -1906,7 +1906,7 @@ public function test_cors_enabled_stylesheet_url() { // Test supplying crossorigin attribute. $url = 'https://fonts.googleapis.com/css?family=Tangerine'; $link = amp_filter_font_style_loader_tag_with_crossorigin_anonymous( "", 'font', $url ); // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet - $document = AMP_DOM_Utils::get_dom( "$link" ); + $document = Document::from_html( "$link" ); $sanitizer = new AMP_Style_Sanitizer( $document, [ 'use_document_element' => true ] ); $sanitizer->sanitize(); $link = $document->getElementsByTagName( 'link' )->item( 0 ); @@ -1915,7 +1915,7 @@ public function test_cors_enabled_stylesheet_url() { // Test that existing crossorigin attribute is not overridden. $link = amp_filter_font_style_loader_tag_with_crossorigin_anonymous( "", 'font', $url ); // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet - $document = AMP_DOM_Utils::get_dom( "$link" ); // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet + $document = Document::from_html( "$link" ); // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet $sanitizer = new AMP_Style_Sanitizer( $document, [ 'use_document_element' => true ] ); $sanitizer->sanitize(); $link = $document->getElementsByTagName( 'link' )->item( 0 ); @@ -2150,7 +2150,7 @@ static function( $preempt, $request, $url ) use ( $mock_response, $stylesheet_ur 3 ); - $dom = AMP_DOM_Utils::get_dom( $markup ); + $dom = Document::from_html( $markup ); if ( ! empty( $options['auto_reject'] ) ) { add_filter( 'amp_validation_error_sanitized', '__return_false' ); @@ -2184,7 +2184,7 @@ public function test_css_import_font() { $markup .= sprintf( '', $stylesheet_url ); $markup .= 'hello'; - $dom = AMP_DOM_Utils::get_dom( $markup ); + $dom = Document::from_html( $markup ); $sanitizer = new AMP_Style_Sanitizer( $dom, [ @@ -2196,8 +2196,7 @@ public function test_css_import_font() { $this->assertCount( 1, $stylesheets ); $this->assertEquals( 'body{color:red}', $stylesheets[0] ); - $xpath = new DOMXPath( $dom ); - $link = $xpath->query( '//link[ @rel = "stylesheet" ]' )->item( 0 ); + $link = $dom->xpath->query( '//link[ @rel = "stylesheet" ]' )->item( 0 ); $this->assertInstanceOf( 'DOMElement', $link ); $this->assertEquals( set_url_scheme( $stylesheet_url, 'https' ), $link->getAttribute( 'href' ) ); } @@ -2246,7 +2245,7 @@ public function test_style_element_cdata() { $html .= ''; $html .= '

Hello World

'; - $dom = AMP_DOM_Utils::get_dom( $html ); + $dom = Document::from_html( $html ); $sanitizer = new AMP_Style_Sanitizer( $dom, [ @@ -2256,8 +2255,7 @@ public function test_style_element_cdata() { $sanitizer->sanitize(); - $xpath = new DOMXPath( $dom ); - $style = $xpath->query( '//style[ @amp-custom ]' )->item( 0 ); + $style = $dom->xpath->query( '//style[ @amp-custom ]' )->item( 0 ); $this->assertInstanceOf( 'DOMElement', $style ); $expected = "body{color:red}body{color:green}body{color:blue}\n\n/*# sourceURL=amp-custom.css */"; @@ -2271,7 +2269,7 @@ public function test_style_element_cdata() { */ public function test_body_font_stylesheet_moved_to_head() { $html = ''; // phpcs:ignore - $dom = AMP_DOM_Utils::get_dom( $html ); + $dom = Document::from_html( $html ); $link = $dom->getElementById( 'the-font' ); $this->assertInstanceOf( 'DOMElement', $link ); @@ -2354,14 +2352,14 @@ function( $original_dom, $original_source, $amphtml_dom, $amphtml_source ) { /** * Vars. * - * @var DOMDocument $original_dom - * @var string $original_source - * @var DOMDocument $amphtml_dom - * @var string $amphtml_source + * @var Document $original_dom + * @var string $original_source + * @var Document $amphtml_dom + * @var string $amphtml_source */ $this->assertInstanceOf( 'DOMElement', $original_dom->getElementById( 'wpadminbar' ), 'Expected admin bar element to be present originally.' ); $this->assertInstanceOf( 'DOMElement', $original_dom->getElementById( 'admin-bar-css' ), 'Expected admin bar CSS to be present originally.' ); - $this->assertContains( 'admin-bar', $original_dom->getElementsByTagName( 'body' )->item( 0 )->getAttribute( 'class' ) ); + $this->assertContains( 'admin-bar', $original_dom->body->getAttribute( 'class' ) ); $this->assertContains( 'earlyprintstyle', $original_source, 'Expected early print style to not be present.' ); $this->assertContains( '.is-style-outline .wp-block-button__link', $amphtml_source, 'Expected block-library/style.css' ); @@ -2369,7 +2367,7 @@ function( $original_dom, $original_source, $amphtml_dom, $amphtml_source ) { $this->assertContains( 'amp-img.amp-wp-enforced-sizes', $amphtml_source, 'Expected amp-default.css' ); $this->assertContains( 'ab-empty-item', $amphtml_source, 'Expected admin-bar.css to still be present.' ); $this->assertNotContains( 'earlyprintstyle', $amphtml_source, 'Expected early print style to not be present.' ); - $this->assertContains( 'admin-bar', $amphtml_dom->getElementsByTagName( 'body' )->item( 0 )->getAttribute( 'class' ) ); + $this->assertContains( 'admin-bar', $amphtml_dom->body->getAttribute( 'class' ) ); $this->assertInstanceOf( 'DOMElement', $amphtml_dom->getElementById( 'wpadminbar' ) ); }, ], @@ -2407,8 +2405,8 @@ public function test_prioritized_stylesheets( $html_generator, $assert ) { $this->go_to( home_url() ); $html = $html_generator(); - $original_dom = AMP_DOM_Utils::get_dom( $html ); - $amphtml_dom = clone $original_dom; + $original_dom = Document::from_html( $html ); + $amphtml_dom = clone( $original_dom ); $error_codes = []; $args = [ @@ -2627,7 +2625,7 @@ public function test_get_stylesheet_priority( $node_data, $expected ) { return 'child-theme'; }; - $dom = new DOMDocument(); + $dom = new Document(); if ( isset( $node_data[0] ) ) { $node = AMP_DOM_Utils::create_node( $dom, $node_data[0], $node_data[1] ); diff --git a/tests/php/test-amp-twitter-embed.php b/tests/php/test-amp-twitter-embed.php index 1e4bdc3eb53..182905db9ad 100644 --- a/tests/php/test-amp-twitter-embed.php +++ b/tests/php/test-amp-twitter-embed.php @@ -132,7 +132,7 @@ public function test__conversion( $source, $expected ) { $content = AMP_DOM_Utils::get_content_from_dom( $dom ); - $this->assertEquals( $content, $expected ); + $this->assertEquals( $expected, $content ); } public function get_scripts_data() { diff --git a/tests/php/test-class-amp-base-sanitizer.php b/tests/php/test-class-amp-base-sanitizer.php index cf333237b33..4cde64bc24f 100644 --- a/tests/php/test-class-amp-base-sanitizer.php +++ b/tests/php/test-class-amp-base-sanitizer.php @@ -5,6 +5,8 @@ * @package AMP */ +use Amp\AmpWP\Dom\Document; + /** * Test AMP_Base_Sanitizer_Test * @@ -185,7 +187,7 @@ public function get_data() { * @covers AMP_Base_Sanitizer::set_layout() */ public function test_set_layout( $source_attributes, $expected_attributes, $args = [] ) { - $sanitizer = new AMP_Test_Stub_Sanitizer( new DOMDocument(), $args ); + $sanitizer = new AMP_Test_Stub_Sanitizer( new Document(), $args ); $returned_attributes = $sanitizer->set_layout( $source_attributes ); $this->assertEquals( $expected_attributes, $returned_attributes ); } @@ -261,7 +263,7 @@ public function get_sanitize_dimension_data() { * @covers AMP_Base_Sanitizer::sanitize_dimension() */ public function test_sanitize_dimension( $source_params, $expected_value, $args = [] ) { - $sanitizer = new AMP_Test_Stub_Sanitizer( new DOMDocument(), $args ); + $sanitizer = new AMP_Test_Stub_Sanitizer( new Document(), $args ); list( $value, $dimension ) = $source_params; $actual_value = $sanitizer->sanitize_dimension( $value, $dimension ); @@ -278,7 +280,7 @@ public function test_sanitize_dimension( $source_params, $expected_value, $args */ public function test_remove_invalid_child() { $parent_tag_name = 'div'; - $dom_document = new DOMDocument( '1.0', 'utf-8' ); + $dom_document = new Document( '1.0', 'utf-8' ); $parent = $dom_document->createElement( $parent_tag_name ); $child = $dom_document->createElement( 'script' ); $child->setAttribute( 'id', 'foo' ); @@ -492,14 +494,14 @@ public function test_remove_invalid_attribute_dev_mode() { */ public function test_get_data_amp_attributes() { $tag = 'figure'; - $dom_document = new DOMDocument( '1.0', 'utf-8' ); + $dom_document = new Document( '1.0', 'utf-8' ); $figure = $dom_document->createElement( $tag ); $amp_img = $dom_document->createElement( 'amp-img' ); $figure->appendChild( $amp_img ); $figure->setAttribute( 'data-amp-noloading', 'true' ); $figure->setAttribute( 'data-amp-layout', 'fixed' ); - $sanitizer = new AMP_Test_Stub_Sanitizer( new DOMDocument(), [] ); + $sanitizer = new AMP_Test_Stub_Sanitizer( new Document(), [] ); $amp_args = $sanitizer->get_data_amp_attributes( $amp_img ); $expected_args = [ @@ -523,7 +525,7 @@ public function test_filter_data_amp_attributes() { $attributes = [ 'width' => 100, ]; - $sanitizer = new AMP_Test_Stub_Sanitizer( new DOMDocument(), [] ); + $sanitizer = new AMP_Test_Stub_Sanitizer( new Document(), [] ); $attributes = $sanitizer->filter_data_amp_attributes( $attributes, $amp_data ); $expected = [ @@ -539,9 +541,9 @@ public function test_filter_data_amp_attributes() { * @covers AMP_Base_Sanitizer::filter_attachment_layout_attributes() */ public function test_filter_attachment_layout_attributes() { - $sanitizer = new AMP_Test_Stub_Sanitizer( new DOMDocument(), [] ); + $sanitizer = new AMP_Test_Stub_Sanitizer( new Document(), [] ); $tag = 'figure'; - $dom_document = new DOMDocument( '1.0', 'utf-8' ); + $dom_document = new Document( '1.0', 'utf-8' ); $figure = $dom_document->createElement( $tag ); $amp_img = $dom_document->createElement( 'amp-img' ); $layout = 'fixed-height'; diff --git a/tests/php/test-class-amp-comments-sanitizer.php b/tests/php/test-class-amp-comments-sanitizer.php index fd63410efa4..3ff2084e6a2 100644 --- a/tests/php/test-class-amp-comments-sanitizer.php +++ b/tests/php/test-class-amp-comments-sanitizer.php @@ -5,6 +5,7 @@ * @package AMP */ +use Amp\AmpWP\Dom\Document; use Amp\AmpWP\Tests\PrivateAccess; /** @@ -19,7 +20,7 @@ class Test_AMP_Comments_Sanitizer extends WP_UnitTestCase { /** * Representation of the DOM. * - * @var DOMDocument + * @var Document */ public $dom; @@ -31,7 +32,7 @@ class Test_AMP_Comments_Sanitizer extends WP_UnitTestCase { public function setUp() { parent::setUp(); $GLOBALS['post'] = self::factory()->post->create_and_get(); - $this->dom = new DOMDocument(); + $this->dom = new Document(); } /** @@ -96,6 +97,11 @@ public function test_process_comment_form() { $this->assertContains( $name, $amp_state->nodeValue ); } foreach ( $form->getElementsByTagName( 'input' ) as $input ) { + /** + * Input. + * + * @var DOMElement $input + */ $on = $input->getAttribute( 'on' ); $this->assertContains( 'change:AMP.setState(', $on ); $this->assertContains( strval( $GLOBALS['post']->ID ), $on ); @@ -121,8 +127,7 @@ public function test_add_amp_live_list_comment_attributes() { $this->create_comments_list( $comment_objects ); $instance->sanitize(); - $xpath = new DOMXPath( $this->dom ); - $comments = $xpath->query( '//*[ starts-with( @id, "comment-" ) ]' ); + $comments = $this->dom->xpath->query( '//*[ starts-with( @id, "comment-" ) ]' ); foreach ( $comments as $comment ) { /** diff --git a/tests/php/test-class-amp-core-theme-sanitizer.php b/tests/php/test-class-amp-core-theme-sanitizer.php index 73702342900..a5c93ed3fd8 100644 --- a/tests/php/test-class-amp-core-theme-sanitizer.php +++ b/tests/php/test-class-amp-core-theme-sanitizer.php @@ -5,6 +5,7 @@ * @package AMP */ +use Amp\AmpWP\Dom\Document; use Amp\AmpWP\Tests\PrivateAccess; /** @@ -38,16 +39,19 @@ public function get_xpath_from_css_selector_data() { * * @dataProvider get_xpath_from_css_selector_data * @covers AMP_Core_Theme_Sanitizer::xpath_from_css_selector() + * + * @param string $css_selector CSS Selector. + * @param string $expected Expected XPath expression. */ public function test_xpath_from_css_selector( $css_selector, $expected ) { - $dom = new DOMDocument(); + $dom = new Document(); $sanitizer = new AMP_Core_Theme_Sanitizer( $dom ); $actual = $this->call_private_method( $sanitizer, 'xpath_from_css_selector', [ $css_selector ] ); $this->assertEquals( $expected, $actual ); } public function get_get_closest_submenu_data() { - $html = ' + $html = ' '; - $dom = AMP_DOM_Utils::get_dom_from_content( $html ); - $xpath = new DOMXPath( $dom ); + $dom = AMP_DOM_Utils::get_dom_from_content( $html ); return [ // First sub-menu. - [ $dom, $xpath, $xpath->query( "//*[ @id = 'menu-item-2' ]" )->item( 0 ), $xpath->query( "//*[ @id = 'sub-menu-1' ]" )->item( 0 ) ], + [ $dom, $dom->xpath->query( "//*[ @id = 'menu-item-2' ]" )->item( 0 ), $dom->xpath->query( "//*[ @id = 'sub-menu-1' ]" )->item( 0 ) ], // Second sub-menu. - [ $dom, $xpath, $xpath->query( "//*[ @id = 'menu-item-5' ]" )->item( 0 ), $xpath->query( "//*[ @id = 'sub-menu-2' ]" )->item( 0 ) ], + [ $dom, $dom->xpath->query( "//*[ @id = 'menu-item-5' ]" )->item( 0 ), $dom->xpath->query( "//*[ @id = 'sub-menu-2' ]" )->item( 0 ) ], // Sub-menu of second sub-menu. - [ $dom, $xpath, $xpath->query( "//*[ @id = 'menu-item-6' ]" )->item( 0 ), $xpath->query( "//*[ @id = 'sub-menu-3' ]" )->item( 0 ) ], + [ $dom, $dom->xpath->query( "//*[ @id = 'menu-item-6' ]" )->item( 0 ), $dom->xpath->query( "//*[ @id = 'sub-menu-3' ]" )->item( 0 ) ], ]; } @@ -90,11 +93,14 @@ public function get_get_closest_submenu_data() { * * @dataProvider get_get_closest_submenu_data * @covers AMP_Core_Theme_Sanitizer::get_closest_submenu() + * + * @param Document $dom Document. + * @param DOMElement $element Element. + * @param DOMElement $expected Expected element. */ - public function test_get_closest_submenu( $dom, $xpath, $element, $expected ) { + public function test_get_closest_submenu( $dom, $element, $expected ) { $sanitizer = new AMP_Core_Theme_Sanitizer( $dom ); - $this->set_private_property( $sanitizer, 'xpath', $xpath ); - $actual = $this->call_private_method( $sanitizer, 'get_closest_submenu', [ $element ] ); + $actual = $this->call_private_method( $sanitizer, 'get_closest_submenu', [ $element ] ); $this->assertEquals( $expected, $actual ); } } diff --git a/tests/php/test-class-amp-dom-document.php b/tests/php/test-class-amp-dom-document.php new file mode 100644 index 00000000000..41a06f04b84 --- /dev/null +++ b/tests/php/test-class-amp-dom-document.php @@ -0,0 +1,449 @@ +'; + + return [ + 'minimum_valid_document' => [ + 'utf-8', + '', + '' . $head . '', + ], + 'valid_document_with_attributes' => [ + 'utf-8', + '' . $head . '

Text

', + '' . $head . '

Text

', + ], + 'missing_head' => [ + 'utf-8', + '

Text

', + '' . $head . '

Text

', + ], + 'missing_body' => [ + 'utf-8', + '' . $head . '

Text

', + '' . $head . '

Text

', + ], + 'missing_head_and_body' => [ + 'utf-8', + '

Text

', + '' . $head . '

Text

', + ], + 'missing_html_and_head_and_body' => [ + 'utf-8', + '

Text

', + '' . $head . '

Text

', + ], + 'content_only' => [ + 'utf-8', + '

Text

', + '' . $head . '

Text

', + ], + 'missing_doctype' => [ + 'utf-8', + '' . $head . '

Text

', + '' . $head . '

Text

', + ], + 'html_4_doctype' => [ + 'utf-8', + '' . $head . '

Text

', + '' . $head . '

Text

', + ], + 'lots_of_whitespace' => [ + 'utf-8', + " \n \n \n \n \n \n \n

\n Text \n

\n\n \n \n ", + '' . $head . '

Text

', + ], + 'utf_8_encoding_predefined' => [ + 'utf-8', + '

مرحبا بالعالم! Check out ‘this’ and “that” and—other things.

', + '' . $head . '

مرحبا بالعالم! Check out ‘this’ and “that” and—other things.

', + ], + 'utf_8_encoding_guessed_via_charset' => [ + '', + '' . $head . '

مرحبا بالعالم! Check out ‘this’ and “that” and—other things.

', + '' . $head . '

مرحبا بالعالم! Check out ‘this’ and “that” and—other things.

', + ], + 'utf_8_encoding_guessed_via_content' => [ + '', + '

مرحبا بالعالم! Check out ‘this’ and “that” and—other things.

', + '' . $head . '

مرحبا بالعالم! Check out ‘this’ and “that” and—other things.

', + ], + 'iso_8859_1_encoding_predefined' => [ + 'iso-8859-1', + utf8_decode( '

ÄÖÜ

' ), + '' . $head . '

ÄÖÜ

', + ], + 'iso_8859_1_encoding_guessed_via_charset' => [ + '', + utf8_decode( '

ÄÖÜ

' ), + '' . $head . '

ÄÖÜ

', + ], + 'iso_8859_1_encoding_guessed_via_content' => [ + '', + utf8_decode( '

ÄÖÜ

' ), + '' . $head . '

ÄÖÜ

', + ], + 'raw_iso_8859_1' => [ + '', + utf8_decode( 'ÄÖÜ' ), + '' . $head . 'ÄÖÜ', + ], + // Make sure we correctly identify the ISO-8859 sub-charsets ("€" does not exist in ISO-8859-1). + 'iso_8859_15_encoding_predefined' => [ + 'iso-8859-1', + mb_convert_encoding( '

', 'ISO-8859-15', 'UTF-8' ), + '' . $head . '

', + ], + 'iso_8859_15_encoding_guessed_via_charset' => [ + '', + mb_convert_encoding( '

', 'ISO-8859-15', 'UTF-8' ), + '' . $head . '

', + ], + 'iso_8859_15_encoding_guessed_via_content' => [ + '', + mb_convert_encoding( '

', 'ISO-8859-15', 'UTF-8' ), + '' . $head . '

', + ], + 'raw_iso_8859_15' => [ + '', + mb_convert_encoding( '€', 'ISO-8859-15', 'UTF-8' ), + '' . $head . '€', + ], + ]; + } + + /** + * Tests loading and saving the content via Amp\AmpWP\Dom\Document. + * + * @param string $charset Charset to use. + * @param string $source Source content. + * @param string $expected Expected target content. + * + * @dataProvider data_dom_document + * @covers Document::loadHTML() + * @covers Document::saveHTML() + */ + public function test_dom_document( $charset, $source, $expected ) { + $document = Document::from_html( $source, $charset ); + $this->assertEqualMarkup( $expected, $document->saveHTML() ); + } + + /** + * Assert markup is equal. + * + * @param string $expected Expected markup. + * @param string $actual Actual markup. + */ + public function assertEqualMarkup( $expected, $actual ) { + $actual = preg_replace( '/\s+/', ' ', $actual ); + $expected = preg_replace( '/\s+/', ' ', $expected ); + $actual = preg_replace( '/(?<=>)\s+(?=<)/', '', trim( $actual ) ); + $expected = preg_replace( '/(?<=>)\s+(?=<)/', '', trim( $expected ) ); + + $this->assertEquals( + array_filter( preg_split( '#(<[^>]+>|[^<>]+)#', $expected, -1, PREG_SPLIT_DELIM_CAPTURE ) ), + array_filter( preg_split( '#(<[^>]+>|[^<>]+)#', $actual, -1, PREG_SPLIT_DELIM_CAPTURE ) ) + ); + } + + /** + * Test convert_amp_bind_attributes. + * + * @covers Document::convert_amp_bind_attributes() + */ + public function test_amp_bind_conversion() { + $original = ''; + $converted = Document::from_html( $original )->saveHTML(); + $this->assertNotEquals( $original, $converted ); + $this->assertContains( Document::AMP_BIND_DATA_ATTR_PREFIX . 'src="myAnimals[currentAnimal].imageUrl"', $converted ); + $this->assertContains( 'width="300" height="200" data-foo="bar" selected', $converted ); + + // Check tag with self-closing attribute. + $original = ''; + $converted = Document::from_html( $original )->saveHTML(); + $this->assertNotEquals( $original, $converted ); + + // Preserve trailing slash that is actually the attribute value. + $original = 'Home'; + $dom = Document::from_html( $original ); + $converted = $dom->saveHTML( $dom->body->firstChild ); + $this->assertEquals( 'Home', $converted ); + + // Test malformed. + $malformed_html = [ + '', + '', + '', + ]; + foreach ( $malformed_html as $html ) { + $converted = Document::from_html( $html )->saveHTML(); + $this->assertNotContains( Document::AMP_BIND_DATA_ATTR_PREFIX, $converted, "Source: {$html}" ); + } + } + + /** + * Get Table Row Iterations + * + * @return array An array of arrays holding an integer representation of iterations. + */ + public function get_table_row_iterations() { + return [ + [ 1 ], + [ 10 ], + [ 100 ], + [ 1000 ], + [ 10000 ], + [ 100000 ], + ]; + } + + /** + * Tests attribute conversions on content with iframe srcdocs of variable lengths. + * + * @dataProvider get_table_row_iterations + * + * @param int $iterations The number of table rows to append to iframe srcdoc. + */ + public function test_attribute_conversion_on_long_iframe_srcdocs( $iterations ) { + $html = ''; + + for ( $i = 0; $i < $iterations; $i++ ) { + $html .= ' + + + + + + + + + + + '; + } + + $html .= '
14531947Pittsburgh Ironmen1242119211111182
'; + + $to_convert = sprintf( + ' ', + htmlentities( $html ) + ); + + Document::from_html( $to_convert )->saveHTML(); + + $this->assertSame( PREG_NO_ERROR, preg_last_error(), 'Probably failed when backtrack limit was exhausted.' ); + } + + /** + * Test that HEAD and BODY elements are always present. + * + * @covers Document::normalize_document_structure() + */ + public function test_ensuring_head_body() { + // The meta charset tag that is automatically added needs to always be taken into account. + + $html = '

Hello

'; + $dom = Document::from_html( $html ); + $this->assertEquals( 'head', $dom->documentElement->firstChild->nodeName ); + $this->assertEquals( 1, $dom->head->childNodes->length ); + $this->assertEquals( 'body', $dom->documentElement->lastChild->nodeName ); + $this->assertEquals( $dom->body, $dom->getElementsByTagName( 'p' )->item( 0 )->parentNode ); + + $html = 'foo'; + $dom = Document::from_html( $html ); + $this->assertEquals( 'head', $dom->documentElement->firstChild->nodeName ); + $this->assertEquals( $dom->head->firstChild, $dom->getElementsByTagName( 'meta' )->item( 0 ) ); + $this->assertEquals( $dom->head->firstChild->nextSibling, $dom->getElementsByTagName( 'title' )->item( 0 ) ); + $this->assertEquals( 'body', $dom->documentElement->lastChild->nodeName ); + $this->assertEquals( 0, $dom->body->childNodes->length ); + + $html = 'foo

no body

'; + $dom = Document::from_html( $html ); + $this->assertEquals( 'head', $dom->documentElement->firstChild->nodeName ); + $this->assertEquals( $dom->head->firstChild, $dom->getElementsByTagName( 'meta' )->item( 0 ) ); + $this->assertEquals( $dom->head->firstChild->nextSibling, $dom->getElementsByTagName( 'title' )->item( 0 ) ); + $p = $dom->getElementsByTagName( 'p' )->item( 0 ); + $this->assertEquals( $dom->body, $p->parentNode ); + $this->assertEquals( 'no body', $p->textContent ); + + $html = 'Hello world'; + $dom = Document::from_html( $html ); + $this->assertEquals( 'head', $dom->documentElement->firstChild->nodeName ); + $this->assertEquals( 1, $dom->head->childNodes->length ); + $this->assertEquals( 'body', $dom->documentElement->lastChild->nodeName ); + $this->assertEquals( 'Hello world', $dom->body->lastChild->textContent ); + } + + /** + * Test that invalid head nodes are moved to body. + * + * @covers Document::move_invalid_head_nodes_to_body() + */ + public function test_invalid_head_nodes() { + // The meta charset tag that is automatically added needs to always be taken into account. + + // Text node. + $html = 'textend'; + $dom = Document::from_html( $html ); + $this->assertEquals( 'meta', $dom->head->firstChild->tagName ); + $this->assertNull( $dom->head->firstChild->nextSibling ); + $body_first_child = $dom->body->firstChild; + $this->assertInstanceOf( 'DOMElement', $body_first_child ); + $this->assertEquals( 'text', $body_first_child->textContent ); + + // Valid nodes. + $html = 'a'; + $dom = Document::from_html( $html ); + $this->assertEquals( 9, $dom->head->childNodes->length ); + $this->assertNull( $dom->body->firstChild ); + + // Invalid nodes. + $html = '

hi

'; + $dom = Document::from_html( $html ); + $this->assertEquals( 'meta', $dom->head->firstChild->tagName ); + $this->assertNull( $dom->head->firstChild->nextSibling ); + $this->assertEquals( 6, $dom->body->childNodes->length ); + } + + /** + * Get head node data. + * + * @return array Head node data. + */ + public function get_head_node_data() { + $dom = new Document(); + return [ + [ + $dom, + AMP_DOM_Utils::create_node( $dom, 'title', [] ), + true, + ], + [ + $dom, + AMP_DOM_Utils::create_node( + $dom, + 'base', + [ 'href' => '/' ] + ), + true, + ], + [ + $dom, + AMP_DOM_Utils::create_node( + $dom, + 'script', + [ 'src' => 'http://example.com/test.js' ] + ), + true, + ], + [ + $dom, + AMP_DOM_Utils::create_node( $dom, 'style', [ 'media' => 'print' ] ), + true, + ], + [ + $dom, + AMP_DOM_Utils::create_node( $dom, 'noscript', [] ), + true, + ], + [ + $dom, + AMP_DOM_Utils::create_node( + $dom, + 'link', + [ + 'rel' => 'stylesheet', + 'href' => 'https://example.com/foo.css', + ] + ), + true, + ], + [ + $dom, + AMP_DOM_Utils::create_node( + $dom, + 'meta', + [ + 'name' => 'foo', + 'content' => 'https://example.com/foo.css', + ] + ), + true, + ], + [ + $dom, + $dom->createTextNode( " \n\t" ), + true, + ], + [ + $dom, + $dom->createTextNode( 'no' ), + false, + ], + [ + $dom, + $dom->createComment( 'hello world' ), + true, + ], + [ + $dom, + $dom->createProcessingInstruction( 'test' ), + false, + ], + [ + $dom, + $dom->createCDATASection( 'nope' ), + false, + ], + [ + $dom, + $dom->createEntityReference( 'bad' ), + false, + ], + [ + $dom, + $dom->createElementNS( 'http://www.w3.org/2000/svg', 'svg' ), + false, + ], + [ + $dom, + AMP_DOM_Utils::create_node( $dom, 'span', [] ), + false, + ], + ]; + } + + /** + * Test is_valid_head_node(). + * + * @dataProvider get_head_node_data + * @covers Document::is_valid_head_node() + * + * @param Document $dom DOM document to use. + * @param DOMNode $node Node. + * @param bool $valid Expected valid. + */ + public function test_is_valid_head_node( $dom, $node, $valid ) { + $this->assertEquals( $valid, $dom->is_valid_head_node( $node ) ); + } +} diff --git a/tests/php/test-class-amp-dom-utils.php b/tests/php/test-class-amp-dom-utils.php index 99999974a90..f69cfbb6c54 100644 --- a/tests/php/test-class-amp-dom-utils.php +++ b/tests/php/test-class-amp-dom-utils.php @@ -1,5 +1,7 @@ assertEquals( $expected, $actual ); } - /** - * Test convert_amp_bind_attributes. - * - * @covers \AMP_DOM_Utils::convert_amp_bind_attributes() - */ - public function test_amp_bind_conversion() { - $original = ''; - $converted = AMP_DOM_Utils::convert_amp_bind_attributes( $original ); - $this->assertNotEquals( $converted, $original ); - $this->assertContains( AMP_DOM_Utils::AMP_BIND_DATA_ATTR_PREFIX . 'src="myAnimals[currentAnimal].imageUrl"', $converted ); - $this->assertContains( 'width=300 height="200" data-foo="bar" selected', $converted ); - - // Check tag with self-closing attribute. - $original = ''; - $converted = AMP_DOM_Utils::convert_amp_bind_attributes( $original ); - $this->assertNotEquals( $converted, $original ); - - // Preserve trailing slash that is actually the attribute value. - $original = 'Home'; - $this->assertEquals( AMP_DOM_Utils::convert_amp_bind_attributes( $original ), $original ); - - // Test malformed. - $malformed_html = [ - '', - '', - '', - ]; - foreach ( $malformed_html as $html ) { - $converted = AMP_DOM_Utils::convert_amp_bind_attributes( $html ); - $this->assertNotContains( AMP_DOM_Utils::AMP_BIND_DATA_ATTR_PREFIX, $converted, "Source: $html" ); - } - } - /** * Test handling of empty elements. * - * @covers \AMP_DOM_Utils::get_dom() + * @covers \Amp\AmpWP\Dom\Document::from_html() * @covers \AMP_DOM_Utils::get_content_from_dom_node() */ public function test_html5_empty_elements() { @@ -222,7 +191,7 @@ public function test_html5_empty_elements() { /** * Test parsing DOM with Mustache or Mustache-like templates. * - * @covers \AMP_DOM_Utils::get_dom() + * @covers \Amp\AmpWP\Dom\Document::from_html() * @covers \AMP_DOM_Utils::get_content_from_dom_node() */ public function test_mustache_replacements() { @@ -255,11 +224,10 @@ public function test_mustache_replacements() { ] ); - $dom = AMP_DOM_Utils::get_dom_from_content( $html ); - $xpath = new DOMXPath( $dom ); + $dom = AMP_DOM_Utils::get_dom_from_content( $html ); // Ensure that JSON in scripts are left intact. - $script = $xpath->query( '//script' )->item( 0 ); + $script = $dom->xpath->query( '//script' )->item( 0 ); $this->assertEquals( $data, json_decode( $script->nodeValue, true ) @@ -273,19 +241,19 @@ public function test_mustache_replacements() { * @var DOMElement $template_img * @var DOMElement $template_blockquote */ - $template_link = $xpath->query( '//template/a' )->item( 0 ); + $template_link = $dom->xpath->query( '//template/a' )->item( 0 ); $this->assertSame( '{{href}}', $template_link->getAttribute( 'href' ) ); $this->assertEquals( 'Hello {{name}}', $template_link->getAttribute( 'title' ) ); // Ensure that mustache var in img[src] attribute is intact. - $template_img = $xpath->query( '//template/a/img' )->item( 0 ); + $template_img = $dom->xpath->query( '//template/a/img' )->item( 0 ); $this->assertEquals( '{{src}}', $template_img->getAttribute( 'src' ) ); // Ensure that mustache var in blockquote[cite] is not changed. - $template_blockquote = $xpath->query( '//template/blockquote' )->item( 0 ); + $template_blockquote = $dom->xpath->query( '//template/blockquote' )->item( 0 ); $this->assertEquals( '{{cite}}', $template_blockquote->getAttribute( 'cite' ) ); - $serialized_html = AMP_DOM_Utils::get_content_from_dom_node( $dom, $dom->documentElement ); + $serialized_html = $dom->saveHTML( $dom->documentElement ); $this->assertContains( '', $serialized_html ); $this->assertContains( '', $serialized_html ); @@ -296,18 +264,18 @@ public function test_mustache_replacements() { /** * Test encoding. * - * @covers \AMP_DOM_Utils::get_dom() + * @covers \Amp\AmpWP\Dom\Document::from_html() */ public function test_get_dom_encoding() { - $html = 'مرحبا بالعالم! Check out ‘this’ and “that” and—other things.'; + $html = 'مرحبا بالعالم! Check out ‘this’ and “that” and—other things.'; $html .= '

مرحبا بالعالم! Check out ‘this’ and “that” and—other things.

'; $html .= '

مرحبا بالعالم! Check out ‘this’ and “that” and—other things.

'; $html .= '

مرحبا بالعالم! Check out ‘this’ and “that” and—other things.

'; $html .= ''; - $document = AMP_DOM_Utils::get_dom( $html ); + $document = Document::from_html( $html ); - $this->assertEquals( 'UTF-8', $document->encoding ); + $this->assertEquals( 'utf-8', $document->encoding ); $paragraphs = $document->getElementsByTagName( 'p' ); $this->assertSame( 3, $paragraphs->length ); $this->assertSame( $paragraphs->item( 0 )->textContent, $paragraphs->item( 1 )->textContent ); @@ -316,60 +284,7 @@ public function test_get_dom_encoding() { } /** - * Get Table Row Iterations - * - * @return array An array of arrays holding an integer representation of iterations. - */ - public function get_table_row_iterations() { - return [ - [ 1 ], - [ 10 ], - [ 100 ], - [ 1000 ], - [ 10000 ], - [ 100000 ], - ]; - } - - /** - * Tests attribute conversions on content with iframe srcdocs of variable lengths. - * - * @dataProvider get_table_row_iterations - * - * @param int $iterations The number of table rows to append to iframe srcdoc. - */ - public function test_attribute_conversion_on_long_iframe_srcdocs( $iterations ) { - $html = ''; - - for ( $i = 0; $i < $iterations; $i++ ) { - $html .= ' - - - - - - - - - - - '; - } - - $html .= '
14531947Pittsburgh Ironmen1242119211111182
'; - - $to_convert = sprintf( - ' ', - htmlentities( $html ) - ); - - AMP_DOM_Utils::convert_amp_bind_attributes( $to_convert ); - - $this->assertSame( PREG_NO_ERROR, preg_last_error(), 'Probably failed when backtrack limit was exhausted.' ); - } - - /** - * Test preserving whitespace when serializing DOMDocument as HTML string. + * Test preserving whitespace when serializing Dom\Document as HTML string. * * @covers \AMP_DOM_Utils::get_content_from_dom_node() * @covers \AMP_DOM_Utils::get_content_from_dom() @@ -379,189 +294,17 @@ public function test_whitespace_preservation() { $body = " start
  • First
  • Second
\t* one\n\t* two\n\t* three
end "; $html = "$body"; - $dom = AMP_DOM_Utils::get_dom( "$html" ); + $dom = Document::from_html( "$html" ); - $output = AMP_DOM_Utils::get_content_from_dom_node( $dom, $dom->documentElement ); + $output = $dom->saveHTML( $dom->documentElement ); $this->assertEquals( $html, $output ); $output = AMP_DOM_Utils::get_content_from_dom( $dom ); $this->assertEquals( $body, $output ); } - /** - * Test that HEAD and BODY elements are always present. - * - * @covers \AMP_DOM_Utils::get_dom() - */ - public function test_ensuring_head_body() { - $html = '

Hello

'; - $dom = AMP_DOM_Utils::get_dom( $html ); - $this->assertEquals( 'head', $dom->documentElement->firstChild->nodeName ); - $this->assertEquals( 0, $dom->documentElement->firstChild->childNodes->length ); - $this->assertEquals( 'body', $dom->documentElement->lastChild->nodeName ); - $this->assertEquals( $dom->documentElement->lastChild, $dom->getElementsByTagName( 'p' )->item( 0 )->parentNode ); - - $html = 'foo'; - $dom = AMP_DOM_Utils::get_dom( $html ); - $this->assertEquals( 'head', $dom->documentElement->firstChild->nodeName ); - $this->assertEquals( $dom->documentElement->firstChild, $dom->getElementsByTagName( 'title' )->item( 0 )->parentNode ); - $this->assertEquals( 'body', $dom->documentElement->lastChild->nodeName ); - $this->assertEquals( 0, $dom->documentElement->lastChild->childNodes->length ); - - $html = 'foo

no body

'; - $dom = AMP_DOM_Utils::get_dom( $html ); - $this->assertEquals( 'head', $dom->documentElement->firstChild->nodeName ); - $this->assertEquals( $dom->documentElement->firstChild, $dom->getElementsByTagName( 'title' )->item( 0 )->parentNode ); - $p = $dom->getElementsByTagName( 'p' )->item( 0 ); - $this->assertEquals( $dom->documentElement->lastChild, $p->parentNode ); - $this->assertEquals( 'no body', $p->textContent ); - - $html = 'Hello world'; - $dom = AMP_DOM_Utils::get_dom( $html ); - $this->assertEquals( 'head', $dom->documentElement->firstChild->nodeName ); - $this->assertEquals( 0, $dom->documentElement->firstChild->childNodes->length ); - $this->assertEquals( 'body', $dom->documentElement->lastChild->nodeName ); - $p = $dom->getElementsByTagName( 'p' )->item( 0 ); - $this->assertEquals( $dom->documentElement->lastChild, $p->parentNode ); - $this->assertEquals( 'Hello world', $p->textContent ); - } - - /** - * Get head node data. - * - * @return array Head node data. - */ - public function get_head_node_data() { - $dom = new DOMDocument(); - return [ - [ - AMP_DOM_Utils::create_node( $dom, 'title', [] ), - true, - ], - [ - AMP_DOM_Utils::create_node( - $dom, - 'base', - [ 'href' => '/' ] - ), - true, - ], - [ - AMP_DOM_Utils::create_node( - $dom, - 'script', - [ 'src' => 'http://example.com/test.js' ] - ), - true, - ], - [ - AMP_DOM_Utils::create_node( $dom, 'style', [ 'media' => 'print' ] ), - true, - ], - [ - AMP_DOM_Utils::create_node( $dom, 'noscript', [] ), - true, - ], - [ - AMP_DOM_Utils::create_node( - $dom, - 'link', - [ - 'rel' => 'stylesheet', - 'href' => 'https://example.com/foo.css', - ] - ), - true, - ], - [ - AMP_DOM_Utils::create_node( - $dom, - 'meta', - [ - 'name' => 'foo', - 'content' => 'https://example.com/foo.css', - ] - ), - true, - ], - [ - $dom->createTextNode( " \n\t" ), - true, - ], - [ - $dom->createTextNode( 'no' ), - false, - ], - [ - $dom->createComment( 'hello world' ), - true, - ], - [ - $dom->createProcessingInstruction( 'test' ), - false, - ], - [ - $dom->createCDATASection( 'nope' ), - false, - ], - [ - $dom->createEntityReference( 'bad' ), - false, - ], - [ - $dom->createElementNS( 'http://www.w3.org/2000/svg', 'svg' ), - false, - ], - [ - AMP_DOM_Utils::create_node( $dom, 'span', [] ), - false, - ], - ]; - } - - /** - * Test is_valid_head_node(). - * - * @dataProvider get_head_node_data - * @covers \AMP_DOM_Utils::is_valid_head_node() - * - * @param DOMNode $node Node. - * @param bool $valid Expected valid. - */ - public function test_is_valid_head_node( $node, $valid ) { - $this->assertEquals( $valid, AMP_DOM_Utils::is_valid_head_node( $node ) ); - } - - /** - * Test that invalid head nodes are moved to body. - * - * @covers \AMP_DOM_Utils::move_invalid_head_nodes_to_body() - */ - public function test_invalid_head_nodes() { - - // Text node. - $html = 'textend'; - $dom = AMP_DOM_Utils::get_dom( $html ); - $this->assertNull( $dom->getElementsByTagName( 'head' )->item( 0 )->firstChild ); - $body_first_child = $dom->getElementsByTagName( 'body' )->item( 0 )->firstChild; - $this->assertInstanceOf( 'DOMElement', $body_first_child ); - $this->assertEquals( 'text', $body_first_child->textContent ); - - // Valid nodes. - $html = 'a'; - $dom = AMP_DOM_Utils::get_dom( $html ); - $this->assertEquals( 8, $dom->getElementsByTagName( 'head' )->item( 0 )->childNodes->length ); - $this->assertNull( $dom->getElementsByTagName( 'body' )->item( 0 )->firstChild ); - - // Invalid nodes. - $html = '

hi

'; - $dom = AMP_DOM_Utils::get_dom( $html ); - $this->assertNull( $dom->getElementsByTagName( 'head' )->item( 0 )->firstChild ); - $this->assertEquals( 6, $dom->getElementsByTagName( 'body' )->item( 0 )->childNodes->length ); - } - public function get_has_class_data() { - $dom = new DOMDocument(); + $dom = new Document(); return [ // Element without class attribute. @@ -592,6 +335,10 @@ public function get_has_class_data() { * * @dataProvider get_has_class_data * @covers \AMP_DOM_Utils::has_class() + * + * @param DOMElement $element Element. + * @param string $class Class names. + * @param bool $expected Expected has class name. */ public function test_has_class( DOMElement $element, $class, $expected ) { $actual = AMP_DOM_Utils::has_class( $element, $class ); @@ -599,7 +346,7 @@ public function test_has_class( DOMElement $element, $class, $expected ) { } public function get_get_element_id_data() { - $dom = new DOMDocument(); + $dom = new Document(); $same_element = AMP_DOM_Utils::create_node( $dom, 'div', [] ); @@ -630,6 +377,10 @@ public function get_get_element_id_data() { * * @dataProvider get_get_element_id_data * @covers \AMP_DOM_Utils::get_element_id() + * + * @param DOMElement $element Element. + * @param string $prefix Prefix. + * @param string $expected Expected element ID. */ public function test_get_element_id( DOMElement $element, $prefix, $expected ) { $actual = AMP_DOM_Utils::get_element_id( $element, $prefix ); @@ -637,7 +388,7 @@ public function test_get_element_id( DOMElement $element, $prefix, $expected ) { } public function get_add_amp_action_data() { - $dom = new DOMDocument(); + $dom = new Document(); $button = AMP_DOM_Utils::create_node( $dom, 'button', [] ); $form = AMP_DOM_Utils::create_node( $dom, 'form', [] ); @@ -668,6 +419,11 @@ public function get_add_amp_action_data() { * * @dataProvider get_add_amp_action_data * @covers \AMP_DOM_Utils::add_amp_action() + * + * @param DOMElement $element Element. + * @param string $event Event. + * @param string $action Action. + * @param string $expected Expected. */ public function test_add_amp_action( DOMElement $element, $event, $action, $expected ) { AMP_DOM_Utils::add_amp_action( $element, $event, $action ); @@ -703,6 +459,10 @@ public function get_merge_amp_actions_data() { * * @dataProvider get_merge_amp_actions_data * @covers \AMP_DOM_Utils::merge_amp_actions() + * + * @param string $first First action. + * @param string $second Second action. + * @param string $expected Expected merged actions. */ public function test_merge_amp_actions( $first, $second, $expected ) { $actual = AMP_DOM_Utils::merge_amp_actions( $first, $second ); @@ -710,7 +470,7 @@ public function test_merge_amp_actions( $first, $second, $expected ) { } public function get_copy_attributes_data() { - $dom = new DOMDocument(); + $dom = new Document(); return [ // No attributes from full to empty. @@ -890,6 +650,11 @@ public function get_copy_attributes_data() { * * @dataProvider get_copy_attributes_data * @covers \AMP_DOM_Utils::copy_attributes() + * + * @param array $attributes Attributes. + * @param DOMElement $from From element. + * @param DOMElement $to To element. + * @param array $expected Expected. */ public function test_copy_attributes( $attributes, DOMElement $from, DOMElement $to, $expected ) { AMP_DOM_Utils::copy_attributes( $attributes, $from, $to ); diff --git a/tests/php/test-class-amp-link-sanitizer.php b/tests/php/test-class-amp-link-sanitizer.php index f1fed58531a..01cca805b5e 100644 --- a/tests/php/test-class-amp-link-sanitizer.php +++ b/tests/php/test-class-amp-link-sanitizer.php @@ -142,10 +142,9 @@ public function test_amp_to_amp_navigation( $paired ) { } // Confirm changes to form. - $xpath = new DOMXPath( $dom ); - $this->assertEquals( 1, $xpath->query( '//form[ @id = "internal-search" ]//input[ @name = "amp" ]' )->length ); - $this->assertEquals( 0, $xpath->query( '//form[ @id = "internal-post" ]//input[ @name = "amp" ]' )->length ); - $this->assertEquals( 0, $xpath->query( '//form[ @id = "external-search" ]//input[ @name = "amp" ]' )->length ); + $this->assertEquals( 1, $dom->xpath->query( '//form[ @id = "internal-search" ]//input[ @name = "amp" ]' )->length ); + $this->assertEquals( 0, $dom->xpath->query( '//form[ @id = "internal-post" ]//input[ @name = "amp" ]' )->length ); + $this->assertEquals( 0, $dom->xpath->query( '//form[ @id = "external-search" ]//input[ @name = "amp" ]' )->length ); } /** @@ -176,13 +175,12 @@ public function get_test_amp_to_amp_meta_tag_data() { * @param string $expected_meta Expected meta content. */ public function test_amp_to_amp_meta_tag( $sanitizer_args, $expected_meta ) { - $dom = AMP_DOM_Utils::get_dom_from_content( '
Hello
' ); - $xpath = new DOMXPath( $dom ); + $dom = AMP_DOM_Utils::get_dom_from_content( '
Hello
' ); $sanitizer = new AMP_Link_Sanitizer( $dom, $sanitizer_args ); $sanitizer->sanitize(); - $meta_tag = $xpath->query( "//meta[ @name = 'amp-to-amp-navigation' ]" )->item( 0 ); + $meta_tag = $dom->xpath->query( "//meta[ @name = 'amp-to-amp-navigation' ]" )->item( 0 ); $this->assertInstanceOf( 'DOMElement', $meta_tag ); $this->assertEquals( $expected_meta, $meta_tag->getAttribute( 'content' ) ); } diff --git a/tests/php/test-class-amp-meta-sanitizer.php b/tests/php/test-class-amp-meta-sanitizer.php new file mode 100644 index 00000000000..0ea2c75af15 --- /dev/null +++ b/tests/php/test-class-amp-meta-sanitizer.php @@ -0,0 +1,110 @@ +', + '', + ], + + // Don't break the correct viewport tag. + [ + '', + '', + ], + + // Move charset and viewport tags from body to head. + [ + '', + '', + ], + + // Add default charset tag if none is present. + [ + '', + '', + ], + + // Add default viewport tag if none is present. + [ + '', + '', + ], + + // Make sure charset is the first meta tag. + [ + '', + '', + ], + + // Concatenate and reposition script hashes. + [ + '', + '', + ], + ]; + } + + /** + * Tests the sanitize method. + * + * @dataProvider get_data_for_sanitize + * @covers \AMP_Meta_Sanitizer::sanitize() + * + * @param string $source_content Source DOM content. + * @param string $expected_content Expected content after sanitization. + */ + public function test_sanitize( $source_content, $expected_content ) { + $dom = Document::from_html( $source_content ); + $sanitizer = new AMP_Meta_Sanitizer( $dom ); + $sanitizer->sanitize(); + + $this->assertEqualMarkup( $expected_content, $dom->saveHTML() ); + } + + /** + * Assert markup is equal. + * + * @param string $expected Expected markup. + * @param string $actual Actual markup. + */ + public function assertEqualMarkup( $expected, $actual ) { + $actual = preg_replace( '/\s+/', ' ', $actual ); + $expected = preg_replace( '/\s+/', ' ', $expected ); + $actual = preg_replace( '/(?<=>)\s+(?=<)/', '', trim( $actual ) ); + $expected = preg_replace( '/(?<=>)\s+(?=<)/', '', trim( $expected ) ); + + $this->assertEquals( + array_filter( preg_split( '#(<[^>]+>|[^<>]+)#', $expected, -1, PREG_SPLIT_DELIM_CAPTURE ) ), + array_filter( preg_split( '#(<[^>]+>|[^<>]+)#', $actual, -1, PREG_SPLIT_DELIM_CAPTURE ) ) + ); + } +} diff --git a/tests/php/test-class-amp-nav-menu-toggle-sanitizer.php b/tests/php/test-class-amp-nav-menu-toggle-sanitizer.php index 5324f57d0dd..22c5e392550 100644 --- a/tests/php/test-class-amp-nav-menu-toggle-sanitizer.php +++ b/tests/php/test-class-amp-nav-menu-toggle-sanitizer.php @@ -5,11 +5,12 @@ * @package AMP */ +use Amp\AmpWP\Dom\Document; + /** * Tests for AMP_Nav_Menu_Toggle_Sanitizer. * * @covers AMP_Nav_Menu_Toggle_Sanitizer - * @group testtt */ class Test_AMP_Nav_Menu_Toggle_Sanitizer extends WP_UnitTestCase { @@ -22,9 +23,9 @@ public function data_converter() { $container_id = 'nav-menu-container'; $toggle_id = 'nav-menu-toggle'; - $head = sprintf( '', get_bloginfo( 'charset' ) ); $container = ''; $toggle = ''; + $head = ''; $amp_state = ''; $amp_get_container_attrs = function( $class = '', $toggle_class = 'toggled-on' ) { @@ -39,7 +40,7 @@ public function data_converter() { return [ 'container_before_toggle' => [ - '' . $head . '' . $container . $toggle . '', + '' . $container . $toggle . '', '' . $head . '' . $amp_state . str_replace( '>', $amp_get_container_attrs( 'nav-menu-wrapper' ) . '>', $container ) . str_replace( '>Toggle', $amp_get_toggle_attrs() . '>Toggle', $toggle ) . '', [ 'nav_container_id' => $container_id, @@ -49,7 +50,7 @@ public function data_converter() { ], ], 'toggle_before_container' => [ - '' . $head . '' . $toggle . $container . '', + '' . $toggle . $container . '', '' . $head . '' . str_replace( '>Toggle', $amp_get_toggle_attrs() . '>Toggle', $toggle ) . $amp_state . str_replace( '>', $amp_get_container_attrs( 'nav-menu-wrapper' ) . '>', $container ) . '', [ 'nav_container_id' => $container_id, @@ -59,7 +60,7 @@ public function data_converter() { ], ], 'container_is_body' => [ - '' . $head . '' . $container . $toggle . '', + '' . $container . $toggle . '', '' . $head . '' . $amp_state . $container . str_replace( '>Toggle', $amp_get_toggle_attrs( '', '' ) . '>Toggle', $toggle ) . '', [ 'nav_container_xpath' => '//body', @@ -68,7 +69,7 @@ public function data_converter() { ], ], 'container_is_html' => [ - '' . $head . '' . $container . $toggle . '', + '' . $container . $toggle . '', '' . $head . '' . $amp_state . $container . str_replace( '>Toggle', $amp_get_toggle_attrs( '', '' ) . '>Toggle', $toggle ) . '', [ 'nav_container_xpath' => '//html', @@ -77,7 +78,7 @@ public function data_converter() { ], ], 'no_container_provided' => [ - '' . $head . '' . $container . $toggle . '', + '' . $container . $toggle . '', '' . $head . '' . $container . '', [ 'menu_button_id' => $toggle_id, @@ -85,7 +86,7 @@ public function data_converter() { ], ], 'no_arguments_provided' => [ - '' . $head . '' . $container . $toggle . '', + '' . $container . $toggle . '', '' . $head . '' . $container . $toggle . '', [], ], @@ -105,12 +106,12 @@ public function data_converter() { * @covers AMP_Nav_Menu_Toggle_Sanitizer::get_menu_button() */ public function test_converter( $source, $expected, $args = [] ) { - $dom = AMP_DOM_Utils::get_dom( $source ); + $dom = Document::from_html( $source ); $sanitizer = new AMP_Nav_Menu_Toggle_Sanitizer( $dom, $args ); $sanitizer->sanitize(); - $content = AMP_DOM_Utils::get_content_from_dom_node( $dom, $dom->documentElement ); + $content = $dom->saveHTML( $dom->documentElement ); $this->assertEquals( $expected, $content ); } diff --git a/tests/php/test-class-amp-theme-support.php b/tests/php/test-class-amp-theme-support.php index a073c9dfda8..2efcf8a44e0 100644 --- a/tests/php/test-class-amp-theme-support.php +++ b/tests/php/test-class-amp-theme-support.php @@ -6,6 +6,7 @@ * @since 0.7 */ +use Amp\AmpWP\Dom\Document; use org\bovigo\vfs; use Amp\AmpWP\Tests\PrivateAccess; @@ -555,7 +556,7 @@ public function test_validate_non_amp_theme() { - + @@ -568,11 +569,11 @@ public function test_validate_non_amp_theme() { $original_html = trim( ob_get_clean() ); $sanitized_html = AMP_Theme_Support::prepare_response( $original_html ); - // Invalid viewport meta tag is not present. + // Insufficient viewport tag was not left in. $this->assertNotContains( '', $sanitized_html ); - // Correct viewport meta tag was added. - $this->assertContains( '', $sanitized_html ); + // Viewport tag was modified to include all requirements. + $this->assertContains( '', $sanitized_html ); // MathML script was added. $this->assertContains( '', $sanitized_html ); // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript @@ -1567,7 +1568,7 @@ public function get_schema_script_data() { */ public function test_ensure_required_markup_schemaorg( $script, $expected ) { $page = 'Test'; - $dom = new DOMDocument(); + $dom = new Document(); $dom->loadHTML( sprintf( $page, $script ) ); AMP_Theme_Support::ensure_required_markup( $dom ); $this->assertEquals( $expected, substr_count( $dom->saveHTML(), 'schema.org' ) ); @@ -1599,10 +1600,9 @@ public function test_scripts_get_moved_to_head() { $html = ob_get_clean(); $html = AMP_Theme_Support::prepare_response( $html ); - $dom = AMP_DOM_Utils::get_dom( $html ); - $xpath = new DOMXPath( $dom ); + $dom = Document::from_html( $html ); - $scripts = $xpath->query( '//script[ not( @type ) or @type = "text/javascript" ]' ); + $scripts = $dom->xpath->query( '//script[ not( @type ) or @type = "text/javascript" ]' ); $this->assertSame( 3, $scripts->length ); foreach ( $scripts as $script ) { $this->assertSame( 'head', $script->parentNode->nodeName ); @@ -1659,12 +1659,11 @@ public function test_unneeded_scripts_get_removed() { $html = ob_get_clean(); $html = AMP_Theme_Support::prepare_response( $html ); - $dom = AMP_DOM_Utils::get_dom( $html ); - $xpath = new DOMXPath( $dom ); + $dom = Document::from_html( $html ); /** @var DOMElement $script Script. */ $actual_script_srcs = []; - foreach ( $xpath->query( '//script[ not( @type ) or @type = "text/javascript" ]' ) as $script ) { + foreach ( $dom->xpath->query( '//script[ not( @type ) or @type = "text/javascript" ]' ) as $script ) { $actual_script_srcs[] = $script->getAttribute( 'src' ); } @@ -1712,8 +1711,7 @@ public function test_duplicate_scripts_are_removed() { $html = ob_get_clean(); $html = AMP_Theme_Support::prepare_response( $html ); - $dom = AMP_DOM_Utils::get_dom( $html ); - $xpath = new DOMXPath( $dom ); + $dom = Document::from_html( $html ); $script_srcs = []; /** @@ -1721,7 +1719,7 @@ public function test_duplicate_scripts_are_removed() { * * @var DOMElement $script */ - $scripts = $xpath->query( '//script[ @src ]' ); + $scripts = $dom->xpath->query( '//script[ @src ]' ); foreach ( $scripts as $script ) { $script_srcs[] = $script->getAttribute( 'src' ); } @@ -1935,7 +1933,7 @@ static function ( $url ) { $ordered_contains = [ '', - '', + '', '', ' - - - - - - - - - - - - - - - - - query( '/html/head/meta[ @name = "amp-script-src" ]' ); - $this->assertSame( 1, $meta_elements->length ); - - $meta = $meta_elements->item( 0 ); - $this->assertSame( "$script1_hash $script2_hash $script3_hash", $meta->getAttribute( 'content' ) ); - } - /** * Test post-processor cache effectiveness in AMP_Theme_Support::prepare_response(). */ @@ -2376,38 +2324,13 @@ public function test_prepare_response_varying_html() { $input = 'Hello'; $output = AMP_Theme_Support::prepare_response( $input ); $this->assertContains( 'assertContains( '', $output ); + $this->assertContains( '', $output ); // HTML with doctype, comments, and whitespace before head. $input = " \n\n \nHello"; $output = AMP_Theme_Support::prepare_response( $input ); $this->assertContains( 'assertContains( '', $output ); - } - - /** - * Test prepare_response to inject html[amp] attribute and ensure HTML5 doctype. - * - * @covers AMP_Theme_Support::prepare_response() - */ - public function test_prepare_response_to_add_html5_doctype_and_amp_attribute() { - wp_scripts(); - wp(); - add_filter( 'amp_validation_error_sanitized', '__return_true' ); - add_theme_support( AMP_Theme_Support::SLUG ); - AMP_Theme_Support::init(); - AMP_Theme_Support::finish_init(); - ob_start(); - ?> - - - assertStringStartsWith( '', $sanitized_html ); - $this->assertContains( 'assertContains( '', $sanitized_html ); + $this->assertContains( '', $output ); } /** diff --git a/tests/php/test-dom-element-list.php b/tests/php/test-dom-element-list.php index c555416fb83..38213b669e9 100644 --- a/tests/php/test-dom-element-list.php +++ b/tests/php/test-dom-element-list.php @@ -1,18 +1,18 @@ [ [], @@ -48,14 +48,14 @@ public function get_dom_element_list_data() { * Test adding images and counting them. * * @dataProvider get_dom_element_list_data - * @covers \Amp\AmpWP\Component\DOMElementList::add() - * @covers \Amp\AmpWP\Component\DOMElementList::count() + * @covers Amp\AmpWP\Dom\ElementList::add() + * @covers Amp\AmpWP\Dom\ElementList::count() * * @param DOMElement[] $images The images to add. * @param string $expected_count The expected count after adding the images. */ public function test_dom_element_list_add( $images, $expected_count ) { - $dom_element_list = new DOMElementList(); + $dom_element_list = new ElementList(); foreach ( $images as $image ) { $dom_element_list = $dom_element_list->add( $image, '' ); } @@ -67,14 +67,14 @@ public function test_dom_element_list_add( $images, $expected_count ) { * Test the iteration of the images. * * @dataProvider get_dom_element_list_data - * @covers \Amp\AmpWP\Component\DOMElementList::add() - * @covers \Amp\AmpWP\Component\DOMElementList::getIterator() + * @covers Amp\AmpWP\Dom\ElementList::add() + * @covers Amp\AmpWP\Dom\ElementList::getIterator() * * @param DOMElement[] $images The images to add. * @param string $expected_count The expected count after adding the images. */ public function test_dom_element_list_get_iterator( $images, $expected_count ) { - $dom_element_list = new DOMElementList(); + $dom_element_list = new ElementList(); foreach ( $images as $image ) { $dom_element_list = $dom_element_list->add( $image, '' ); } @@ -94,7 +94,7 @@ public function test_dom_element_list_get_iterator( $images, $expected_count ) { * @covers \Amp\AmpWP\Component\CaptionedSlide::get_caption() */ public function test_get_caption() { - $image_node = AMP_DOM_Utils::create_node( new DOMDocument(), 'amp-img', [] ); + $image_node = AMP_DOM_Utils::create_node( new Document(), 'amp-img', [] ); $caption = 'This is a caption'; $captioned_image = new CaptionedSlide( $image_node, $caption ); $this->assertEquals( $caption, $captioned_image->get_caption() ); @@ -103,10 +103,10 @@ public function test_get_caption() { /** * Test get_slide_node. * - * @covers \Amp\AmpWP\Component\Image::get_slide_node() + * @covers \Amp\AmpWP\Component\CaptionedSlide::get_slide_node() */ public function test_get_slide_node() { - $image_node = AMP_DOM_Utils::create_node( new DOMDocument(), 'amp-img', [] ); + $image_node = AMP_DOM_Utils::create_node( new Document(), 'amp-img', [] ); $amp_image = new CaptionedSlide( $image_node, '' ); $this->assertEquals( $image_node, $amp_image->get_slide_node() ); } diff --git a/tests/php/test-tag-and-attribute-sanitizer.php b/tests/php/test-tag-and-attribute-sanitizer.php index 163fcd3db27..b8069f5ecf9 100644 --- a/tests/php/test-tag-and-attribute-sanitizer.php +++ b/tests/php/test-tag-and-attribute-sanitizer.php @@ -5,6 +5,8 @@ * @package AMP */ +use Amp\AmpWP\Dom\Document; + /** * Test AMP_Tag_And_Attribute_Sanitizer * @@ -2113,9 +2115,7 @@ public function get_html_data() { ], 'bad_meta_charset' => [ 'Mojibake?', - 'Mojibake?', // Note the charset attribute is removed because it violates the attribute spec, but the entire element is not removed because charset is not mandatory. - [], - [ AMP_Tag_And_Attribute_Sanitizer::DISALLOWED_ATTR ], // @todo Should actually be invalid_mandatory_attribute? + 'Mojibake?', // Note the charset attribute is removed because it violates the attribute spec, but the entire element is not removed because charset is not mandatory. ], 'bad_meta_viewport' => [ '', @@ -2148,7 +2148,7 @@ public function get_html_data() { ], 'head_with_duplicate_charset' => [ '

Content

', - '

Content

', + '

Content

', [], [ AMP_Tag_And_Attribute_Sanitizer::DUPLICATE_UNIQUE_TAG ], ], @@ -2312,7 +2312,7 @@ public function get_html_data() { */ public function test_sanitize( $source, $expected = null, $expected_scripts = [], $expected_errors = [] ) { $expected = isset( $expected ) ? $expected : $source; - $dom = AMP_DOM_Utils::get_dom( $source ); + $dom = Document::from_html( $source ); $actual_errors = []; $sanitizer = new AMP_Tag_And_Attribute_Sanitizer( $dom, @@ -2325,7 +2325,7 @@ public function test_sanitize( $source, $expected = null, $expected_scripts = [] ] ); $sanitizer->sanitize(); - $content = AMP_DOM_Utils::get_content_from_dom_node( $dom, $dom->documentElement ); + $content = $dom->saveHTML( $dom->documentElement ); $this->assertEqualMarkup( $expected, $content ); $this->assertEqualSets( $expected_scripts, array_keys( $sanitizer->get_scripts() ) ); @@ -2355,7 +2355,7 @@ public function test_sanitize_body_only() { $source = 'Hello'; $expected = 'Hello'; - $dom = AMP_DOM_Utils::get_dom( $source ); + $dom = Document::from_html( $source ); $actual_errors = []; $sanitizer = new AMP_Tag_And_Attribute_Sanitizer( $dom, @@ -2394,7 +2394,7 @@ public function test_is_missing_mandatory_attribute() { ], 'noloading' => [], ]; - $dom = new DOMDocument(); + $dom = new Document(); $node = new DOMElement( 'amp-gist' ); $dom->appendChild( $node ); $sanitizer = new AMP_Tag_And_Attribute_Sanitizer( $dom ); diff --git a/tests/php/validation/test-class-amp-validation-manager.php b/tests/php/validation/test-class-amp-validation-manager.php index 7874e2a0352..c272ba0311a 100644 --- a/tests/php/validation/test-class-amp-validation-manager.php +++ b/tests/php/validation/test-class-amp-validation-manager.php @@ -7,6 +7,8 @@ // phpcs:disable Generic.Formatting.MultipleStatementAlignment.NotSameWarning +use Amp\AmpWP\Dom\Document; + /** * Tests for AMP_Validation_Manager class. * @@ -89,8 +91,9 @@ class Test_AMP_Validation_Manager extends WP_UnitTestCase { public function setUp() { unset( $GLOBALS['wp_scripts'], $GLOBALS['wp_styles'] ); parent::setUp(); - $dom_document = new DOMDocument( '1.0', 'utf-8' ); + $dom_document = new Document( '1.0', 'utf-8' ); $this->node = $dom_document->createElement( self::TAG_NAME ); + $dom_document->appendChild( $this->node ); AMP_Validation_Manager::reset_validation_results(); $this->original_wp_registered_widgets = $GLOBALS['wp_registered_widgets']; @@ -814,8 +817,7 @@ public function test_source_comments() { * @var DOMComment[] $comments */ $comments = []; - $xpath = new DOMXPath( $dom ); - foreach ( $xpath->query( '//comment()' ) as $comment ) { + foreach ( $dom->xpath->query( '//comment()' ) as $comment ) { $comments[] = $comment; } $this->assertCount( 4, $comments ); @@ -1607,7 +1609,7 @@ public function test_finalize_validation() { loadHTML( $html ); $this->assertInstanceOf( 'DOMElement', $dom->getElementById( 'wp-admin-bar-amp' ) );