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('
');
+ 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('
');
+ 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 })
};