Skip to content
16 changes: 1 addition & 15 deletions plugins/webp-uploads/picture-element.php
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might need some test cases to be written/updated?

Original file line number Diff line number Diff line change
Expand Up @@ -131,24 +131,10 @@ function webp_uploads_wrap_image_in_picture( string $image, string $context, int
);
}

// Fall back to the original image without a srcset.
$original_sizes = array( $image_src[1], $image_src[2] );
$original_image = wp_get_original_image_url( $attachment_id );
// Fail gracefully if the original image is not found.
if ( false === $original_image ) {
return $image;
}
$filter = static function (): bool {
return false;
};
add_filter( 'wp_calculate_image_srcset_meta', $filter );
$original_image_without_srcset = wp_get_attachment_image( $attachment_id, $original_sizes, false, array( 'src' => $original_image ) );
remove_filter( 'wp_calculate_image_srcset_meta', $filter );

return sprintf(
'<picture class="%s" style="display: contents;">%s%s</picture>',
esc_attr( 'wp-picture-' . $attachment_id ),
$picture_sources,
$original_image_without_srcset
$image
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adamsilverstein As a fallback image, we should return the original image. In this MDN documentation, they return the original image instead of mime-type-specific images.

I couldn't find proper documentation for this. What do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think this is right.

Also, I think the source being generated for 'image/jpeg' can also be removed as well, because if the browser doesn't support AVIF/WebP then it can just fall back to the img which is a JPEG.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$enabled_mime_types = (array) apply_filters(
'webp_uploads_picture_element_mime_types',
array(
'image/avif',
'image/webp',
'image/jpeg',
),
$attachment_id
);

Should we remove line 61 for the same.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can open a separate PR for this issue to avoid complicating the current one. This will allow us to address it more effectively.

);
}
74 changes: 58 additions & 16 deletions plugins/webp-uploads/tests/test-picture-element.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ class Test_WebP_Uploads_Picture_Element extends TestCase {
*
* @dataProvider data_provider_it_should_maybe_wrap_images_in_picture_element
*
* @param bool $jpeg_and_webp Whether to enable JPEG and WebP output.
* @param bool $picture_element Whether to enable picture element output.
* @param bool $expect_picture_element Whether to expect the image to be wrapped in a picture element.
* @param bool $jpeg_and_webp Whether to enable JPEG and WebP output.
* @param bool $picture_element Whether to enable picture element output.
* @param bool $expect_picture_element Whether to expect the image to be wrapped in a picture element.
* @param string $expected_html The expected HTML output.
*/
public function test_maybe_wrap_images_in_picture_element( bool $jpeg_and_webp, bool $picture_element, bool $expect_picture_element ): void {
public function test_maybe_wrap_images_in_picture_element( bool $jpeg_and_webp, bool $picture_element, bool $expect_picture_element, string $expected_html ): void {
$mime_type = 'image/webp';
if ( ! wp_image_editor_supports( array( 'mime_type' => $mime_type ) ) ) {
$this->markTestSkipped( "Mime type $mime_type is not supported." );
Expand All @@ -37,49 +38,90 @@ public function test_maybe_wrap_images_in_picture_element( bool $jpeg_and_webp,
$attachment_id = self::factory()->attachment->create_upload_object( TESTS_PLUGIN_DIR . '/tests/data/images/leaves.jpg' );

// Create some content with the image.
$the_image = wp_get_attachment_image( $attachment_id, 'medium', false, array( 'class' => "wp-image-{$attachment_id}" ) );
$the_image = wp_get_attachment_image(
$attachment_id,
'medium',
false,
array(
'class' => "wp-image-{$attachment_id}",
'alt' => 'Green Leaves',
)
);

// Apply the wp_content_img_tag filter.
$the_image = apply_filters( 'wp_content_img_tag', $the_image, 'the_content', $attachment_id );
$processor = new WP_HTML_Tag_Processor( $the_image );
$this->assertTrue( $processor->next_tag( array( 'tag_name' => 'IMG' ) ) );
$width = (int) $processor->get_attribute( 'width' );
$height = (int) $processor->get_attribute( 'height' );
$alt = (string) $processor->get_attribute( 'alt' );

// Check that the image is wrapped in a picture element with the correct class.
$picture_element = sprintf( '<picture class="wp-picture-%s" style="display: contents;">', $attachment_id );
if ( $expect_picture_element ) {
$this->assertStringContainsString( $picture_element, $the_image );
} else {
$this->assertStringNotContainsString( $picture_element, $the_image );
$size_to_use = ( $width > 0 && $height > 0 ) ? array( $width, $height ) : 'full';
$image_src = wp_get_attachment_image_src( $attachment_id, $size_to_use );
list( $src, $width, $height ) = $image_src;
$size_array = array( absint( $width ), absint( $height ) );
$image_meta = wp_get_attachment_metadata( $attachment_id );
$sizes = wp_calculate_image_sizes( $size_array, $src, $image_meta, $attachment_id );
$image_srcset = wp_get_attachment_image_srcset( $attachment_id, $size_to_use );

$img_src = '';
if ( $image_src ) {
$img_src = $image_src[0];
}
// Remove the last size in the srcset, as it is not needed.
$jpeg_srcset = substr( $image_srcset, 0, strrpos( $image_srcset, ',' ) );
$webp_srcset = str_replace( '.jpg', '-jpg.webp', $jpeg_srcset );

// Prepare the expected HTML by replacing placeholders with expected values.
$replacements = array(
'{{img-width}}' => $width,
'{{img-height}}' => $height,
'{{img-src}}' => $img_src,
'{{img-attachment-id}}' => $attachment_id,
'{{img-alt}}' => $alt,
'{{img-srcset}}' => $image_srcset,
'{{img-sizes}}' => $sizes,
'{{jpeg-srcset}}' => $jpeg_srcset,
'{{webp-srcset}}' => $webp_srcset,
);

$expected_html = str_replace( array_keys( $replacements ), array_values( $replacements ), $expected_html );

// Apply the wp_content_img_tag filter.
$the_image = apply_filters( 'wp_content_img_tag', $the_image, 'the_content', $attachment_id );

// When both features are enabled, the picture element will contain two srcset elements.
$this->assertEquals( ( $jpeg_and_webp && $expect_picture_element ) ? 2 : 1, substr_count( $the_image, 'srcset=' ) );
// Check that the image has the expected HTML.
$this->assertEquals( $expected_html, $the_image );
}

/**
* Data provider for it_should_maybe_wrap_images_in_picture_element.
*
* @return array<string, array<string, bool>>
* @return array<string, array<string, bool|string>>
*/
public function data_provider_it_should_maybe_wrap_images_in_picture_element(): array {
return array(
'jpeg and picture enabled' => array(
'jpeg_and_webp' => true,
'picture_element' => true,
'expect_picture_element' => true,
'expected_html' => '<picture class="wp-picture-{{img-attachment-id}}" style="display: contents;"><source type="image/webp" srcset="{{webp-srcset}}" sizes="{{img-sizes}}"><source type="image/jpeg" srcset="{{jpeg-srcset}}" sizes="{{img-sizes}}"><img width="{{img-width}}" height="{{img-height}}" src="{{img-src}}" class="wp-image-{{img-attachment-id}}" alt="{{img-alt}}" decoding="async" loading="lazy" srcset="{{img-srcset}}" sizes="{{img-sizes}}" /></picture>',
),
'only picture enabled' => array(
'jpeg_and_webp' => false,
'picture_element' => true,
'expect_picture_element' => true,
'expected_html' => '<picture class="wp-picture-{{img-attachment-id}}" style="display: contents;"><source type="image/webp" srcset="{{webp-srcset}}" sizes="{{img-sizes}}"><img width="{{img-width}}" height="{{img-height}}" src="{{img-src}}" class="wp-image-{{img-attachment-id}}" alt="{{img-alt}}" decoding="async" loading="lazy" srcset="{{img-srcset}}" sizes="{{img-sizes}}" /></picture>',
),
'only jpeg enabled' => array(
'jpeg_and_webp' => true,
'picture_element' => false,
'expect_picture_element' => false,
'expected_html' => '<img width="{{img-width}}" height="{{img-height}}" src="{{img-src}}" class="wp-image-{{img-attachment-id}}" alt="{{img-alt}}" decoding="async" loading="lazy" srcset="{{img-srcset}}" sizes="{{img-sizes}}" />',
),
'neither enabled' => array(
'jpeg_and_webp' => false,
'picture_element' => false,
'expect_picture_element' => false,
'expected_html' => '<img width="{{img-width}}" height="{{img-height}}" src="{{img-src}}" class="wp-image-{{img-attachment-id}}" alt="{{img-alt}}" decoding="async" loading="lazy" srcset="{{img-srcset}}" sizes="{{img-sizes}}" />',
),
);
}
Expand Down