<Picture> component should pass all unknown attributes to the <img> element (#3961)

* <Picture /> should pass all unrecognized props down to the <img> element

* chore: add changeset

* Adding test coverage for custom <img> attributes

* chore: adding a README note for passing attributes to the picture's img

* Revert "<Picture /> should pass all unrecognized props down to the <img> element"

This reverts commit ce3e33930f.

* Picture should pass alt text to the img
This commit is contained in:
Tony Sullivan 2022-07-19 19:21:58 +00:00 committed by GitHub
parent b37d7078a0
commit d73c04a9e5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 38 additions and 19 deletions

View file

@ -0,0 +1,5 @@
---
'@astrojs/image': patch
---
Updates the <Picture /> component to pass the `alt` attribute down to the <img> element

View file

@ -188,13 +188,13 @@ const imageUrl = 'https://www.google.com/images/branding/googlelogo/2x/googlelog
--- ---
// Local image with multiple sizes // Local image with multiple sizes
<Picture src={hero} widths={[200, 400, 800]} sizes="(max-width: 800px) 100vw, 800px" /> <Picture src={hero} widths={[200, 400, 800]} sizes="(max-width: 800px) 100vw, 800px" alt="My hero image" />
// Remote image (aspect ratio is required) // Remote image (aspect ratio is required)
<Picture src={imageUrl} widths={[200, 400, 800]} aspectRatio="4:3" sizes="(max-width: 800px) 100vw, 800px" /> <Picture src={imageUrl} widths={[200, 400, 800]} aspectRatio="4:3" sizes="(max-width: 800px) 100vw, 800px" alt="My hero image" />
// Inlined imports are supported // Inlined imports are supported
<Picture src={import("../assets/hero.png")} widths={[200, 400, 800]} sizes="(max-width: 800px) 100vw, 800px" /> <Picture src={import("../assets/hero.png")} widths={[200, 400, 800]} sizes="(max-width: 800px) 100vw, 800px" alt="My hero image" />
``` ```
</details> </details>

View file

@ -4,15 +4,17 @@ import loader from 'virtual:image-loader';
import { getPicture } from '../src/get-picture.js'; import { getPicture } from '../src/get-picture.js';
import type { ImageAttributes, ImageMetadata, OutputFormat, PictureAttributes, TransformOptions } from '../src/types.js'; import type { ImageAttributes, ImageMetadata, OutputFormat, PictureAttributes, TransformOptions } from '../src/types.js';
export interface LocalImageProps extends Omit<PictureAttributes, 'src' | 'width' | 'height'>, Omit<TransformOptions, 'src'>, Omit<ImageAttributes, 'src' | 'width' | 'height'> { export interface LocalImageProps extends Omit<PictureAttributes, 'src' | 'width' | 'height'>, Omit<TransformOptions, 'src'>, Pick<ImageAttributes, 'loading' | 'decoding'> {
src: ImageMetadata | Promise<{ default: ImageMetadata }>; src: ImageMetadata | Promise<{ default: ImageMetadata }>;
alt?: string;
sizes: HTMLImageElement['sizes']; sizes: HTMLImageElement['sizes'];
widths: number[]; widths: number[];
formats?: OutputFormat[]; formats?: OutputFormat[];
} }
export interface RemoteImageProps extends Omit<PictureAttributes, 'src' | 'width' | 'height'>, TransformOptions, Omit<ImageAttributes, 'src' | 'width' | 'height'> { export interface RemoteImageProps extends Omit<PictureAttributes, 'src' | 'width' | 'height'>, TransformOptions, Pick<ImageAttributes, 'loading' | 'decoding'> {
src: string; src: string;
alt?: string;
sizes: HTMLImageElement['sizes']; sizes: HTMLImageElement['sizes'];
widths: number[]; widths: number[];
aspectRatio: TransformOptions['aspectRatio']; aspectRatio: TransformOptions['aspectRatio'];
@ -21,7 +23,7 @@ export interface RemoteImageProps extends Omit<PictureAttributes, 'src' | 'width
export type Props = LocalImageProps | RemoteImageProps; export type Props = LocalImageProps | RemoteImageProps;
const { src, sizes, widths, aspectRatio, formats = ['avif', 'webp'], loading = 'lazy', decoding = 'async', ...attrs } = Astro.props as Props; const { src, alt, sizes, widths, aspectRatio, formats = ['avif', 'webp'], loading = 'lazy', decoding = 'async', ...attrs } = Astro.props as Props;
const { image, sources } = await getPicture({ loader, src, widths, formats, aspectRatio }); const { image, sources } = await getPicture({ loader, src, widths, formats, aspectRatio });
--- ---
@ -29,7 +31,7 @@ const { image, sources } = await getPicture({ loader, src, widths, formats, aspe
<picture {...attrs}> <picture {...attrs}>
{sources.map(attrs => ( {sources.map(attrs => (
<source {...attrs} {sizes}>))} <source {...attrs} {sizes}>))}
<img {...image} {loading} {decoding} /> <img {...image} {loading} {decoding} {alt} />
</picture> </picture>
<style> <style>

View file

@ -8,10 +8,10 @@ import { Picture } from '@astrojs/image';
<!-- Head Stuff --> <!-- Head Stuff -->
</head> </head>
<body> <body>
<Picture id="social-jpg" src={socialJpg} sizes="(min-width: 640px) 50vw, 100vw" widths={[253, 506]} /> <Picture id="social-jpg" src={socialJpg} sizes="(min-width: 640px) 50vw, 100vw" widths={[253, 506]} alt="Social image" />
<br /> <br />
<Picture id="google" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" sizes="(min-width: 640px) 50vw, 100vw" widths={[272, 544]} aspectRatio={544/184} /> <Picture id="google" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" sizes="(min-width: 640px) 50vw, 100vw" widths={[272, 544]} aspectRatio={544/184} alt="Google logo" />
<br /> <br />
<Picture id='inline' src={import('../assets/social.jpg')} sizes="(min-width: 640px) 50vw, 100vw" widths={[253, 506]} /> <Picture id='inline' src={import('../assets/social.jpg')} sizes="(min-width: 640px) 50vw, 100vw" widths={[253, 506]} alt="Inline social image" />
</body> </body>
</html> </html>

View file

@ -45,12 +45,13 @@ describe('SSG pictures', function () {
// TODO: better coverage to verify source props // TODO: better coverage to verify source props
}); });
it('includes src, width, and height attributes', () => { it('includes <img> attributes', () => {
const image = $('#social-jpg img'); const image = $('#social-jpg img');
expect(image.attr('src')).to.equal('/_image/assets/social_506x253.jpg'); expect(image.attr('src')).to.equal('/_image/assets/social_506x253.jpg');
expect(image.attr('width')).to.equal('506'); expect(image.attr('width')).to.equal('506');
expect(image.attr('height')).to.equal('253'); expect(image.attr('height')).to.equal('253');
expect(image.attr('alt')).to.equal('Social image');
}); });
it('built the optimized image', () => { it('built the optimized image', () => {
@ -72,12 +73,13 @@ describe('SSG pictures', function () {
// TODO: better coverage to verify source props // TODO: better coverage to verify source props
}); });
it('includes src, width, and height attributes', () => { it('includes <img> attributes', () => {
const image = $('#inline img'); const image = $('#inline img');
expect(image.attr('src')).to.equal('/_image/assets/social_506x253.jpg'); expect(image.attr('src')).to.equal('/_image/assets/social_506x253.jpg');
expect(image.attr('width')).to.equal('506'); expect(image.attr('width')).to.equal('506');
expect(image.attr('height')).to.equal('253'); expect(image.attr('height')).to.equal('253');
expect(image.attr('alt')).to.equal('Inline social image');
}); });
it('built the optimized image', () => { it('built the optimized image', () => {
@ -103,12 +105,13 @@ describe('SSG pictures', function () {
// TODO: better coverage to verify source props // TODO: better coverage to verify source props
}); });
it('includes src, width, and height attributes', () => { it('includes <img> attributes', () => {
const image = $('#google img'); const image = $('#google img');
expect(image.attr('src')).to.equal(`/_image/googlelogo_color_272x92dp-${HASH}_544x184.png`); expect(image.attr('src')).to.equal(`/_image/googlelogo_color_272x92dp-${HASH}_544x184.png`);
expect(image.attr('width')).to.equal('544'); expect(image.attr('width')).to.equal('544');
expect(image.attr('height')).to.equal('184'); expect(image.attr('height')).to.equal('184');
expect(image.attr('alt')).to.equal('Google logo');
}); });
it('built the optimized image', () => { it('built the optimized image', () => {
@ -169,7 +172,7 @@ describe('SSG pictures', function () {
// TODO: better coverage to verify source props // TODO: better coverage to verify source props
}); });
it('includes src, width, and height attributes', () => { it('includes <img> attributes', () => {
const image = $('#social-jpg img'); const image = $('#social-jpg img');
const src = image.attr('src'); const src = image.attr('src');
@ -184,6 +187,7 @@ describe('SSG pictures', function () {
expect(searchParams.get('h')).to.equal('253'); expect(searchParams.get('h')).to.equal('253');
// TODO: possible to avoid encoding the full image path? // TODO: possible to avoid encoding the full image path?
expect(searchParams.get('href').endsWith('/assets/social.jpg')).to.equal(true); expect(searchParams.get('href').endsWith('/assets/social.jpg')).to.equal(true);
expect(image.attr('alt')).to.equal('Social image');
}); });
it('returns the optimized image', async () => { it('returns the optimized image', async () => {
@ -207,7 +211,7 @@ describe('SSG pictures', function () {
// TODO: better coverage to verify source props // TODO: better coverage to verify source props
}); });
it('includes src, width, and height attributes', () => { it('includes <img> attributes', () => {
const image = $('#inline img'); const image = $('#inline img');
const src = image.attr('src'); const src = image.attr('src');
@ -222,6 +226,7 @@ describe('SSG pictures', function () {
expect(searchParams.get('h')).to.equal('253'); expect(searchParams.get('h')).to.equal('253');
// TODO: possible to avoid encoding the full image path? // TODO: possible to avoid encoding the full image path?
expect(searchParams.get('href').endsWith('/assets/social.jpg')).to.equal(true); expect(searchParams.get('href').endsWith('/assets/social.jpg')).to.equal(true);
expect(image.attr('alt')).to.equal('Inline social image');
}); });
it('returns the optimized image', async () => { it('returns the optimized image', async () => {
@ -245,7 +250,7 @@ describe('SSG pictures', function () {
// TODO: better coverage to verify source props // TODO: better coverage to verify source props
}); });
it('includes src, width, and height attributes', () => { it('includes <img> attributes', () => {
const image = $('#google img'); const image = $('#google img');
const src = image.attr('src'); const src = image.attr('src');
@ -261,6 +266,7 @@ describe('SSG pictures', function () {
expect(searchParams.get('href')).to.equal( expect(searchParams.get('href')).to.equal(
'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png' 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png'
); );
expect(image.attr('alt')).to.equal('Google logo');
}); });
}); });
}); });

View file

@ -33,7 +33,7 @@ describe('SSR pictures - build', function () {
// TODO: better coverage to verify source props // TODO: better coverage to verify source props
}); });
it('includes src, width, and height attributes', async () => { it('includes <img> attributes', async () => {
const app = await fixture.loadTestAdapterApp(); const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/'); const request = new Request('http://example.com/');
@ -55,6 +55,7 @@ describe('SSR pictures - build', function () {
expect(searchParams.get('h')).to.equal('253'); expect(searchParams.get('h')).to.equal('253');
// TODO: possible to avoid encoding the full image path? // TODO: possible to avoid encoding the full image path?
expect(searchParams.get('href').endsWith('/assets/social.jpg')).to.equal(true); expect(searchParams.get('href').endsWith('/assets/social.jpg')).to.equal(true);
expect(image.attr('alt')).to.equal('Social image');
}); });
// TODO: Track down why the fixture.fetch is failing with the test adapter // TODO: Track down why the fixture.fetch is failing with the test adapter
@ -93,7 +94,7 @@ describe('SSR pictures - build', function () {
// TODO: better coverage to verify source props // TODO: better coverage to verify source props
}); });
it('includes src, width, and height attributes', async () => { it('includes <img> attributes', async () => {
const app = await fixture.loadTestAdapterApp(); const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/'); const request = new Request('http://example.com/');
@ -115,6 +116,7 @@ describe('SSR pictures - build', function () {
expect(searchParams.get('h')).to.equal('253'); expect(searchParams.get('h')).to.equal('253');
// TODO: possible to avoid encoding the full image path? // TODO: possible to avoid encoding the full image path?
expect(searchParams.get('href').endsWith('/assets/social.jpg')).to.equal(true); expect(searchParams.get('href').endsWith('/assets/social.jpg')).to.equal(true);
expect(image.attr('alt')).to.equal('Inline social image');
}); });
}); });
@ -134,7 +136,7 @@ describe('SSR pictures - build', function () {
// TODO: better coverage to verify source props // TODO: better coverage to verify source props
}); });
it('includes src, width, and height attributes', async () => { it('includes <img> attributes', async () => {
const app = await fixture.loadTestAdapterApp(); const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/'); const request = new Request('http://example.com/');
@ -156,6 +158,7 @@ describe('SSR pictures - build', function () {
expect(searchParams.get('h')).to.equal('184'); expect(searchParams.get('h')).to.equal('184');
// TODO: possible to avoid encoding the full image path? // TODO: possible to avoid encoding the full image path?
expect(searchParams.get('href').endsWith('googlelogo_color_272x92dp.png')).to.equal(true); expect(searchParams.get('href').endsWith('googlelogo_color_272x92dp.png')).to.equal(true);
expect(image.attr('alt')).to.equal('Google logo');
}); });
}); });
}); });
@ -207,6 +210,7 @@ describe('SSR images - dev', function () {
expect(searchParams.get('h')).to.equal('253'); expect(searchParams.get('h')).to.equal('253');
// TODO: possible to avoid encoding the full image path? // TODO: possible to avoid encoding the full image path?
expect(searchParams.get('href').endsWith('/assets/social.jpg')).to.equal(true); expect(searchParams.get('href').endsWith('/assets/social.jpg')).to.equal(true);
expect(image.attr('alt')).to.equal('Social image');
}); });
it('returns the optimized image', async () => { it('returns the optimized image', async () => {
@ -245,6 +249,7 @@ describe('SSR images - dev', function () {
expect(searchParams.get('h')).to.equal('253'); expect(searchParams.get('h')).to.equal('253');
// TODO: possible to avoid encoding the full image path? // TODO: possible to avoid encoding the full image path?
expect(searchParams.get('href').endsWith('/assets/social.jpg')).to.equal(true); expect(searchParams.get('href').endsWith('/assets/social.jpg')).to.equal(true);
expect(image.attr('alt')).to.equal('Inline social image');
}); });
}); });
@ -273,6 +278,7 @@ describe('SSR images - dev', function () {
expect(searchParams.get('href')).to.equal( expect(searchParams.get('href')).to.equal(
'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png' 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png'
); );
expect(image.attr('alt')).to.equal('Google logo');
}); });
}); });
}); });