Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor component to render <picture> element, supporting multiple image types #128

Merged
merged 1 commit into from
Feb 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions addon/components/responsive-image.hbs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
<img
src={{this.src}}
srcset={{this.srcset}}
sizes={{if @size (concat @size "vw") @sizes}}
...attributes
/>
<picture>
{{#each this.sources as |s|}}
<source
srcset={{s.srcset}}
type={{s.type}}
sizes={{s.sizes}}
/>
{{/each}}
<img
src={{this.src}}
...attributes
/>
</picture>
36 changes: 24 additions & 12 deletions addon/components/responsive-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,34 @@ interface ResponsiveImageComponentArgs {
sizes?: string;
}

interface PictureSource {
srcset: string;
type: string;
sizes?: string;
}

export default class ResponsiveImageComponent extends Component<ResponsiveImageComponentArgs> {
@service
responsiveImage!: ResponsiveImageService;

get sources(): PictureSource[] {
return this.responsiveImage
.getAvailableTypes(this.args.image)
.map((type) => {
const sources = this.responsiveImage
.getImages(this.args.image, type)
.map((imageMeta) => `${imageMeta.image} ${imageMeta.width}w`);

return {
srcset: sources.join(', '),
sizes:
this.args.sizes ??
(this.args.size ? `${this.args.size}vw` : undefined),
type: `image/${type}`,
};
});
}

/**
* the image source which fits at best for the size and screen
*/
Expand All @@ -20,16 +44,4 @@ export default class ResponsiveImageComponent extends Component<ResponsiveImageC
? this.responsiveImage.getImageBySize(this.args.image, this.args.size)
: undefined;
}

/**
* the generated source set
*/
get srcset(): string {
const sources = this.responsiveImage
.getImages(this.args.image)
.map((item) => {
return `${item.image} ${item.width}w`;
}, this);
return sources.join(', ');
}
}
19 changes: 17 additions & 2 deletions addon/services/responsive-image.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ export default class ResponsiveImageService extends Service {
*
* @method getImages
* @param {String} imageName The origin name of the Image
* @param {String} type The image type (jpeg, png, webp etc.). Optional, leave undefined to get all images
* @returns {Array} An array of objects with the image name and width, e.g. [{ width: 40, height: 20, image: myImage40w.jpg}, ...]
* @public
*/
getImages(imageName: string): ImageMeta[] {
getImages(imageName: string, type?: ImageType): ImageMeta[] {
assert(
`There is no data for image ${imageName}: ${this.meta}`,
Object.prototype.hasOwnProperty.call(this.meta, imageName)
Expand All @@ -65,7 +66,12 @@ export default class ResponsiveImageService extends Service {
`There is no image data for image ${imageName}`,
Object.prototype.hasOwnProperty.call(this.meta[imageName], 'images')
);
return this.meta[imageName].images;
let images = this.meta[imageName].images;
if (type) {
images = images.filter((image) => image.type === type);
}

return images;
}

private getType(imageName: string): ImageType {
Expand All @@ -74,6 +80,15 @@ export default class ResponsiveImageService extends Service {
return extentionTypeMapping.get(extension) ?? (extension as ImageType);
}

getAvailableTypes(imageName: string): ImageType[] {
return (
this.getImages(imageName)
.map((image) => image.type)
// unique
.filter((value, index, self) => self.indexOf(value) === index)
);
}

/**
* returns the image which fits for given size
*
Expand Down
86 changes: 78 additions & 8 deletions tests/integration/components/responsive-image-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,51 +6,121 @@ import { test, module } from 'qunit';
module('Integration: Responsive Image Component', function (hooks) {
setupRenderingTest(hooks);

test('it renders a source for every format', async function (assert) {
await render(hbs`<ResponsiveImage @image="assets/images/test.png"/>`);

assert.dom('picture').exists({ count: 1 });
assert.dom('picture source').exists({ count: 2 });
assert.dom('picture source[type="image/png"]').exists({ count: 1 });
assert.dom('picture source[type="image/webp"]').exists({ count: 1 });
});

test('it renders the correct sourceset', async function (assert) {
await render(hbs`<ResponsiveImage @image="assets/images/test.png"/>`);
// png
assert
.dom('img')
.dom('picture source[type="image/png"]')
.hasAttribute('srcset', new RegExp('/assets/images/test100w.png 100w'));
assert
.dom('img')
.dom('picture source[type="image/png"]')
.hasAttribute('srcset', new RegExp('/assets/images/test50w.png 50w'));

// webp
assert
.dom('picture source[type="image/webp"]')
.hasAttribute('srcset', new RegExp('/assets/images/test100w.webp 100w'));
assert
.dom('picture source[type="image/webp"]')
.hasAttribute('srcset', new RegExp('/assets/images/test50w.webp 50w'));

await render(hbs`<ResponsiveImage @image="assets/images/small.png"/>`);
// png
assert
.dom('picture source[type="image/png"]')
.hasAttribute('srcset', new RegExp('/assets/images/small10w.png 10w'));
assert
.dom('picture source[type="image/png"]')
.hasAttribute('srcset', new RegExp('/assets/images/small25w.png 25w'));

// webp
assert
.dom('picture source[type="image/webp"]')
.hasAttribute('srcset', new RegExp('/assets/images/small10w.webp 10w'));
assert
.dom('picture source[type="image/webp"]')
.hasAttribute('srcset', new RegExp('/assets/images/small25w.webp 25w'));

await render(hbs`<ResponsiveImage @image="assets/images/small.png"/>`);
// png
assert
.dom('img')
.dom('picture source[type="image/png"]')
.hasAttribute('srcset', new RegExp('/assets/images/small10w.png 10w'));
assert
.dom('img')
.dom('picture source[type="image/png"]')
.hasAttribute('srcset', new RegExp('/assets/images/small25w.png 25w'));

// webp
assert
.dom('picture source[type="image/webp"]')
.hasAttribute('srcset', new RegExp('/assets/images/small10w.webp 10w'));
assert
.dom('picture source[type="image/webp"]')
.hasAttribute('srcset', new RegExp('/assets/images/small25w.webp 25w'));

await render(
hbs`<ResponsiveImage @image="assets/images/recursive/dir/test.png"/>`
);
// png
assert
.dom('img')
.dom('picture source[type="image/png"]')
.hasAttribute(
'srcset',
new RegExp('/assets/images/recursive/dir/test100w.png 100w')
);
assert
.dom('img')
.dom('picture source[type="image/png"]')
.hasAttribute(
'srcset',
new RegExp('/assets/images/recursive/dir/test50w.png 50w')
);

// webp
assert
.dom('picture source[type="image/webp"]')
.hasAttribute(
'srcset',
new RegExp('/assets/images/recursive/dir/test100w.webp 100w')
);
assert
.dom('picture source[type="image/webp"]')
.hasAttribute(
'srcset',
new RegExp('/assets/images/recursive/dir/test50w.webp 50w')
);
});

test('it renders a given size as sizes', async function (assert) {
await render(
hbs`<ResponsiveImage @image="assets/images/test.png" @size="40"/>`
);
assert.dom('img').hasAttribute('sizes', '40vw');
assert
.dom('picture source[type="image/png"]')
.hasAttribute('sizes', '40vw');
assert
.dom('picture source[type="image/webp"]')
.hasAttribute('sizes', '40vw');
});

test('it renders with given sizes', async function (assert) {
await render(
hbs`<ResponsiveImage @image="assets/images/test.png" @sizes="(max-width: 767px) 100vw, 50vw"/>`
);
assert.equal(
find('img').getAttribute('sizes'),
find('picture source[type="image/png"]').getAttribute('sizes'),
'(max-width: 767px) 100vw, 50vw'
);
assert.equal(
find('picture source[type="image/webp"]').getAttribute('sizes'),
'(max-width: 767px) 100vw, 50vw'
);
});
Expand Down
15 changes: 15 additions & 0 deletions tests/unit/services/responsive-image-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,21 @@ module('ResponsiveImageService', function (hooks) {
assert.deepEqual(images, meta['test.png'].images);
});

test('retrieve generated images by name and type', function (assert) {
let service = this.owner.lookup('service:responsive-image');
let images = service.getImages('test.png', 'png');
assert.deepEqual(images, meta['test.png'].images.slice(0, 2));

images = service.getImages('test.png', 'webp');
assert.deepEqual(images, meta['test.png'].images.slice(2, 4));
});

test('get available types', function (assert) {
let service = this.owner.lookup('service:responsive-image');
let types = service.getAvailableTypes('test.png');
assert.deepEqual(types, ['png', 'webp']);
});

test('retrieve generated image data by size', function (assert) {
let service = this.owner.lookup('service:responsive-image');
service.physicalWidth = 100;
Expand Down