diff --git a/packages/angular/projects/cloudinary-library/src/tests/cloudinary-image.component.spec.ts b/packages/angular/projects/cloudinary-library/src/tests/cloudinary-image.component.spec.ts index b4e91f38..7aefd3a3 100644 --- a/packages/angular/projects/cloudinary-library/src/tests/cloudinary-image.component.spec.ts +++ b/packages/angular/projects/cloudinary-library/src/tests/cloudinary-image.component.spec.ts @@ -52,21 +52,28 @@ describe('CloudinaryImageComponent render', () => { tick(0); const imgElement: HTMLImageElement = fixture.nativeElement; const img = imgElement.querySelector('img'); - expect(img.outerHTML).toBe('text text text'); + expect(img.getAttribute('alt')).toBe('text text text'); + expect(img.getAttribute('width')).toBe('400px'); + expect(img.getAttribute('height')).toBe('500px'); + expect(img.getAttribute('loading')).toBe('eager'); + expect(img.src).toBe('https://res.cloudinary.com/demo/image/upload/sample'); component.width = '800px'; component.alt = 'updated alt text'; component.height = '1000px'; component.loading = 'lazy'; component.ngOnChanges(); - expect(img.outerHTML).toBe('updated alt text'); + expect(img.getAttribute('alt')).toBe('updated alt text'); + expect(img.getAttribute('width')).toBe('800px'); + expect(img.getAttribute('height')).toBe('1000px'); + expect(img.getAttribute('loading')).toBe('lazy'); + expect(img.src).toBe('https://res.cloudinary.com/demo/image/upload/sample'); component.width = undefined; component.height = undefined; component.alt = ''; component.loading = 'lazy'; component.ngOnChanges(); - expect(img.outerHTML).toBe(''); + expect(img.getAttribute('alt')).toBe(''); + expect(img.getAttribute('loading')).toBe('lazy'); + expect(img.src).toBe('https://res.cloudinary.com/demo/image/upload/sample'); })); }); diff --git a/packages/html/src/types.ts b/packages/html/src/types.ts index 7500bf08..6f9ba5f9 100644 --- a/packages/html/src/types.ts +++ b/packages/html/src/types.ts @@ -24,7 +24,7 @@ export type PictureSources = {minWidth?: number, maxWidth?: number, image: Cloud export type PictureSource = {minWidth?: number, maxWidth?: number, image: CloudinaryImage, sizes?: string}; -export type BaseAnalyticsOptions = {sdkSemver: string, techVersion: string, sdkCode: string}; +export type BaseAnalyticsOptions = {sdkSemver: string, techVersion: string, sdkCode: string, product?: string}; export type AnalyticsOptions = Parameters[0]; diff --git a/packages/html/src/utils/analytics.ts b/packages/html/src/utils/analytics.ts index cc052398..ac4a4c5f 100644 --- a/packages/html/src/utils/analytics.ts +++ b/packages/html/src/utils/analytics.ts @@ -6,6 +6,7 @@ export const getAnalyticsOptions = (options?: BaseAnalyticsOptions, features: vo sdkCode: options.sdkCode, sdkSemver: options.sdkSemver, techVersion: options.techVersion, + ...(options.product !== undefined && { product: options.product }), ...features } } : null diff --git a/packages/react/__tests__/analytics.test.tsx b/packages/react/__tests__/analytics.test.tsx index 96f468d6..f0bf1d83 100644 --- a/packages/react/__tests__/analytics.test.tsx +++ b/packages/react/__tests__/analytics.test.tsx @@ -26,3 +26,40 @@ describe('analytics', () => { }, 0);// one tick }); }); + +describe('analytics when created via CLI', () => { + let AdvancedImageCLI: typeof AdvancedImage; + let CloudinaryImageCLI: typeof CloudinaryImage; + + beforeAll(() => { + process.env.CLOUDINARY_SOURCE = 'cli'; + jest.resetModules(); + const src = require('../src'); + const constants = require('../src/internal/SDKAnalyticsConstants'); + AdvancedImageCLI = src.AdvancedImage; + CloudinaryImageCLI = require('@cloudinary/url-gen/assets/CloudinaryImage').CloudinaryImage; + const SDKAnalyticsConstantsCLI = constants.SDKAnalyticsConstants; + SDKAnalyticsConstantsCLI.sdkSemver = '1.0.0'; + SDKAnalyticsConstantsCLI.techVersion = '10.2.5'; + }); + + afterAll(() => { + delete process.env.CLOUDINARY_SOURCE; + jest.resetModules(); + }); + + it('generates analytics with Product B (Integrations) and sdkCode H (React CLI)', function (done) { + const cldImg = new CloudinaryImageCLI('sample', { cloudName: 'demo' }); + const component = mount(); + setTimeout(() => { + const html = component.html(); + const match = html.match(/_a=([A-Za-z0-9]+)/); + expect(match).toBeTruthy(); + const token = match![1]; + // Algorithm B: 1st = algo, 2nd = product (B = Integrations), 3rd = sdkCode (H = React CLI) + expect(token[1]).toBe('B'); + expect(token[2]).toBe('H'); + done(); + }, 0); + }); +}); diff --git a/packages/react/src/internal/SDKAnalyticsConstants.ts b/packages/react/src/internal/SDKAnalyticsConstants.ts index 14fd72de..a3e68239 100644 --- a/packages/react/src/internal/SDKAnalyticsConstants.ts +++ b/packages/react/src/internal/SDKAnalyticsConstants.ts @@ -1,7 +1,19 @@ import React from 'react' +// Detect if this project was created via create-cloudinary-react CLI (Integrations) +function isCLI(): boolean { + if (typeof process !== 'undefined' && process.env) { + return process.env.CLOUDINARY_SOURCE === 'cli' || process.env.CLD_CLI === 'true'; + } + return false; +} + +// When CLI: use Algorithm B with Product B (Integrations) and sdkCode H (React CLI). Otherwise React SDK: sdkCode J. +const isCLIDetected = isCLI(); + export const SDKAnalyticsConstants = { sdkSemver: 'PACKAGE_VERSION_INJECTED_DURING_BUILD', techVersion: React.version, - sdkCode: 'J' + sdkCode: isCLIDetected ? 'H' : 'J', + ...(isCLIDetected && { product: 'B' as const }) };