Skip to content

Commit 4352171

Browse files
author
Nir Maoz
authored
Fix responsive prop, add a warning when it's not affecting the transformation (#176)
1 parent 970e978 commit 4352171

File tree

5 files changed

+75
-22
lines changed

5 files changed

+75
-22
lines changed
Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,44 @@
1+
/**
2+
* When 'responsive' is passed to the Image component, cloudinary-core generates a data-src attribute instead of src.
3+
* Then cloudinary.responsive() is called, it will add a 'src' attribute with calculated width according to the size
4+
* of the <img> element's container.
5+
* In these tests we first test for existence of 'data-src' attribute, and then we check that 'src' was also generated.
6+
* Remember that Cypress removes the need to add a wait or timeout, and waits for a test to be true until it's default
7+
* timeout is reached.
8+
*/
19
describe('Responsive Image', () => {
210
beforeEach(() => {
311
// runs before each test in the block
412
cy.visit('/');
513
cy.get('#responsiveBtn').click(); // Click on button
614
});
15+
// Here responsive() is called, but since a specified width value was passed (100) alongside responsive,
16+
// The responsive() function does not change the width param of the transformation, because the specified width
17+
// "overrides" the responsiveness. in other words, specifying a width value is stronger then the 'responsive' prop.
18+
it('Should generate transformation with specified width value', () => {
19+
cy.get('#responsive-override')
20+
.should('have.attr', 'data-src').should('equal', 'http://res.cloudinary.com/demo/image/upload/c_scale,w_100/sample')
21+
cy.get('#responsive-override')
22+
.should('have.attr', 'src').should('equal', 'http://res.cloudinary.com/demo/image/upload/c_scale,w_100/sample')
23+
cy.get('#responsive-override')
24+
.should('have.attr', 'width').should('equal', '100')
25+
});
726
it('Responsive Image', () => {
827
cy.get('#responsive')
9-
.should('have.attr', 'data-src').should('equal','http://res.cloudinary.com/demo/image/upload/c_scale,w_auto/sample')
28+
.should('have.attr', 'data-src').should('equal', 'http://res.cloudinary.com/demo/image/upload/c_scale,w_auto/sample')
1029
cy.get('#responsive')
11-
.should('have.attr', 'src').should('equal','http://res.cloudinary.com/demo/image/upload/c_scale,w_400/sample')
30+
.should('have.attr', 'src').should('equal', 'http://res.cloudinary.com/demo/image/upload/c_scale,w_400/sample')
1231
});
1332
it('Disabled Breakpoints', () => {
1433
cy.get('#responsive')
15-
.should('have.attr', 'data-src').should('equal','http://res.cloudinary.com/demo/image/upload/c_scale,w_auto/sample')
34+
.should('have.attr', 'data-src').should('equal', 'http://res.cloudinary.com/demo/image/upload/c_scale,w_auto/sample')
1635
cy.get('#disable-breakpoints')
17-
.should('have.attr', 'src').should('equal','http://res.cloudinary.com/demo/image/upload/c_scale,w_330/sample')
36+
.should('have.attr', 'src').should('equal', 'http://res.cloudinary.com/demo/image/upload/c_scale,w_330/sample')
1837
});
1938
it('Enabled Breakpoints', () => {
2039
cy.get('#responsive')
21-
.should('have.attr', 'data-src').should('equal','http://res.cloudinary.com/demo/image/upload/c_scale,w_auto/sample')
40+
.should('have.attr', 'data-src').should('equal', 'http://res.cloudinary.com/demo/image/upload/c_scale,w_auto/sample')
2241
cy.get('#breakpoints')
23-
.should('have.attr', 'src').should('equal','http://res.cloudinary.com/demo/image/upload/c_scale,w_450/sample')
42+
.should('have.attr', 'src').should('equal', 'http://res.cloudinary.com/demo/image/upload/c_scale,w_450/sample')
2443
});
2544
});

e2e-test/src/App.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ function App() {
2828
{test === 'responsive' &&
2929
<Fragment>
3030
<h1>Responsive Image</h1>
31+
<div style={{width: "330px"}}>
32+
<Image id="responsive-override" publicId="sample" cloudName="demo" width={100} crop="scale" responsive/>
33+
</div>
3134
<div style={{width: "330px"}}>
3235
<Image id="responsive" publicId="sample" cloudName="demo" width="auto" crop="scale" responsive/>
3336
</div>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"webpack-cli": "^3.1.2"
5858
},
5959
"dependencies": {
60-
"cloudinary-core": "^2.10.2",
60+
"cloudinary-core": "^2.10.3",
6161
"prop-types": "^15.6.2"
6262
},
6363
"peerDependencies": {

src/components/Image/Image.js

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
import React, {createRef, Fragment} from 'react';
22
import CloudinaryComponent from '../CloudinaryComponent';
3-
import {extractCloudinaryProps, getImageTag, makeElementResponsive, getConfiguredCloudinary} from "../../Util";
3+
import {extractCloudinaryProps, getImageTag, makeElementResponsive} from "../../Util";
44
import {Util} from "cloudinary-core";
55
import PropTypes from "prop-types";
66

7+
const RESPONSIVE_OVERRIDE_WARNING = [
8+
`Warning: passing a number value for width cancels the 'responsive' prop's effect on the image transformation.`,
9+
`The 'responsive' prop affects the image transformation only when width === 'auto'.`,
10+
`Passing 'width="auto" responsive' will affect the actual image width that is fetched from Cloudinary.`,
11+
`The 'responsive' prop causes the Image component to request an image which width is equal to the width of it's container.`,
12+
`When passing 'width="auto" responsive', you can set the <img> element width by passing a 'style' prop`
13+
].join('\n');
14+
715
/**
816
* A component representing a Cloudinary served image
917
*/
@@ -19,7 +27,12 @@ class Image extends CloudinaryComponent {
1927
* @return true when this image element should be made responsive, false otherwise.
2028
*/
2129
isResponsive = () => {
22-
return this.props.responsive && this.imgElement && this.imgElement.current;
30+
const {responsive, width} = this.getExtendedProps();
31+
if (responsive && width !== 'auto') {
32+
console.warn(RESPONSIVE_OVERRIDE_WARNING);
33+
}
34+
35+
return responsive && this.imgElement && this.imgElement.current;
2336
}
2437

2538
/**
@@ -44,33 +57,46 @@ class Image extends CloudinaryComponent {
4457

4558
let attributes = {...getImageTag(options).attributes(), ...nonCloudinaryProps};
4659

60+
//React requires camelCase instead of snake_case attributes
61+
attributes = Util.withCamelCaseKeys(attributes);
62+
4763
// Set placeholder Id
4864
if (placeholder && attributes.id) {
4965
attributes.id = attributes.id + '-cld-placeholder';
5066
}
5167

68+
// Set dataSrc if lazy loading and not in view
69+
if (!isInView && this.shouldLazyLoad(options)) {
70+
attributes.dataSrc = attributes.dataSrc || attributes.src;
71+
delete attributes.src;
72+
}
73+
74+
// The data-src attribute was turned into dataSrc by the camelCase function,
75+
// But it's needed by cloudinary-core's responsive() function. Notice that it's not snake_case.
76+
if (attributes.dataSrc) {
77+
attributes['data-src'] = attributes.dataSrc;
78+
}
79+
5280
// Remove unneeded attributes,
53-
["src", "accessibility", "placeholder"].forEach(attr => {
81+
['dataSrc', 'accessibility', 'placeholder', 'breakpoints'].forEach(attr => {
5482
delete attributes[attr];
5583
});
5684

57-
// Set src or data-src attribute
58-
const srcAttrName = isInView || !this.shouldLazyLoad(options) ? "src" : "data-src";
59-
attributes[srcAttrName] = getConfiguredCloudinary(options).url(options.publicId, options);
60-
6185
return attributes;
6286
}
6387

6488
/**
6589
* Update this image using cloudinary-core
6690
*/
6791
update = () => {
92+
const {isInView} = this.state;
93+
6894
if (this.isResponsive()) {
6995
const removeListener = makeElementResponsive(this.imgElement.current, this.getOptions());
7096
this.listenerRemovers.push(removeListener);
7197
}
7298

73-
if (this.shouldLazyLoad(this.getExtendedProps())) {
99+
if (!isInView && this.shouldLazyLoad(this.getExtendedProps())) {
74100
Util.detectIntersection(this.imgElement.current, this.onIntersect);
75101
}
76102
}
@@ -153,7 +179,6 @@ class Image extends CloudinaryComponent {
153179
</Fragment>
154180
);
155181
}
156-
157182
return <img ref={attachRef} {...attributes}/>
158183
}
159184
}

test/ImageTest.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,6 @@ describe('Image', () => {
9494

9595
expect(tag.find('img').prop('src')).to.match(/fn_wasm:blur.wasm\/sample/);
9696
});
97-
it('should support responsive prop', () => {
98-
let tag = mount(<Image publicId="sample" cloudName="demo"/>);
99-
tag.setProps({responsive: true});
100-
expect(tag.find('img').prop('data-src')).to.equal('http://res.cloudinary.com/demo/image/upload/sample');
101-
});
10297
it('Should support forwarding innerRef to underlying image element', function () {
10398
const expected = 'http://res.cloudinary.com/demo/image/upload/sample';
10499
let myRef = React.createRef();
@@ -129,6 +124,17 @@ describe('Image', () => {
129124

130125
expect(tag.find('img').prop('src')).to.equal(expected);
131126
});
127+
describe('Responsive', () => {
128+
it('should support responsive prop', () => {
129+
let tag = mount(<Image publicId="sample" cloudName="demo"/>);
130+
tag.setProps({responsive: true});
131+
expect(tag.find('img').prop('data-src')).to.equal('http://res.cloudinary.com/demo/image/upload/sample');
132+
});
133+
it('should set image width even when responsive prop is passed', () => {
134+
let tag = mount(<Image publicId="sample" cloudName="demo" width={100} responsive/>);
135+
expect(tag.find('img').prop('width')).to.equal(100);
136+
});
137+
});
132138
describe('Placeholder', () => {
133139
it(`should not have opacity and position when placeholder doesn't exists`, () => {
134140
const tag = shallow(<Image publicId="sample" cloudName="demo"/>);
@@ -185,7 +191,7 @@ describe('Image', () => {
185191
});
186192
describe('Responsive Placeholder With Lazy Loading', () => {
187193
let tag = shallow(
188-
<Image publicId="sample" cloudName="demo" loading="lazy" responsive width="auto" crop="scale">
194+
<Image publicId="sample" cloudName="demo" loading="lazy" width="auto" crop="scale" responsive>
189195
<Placeholder/>
190196
</Image>
191197
);

0 commit comments

Comments
 (0)