Skip to content

Commit 48bc98e

Browse files
author
Nir Maoz
authored
Fix duplicate attributes for kebab-case props (#196)
1 parent 59afa33 commit 48bc98e

File tree

5 files changed

+33
-23
lines changed

5 files changed

+33
-23
lines changed

src/Util/Util.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export extractCloudinaryProps from './extractCloudinaryProps';
21
export * from './cloudinaryCoreUtils';
2+
export * from './cloudinaryReactUtils'
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
1-
import {Transformation, Util} from 'cloudinary-core';
2-
3-
const CLOUDINARY_REACT_PROPS = {includeOwnBody: true};
1+
import {Transformation, Util} from "cloudinary-core";
2+
// props passed to cloudinary-core but should not be rendered as dom attributes
3+
const CLOUDINARY_REACT_PROPS = ['accessibility', 'breakpoints', 'dataSrc', 'placeholder', 'publicId', 'signature'];
44

5+
// props passed to cloudinary-core for dom elements attributes generation
56
// Map Cloudinary props from array to object for efficient lookup
6-
const CLOUDINARY_PROPS = Transformation.PARAM_NAMES.map(Util.camelCase).concat(['publicId', 'breakpoints']).reduce(
7+
const CLOUDINARY_PROPS = [...Transformation.PARAM_NAMES, ...CLOUDINARY_REACT_PROPS].map(Util.camelCase).reduce(
78
(accumulator, cloudinaryPropName) => {
89
accumulator[cloudinaryPropName] = true;
910
return accumulator;
1011
},
1112
{}
1213
);
1314

14-
const isDefined = (props, key) => {
15-
return (props[key] !== undefined && props[key] !== null);
16-
};
17-
1815
/**
1916
* Extracts cloudinaryProps and nonCloudinaryProps from given props
2017
*
2118
* @param props
2219
* @returns {{children: *, cloudinaryReactProps: {}, cloudinaryProps: {}, nonCloudinaryProps: {}}}
2320
*/
24-
export default ({children, ...props}) => {
21+
const extractCloudinaryProps = ({children, ...props}) => {
2522
let result = {
2623
children,
2724
cloudinaryProps: {},
@@ -35,10 +32,10 @@ export default ({children, ...props}) => {
3532

3633
//if valid and defined add to cloudinaryProps
3734
if (CLOUDINARY_PROPS[camelKey]) {
38-
if (isDefined(props, key)) {
35+
if (props[key] !== undefined && props[key] !== null) {
3936
result.cloudinaryProps[camelKey] = value;
4037
}
41-
} else if (CLOUDINARY_REACT_PROPS[camelKey]) { //cloudinary-react spesific prop
38+
} else if (camelKey === 'includeOwnBody') { //cloudinary-react specific prop
4239
result.cloudinaryReactProps[camelKey] = value;
4340
} else { //not valid so add to nonCloudinaryProps
4441
result.nonCloudinaryProps[key] = value;
@@ -47,3 +44,8 @@ export default ({children, ...props}) => {
4744

4845
return result;
4946
};
47+
48+
export {
49+
CLOUDINARY_REACT_PROPS,
50+
extractCloudinaryProps
51+
};

src/components/Image/Image.js

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

@@ -56,11 +56,13 @@ class Image extends CloudinaryComponent {
5656
*/
5757
getAttributes = (additionalOptions = {}) => {
5858
const {placeholder} = additionalOptions;
59-
const options = {...this.getOptions(), ...additionalOptions};
60-
const {nonCloudinaryProps} = extractCloudinaryProps(options);
59+
const options = extractCloudinaryProps({...this.getOptions(), ...additionalOptions});
60+
const {cloudinaryProps, nonCloudinaryProps, cloudinaryReactProps} = options;
61+
const imageTag = getImageTag({...cloudinaryProps, ...cloudinaryReactProps});
62+
const cloudinaryAttributes = Util.withCamelCaseKeys(imageTag.attributes());
6163

6264
// React requires camelCase instead of snake_case attributes
63-
const attributes = ({...Util.withCamelCaseKeys(getImageTag(options).attributes()), ...nonCloudinaryProps});
65+
const attributes = ({...cloudinaryAttributes, ...nonCloudinaryProps});
6466

6567
// We want to keep 'data-src' if it exists
6668
if (attributes.dataSrc) {
@@ -79,7 +81,7 @@ class Image extends CloudinaryComponent {
7981
}
8082

8183
// Remove unneeded attributes,
82-
['dataSrc', 'accessibility', 'placeholder', 'breakpoints'].forEach(attr => {
84+
CLOUDINARY_REACT_PROPS.forEach(attr => {
8385
delete attributes[attr];
8486
});
8587

src/components/Video/Video.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22
import PropTypes from 'prop-types';
33
import {Cloudinary, Util} from 'cloudinary-core';
44
import CloudinaryComponent from '../CloudinaryComponent';
5-
import extractCloudinaryProps from "../../Util/extractCloudinaryProps";
5+
import {extractCloudinaryProps} from "../../Util";
66

77
/**
88
* A component representing a Cloudinary served video

test/ImageTest.js

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

9595
expect(tag.find('img').prop('src')).to.match(/fn_wasm:blur.wasm\/sample/);
9696
});
97-
it('should not change kebab-case param names', () => {
97+
describe('prop names', () => {
9898
let tag = mount(
99-
<Image publicId="sample" cloudName="demo" data-testid="testing"/>
99+
<Image publicId="sample" cloudName="demo" data-testid="testing" aria-describedby="testValue"/>
100100
);
101-
102-
expect(tag.find('img').prop('data-testid')).to.equal('testing');
103-
expect(tag.find('img').prop('datatestid')).to.equal(undefined);
101+
it('should not change kebab-case prop names', () => {
102+
expect(tag.find('img').prop('data-testid')).to.equal('testing');
103+
expect(tag.find('img').prop('aria-describedby')).to.equal('testValue');
104+
});
105+
it('should not duplicate attributes for kebab-case props', () => {
106+
expect(tag.find('img').prop('datatestid')).to.not.exist;
107+
expect(tag.find('img').prop('ariadescribedby')).to.not.exist;
108+
expect(tag.html()).to.equal('<img src="http://res.cloudinary.com/demo/image/upload/sample" data-testid="testing" aria-describedby="testValue">');
109+
});
104110
});
105111
it('should update on prop change', () => {
106112
let tag = mount(<Image publicId="sample" cloudName="demo"/>);

0 commit comments

Comments
 (0)