@designcrowd/library.brand-page 6.2.5 → 6.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-CkJM0rEy.js → CreatedOnBrandCrowd-de-de-DpeSEnwP.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-CKOotMQK.js → CreatedOnBrandCrowd-en-us-CxYv3NzT.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-mrDI8AaY.js → CreatedOnBrandCrowd-es-es-CzQ1HRHw.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-DtmVXezM.js → CreatedOnBrandCrowd-fr-ca-J77YZaT1.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-DtmVXezM.js → CreatedOnBrandCrowd-fr-fr-J77YZaT1.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-C8Cy74df.js → CreatedOnBrandCrowd-pt-br-1vh4Smqt.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-C8Cy74df.js → CreatedOnBrandCrowd-pt-pt-1vh4Smqt.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-FVl5OqQM.js → CreatedOnDcom-de-de-CXX6W_29.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-e0s7gZGG.js → CreatedOnDcom-en-us-D78BmX6f.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-BXMekiTg.js → CreatedOnDcom-es-es-FfbPM2vD.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-74Vm0wzS.js → CreatedOnDcom-fr-ca-C6L60rxJ.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-C6TT73Rw.js → CreatedOnDcom-fr-fr-BEDnW9w-.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-Drg2eaLk.js → CreatedOnDcom-pt-br-CZXVvc1n.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-BgsEIMfv.js → CreatedOnDcom-pt-pt-IQq744nb.js} +1 -1
  15. package/lib/src/brand-page/utils/logo.util.js +2 -2
  16. package/lib/src/brand-page/utils/migration/migration-07.js +3 -2
  17. package/lib/src/brand-page/utils/migration/migration-08.js +3 -2
  18. package/lib/src/clients/fabric-render-client.d.ts +20 -0
  19. package/lib/src/clients/fabric-render-client.js +43 -0
  20. package/lib/src/index.mjs +1 -1
  21. package/lib/src/{lib--ZDYWLFf.js → lib-CyVd3FMn.js} +3399 -3361
  22. package/lib/src/shared/content-settings/components/GenericTextInput.mixin.d.ts +1 -1
  23. package/lib/src/shared/content-settings/components/email-field/EmailField.mixin.d.ts +1 -1
  24. package/lib/src/shared/content-settings/components/tel-field/TelField.mixin.d.ts +1 -1
  25. package/lib/src/shared/content-settings/components/text-field/TextField.mixin.d.ts +1 -1
  26. package/lib/src/shared/content-settings/components/textarea-field/TextareaField.mixin.d.ts +1 -1
  27. package/lib/src/shared/content-settings/components/url-field/UrlField.mixin.d.ts +1 -1
  28. package/lib/src/shared/content-settings/components/youtube-video-field/YoutubeVideoField.mixin.d.ts +1 -1
  29. package/package.json +1 -1
  30. package/src/brand-page/utils/logo.util.recalculate.test.ts +204 -0
  31. package/src/brand-page/utils/logo.util.ts +2 -2
  32. package/src/brand-page/utils/migration/migration-07.dimension.test.ts +274 -0
  33. package/src/brand-page/utils/migration/migration-07.ts +3 -2
  34. package/src/brand-page/utils/migration/migration-08.test.ts +181 -22
  35. package/src/brand-page/utils/migration/migration-08.ts +3 -2
  36. package/src/clients/fabric-render-client.test.ts +198 -0
  37. package/src/clients/fabric-render-client.ts +85 -0
@@ -8,8 +8,102 @@ import { FabricRenderClient } from '../../../clients/fabric-render-client';
8
8
  const mockFabricClientInstance = {
9
9
  getLogoSvgUrlByLogoImageAsync: vi.fn(),
10
10
  getLogoSvgUrlByLogoTemplateImageAsync: vi.fn(),
11
+ getLogoDimensionsByLogoImageAsync: vi.fn(),
12
+ getLogoDimensionsByLogoTemplateImageAsync: vi.fn(),
11
13
  };
12
14
 
15
+ const expectNoDrawingRequest = () => {
16
+ expect(
17
+ mockFabricClientInstance.getLogoSvgUrlByLogoImageAsync,
18
+ ).not.toHaveBeenCalled();
19
+ expect(
20
+ mockFabricClientInstance.getLogoSvgUrlByLogoTemplateImageAsync,
21
+ ).not.toHaveBeenCalled();
22
+ };
23
+
24
+ type Size = { width?: number; height?: number } | undefined;
25
+
26
+ // The drawing and size-only lookups answer alike, so these pins hold before and after the switch (R3).
27
+ const answerBoth = (
28
+ template: (token: string) => Size,
29
+ draft: (token: string) => Size,
30
+ ) => {
31
+ const withData = (size: Size) =>
32
+ size && { data: 'data:image/svg+xml;base64,', ...size };
33
+
34
+ mockFabricClientInstance.getLogoDimensionsByLogoTemplateImageAsync.mockImplementation(
35
+ async ({ logoTemplateToken }) => template(logoTemplateToken),
36
+ );
37
+ mockFabricClientInstance.getLogoDimensionsByLogoImageAsync.mockImplementation(
38
+ async ({ logoDraftToken }) => draft(logoDraftToken),
39
+ );
40
+ mockFabricClientInstance.getLogoSvgUrlByLogoTemplateImageAsync.mockImplementation(
41
+ async ({ logoTemplateToken }) => withData(template(logoTemplateToken)),
42
+ );
43
+ mockFabricClientInstance.getLogoSvgUrlByLogoImageAsync.mockImplementation(
44
+ async ({ logoDraftToken }) => withData(draft(logoDraftToken)),
45
+ );
46
+ };
47
+
48
+ const sizeFor = (token: string): Size =>
49
+ token.endsWith('-wide')
50
+ ? { width: 300, height: 100 }
51
+ : { width: 100, height: 300 };
52
+
53
+ // Two pages of logo contents: a template on the first, a draft on the second.
54
+ const createTwoPageData = (brandPageType: string) =>
55
+ createTestBrandPageData({
56
+ brandPageType,
57
+ pages: [
58
+ {
59
+ title: 'Home',
60
+ slug: 'home',
61
+ contents: [
62
+ {
63
+ id: '1',
64
+ type: 'logo',
65
+ logoTemplateImage: {
66
+ logoTemplateToken: 'template-wide',
67
+ logoTemplateText: 'Acme',
68
+ logoTemplateVersion: 3,
69
+ },
70
+ logoImageWidth: '12rem',
71
+ logoImageHeight: '9rem',
72
+ isLogoVisible: true,
73
+ backgroundStyle: {},
74
+ },
75
+ ],
76
+ },
77
+ {
78
+ title: 'About',
79
+ slug: 'about',
80
+ contents: [
81
+ {
82
+ id: '2',
83
+ type: 'logo',
84
+ logoImage: { logoDraftToken: 'draft-tall', logoVariation: 1 },
85
+ logoImageWidth: '9rem',
86
+ logoImageHeight: '10rem',
87
+ isLogoVisible: true,
88
+ backgroundStyle: {},
89
+ },
90
+ ],
91
+ },
92
+ ],
93
+ });
94
+
95
+ const logoSizes = (result: MigrationData) =>
96
+ result.brandPageModel.pages.map((page) => {
97
+ const logo = page.contents[0] as LogoContent;
98
+
99
+ return [logo.logoImageWidth, logo.logoImageHeight];
100
+ });
101
+
102
+ const ORIGINAL_SIZES = [
103
+ ['12rem', '9rem'],
104
+ ['9rem', '10rem'],
105
+ ];
106
+
13
107
  // Mock FabricRenderClient
14
108
  vi.mock('../../../clients/fabric-render-client', () => ({
15
109
  FabricRenderClient: {
@@ -101,11 +195,12 @@ describe('migration08', () => {
101
195
  const migrationData = createMigrationData(brandPageData);
102
196
 
103
197
  // Mock the fabric client response
104
- mockFabricClientInstance.getLogoSvgUrlByLogoImageAsync.mockResolvedValue({
105
- width: 120,
106
- height: 80,
107
- data: 'test-svg-data',
108
- });
198
+ mockFabricClientInstance.getLogoDimensionsByLogoImageAsync.mockResolvedValue(
199
+ {
200
+ width: 120,
201
+ height: 80,
202
+ },
203
+ );
109
204
 
110
205
  const result = await migration08(migrationData, {
111
206
  isServerRender: false,
@@ -114,11 +209,12 @@ describe('migration08', () => {
114
209
 
115
210
  expect(result.useOldLogoRenderSize).toBe(false);
116
211
  expect(
117
- mockFabricClientInstance.getLogoSvgUrlByLogoImageAsync,
212
+ mockFabricClientInstance.getLogoDimensionsByLogoImageAsync,
118
213
  ).toHaveBeenCalledWith({
119
214
  logoDraftToken: 'test-token',
120
215
  logoVariation: 0,
121
216
  });
217
+ expectNoDrawingRequest();
122
218
  });
123
219
 
124
220
  test('should skip brandSite type (brandSite does not have logo content)', async () => {
@@ -170,11 +266,12 @@ describe('migration08', () => {
170
266
 
171
267
  // Fabric client should not have been called
172
268
  expect(
173
- mockFabricClientInstance.getLogoSvgUrlByLogoImageAsync,
269
+ mockFabricClientInstance.getLogoDimensionsByLogoImageAsync,
174
270
  ).not.toHaveBeenCalled();
175
271
  expect(
176
- mockFabricClientInstance.getLogoSvgUrlByLogoTemplateImageAsync,
272
+ mockFabricClientInstance.getLogoDimensionsByLogoTemplateImageAsync,
177
273
  ).not.toHaveBeenCalled();
274
+ expectNoDrawingRequest();
178
275
  });
179
276
 
180
277
  test('should handle brandContact type with logo content', async () => {
@@ -206,6 +303,13 @@ describe('migration08', () => {
206
303
  });
207
304
 
208
305
  expect(result.useOldLogoRenderSize).toBe(false);
306
+ expect(
307
+ mockFabricClientInstance.getLogoDimensionsByLogoImageAsync,
308
+ ).toHaveBeenCalledWith({
309
+ logoDraftToken: 'test-token',
310
+ logoVariation: 0,
311
+ });
312
+ expectNoDrawingRequest();
209
313
  });
210
314
 
211
315
  test('should handle multiple pages with logo content', async () => {
@@ -249,13 +353,12 @@ describe('migration08', () => {
249
353
  // Mock the fabric client response
250
354
  const mockFabricClient = FabricRenderClient.getInstance();
251
355
 
252
- vi.mocked(mockFabricClient.getLogoSvgUrlByLogoImageAsync).mockResolvedValue(
253
- {
254
- width: 120,
255
- height: 80,
256
- data: 'test-svg-data',
257
- },
258
- );
356
+ vi.mocked(
357
+ mockFabricClient.getLogoDimensionsByLogoImageAsync,
358
+ ).mockResolvedValue({
359
+ width: 120,
360
+ height: 80,
361
+ });
259
362
 
260
363
  const result = await migration08(migrationData, {
261
364
  isServerRender: false,
@@ -264,8 +367,9 @@ describe('migration08', () => {
264
367
 
265
368
  expect(result.useOldLogoRenderSize).toBe(false);
266
369
  expect(
267
- mockFabricClient.getLogoSvgUrlByLogoImageAsync,
370
+ mockFabricClient.getLogoDimensionsByLogoImageAsync,
268
371
  ).toHaveBeenCalledTimes(2);
372
+ expectNoDrawingRequest();
269
373
  });
270
374
 
271
375
  test('should handle logo content without logo tokens', async () => {
@@ -305,6 +409,13 @@ describe('migration08', () => {
305
409
  (result.brandPageModel.pages[0].contents[0] as LogoContent)
306
410
  .logoImageWidth,
307
411
  ).toBe('15rem');
412
+ expect(
413
+ mockFabricClientInstance.getLogoDimensionsByLogoImageAsync,
414
+ ).not.toHaveBeenCalled();
415
+ expect(
416
+ mockFabricClientInstance.getLogoDimensionsByLogoTemplateImageAsync,
417
+ ).not.toHaveBeenCalled();
418
+ expectNoDrawingRequest();
308
419
  });
309
420
 
310
421
  test('should handle logo template images', async () => {
@@ -334,11 +445,10 @@ describe('migration08', () => {
334
445
  const mockFabricClient = FabricRenderClient.getInstance();
335
446
 
336
447
  vi.mocked(
337
- mockFabricClient.getLogoSvgUrlByLogoTemplateImageAsync,
448
+ mockFabricClient.getLogoDimensionsByLogoTemplateImageAsync,
338
449
  ).mockResolvedValue({
339
450
  width: 200,
340
451
  height: 100,
341
- data: 'test-template-svg-data',
342
452
  });
343
453
 
344
454
  const _result = await migration08(migrationData, {
@@ -347,10 +457,11 @@ describe('migration08', () => {
347
457
  });
348
458
 
349
459
  expect(
350
- mockFabricClient.getLogoSvgUrlByLogoTemplateImageAsync,
460
+ mockFabricClient.getLogoDimensionsByLogoTemplateImageAsync,
351
461
  ).toHaveBeenCalledWith({
352
462
  logoTemplateToken: 'template-token',
353
463
  });
464
+ expectNoDrawingRequest();
354
465
  });
355
466
 
356
467
  test('should handle error gracefully', async () => {
@@ -379,9 +490,9 @@ describe('migration08', () => {
379
490
  // Mock the fabric client to throw an error
380
491
  const mockFabricClient = FabricRenderClient.getInstance();
381
492
 
382
- vi.mocked(mockFabricClient.getLogoSvgUrlByLogoImageAsync).mockRejectedValue(
383
- new Error('API Error'),
384
- );
493
+ vi.mocked(
494
+ mockFabricClient.getLogoDimensionsByLogoImageAsync,
495
+ ).mockRejectedValue(new Error('API Error'));
385
496
 
386
497
  const result = await migration08(migrationData, {
387
498
  isServerRender: false,
@@ -423,4 +534,52 @@ describe('migration08', () => {
423
534
  expect(result.useOldLogoRenderSize).toBe(false);
424
535
  expect(result.brandPageModel.pages[0].contents.length).toBe(2);
425
536
  });
537
+
538
+ test.each([BRAND_PAGE_TYPES.default, BRAND_PAGE_TYPES.brandContact])(
539
+ 'same sizes: pins each %s logo content from the reply',
540
+ async (brandPageType) => {
541
+ answerBoth(sizeFor, sizeFor);
542
+
543
+ const result = await migration08(
544
+ createMigrationData(createTwoPageData(brandPageType)),
545
+ { isServerRender: false, isUserDraft: false },
546
+ );
547
+
548
+ expect(result.useOldLogoRenderSize).toBe(false);
549
+ expect(logoSizes(result)).toEqual([
550
+ ['4rem', '4rem'],
551
+ ['10rem', '10rem'],
552
+ ]);
553
+ },
554
+ );
555
+
556
+ test('not OK: an empty reply leaves every size unchanged', async () => {
557
+ answerBoth(
558
+ () => undefined,
559
+ () => undefined,
560
+ );
561
+
562
+ const result = await migration08(
563
+ createMigrationData(createTwoPageData(BRAND_PAGE_TYPES.default)),
564
+ { isServerRender: false, isUserDraft: false },
565
+ );
566
+
567
+ expect(result.useOldLogoRenderSize).toBe(false);
568
+ expect(logoSizes(result)).toEqual(ORIGINAL_SIZES);
569
+ });
570
+
571
+ test('missing size: a zero or absent dimension leaves every size unchanged', async () => {
572
+ answerBoth(
573
+ () => ({ width: 0, height: 100 }),
574
+ () => ({ width: 120 }),
575
+ );
576
+
577
+ const result = await migration08(
578
+ createMigrationData(createTwoPageData(BRAND_PAGE_TYPES.default)),
579
+ { isServerRender: false, isUserDraft: false },
580
+ );
581
+
582
+ expect(result.useOldLogoRenderSize).toBe(false);
583
+ expect(logoSizes(result)).toEqual(ORIGINAL_SIZES);
584
+ });
426
585
  });
@@ -45,10 +45,11 @@ export const migration08 = async (
45
45
  });
46
46
  });
47
47
 
48
+ // Size-only lookups, by recorded exception: 0031-released-logo-size-migrations-may-use-a-size-only-address.md
48
49
  const logoDataPromises = logoContents.map(async ({ content }) => {
49
50
  if (content.logoTemplateImage?.logoTemplateToken) {
50
51
  const logoData =
51
- await fabricClient.getLogoSvgUrlByLogoTemplateImageAsync(
52
+ await fabricClient.getLogoDimensionsByLogoTemplateImageAsync(
52
53
  content.logoTemplateImage,
53
54
  );
54
55
 
@@ -56,7 +57,7 @@ export const migration08 = async (
56
57
  }
57
58
 
58
59
  if (content.logoImage?.logoDraftToken) {
59
- const logoData = await fabricClient.getLogoSvgUrlByLogoImageAsync({
60
+ const logoData = await fabricClient.getLogoDimensionsByLogoImageAsync({
60
61
  logoDraftToken: content.logoImage.logoDraftToken,
61
62
  logoVariation: content.logoImage.logoVariation,
62
63
  });
@@ -0,0 +1,198 @@
1
+ import { describe, test, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { FabricRenderClient } from './fabric-render-client';
3
+
4
+ const BASE_URL = 'https://fabric.test';
5
+
6
+ const TEMPLATE_IMAGE = {
7
+ logoTemplateToken: 'template-token',
8
+ logoTemplateText: 'A & B',
9
+ logoTemplateVersion: 3,
10
+ layout: 'stacked',
11
+ };
12
+
13
+ const LOGO_IMAGE = {
14
+ logoDraftToken: 'draft-token',
15
+ logoVariation: 2,
16
+ logoLastUpdate: '99',
17
+ };
18
+
19
+ const jsonReply = (body: unknown, status = 200) =>
20
+ new Response(JSON.stringify(body), {
21
+ status,
22
+ headers: { 'Content-Type': 'application/json' },
23
+ });
24
+
25
+ // A size request goes to the drawing address plus `/dimensions`, with the drawing's query minus outputFormat.
26
+ const expectSizeRequestFor = (requested: unknown, drawingUrl: string) => {
27
+ const size = new URL(String(requested));
28
+ const drawing = new URL(drawingUrl);
29
+
30
+ drawing.searchParams.delete('outputFormat');
31
+
32
+ expect(`${size.origin}${size.pathname}`).toBe(
33
+ `${drawing.origin}${drawing.pathname}/dimensions`,
34
+ );
35
+ expect([...size.searchParams]).toEqual([...drawing.searchParams]);
36
+ };
37
+
38
+ const createClient = (): FabricRenderClient => {
39
+ FabricRenderClient.overrideInstance(undefined);
40
+ FabricRenderClient.setInstance({ baseUrl: BASE_URL });
41
+
42
+ return FabricRenderClient.getInstance();
43
+ };
44
+
45
+ describe('FabricRenderClient logo lookups', () => {
46
+ let client: FabricRenderClient;
47
+ let fetchMock: ReturnType<typeof vi.fn>;
48
+
49
+ beforeEach(() => {
50
+ client = createClient();
51
+ fetchMock = vi.fn();
52
+ vi.stubGlobal('fetch', fetchMock);
53
+ });
54
+
55
+ afterEach(() => {
56
+ vi.unstubAllGlobals();
57
+ FabricRenderClient.overrideInstance(undefined);
58
+ });
59
+
60
+ describe('drawing requests unchanged', () => {
61
+ test('the template drawing still fetches base64svgjson and returns data, width and height', async () => {
62
+ fetchMock.mockResolvedValue(
63
+ jsonReply({ data: 'data:svg', width: 300, height: 100 }),
64
+ );
65
+
66
+ const result =
67
+ await client.getLogoSvgUrlByLogoTemplateImageAsync(TEMPLATE_IMAGE);
68
+
69
+ expect(fetchMock).toHaveBeenCalledTimes(1);
70
+ expect(fetchMock).toHaveBeenCalledWith(
71
+ `${BASE_URL}/asset/logotemplate/brand-page/template-token?dimensionsType=preview_image_transparent_no_buffer&outputFormat=base64svgjson&logoTemplateVersion=3&text=A%20%26%20B&layout=stacked`,
72
+ );
73
+ expect(result).toEqual({ data: 'data:svg', width: 300, height: 100 });
74
+ });
75
+
76
+ test('the draft drawing still fetches base64svgjson and returns data, width and height', async () => {
77
+ fetchMock.mockResolvedValue(
78
+ jsonReply({ data: 'data:svg', width: 100, height: 300 }),
79
+ );
80
+
81
+ const result = await client.getLogoSvgUrlByLogoImageAsync(LOGO_IMAGE);
82
+
83
+ expect(fetchMock).toHaveBeenCalledTimes(1);
84
+ expect(fetchMock).toHaveBeenCalledWith(
85
+ `${BASE_URL}/asset/logodraft/brand-page/draft-token?v=99&outputFormat=base64svgjson&dimensionsType=preview_image_transparent_no_buffer&variation=icontransparentbackgroundonly`,
86
+ );
87
+ expect(result).toEqual({ data: 'data:svg', width: 100, height: 300 });
88
+ });
89
+ });
90
+
91
+ test('template size request: asks the /dimensions companion and returns only width and height', async () => {
92
+ fetchMock.mockResolvedValue(jsonReply({ width: 300, height: 100 }));
93
+
94
+ const result =
95
+ await client.getLogoDimensionsByLogoTemplateImageAsync(TEMPLATE_IMAGE);
96
+
97
+ expect(fetchMock).toHaveBeenCalledTimes(1);
98
+ expect(String(fetchMock.mock.calls[0][0])).toMatch(
99
+ `${BASE_URL}/asset/logotemplate/brand-page/template-token/dimensions?`,
100
+ );
101
+ expectSizeRequestFor(
102
+ fetchMock.mock.calls[0][0],
103
+ client.getLogoTemplateUrlByLogoTemplateImage(
104
+ TEMPLATE_IMAGE,
105
+ 'base64svgjson',
106
+ ),
107
+ );
108
+ expect(result).toStrictEqual({ width: 300, height: 100 });
109
+ });
110
+
111
+ test('draft size request: asks the /dimensions companion and returns only width and height', async () => {
112
+ fetchMock.mockResolvedValue(jsonReply({ width: 100, height: 300 }));
113
+
114
+ const result = await client.getLogoDimensionsByLogoImageAsync(LOGO_IMAGE);
115
+
116
+ expect(fetchMock).toHaveBeenCalledTimes(1);
117
+ expect(String(fetchMock.mock.calls[0][0])).toMatch(
118
+ `${BASE_URL}/asset/logodraft/brand-page/draft-token/dimensions?`,
119
+ );
120
+ expectSizeRequestFor(
121
+ fetchMock.mock.calls[0][0],
122
+ client.getLogoVariationUrlByLogoImage(LOGO_IMAGE, 'base64svgjson') ?? '',
123
+ );
124
+ expect(result).toStrictEqual({ width: 100, height: 300 });
125
+ });
126
+
127
+ test('not deployed: a 404 from the companion answers undefined after one request, never the drawing', async () => {
128
+ fetchMock.mockImplementation(async () => jsonReply({}, 404));
129
+
130
+ await expect(
131
+ client.getLogoDimensionsByLogoTemplateImageAsync(TEMPLATE_IMAGE),
132
+ ).resolves.toBeUndefined();
133
+ expect(fetchMock).toHaveBeenCalledTimes(1);
134
+
135
+ await expect(
136
+ client.getLogoDimensionsByLogoImageAsync(LOGO_IMAGE),
137
+ ).resolves.toBeUndefined();
138
+ expect(fetchMock).toHaveBeenCalledTimes(2);
139
+ expect(
140
+ fetchMock.mock.calls.every(
141
+ ([url]) =>
142
+ new URL(String(url)).pathname.endsWith('/dimensions') &&
143
+ !String(url).includes('outputFormat'),
144
+ ),
145
+ ).toBe(true);
146
+ });
147
+
148
+ describe('no logo reference', () => {
149
+ test('an empty draft token answers undefined without a request', async () => {
150
+ await expect(
151
+ client.getLogoDimensionsByLogoImageAsync({
152
+ logoDraftToken: '',
153
+ logoVariation: 0,
154
+ }),
155
+ ).resolves.toBeUndefined();
156
+ expect(fetchMock).not.toHaveBeenCalled();
157
+ });
158
+
159
+ test('an undefined logo image throws a TypeError, as its drawing twin does', async () => {
160
+ const missing = undefined as unknown as typeof LOGO_IMAGE;
161
+
162
+ await expect(
163
+ client.getLogoDimensionsByLogoImageAsync(missing),
164
+ ).rejects.toBeInstanceOf(TypeError);
165
+ await expect(
166
+ client.getLogoSvgUrlByLogoImageAsync(missing),
167
+ ).rejects.toBeInstanceOf(TypeError);
168
+ expect(fetchMock).not.toHaveBeenCalled();
169
+ });
170
+ });
171
+
172
+ test('unreadable reply: a 200 PNG rejects after one request, with no fallback to the drawing', async () => {
173
+ fetchMock.mockImplementation(
174
+ async () =>
175
+ new Response(new Uint8Array([0x89, 0x50, 0x4e, 0x47]), {
176
+ status: 200,
177
+ headers: { 'Content-Type': 'image/png' },
178
+ }),
179
+ );
180
+
181
+ await expect(
182
+ client.getLogoDimensionsByLogoTemplateImageAsync(TEMPLATE_IMAGE),
183
+ ).rejects.toThrow();
184
+ expect(fetchMock).toHaveBeenCalledTimes(1);
185
+
186
+ await expect(
187
+ client.getLogoDimensionsByLogoImageAsync(LOGO_IMAGE),
188
+ ).rejects.toThrow();
189
+ expect(fetchMock).toHaveBeenCalledTimes(2);
190
+ expect(
191
+ fetchMock.mock.calls.every(
192
+ ([url]) =>
193
+ new URL(String(url)).pathname.endsWith('/dimensions') &&
194
+ !String(url).includes('outputFormat'),
195
+ ),
196
+ ).toBe(true);
197
+ });
198
+ });
@@ -317,4 +317,89 @@ export class FabricRenderClient {
317
317
 
318
318
  return { data, width, height };
319
319
  }
320
+
321
+ // Released migrations 07/08 call the two size methods: 0031-released-logo-size-migrations-may-use-a-size-only-address.md
322
+ async getLogoDimensionsByLogoTemplateImageAsync(logoTemplateImage: {
323
+ logoTemplateToken: string;
324
+ logoTemplateText: string;
325
+ logoTemplateVersion: number;
326
+ layout?: string;
327
+ }): Promise<{ width: number; height: number } | undefined> {
328
+ return this.fetchLogoDimensionsAsync(
329
+ this.getLogoTemplateDimensionsUrl(logoTemplateImage),
330
+ );
331
+ }
332
+
333
+ async getLogoDimensionsByLogoImageAsync(logoImage: {
334
+ logoDraftToken: string;
335
+ logoVariation: number;
336
+ logoLastUpdate?: string;
337
+ }): Promise<{ width: number; height: number } | undefined> {
338
+ return this.fetchLogoDimensionsAsync(
339
+ this.getLogoDraftDimensionsUrl(logoImage),
340
+ );
341
+ }
342
+
343
+ // Each brand-page address has a size-only companion at `/dimensions`, which reads only what these two builders send.
344
+ // extract if this grows: this repeats getLogoTemplateUrlByLogoTemplateImage's query.
345
+ private getLogoTemplateDimensionsUrl(logoTemplateImage: {
346
+ logoTemplateToken: string;
347
+ logoTemplateText: string;
348
+ logoTemplateVersion: number;
349
+ layout?: string;
350
+ }): string {
351
+ const url = `${this.baseUrl}/asset/logotemplate/brand-page/${logoTemplateImage.logoTemplateToken}/dimensions?dimensionsType=preview_image_transparent_no_buffer&logoTemplateVersion=${logoTemplateImage.logoTemplateVersion}&text=${encodeURIComponent(logoTemplateImage.logoTemplateText)}`;
352
+
353
+ return logoTemplateImage.layout
354
+ ? `${url}&layout=${logoTemplateImage.layout}`
355
+ : url;
356
+ }
357
+
358
+ private getLogoDraftDimensionsUrl(logoImage: {
359
+ logoDraftToken: string;
360
+ logoVariation: number;
361
+ logoLastUpdate?: string;
362
+ }): string | undefined {
363
+ if (!logoImage.logoDraftToken) {
364
+ console.warn('getLogoDraftDimensionsUrl: invalid logo draft token');
365
+
366
+ return undefined;
367
+ }
368
+
369
+ const definition =
370
+ LogoVariationDefinitions[
371
+ typeof logoImage.logoVariation === 'number'
372
+ ? logoImage.logoVariation
373
+ : 0
374
+ ];
375
+
376
+ if (!definition) {
377
+ return undefined;
378
+ }
379
+
380
+ const variationQuery = Object.entries(definition)
381
+ .map(([key, value]) => `${key}=${value}`)
382
+ .join('&');
383
+
384
+ return `${this.baseUrl}/asset/logodraft/brand-page/${logoImage.logoDraftToken}/dimensions?v=${logoImage.logoLastUpdate || ''}&${variationQuery}`;
385
+ }
386
+
387
+ // extract if this grows: the four logo JSON fetches repeat this shape.
388
+ private async fetchLogoDimensionsAsync(
389
+ url: string | undefined,
390
+ ): Promise<{ width: number; height: number } | undefined> {
391
+ if (!url) {
392
+ return undefined;
393
+ }
394
+
395
+ const response = await fetch(url);
396
+
397
+ if (response.status !== 200) {
398
+ return undefined;
399
+ }
400
+
401
+ const { width, height } = await response.json();
402
+
403
+ return { width, height };
404
+ }
320
405
  }