Skip to content

Commit cd169a2

Browse files
authored
Add cld-responsive as an alias for responsive attribute (#91)
* Add cld-responsive as an alias for responsive attribute * Allow any attribute to be prefixed with cld "cld" Prefix can also include an optional dash or underscore. Prefix is removed by the SDK when using the attribute as an <img> attribute to provide aliases to attributes that may be used by other directives
1 parent ba2b492 commit cd169a2

File tree

4 files changed

+67
-20
lines changed

4 files changed

+67
-20
lines changed

src/cloudinary-image.component.spec.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ describe('CloudinaryImage', () => {
3333
}).createComponent(TestComponent);
3434

3535
fixture.detectChanges(); // initial binding
36+
expect(localCloudinary.responsive).toHaveBeenCalled();
3637

3738
// all elements with an attached CloudinaryImage
3839
des = fixture.debugElement.query(By.directive(CloudinaryImage));
@@ -218,5 +219,41 @@ describe('CloudinaryImage', () => {
218219
expect(img.src).toEqual(jasmine.stringMatching(/image\/upload\/updatedId/));
219220
});
220221
});
221-
});
222222

223+
describe('responsive images with nested transformations using the cld-responsive attribute', () => {
224+
@Component({
225+
template: `<cl-image cld-responsive id="image1" public-id="responsive_sample.jpg">
226+
<cl-transformation width="300" crop="scale" overlay="text:roboto_25_bold:SDK"></cl-transformation>
227+
<cl-transformation effect="art:hokusai"></cl-transformation>
228+
<cl-transformation fetch-format="auto"></cl-transformation>
229+
</cl-image>`
230+
})
231+
class TestComponent { }
232+
233+
let fixture: ComponentFixture<TestComponent>;
234+
let des: DebugElement; // the elements w/ the directive
235+
236+
beforeEach(() => {
237+
fixture = TestBed.configureTestingModule({
238+
declarations: [CloudinaryTransformationDirective, CloudinaryImage, TestComponent],
239+
providers: [{ provide: Cloudinary, useValue: localCloudinary }]
240+
}).createComponent(TestComponent);
241+
242+
fixture.detectChanges(); // initial binding
243+
expect(localCloudinary.responsive).toHaveBeenCalled();
244+
245+
// all elements with an attached CloudinaryImage
246+
des = fixture.debugElement.query(By.directive(CloudinaryImage));
247+
});
248+
249+
it('creates an img element which encodes the directive attributes to the URL', () => {
250+
const img = des.children[0].nativeElement as HTMLImageElement;
251+
expect(img.src).toEqual(jasmine.stringMatching
252+
(/c_scale,l_text:roboto_25_bold:SDK,w_300\/e_art:hokusai\/f_auto\/responsive_sample.jpg/));
253+
console.log('img', img);
254+
expect(img.attributes.getNamedItem('data-src').value).toEqual(jasmine.stringMatching(
255+
/c_scale,l_text:roboto_25_bold:SDK,w_300\/e_art:hokusai\/f_auto\/responsive_sample.jpg/));
256+
});
257+
});
258+
259+
});

src/cloudinary-image.component.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ export class CloudinaryImage implements AfterViewInit, OnInit, OnChanges, OnDest
6464
const nativeElement = this.el.nativeElement;
6565
const image = nativeElement.children[0];
6666
const options = this.cloudinary.toCloudinaryAttributes(nativeElement.attributes, this.transformations);
67-
6867
const imageTag = this.cloudinary.imageTag(this.publicId, options);
6968
this.setElementAttributes(image, imageTag.attributes());
7069
if (options.responsive) {

src/cloudinary.service.spec.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
Cloudinary,
66
isJsonLikeString,
77
isNamedNodeMap,
8-
transformKeyNamesFromKebabToSnakeCase
8+
transformKeyNames
99
} from './cloudinary.service';
1010
import CloudinaryConfiguration from './cloudinary-configuration.class';
1111

@@ -70,9 +70,9 @@ describe('Cloudinary service', () => {
7070
});
7171
});
7272

73-
describe('transformKeyNamesFromKebabToSnakeCase', () => {
73+
describe('transformKeyNames', () => {
7474
it('Transforms property names of json-like strings from kebab-case to snake_case', () => {
75-
expect(transformKeyNamesFromKebabToSnakeCase('{"aaa-aaa":"1", "bbb-bbb":"2", "cc": "ccc-ccc"}')).toEqual(
75+
expect(transformKeyNames('{"aaa-aaa":"1", "bbb-bbb":"2", "cc": "ccc-ccc"}')).toEqual(
7676
{
7777
aaa_aaa: '1',
7878
bbb_bbb: '2',
@@ -81,7 +81,7 @@ describe('Cloudinary service', () => {
8181
);
8282
});
8383
it('Transforms property names of json-like strings spanning multi-lines from kebab-case to snake_case', () => {
84-
expect(transformKeyNamesFromKebabToSnakeCase(`{"aaa-aaa":"1",
84+
expect(transformKeyNames(`{"aaa-aaa":"1",
8585
"bbb-bbb":"2",
8686
"cc": "ccc-ccc"}`)).toEqual(
8787
{
@@ -92,22 +92,32 @@ describe('Cloudinary service', () => {
9292
);
9393
});
9494
it('Transforms property names of objects from kebab-case to snake_case', () => {
95-
expect(transformKeyNamesFromKebabToSnakeCase({'aaa-aaa': 1, 'bbb-bbb': 2, cc: 'ccc-ccc'})).toEqual(
95+
expect(transformKeyNames({'aaa-aaa': 1, 'bbb-bbb': 2, cc: 'ccc-ccc'})).toEqual(
9696
{
9797
aaa_aaa: 1,
9898
bbb_bbb: 2,
9999
cc: 'ccc-ccc'
100100
}
101101
);
102102
});
103+
it('Transforms property names by stripping cld prefix', () => {
104+
// "cld" prefix can be followed by an optional dash or underscore
105+
expect(transformKeyNames('{"cldResponsive":"1", "cld-width":"2", "cld_height": "ccc-ccc"}')).toEqual(
106+
{
107+
responsive: '1',
108+
width: '2',
109+
height: 'ccc-ccc'
110+
}
111+
);
112+
});
103113
it('does not affect primitive values', () => {
104-
expect(transformKeyNamesFromKebabToSnakeCase(123)).toEqual(123);
105-
expect(transformKeyNamesFromKebabToSnakeCase(undefined)).toBeUndefined();
106-
expect(transformKeyNamesFromKebabToSnakeCase('')).toEqual('');
107-
expect(transformKeyNamesFromKebabToSnakeCase('a b c')).toEqual('a b c');
114+
expect(transformKeyNames(123)).toEqual(123);
115+
expect(transformKeyNames(undefined)).toBeUndefined();
116+
expect(transformKeyNames('')).toEqual('');
117+
expect(transformKeyNames('a b c')).toEqual('a b c');
108118
});
109119
it('iterates over array elements to transform its members', () => {
110-
expect(transformKeyNamesFromKebabToSnakeCase([{
120+
expect(transformKeyNames([{
111121
'aaa-aaa': 'aaa-aaa',
112122
'bbb-bbb': 'bbb-bbb',
113123
'ccc': 'ccc'
@@ -125,7 +135,7 @@ describe('Cloudinary service', () => {
125135
]);
126136
});
127137
it('transforms complex json-like objects into options', () => {
128-
expect(transformKeyNamesFromKebabToSnakeCase(`{"aaa-aaa":"1",
138+
expect(transformKeyNames(`{"aaa-aaa":"1",
129139
"bbb-bbb":"2",
130140
"transform-ation": [{ "effect": "sepia", "fetch_format": "auto"}]
131141
}`)).toEqual(

src/cloudinary.service.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ const namedNodeMapToObject = function (source: NamedNodeMap): any {
2929
return target;
3030
};
3131

32-
const transformKeyNamesFromKebabToSnakeCase = function (obj: any): any {
32+
const transformKeyNames = function (obj: any): any {
3333
let _obj = obj;
3434
if (isJsonLikeString(obj)) {
3535
// Given attribute value is in the form of a JSON object -
@@ -42,13 +42,14 @@ const transformKeyNamesFromKebabToSnakeCase = function (obj: any): any {
4242
if (Array.isArray(_obj)) {
4343
// Transform all the array values (e.g. transformation array)
4444
_obj = _obj.map(currentValue => {
45-
return transformKeyNamesFromKebabToSnakeCase(currentValue);
45+
return transformKeyNames(currentValue);
4646
});
4747
} else if (typeof _obj === 'object') {
4848
Object.keys(_obj).forEach(key => {
4949
// Replace the key name with the snake_case
50-
const kebabKey = key.replace(/-/g, '_').toLocaleLowerCase();
51-
const kebabValue = transformKeyNamesFromKebabToSnakeCase(_obj[key]);
50+
// Then strip cld prefix if it exists (with an optional dash or underscore)
51+
const kebabKey = key.replace(/-/g, '_').toLocaleLowerCase().replace(/^cld(-|_)?/, '');
52+
const kebabValue = transformKeyNames(_obj[key]);
5253
delete _obj[key];
5354
_obj[kebabKey] = kebabValue;
5455
});
@@ -104,7 +105,7 @@ export class Cloudinary {
104105
*/
105106
toCloudinaryAttributes(attributes: NamedNodeMap,
106107
childTransformations?: QueryList<CloudinaryTransformationDirective>): any {
107-
const options = transformKeyNamesFromKebabToSnakeCase(attributes);
108+
const options = transformKeyNames(attributes);
108109

109110
// Add chained transformations
110111
if (childTransformations && childTransformations.length > 0) {
@@ -126,8 +127,8 @@ export class Cloudinary {
126127

127128
/* Return a provider object that creates our configurable service */
128129
export function provideCloudinary(cloudinaryJsLib: any, configuration: CloudinaryConfiguration) {
129-
return { provide: Cloudinary, useFactory: () => new Cloudinary(cloudinaryJsLib, configuration) };
130+
return { provide: Cloudinary, useFactory: () => new Cloudinary(cloudinaryJsLib, configuration) };
130131
};
131132

132133
// For unit tests
133-
export { isJsonLikeString, isNamedNodeMap, transformKeyNamesFromKebabToSnakeCase, namedNodeMapToObject };
134+
export { isJsonLikeString, isNamedNodeMap, transformKeyNames, namedNodeMapToObject };

0 commit comments

Comments
 (0)