-
Notifications
You must be signed in to change notification settings - Fork 107
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1322 from WordPress/add/auto-sizes-image-prioriti…
…zer-integration Integrate Auto Sizes with Image Prioritizer to ensure correct sizes=auto
- Loading branch information
Showing
5 changed files
with
257 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?php | ||
/** | ||
* Optimization Detective extensions by Auto Sizes. | ||
* | ||
* @since n.e.x.t | ||
* @package auto-sizes | ||
*/ | ||
|
||
if ( ! defined( 'ABSPATH' ) ) { | ||
exit; // Exit if accessed directly. | ||
} | ||
|
||
/** | ||
* Visits responsive lazy-loaded IMG tags to ensure they include sizes=auto. | ||
* | ||
* @since n.e.x.t | ||
* | ||
* @param OD_Tag_Visitor_Context $context Tag visitor context. | ||
* @return false Whether the tag should be recorded in URL metrics. | ||
*/ | ||
function auto_sizes_visit_tag( OD_Tag_Visitor_Context $context ): bool { | ||
if ( 'IMG' !== $context->processor->get_tag() ) { | ||
return false; | ||
} | ||
|
||
$sizes = $context->processor->get_attribute( 'sizes' ); | ||
if ( ! is_string( $sizes ) ) { | ||
return false; | ||
} | ||
|
||
$sizes = preg_split( '/\s*,\s*/', $sizes ); | ||
if ( ! is_array( $sizes ) ) { | ||
return false; | ||
} | ||
|
||
$is_lazy_loaded = ( 'lazy' === $context->processor->get_attribute( 'loading' ) ); | ||
$has_auto_sizes = in_array( 'auto', $sizes, true ); | ||
|
||
$changed = false; | ||
if ( $is_lazy_loaded && ! $has_auto_sizes ) { | ||
array_unshift( $sizes, 'auto' ); | ||
$changed = true; | ||
} elseif ( ! $is_lazy_loaded && $has_auto_sizes ) { | ||
$sizes = array_diff( $sizes, array( 'auto' ) ); | ||
$changed = true; | ||
} | ||
if ( $changed ) { | ||
$context->processor->set_attribute( 'sizes', join( ', ', $sizes ) ); | ||
} | ||
|
||
return false; // Since this tag visitor does not require this tag to be included in the URL Metrics. | ||
} | ||
|
||
/** | ||
* Registers the tag visitor for image tags. | ||
* | ||
* @since n.e.x.t | ||
* | ||
* @param OD_Tag_Visitor_Registry $registry Tag visitor registry. | ||
*/ | ||
function auto_sizes_register_tag_visitors( OD_Tag_Visitor_Registry $registry ): void { | ||
$registry->register( 'auto-sizes', 'auto_sizes_visit_tag' ); | ||
} | ||
|
||
// Important: The Image Prioritizer's IMG tag visitor is registered at priority 10, so priority 100 ensures that the loading attribute has been correctly set by the time the Auto Sizes visitor runs. | ||
add_action( 'od_register_tag_visitors', 'auto_sizes_register_tag_visitors', 100 ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
<?php | ||
/** | ||
* Test Bootstrap for Auto Sizes. | ||
* | ||
* @package auto-sizes | ||
*/ | ||
|
||
// Require the suggested plugins. | ||
require_once __DIR__ . '/../../optimization-detective/load.php'; | ||
require_once __DIR__ . '/../../image-prioritizer/load.php'; |
177 changes: 177 additions & 0 deletions
177
plugins/auto-sizes/tests/test-optimization-detective.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
<?php | ||
/** | ||
* Tests for auto-sizes plugin's optimization-detective.php. | ||
* | ||
* @package auto-sizes | ||
*/ | ||
|
||
class Test_Auto_Sizes_Optimization_Detective extends WP_UnitTestCase { | ||
/** | ||
* Runs the routine before each test is executed. | ||
*/ | ||
public function set_up(): void { | ||
parent::set_up(); | ||
if ( ! defined( 'OPTIMIZATION_DETECTIVE_VERSION' ) ) { | ||
$this->markTestSkipped( 'Optimization Detective is not active.' ); | ||
} | ||
} | ||
|
||
/** | ||
* Tests auto_sizes_register_tag_visitors(). | ||
* | ||
* @covers ::auto_sizes_register_tag_visitors | ||
*/ | ||
public function test_auto_sizes_register_tag_visitors(): void { | ||
if ( ! class_exists( OD_Tag_Visitor_Registry::class ) ) { | ||
$this->markTestSkipped( 'Optimization Detective is not active.' ); | ||
} | ||
$registry = new OD_Tag_Visitor_Registry(); | ||
auto_sizes_register_tag_visitors( $registry ); | ||
$this->assertTrue( $registry->is_registered( 'auto-sizes' ) ); | ||
$this->assertEquals( 'auto_sizes_visit_tag', $registry->get_registered( 'auto-sizes' ) ); | ||
} | ||
|
||
/** | ||
* Data provider. | ||
* | ||
* @return array<string, mixed> Data. | ||
*/ | ||
public function data_provider_test_od_optimize_template_output_buffer(): array { | ||
return array( | ||
// Note: The Image Prioritizer plugin removes the loading attribute, and so then Auto Sizes does not then add sizes=auto. | ||
'wrongly_lazy_responsive_img' => array( | ||
'element_metrics' => array( | ||
'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', | ||
'isLCP' => false, | ||
'intersectionRatio' => 1, | ||
), | ||
'buffer' => '<img src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" loading="lazy" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="(max-width: 600px) 480px, 800px">', | ||
'expected' => '<img data-od-removed-loading="lazy" src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="(max-width: 600px) 480px, 800px">', | ||
), | ||
|
||
'non_responsive_image' => array( | ||
'element_metrics' => array( | ||
'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', | ||
'isLCP' => false, | ||
'intersectionRatio' => 0, | ||
), | ||
'buffer' => '<img src="https://example.com/foo.jpg" alt="Quux" width="1200" height="800" loading="lazy">', | ||
'expected' => '<img src="https://example.com/foo.jpg" alt="Quux" width="1200" height="800" loading="lazy">', | ||
), | ||
|
||
'auto_sizes_added' => array( | ||
'element_metrics' => array( | ||
'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', | ||
'isLCP' => false, | ||
'intersectionRatio' => 0, | ||
), | ||
'buffer' => '<img src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" loading="lazy" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="(max-width: 600px) 480px, 800px">', | ||
'expected' => '<img data-od-replaced-sizes="(max-width: 600px) 480px, 800px" src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" loading="lazy" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="auto, (max-width: 600px) 480px, 800px">', | ||
), | ||
|
||
'auto_sizes_already_added' => array( | ||
'element_metrics' => array( | ||
'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', | ||
'isLCP' => false, | ||
'intersectionRatio' => 0, | ||
), | ||
'buffer' => '<img src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" loading="lazy" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="auto, (max-width: 600px) 480px, 800px">', | ||
'expected' => '<img src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" loading="lazy" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="auto, (max-width: 600px) 480px, 800px">', | ||
), | ||
|
||
// If Auto Sizes added the sizes=auto attribute but Image Prioritizer ended up removing it due to the image not being lazy-loaded, remove sizes=auto again. | ||
'wrongly_auto_sized_responsive_img' => array( | ||
'element_metrics' => array( | ||
'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', | ||
'isLCP' => false, | ||
'intersectionRatio' => 1, | ||
), | ||
'buffer' => '<img src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" loading="lazy" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="auto, (max-width: 600px) 480px, 800px">', | ||
'expected' => '<img data-od-replaced-sizes="auto, (max-width: 600px) 480px, 800px" data-od-removed-loading="lazy" src="https://example.com/foo.jpg" alt="Foo" width="1200" height="800" srcset="https://example.com/foo-480w.jpg 480w, https://example.com/foo-800w.jpg 800w" sizes="(max-width: 600px) 480px, 800px">', | ||
), | ||
); | ||
} | ||
|
||
/** | ||
* Test auto_sizes_visit_tag(). | ||
* | ||
* @covers ::auto_sizes_visit_tag | ||
* | ||
* @dataProvider data_provider_test_od_optimize_template_output_buffer | ||
* @throws Exception But it won't. | ||
* @phpstan-param array<string, mixed> $element_metrics | ||
*/ | ||
public function test_od_optimize_template_output_buffer( array $element_metrics, string $buffer, string $expected ): void { | ||
$slug = od_get_url_metrics_slug( od_get_normalized_query_vars() ); | ||
$sample_size = od_get_url_metrics_breakpoint_sample_size(); | ||
foreach ( array_merge( od_get_breakpoint_max_widths(), array( 1000 ) ) as $viewport_width ) { | ||
for ( $i = 0; $i < $sample_size; $i++ ) { | ||
OD_URL_Metrics_Post_Type::store_url_metric( | ||
$slug, | ||
$this->get_validated_url_metric( | ||
$viewport_width, | ||
array( | ||
$element_metrics, | ||
) | ||
) | ||
); | ||
} | ||
} | ||
|
||
$remove_initial_tabs = static function ( string $input ): string { | ||
return (string) preg_replace( '/^\t+/m', '', $input ); | ||
}; | ||
|
||
$html_start_doc = '<html lang="en"><head><meta charset="utf-8"><title>...</title></head><body>'; | ||
$html_end_doc = '</body></html>'; | ||
|
||
$expected = $remove_initial_tabs( $expected ); | ||
$buffer = $remove_initial_tabs( $buffer ); | ||
|
||
$buffer = od_optimize_template_output_buffer( $html_start_doc . $buffer . $html_end_doc ); | ||
$buffer = preg_replace( '#.+?<body[^>]*>#s', '', $buffer ); | ||
$buffer = preg_replace( '#</body>.*$#s', '', $buffer ); | ||
|
||
$this->assertEquals( $expected, $buffer ); | ||
} | ||
|
||
/** | ||
* Gets a validated URL metric. | ||
* | ||
* @param int $viewport_width Viewport width for the URL metric. | ||
* @param array<array{xpath: string, isLCP: bool}> $elements Elements. | ||
* @return OD_URL_Metric URL metric. | ||
* @throws Exception From OD_URL_Metric if there is a parse error, but there won't be. | ||
*/ | ||
private function get_validated_url_metric( int $viewport_width, array $elements = array() ): OD_URL_Metric { | ||
$data = array( | ||
'url' => home_url( '/' ), | ||
'viewport' => array( | ||
'width' => $viewport_width, | ||
'height' => 800, | ||
), | ||
'timestamp' => microtime( true ), | ||
'elements' => array_map( | ||
static function ( array $element ): array { | ||
return array_merge( | ||
array( | ||
'isLCPCandidate' => true, | ||
'intersectionRatio' => 1, | ||
'intersectionRect' => array( | ||
'width' => 100, | ||
'height' => 100, | ||
), | ||
'boundingClientRect' => array( | ||
'width' => 100, | ||
'height' => 100, | ||
), | ||
), | ||
$element | ||
); | ||
}, | ||
$elements | ||
), | ||
); | ||
return new OD_URL_Metric( $data ); | ||
} | ||
} |