diff --git a/src/cloudinary-image.component.spec.ts b/src/cloudinary-image.component.spec.ts index f7780d49..5a8dcbbe 100644 --- a/src/cloudinary-image.component.spec.ts +++ b/src/cloudinary-image.component.spec.ts @@ -33,6 +33,7 @@ describe('CloudinaryImage', () => { }).createComponent(TestComponent); fixture.detectChanges(); // initial binding + expect(localCloudinary.responsive).toHaveBeenCalled(); // all elements with an attached CloudinaryImage des = fixture.debugElement.query(By.directive(CloudinaryImage)); @@ -218,5 +219,41 @@ describe('CloudinaryImage', () => { expect(img.src).toEqual(jasmine.stringMatching(/image\/upload\/updatedId/)); }); }); -}); + describe('responsive images with nested transformations using the cld-responsive attribute', () => { + @Component({ + template: ` + + + + ` + }) + class TestComponent { } + + let fixture: ComponentFixture; + let des: DebugElement; // the elements w/ the directive + + beforeEach(() => { + fixture = TestBed.configureTestingModule({ + declarations: [CloudinaryTransformationDirective, CloudinaryImage, TestComponent], + providers: [{ provide: Cloudinary, useValue: localCloudinary }] + }).createComponent(TestComponent); + + fixture.detectChanges(); // initial binding + expect(localCloudinary.responsive).toHaveBeenCalled(); + + // all elements with an attached CloudinaryImage + des = fixture.debugElement.query(By.directive(CloudinaryImage)); + }); + + it('creates an img element which encodes the directive attributes to the URL', () => { + const img = des.children[0].nativeElement as HTMLImageElement; + expect(img.src).toEqual(jasmine.stringMatching + (/c_scale,l_text:roboto_25_bold:SDK,w_300\/e_art:hokusai\/f_auto\/responsive_sample.jpg/)); + console.log('img', img); + expect(img.attributes.getNamedItem('data-src').value).toEqual(jasmine.stringMatching( + /c_scale,l_text:roboto_25_bold:SDK,w_300\/e_art:hokusai\/f_auto\/responsive_sample.jpg/)); + }); + }); + +}); diff --git a/src/cloudinary-image.component.ts b/src/cloudinary-image.component.ts index 4b230cb8..997c4bc4 100644 --- a/src/cloudinary-image.component.ts +++ b/src/cloudinary-image.component.ts @@ -64,7 +64,6 @@ export class CloudinaryImage implements AfterViewInit, OnInit, OnChanges, OnDest const nativeElement = this.el.nativeElement; const image = nativeElement.children[0]; const options = this.cloudinary.toCloudinaryAttributes(nativeElement.attributes, this.transformations); - const imageTag = this.cloudinary.imageTag(this.publicId, options); this.setElementAttributes(image, imageTag.attributes()); if (options.responsive) { diff --git a/src/cloudinary.service.spec.ts b/src/cloudinary.service.spec.ts index cd49b568..67414b67 100644 --- a/src/cloudinary.service.spec.ts +++ b/src/cloudinary.service.spec.ts @@ -5,7 +5,7 @@ import { Cloudinary, isJsonLikeString, isNamedNodeMap, - transformKeyNamesFromKebabToSnakeCase + transformKeyNames } from './cloudinary.service'; import CloudinaryConfiguration from './cloudinary-configuration.class'; @@ -70,9 +70,9 @@ describe('Cloudinary service', () => { }); }); - describe('transformKeyNamesFromKebabToSnakeCase', () => { + describe('transformKeyNames', () => { it('Transforms property names of json-like strings from kebab-case to snake_case', () => { - expect(transformKeyNamesFromKebabToSnakeCase('{"aaa-aaa":"1", "bbb-bbb":"2", "cc": "ccc-ccc"}')).toEqual( + expect(transformKeyNames('{"aaa-aaa":"1", "bbb-bbb":"2", "cc": "ccc-ccc"}')).toEqual( { aaa_aaa: '1', bbb_bbb: '2', @@ -81,7 +81,7 @@ describe('Cloudinary service', () => { ); }); it('Transforms property names of json-like strings spanning multi-lines from kebab-case to snake_case', () => { - expect(transformKeyNamesFromKebabToSnakeCase(`{"aaa-aaa":"1", + expect(transformKeyNames(`{"aaa-aaa":"1", "bbb-bbb":"2", "cc": "ccc-ccc"}`)).toEqual( { @@ -92,7 +92,7 @@ describe('Cloudinary service', () => { ); }); it('Transforms property names of objects from kebab-case to snake_case', () => { - expect(transformKeyNamesFromKebabToSnakeCase({'aaa-aaa': 1, 'bbb-bbb': 2, cc: 'ccc-ccc'})).toEqual( + expect(transformKeyNames({'aaa-aaa': 1, 'bbb-bbb': 2, cc: 'ccc-ccc'})).toEqual( { aaa_aaa: 1, bbb_bbb: 2, @@ -100,14 +100,24 @@ describe('Cloudinary service', () => { } ); }); + it('Transforms property names by stripping cld prefix', () => { + // "cld" prefix can be followed by an optional dash or underscore + expect(transformKeyNames('{"cldResponsive":"1", "cld-width":"2", "cld_height": "ccc-ccc"}')).toEqual( + { + responsive: '1', + width: '2', + height: 'ccc-ccc' + } + ); + }); it('does not affect primitive values', () => { - expect(transformKeyNamesFromKebabToSnakeCase(123)).toEqual(123); - expect(transformKeyNamesFromKebabToSnakeCase(undefined)).toBeUndefined(); - expect(transformKeyNamesFromKebabToSnakeCase('')).toEqual(''); - expect(transformKeyNamesFromKebabToSnakeCase('a b c')).toEqual('a b c'); + expect(transformKeyNames(123)).toEqual(123); + expect(transformKeyNames(undefined)).toBeUndefined(); + expect(transformKeyNames('')).toEqual(''); + expect(transformKeyNames('a b c')).toEqual('a b c'); }); it('iterates over array elements to transform its members', () => { - expect(transformKeyNamesFromKebabToSnakeCase([{ + expect(transformKeyNames([{ 'aaa-aaa': 'aaa-aaa', 'bbb-bbb': 'bbb-bbb', 'ccc': 'ccc' @@ -125,7 +135,7 @@ describe('Cloudinary service', () => { ]); }); it('transforms complex json-like objects into options', () => { - expect(transformKeyNamesFromKebabToSnakeCase(`{"aaa-aaa":"1", + expect(transformKeyNames(`{"aaa-aaa":"1", "bbb-bbb":"2", "transform-ation": [{ "effect": "sepia", "fetch_format": "auto"}] }`)).toEqual( diff --git a/src/cloudinary.service.ts b/src/cloudinary.service.ts index 73692179..186bb35b 100644 --- a/src/cloudinary.service.ts +++ b/src/cloudinary.service.ts @@ -29,7 +29,7 @@ const namedNodeMapToObject = function (source: NamedNodeMap): any { return target; }; -const transformKeyNamesFromKebabToSnakeCase = function (obj: any): any { +const transformKeyNames = function (obj: any): any { let _obj = obj; if (isJsonLikeString(obj)) { // Given attribute value is in the form of a JSON object - @@ -42,13 +42,14 @@ const transformKeyNamesFromKebabToSnakeCase = function (obj: any): any { if (Array.isArray(_obj)) { // Transform all the array values (e.g. transformation array) _obj = _obj.map(currentValue => { - return transformKeyNamesFromKebabToSnakeCase(currentValue); + return transformKeyNames(currentValue); }); } else if (typeof _obj === 'object') { Object.keys(_obj).forEach(key => { // Replace the key name with the snake_case - const kebabKey = key.replace(/-/g, '_').toLocaleLowerCase(); - const kebabValue = transformKeyNamesFromKebabToSnakeCase(_obj[key]); + // Then strip cld prefix if it exists (with an optional dash or underscore) + const kebabKey = key.replace(/-/g, '_').toLocaleLowerCase().replace(/^cld(-|_)?/, ''); + const kebabValue = transformKeyNames(_obj[key]); delete _obj[key]; _obj[kebabKey] = kebabValue; }); @@ -104,7 +105,7 @@ export class Cloudinary { */ toCloudinaryAttributes(attributes: NamedNodeMap, childTransformations?: QueryList): any { - const options = transformKeyNamesFromKebabToSnakeCase(attributes); + const options = transformKeyNames(attributes); // Add chained transformations if (childTransformations && childTransformations.length > 0) { @@ -126,8 +127,8 @@ export class Cloudinary { /* Return a provider object that creates our configurable service */ export function provideCloudinary(cloudinaryJsLib: any, configuration: CloudinaryConfiguration) { - return { provide: Cloudinary, useFactory: () => new Cloudinary(cloudinaryJsLib, configuration) }; + return { provide: Cloudinary, useFactory: () => new Cloudinary(cloudinaryJsLib, configuration) }; }; // For unit tests -export { isJsonLikeString, isNamedNodeMap, transformKeyNamesFromKebabToSnakeCase, namedNodeMapToObject }; +export { isJsonLikeString, isNamedNodeMap, transformKeyNames, namedNodeMapToObject };