Skip to content

Commit 3a70fb4

Browse files
author
Nir Maoz
authored
Fix responsiveUseBreakpoints not affecting image size
1 parent 1f5206c commit 3a70fb4

File tree

3 files changed

+64
-35
lines changed

3 files changed

+64
-35
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"main": "lib/index.js",
66
"scripts": {
77
"test": "node_modules/.bin/mocha --require @babel/register test/.setup.js --recursive test",
8+
"test:all": "run-s compile dist test test-dist test-lib",
89
"test-dist": "TEST_SUBJECT=dist node_modules/.bin/mocha --require @babel/register test/.setup.js --recursive test",
910
"test-lib": "TEST_SUBJECT=lib node_modules/.bin/mocha --require @babel/register test/.setup.js --recursive test",
1011
"compile": "node_modules/.bin/babel -v --copy-files src -d lib ",
@@ -43,6 +44,7 @@
4344
"enzyme-adapter-react-16": "^1.7.1",
4445
"jsdom": "^11.12.0",
4546
"mocha": "^4.0.1",
47+
"npm-run-all": "^4.1.5",
4648
"react-dom": "^16.2.0",
4749
"webpack": "4.27.1",
4850
"webpack-cli": "^3.1.2"

src/components/Image/Image.js

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
1-
import React, {Component} from 'react';
1+
import React from 'react';
22
import cloudinary, {Util} from 'cloudinary-core';
33
import CloudinaryComponent from '../CloudinaryComponent';
44
import {debounce, firstDefined, closestAbove, requestAnimationFrame, isElement} from '../../Util';
55

6+
const defaultBreakpoints = (width, steps = 100) => {
7+
return steps * Math.ceil(width / steps);
8+
};
9+
610
/**
711
* A component representing a Cloudinary served image
812
*/
913
class Image extends CloudinaryComponent {
1014
constructor(props, context) {
11-
function defaultBreakpoints(width, steps = 100) {
12-
return steps * Math.ceil(width / steps);
13-
}
14-
1515
super(props, context);
1616
this.handleResize = this.handleResize.bind(this);
1717

18+
this.state = {};
19+
1820
let state = {responsive: false, url: undefined, breakpoints: defaultBreakpoints};
1921
this.state = Object.assign(state, this.prepareState(props, context));
2022
}
@@ -26,7 +28,7 @@ class Image extends CloudinaryComponent {
2628
*/
2729
get window() {
2830
let windowRef = null;
29-
if(typeof window !== "undefined"){
31+
if (typeof window !== "undefined") {
3032
windowRef = window
3133
}
3234
return (this.element && this.element.ownerDocument) ? (this.element.ownerDocument.defaultView || windowRef) : windowRef;
@@ -37,38 +39,39 @@ class Image extends CloudinaryComponent {
3739
this.setState(state);
3840
}
3941

40-
/**
41-
* Generate update state of this element
42-
* @param {Object} [props=this.props]
43-
* @param {Object} [context=this.context]
44-
* @returns {Object} state updates
45-
* @private
46-
*/
4742
prepareState(props = this.props, context = this.context) {
4843
let extendedProps = CloudinaryComponent.normalizeOptions(context, props);
4944
let url = this.getUrl(extendedProps);
5045
let state = {};
46+
let updatedOptions = {};
47+
5148
if (extendedProps.breakpoints !== undefined) {
5249
state.breakpoints = extendedProps.breakpoints;
5350
}
5451
if (extendedProps.responsive) {
5552
state.responsive = true;
56-
url = this.cloudinary_update(url, state);
53+
updatedOptions = this.cloudinaryUpdate(url, state);
54+
url = updatedOptions.url;
5755
}
5856

5957
let currentState = this.state || {};
58+
59+
state.width = updatedOptions.width;
60+
6061
if (!Util.isEmpty(url) && url !== currentState.url) {
6162
state.url = url;
6263
}
64+
65+
6366
return state;
6467
}
6568

66-
handleResize(e) {
69+
handleResize() {
6770
if (!this.props.responsive || this.rqf) return;
6871
this.rqf = requestAnimationFrame(() => {
6972
this.rqf = null;
7073
let newState = this.prepareState();
71-
if(!Util.isEmpty(newState.url)) {
74+
if (!Util.isEmpty(newState.url)) {
7275
this.setState(newState);
7376
}
7477
});
@@ -124,10 +127,9 @@ class Image extends CloudinaryComponent {
124127
};
125128

126129
applyBreakpoints(width, steps, options) {
127-
var responsive_use_breakpoints;
128130
options = CloudinaryComponent.normalizeOptions(this.context, this.props, options);
129-
responsive_use_breakpoints = options.responsive_use_breakpoints;
130-
if ((!responsive_use_breakpoints) || (responsive_use_breakpoints === 'resize' && !options.resizing)) {
131+
let responsiveUseBreakpoints = options.responsiveUseBreakpoints;
132+
if ((!responsiveUseBreakpoints) || (responsiveUseBreakpoints === 'resize' && !options.resizing)) {
131133
return width;
132134
} else {
133135
return this.calc_breakpoint(width, steps);
@@ -136,7 +138,7 @@ class Image extends CloudinaryComponent {
136138

137139
calc_breakpoint(width, steps) {
138140
var breakpoints, point;
139-
breakpoints = this.state.breakpoints || defaultBreakpoints;
141+
breakpoints = (this.state && this.state.breakpoints) || defaultBreakpoints;
140142
if (Util.isFunction(breakpoints)) {
141143
return breakpoints(width, steps);
142144
} else {
@@ -179,16 +181,10 @@ class Image extends CloudinaryComponent {
179181
};
180182

181183
maxWidth(requiredWidth) {
182-
var imageWidth;
183-
imageWidth = this.state.width || 0;
184-
if (requiredWidth > imageWidth) {
185-
imageWidth = requiredWidth;
186-
this.setState({width: requiredWidth});
187-
}
188-
return imageWidth;
184+
return Math.max((this.state && this.state.width) || 0, requiredWidth);
189185
};
190186

191-
cloudinary_update(url, options = {}) {
187+
cloudinaryUpdate(url, options = {}) {
192188
var requiredWidth;
193189
var match;
194190
let resultUrl = this.updateDpr(url, options.roundDpr);
@@ -199,16 +195,19 @@ class Image extends CloudinaryComponent {
199195
requiredWidth = this.maxWidth(containerWidth, this.element);
200196
resultUrl = resultUrl.replace(/w_auto:breakpoints([_0-9]*)(:[0-9]+)?/,
201197
"w_auto:breakpoints$1:" + requiredWidth);
202-
} else if (match = /w_auto(:(\d+))?/.exec(resultUrl)) {
203-
requiredWidth = this.applyBreakpoints(containerWidth, match[2], options);
204-
requiredWidth = this.maxWidth(requiredWidth, this.element);
205-
resultUrl = resultUrl.replace(/w_auto[^,\/]*/g, "w_" + requiredWidth);
198+
} else {
199+
match = /w_auto(:(\d+))?/.exec(resultUrl);
200+
if (match) {
201+
requiredWidth = this.applyBreakpoints(containerWidth, match[2], options);
202+
requiredWidth = this.maxWidth(requiredWidth, this.element);
203+
resultUrl = resultUrl.replace(/w_auto[^,\/]*/g, "w_" + requiredWidth);
204+
}
206205
}
207206
} else {
208207
resultUrl = "";
209208
}
210209
}
211-
return resultUrl;
210+
return {url: resultUrl, width: requiredWidth};
212211
}
213212
}
214213

test/ImageTest.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@ describe('Image', () => {
2929
});
3030
it("should not pass-through Cloudinary attributes", function() {
3131
let width = 300;
32-
33-
let tag2 = mount(<div width="300"><Image publicId="sample" cloudName="demo" width="auto" crop="scale" privateCdn="private" defaultImage="foobar" responsive responsiveUseBreakpoints /></div>);
3432
let tag = shallow(<Image publicId="sample" cloudName="demo" width="auto" crop="scale" privateCdn="private" defaultImage="foobar" responsive responsiveUseBreakpoints />);
33+
3534
expect(tag.type()).to.equal("img");
3635
expect(tag.state("url")).to.equal(undefined);
3736
expect(tag.props()).to.have.property('src');
@@ -70,4 +69,33 @@ describe('Image', () => {
7069
let tag = mount(<Image cloudName="demo" />);
7170
expect(tag.find('img').prop('src')).to.equal(undefined);
7271
});
72+
it('should use breakpoints to calculate image width', function () {
73+
const expectedSizing = [
74+
{containerWidth: 225, imageWidth: 700},
75+
{containerWidth: 275, imageWidth: 300},
76+
{containerWidth: 350, imageWidth: 400}
77+
];
78+
79+
const context = {
80+
cloudName: "demo",
81+
responsive: true,
82+
responsiveUseBreakpoints: true
83+
};
84+
85+
expectedSizing.forEach(({containerWidth, imageWidth}) => {
86+
Image.prototype.findContainerWidth = () => containerWidth;
87+
88+
const tag = shallow(
89+
<Image
90+
publicId="sample"
91+
width="auto"
92+
crop="scale"
93+
/>, {
94+
context: context
95+
});
96+
97+
expect(tag.type()).to.equal("img");
98+
expect(tag.state("url")).to.equal(`http://res.cloudinary.com/demo/image/upload/c_scale,w_${Math.ceil(containerWidth / 100) * 100}/sample`);
99+
});
100+
});
73101
});

0 commit comments

Comments
 (0)