@designcrowd/library.brand-page 5.19.1-id-based-items-1 → 5.19.1

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 (47) hide show
  1. package/lib/src/{CreatedOnBrandCrowd-de-de-0PcGqy0N.js → CreatedOnBrandCrowd-de-de-BmeMc9oz.js} +1 -1
  2. package/lib/src/{CreatedOnBrandCrowd-en-us-DB8wcFMI.js → CreatedOnBrandCrowd-en-us-CQNdm_6P.js} +1 -1
  3. package/lib/src/{CreatedOnBrandCrowd-es-es-DVWQ243C.js → CreatedOnBrandCrowd-es-es-BrmHWeAM.js} +1 -1
  4. package/lib/src/{CreatedOnBrandCrowd-fr-fr-D-M_L0Du.js → CreatedOnBrandCrowd-fr-ca-CaIyjTXl.js} +1 -1
  5. package/lib/src/{CreatedOnBrandCrowd-fr-ca-D-M_L0Du.js → CreatedOnBrandCrowd-fr-fr-CaIyjTXl.js} +1 -1
  6. package/lib/src/{CreatedOnBrandCrowd-pt-pt-CNpzknxU.js → CreatedOnBrandCrowd-pt-br-BClE0Nd9.js} +1 -1
  7. package/lib/src/{CreatedOnBrandCrowd-pt-br-CNpzknxU.js → CreatedOnBrandCrowd-pt-pt-BClE0Nd9.js} +1 -1
  8. package/lib/src/{CreatedOnDcom-de-de-DMJoD5wE.js → CreatedOnDcom-de-de-CkrdlPqK.js} +1 -1
  9. package/lib/src/{CreatedOnDcom-en-us-B-F3Pmjv.js → CreatedOnDcom-en-us-D33J9qky.js} +1 -1
  10. package/lib/src/{CreatedOnDcom-es-es-DmLk-cA2.js → CreatedOnDcom-es-es-BsmInngw.js} +1 -1
  11. package/lib/src/{CreatedOnDcom-fr-ca-ntnsCzvA.js → CreatedOnDcom-fr-ca-DOXlsRea.js} +1 -1
  12. package/lib/src/{CreatedOnDcom-fr-fr-DFcO0gHL.js → CreatedOnDcom-fr-fr-SfFYLtyn.js} +1 -1
  13. package/lib/src/{CreatedOnDcom-pt-br-CtuJoVXW.js → CreatedOnDcom-pt-br-DcWeuZUy.js} +1 -1
  14. package/lib/src/{CreatedOnDcom-pt-pt-B7shZVVn.js → CreatedOnDcom-pt-pt-DZsZZ4iX.js} +1 -1
  15. package/lib/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.d.ts +1 -1
  16. package/lib/src/brand-page/models/index.d.ts +0 -12
  17. package/lib/src/brand-page-maker/stores/ai-checkpoint/ai-checkpoint-store.d.ts +0 -18
  18. package/lib/src/index.mjs +1 -1
  19. package/lib/src/{lib-BQmJBVbS.js → lib-C5vGkjfs.js} +17561 -17370
  20. package/lib/src/metadata-builder/builders/footer-builder.js +44 -34
  21. package/lib/src/metadata-builder/builders/section-builders/image-gallery-builder.js +68 -57
  22. package/lib/src/metadata-builder/builders/section-builders/images-section-builder.js +93 -58
  23. package/lib/src/metadata-builder/builders/section-builders/shapes-section-builder.js +99 -66
  24. package/lib/src/metadata-builder/builders/section-builders/social-links-builder.js +17 -57
  25. package/lib/src/metadata-builder/builders/section-builders/testimonial-section-builder.js +85 -51
  26. package/lib/src/metadata-builder/builders/section-builders/text-section-builder.js +74 -49
  27. package/lib/src/metadata-builder/parsers/ai-toolcall-parser.d.ts +4 -13
  28. package/lib/src/metadata-builder/parsers/section-tool-arguments.d.ts +48 -66
  29. package/lib/src/shared/page-settings/components/page-settings/PageSettings.mixin.d.ts +0 -2
  30. package/lib/src/shared/page-settings/components/page-settings-section-styles/PageSettingsSectionStyles.mixin.d.ts +0 -2
  31. package/package.json +1 -1
  32. package/src/brand-page/components/image-gallery/masonry/MasonryGallery.mixin.ts +1 -2
  33. package/src/brand-page/models/index.ts +0 -12
  34. package/src/metadata-builder/builders/footer-builder.test.ts +0 -298
  35. package/src/metadata-builder/builders/footer-builder.ts +65 -56
  36. package/src/metadata-builder/builders/section-builders/image-gallery-builder.ts +88 -62
  37. package/src/metadata-builder/builders/section-builders/images-section-builder.ts +129 -66
  38. package/src/metadata-builder/builders/section-builders/shapes-section-builder.ts +160 -105
  39. package/src/metadata-builder/builders/section-builders/social-links-builder.ts +20 -65
  40. package/src/metadata-builder/builders/section-builders/testimonial-section-builder.ts +146 -83
  41. package/src/metadata-builder/builders/section-builders/text-section-builder.ts +122 -70
  42. package/src/metadata-builder/parsers/ai-toolcall-parser.ts +4 -13
  43. package/src/metadata-builder/parsers/section-tool-arguments.ts +48 -66
  44. package/lib/src/metadata-builder/builders/item-id-utils.d.ts +0 -41
  45. package/lib/src/metadata-builder/builders/item-id-utils.js +0 -68
  46. package/src/metadata-builder/builders/item-id-utils.test.ts +0 -118
  47. package/src/metadata-builder/builders/item-id-utils.ts +0 -85
@@ -92,15 +92,10 @@ export interface ProfileSectionArguments {
92
92
  background_color?: string | null;
93
93
  }
94
94
  export interface TextSectionItem {
95
- /** Stable id of the existing item to update or move. Omit to ADD a new item. */
96
- item_id?: string;
97
- /**
98
- * Id of an existing item to position this item before (insert for new items,
99
- * move for existing ones). Use "__end__" for the end of the list. Omit to
100
- * leave an existing item in place / append a new item. Mirrors the
101
- * section-level move_before_content_id.
102
- */
103
- move_before_item_id?: string;
95
+ /** 0-based index of existing item to update in-place */
96
+ item_index?: number;
97
+ /** 0-based position to insert a new item at */
98
+ insert_at?: number;
104
99
  text?: RichTextField;
105
100
  text_style?: TextStyleArguments;
106
101
  }
@@ -113,8 +108,10 @@ export interface TextSectionArguments {
113
108
  /** Multiple text items (BrandSite) */
114
109
  items?: TextSectionItem[];
115
110
  _replace_items?: boolean;
116
- /** Stable ids of items to remove. Order-independent (no index shifting). */
117
- _remove_item_ids?: string[];
111
+ /** 0-based indices of items to remove */
112
+ _remove_item_indices?: number[];
113
+ /** New order as array of current 0-based indices */
114
+ _reorder_items?: number[];
118
115
  background_color?: string | null;
119
116
  }
120
117
  export interface ImageSectionArguments {
@@ -122,15 +119,10 @@ export interface ImageSectionArguments {
122
119
  image_title?: string;
123
120
  }
124
121
  export interface ImageGalleryItem {
125
- /** Stable id of the existing item to update or move. Omit to ADD a new item. */
126
- item_id?: string;
127
- /**
128
- * Id of an existing item to position this item before (insert for new items,
129
- * move for existing ones). Use "__end__" for the end of the list. Omit to
130
- * leave an existing item in place / append a new item. Mirrors the
131
- * section-level move_before_content_id.
132
- */
133
- move_before_item_id?: string;
122
+ /** 0-based index of existing item to update in-place */
123
+ item_index?: number;
124
+ /** 0-based position to insert a new item at */
125
+ insert_at?: number;
134
126
  image_url?: string;
135
127
  caption?: string;
136
128
  }
@@ -139,8 +131,10 @@ export interface ImageGallerySectionArguments {
139
131
  gallery_type?: 'grid' | 'carousel' | 'masonry';
140
132
  is_full_width?: boolean;
141
133
  _replace_items?: boolean;
142
- /** Stable ids of items to remove. Order-independent (no index shifting). */
143
- _remove_item_ids?: string[];
134
+ /** 0-based indices of items to remove */
135
+ _remove_item_indices?: number[];
136
+ /** New order as array of current 0-based indices */
137
+ _reorder_items?: number[];
144
138
  }
145
139
  export interface GeneralLinkSectionArguments {
146
140
  title?: string;
@@ -195,15 +189,10 @@ export interface TiktokSectionArguments {
195
189
  url?: string;
196
190
  }
197
191
  export interface SocialLink {
198
- /** Stable id of the existing item to update or move. Omit to ADD a new item. */
199
- item_id?: string;
200
- /**
201
- * Id of an existing item to position this item before (insert for new items,
202
- * move for existing ones). Use "__end__" for the end of the list. Omit to
203
- * leave an existing item in place / append a new item. Mirrors the
204
- * section-level move_before_content_id.
205
- */
206
- move_before_item_id?: string;
192
+ /** 0-based index of existing item to update in-place */
193
+ item_index?: number;
194
+ /** 0-based position to insert a new item at */
195
+ insert_at?: number;
207
196
  platform?: 'facebook' | 'x' | 'instagram' | 'youtube' | 'linkedin' | 'pinterest' | 'twitch' | 'tiktok';
208
197
  url?: string;
209
198
  }
@@ -215,8 +204,10 @@ export interface SocialLinksSectionArguments {
215
204
  /** Style for the icon container background (used when icon_style is 'square' or 'circle'). */
216
205
  icon_background_style?: TextStyleArguments;
217
206
  _replace_items?: boolean;
218
- /** Stable ids of items to remove. Order-independent (no index shifting). */
219
- _remove_item_ids?: string[];
207
+ /** 0-based indices of items to remove */
208
+ _remove_item_indices?: number[];
209
+ /** New order as array of current 0-based indices */
210
+ _reorder_items?: number[];
220
211
  }
221
212
  export interface MailingListSectionArguments {
222
213
  title?: RichTextField;
@@ -277,15 +268,10 @@ export interface AboutSectionArguments {
277
268
  background_color?: string | null;
278
269
  }
279
270
  export interface ImagesSectionItem {
280
- /** Stable id of the existing item to update or move. Omit to ADD a new item. */
281
- item_id?: string;
282
- /**
283
- * Id of an existing item to position this item before (insert for new items,
284
- * move for existing ones). Use "__end__" for the end of the list. Omit to
285
- * leave an existing item in place / append a new item. Mirrors the
286
- * section-level move_before_content_id.
287
- */
288
- move_before_item_id?: string;
271
+ /** 0-based index of existing item to update in-place */
272
+ item_index?: number;
273
+ /** 0-based position to insert a new item at */
274
+ insert_at?: number;
289
275
  image_url?: string;
290
276
  heading?: string;
291
277
  text?: string;
@@ -297,8 +283,10 @@ export interface ImagesSectionArguments {
297
283
  items?: ImagesSectionItem[];
298
284
  display_style?: 'left-aligned' | 'right-aligned' | 'centered' | 'media-only' | 'center-card-aligned-image' | 'alternate-image';
299
285
  _replace_items?: boolean;
300
- /** Stable ids of items to remove. Order-independent (no index shifting). */
301
- _remove_item_ids?: string[];
286
+ /** 0-based indices of items to remove */
287
+ _remove_item_indices?: number[];
288
+ /** New order as array of current 0-based indices */
289
+ _reorder_items?: number[];
302
290
  title_style?: TextStyleArguments;
303
291
  description_style?: TextStyleArguments;
304
292
  heading_style?: TextStyleArguments;
@@ -310,15 +298,10 @@ export interface ImagesSectionArguments {
310
298
  card_style?: CardStyleArguments;
311
299
  }
312
300
  export interface TestimonialItem {
313
- /** Stable id of the existing item to update or move. Omit to ADD a new item. */
314
- item_id?: string;
315
- /**
316
- * Id of an existing item to position this item before (insert for new items,
317
- * move for existing ones). Use "__end__" for the end of the list. Omit to
318
- * leave an existing item in place / append a new item. Mirrors the
319
- * section-level move_before_content_id.
320
- */
321
- move_before_item_id?: string;
301
+ /** 0-based index of existing item to update in-place */
302
+ item_index?: number;
303
+ /** 0-based position to insert a new item at */
304
+ insert_at?: number;
322
305
  testimonial?: string;
323
306
  name?: string;
324
307
  position?: string;
@@ -331,8 +314,10 @@ export interface TestimonialSectionArguments {
331
314
  items?: TestimonialItem[];
332
315
  display_style?: 'centered' | 'big-image' | 'columns';
333
316
  _replace_items?: boolean;
334
- /** Stable ids of items to remove. Order-independent (no index shifting). */
335
- _remove_item_ids?: string[];
317
+ /** 0-based indices of items to remove */
318
+ _remove_item_indices?: number[];
319
+ /** New order as array of current 0-based indices */
320
+ _reorder_items?: number[];
336
321
  title_style?: TextStyleArguments;
337
322
  testimonial_style?: TextStyleArguments;
338
323
  name_style?: TextStyleArguments;
@@ -341,15 +326,10 @@ export interface TestimonialSectionArguments {
341
326
  card_style?: CardStyleArguments;
342
327
  }
343
328
  export interface ShapesSectionItem {
344
- /** Stable id of the existing item to update or move. Omit to ADD a new item. */
345
- item_id?: string;
346
- /**
347
- * Id of an existing item to position this item before (insert for new items,
348
- * move for existing ones). Use "__end__" for the end of the list. Omit to
349
- * leave an existing item in place / append a new item. Mirrors the
350
- * section-level move_before_content_id.
351
- */
352
- move_before_item_id?: string;
329
+ /** 0-based index of existing item to update in-place */
330
+ item_index?: number;
331
+ /** 0-based position to insert a new item at */
332
+ insert_at?: number;
353
333
  icon_image_url?: string;
354
334
  shape_url?: string;
355
335
  color_theme?: string;
@@ -362,8 +342,10 @@ export interface ShapesSectionArguments {
362
342
  items?: ShapesSectionItem[];
363
343
  display_style?: 'left-aligned' | 'right-aligned' | 'centered' | 'media-only' | 'center-card-aligned-image' | 'alternate-image';
364
344
  _replace_items?: boolean;
365
- /** Stable ids of items to remove. Order-independent (no index shifting). */
366
- _remove_item_ids?: string[];
345
+ /** 0-based indices of items to remove */
346
+ _remove_item_indices?: number[];
347
+ /** New order as array of current 0-based indices */
348
+ _reorder_items?: number[];
367
349
  title_style?: TextStyleArguments;
368
350
  description_style?: TextStyleArguments;
369
351
  heading_style?: TextStyleArguments;
@@ -2866,7 +2866,6 @@ declare const _default: import("vue").DefineComponent<{}, {
2866
2866
  logoImageBase64Url?: string | undefined;
2867
2867
  socialLinks: {
2868
2868
  url: string;
2869
- id?: string | undefined;
2870
2869
  imageUrl: string;
2871
2870
  size?: string | undefined;
2872
2871
  imageTitle: string;
@@ -3091,7 +3090,6 @@ declare const _default: import("vue").DefineComponent<{}, {
3091
3090
  logoImageBase64Url?: string | undefined;
3092
3091
  socialLinks: {
3093
3092
  url: string;
3094
- id?: string | undefined;
3095
3093
  imageUrl: string;
3096
3094
  size?: string | undefined;
3097
3095
  imageTitle: string;
@@ -2824,7 +2824,6 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
2824
2824
  logoImageBase64Url?: string | undefined;
2825
2825
  socialLinks: {
2826
2826
  url: string;
2827
- id?: string | undefined;
2828
2827
  imageUrl: string;
2829
2828
  size?: string | undefined;
2830
2829
  imageTitle: string;
@@ -3049,7 +3048,6 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
3049
3048
  logoImageBase64Url?: string | undefined;
3050
3049
  socialLinks: {
3051
3050
  url: string;
3052
- id?: string | undefined;
3053
3051
  imageUrl: string;
3054
3052
  size?: string | undefined;
3055
3053
  imageTitle: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@designcrowd/library.brand-page",
3
3
  "description": "A collection of Maker/Designer applications",
4
- "version": "5.19.1-id-based-items-1",
4
+ "version": "5.19.1",
5
5
  "author": "Design.com Eng",
6
6
  "license": "UNLICENSED",
7
7
  "type": "module",
@@ -7,8 +7,7 @@ import type { ImageGalleryImageClickPayload } from '../models';
7
7
  import type { BrandPageContext, ImageGalleryItem } from '../../../models';
8
8
  import { getImageAltText } from '../../../utils';
9
9
 
10
- interface IndexedImageGalleryItem extends Omit<ImageGalleryItem, 'id'> {
11
- // Local rendering index, distinct from the stable string `id` on ImageItem.
10
+ interface IndexedImageGalleryItem extends ImageGalleryItem {
12
11
  id: number;
13
12
  }
14
13
 
@@ -234,13 +234,6 @@ export interface ImageContent extends Content {
234
234
  }
235
235
 
236
236
  export interface ImageItem {
237
- /**
238
- * Stable per-item identifier. Mirrors the section-level `Content.id` one
239
- * level deeper so AI tool calls can reference items by id rather than by
240
- * positional index. Optional for backward compatibility with persisted data
241
- * that predates item ids — backfilled on load (see ensureItemIds).
242
- */
243
- id?: string;
244
237
  imageUrl: string;
245
238
  imageTitle: string;
246
239
  width: number;
@@ -473,8 +466,6 @@ export interface ShapeItem extends ImageItem {
473
466
  }
474
467
 
475
468
  export interface TestimonialItems {
476
- /** Stable per-item identifier. See ImageItem.id. */
477
- id?: string;
478
469
  imageUrl: string;
479
470
  name: string;
480
471
  position: string;
@@ -492,8 +483,6 @@ export interface TestimonialItems {
492
483
  }
493
484
 
494
485
  export interface TextItem {
495
- /** Stable per-item identifier. See ImageItem.id. */
496
- id?: string;
497
486
  text: string;
498
487
  textStyle: TextStyleDeclaration;
499
488
 
@@ -563,7 +552,6 @@ export interface AboutSectionContent extends Content {
563
552
  }
564
553
 
565
554
  export interface LinksItem {
566
- id?: string;
567
555
  imageUrl: string;
568
556
  url: string;
569
557
  size?: string;
@@ -14,10 +14,6 @@ import { FooterBuilder } from './footer-builder';
14
14
  * The fix routes copyright style writes through `applyDetachedStyleOverride`,
15
15
  * which resolves the preset in and clears `textStyleType` so the per-element
16
16
  * override sticks.
17
- *
18
- * BP-5603: social link mutations converted from index-based to id-based
19
- * (item_id / move_before_item_id / _remove_social_link_ids), mirroring the
20
- * section-level move_before_content_id contract one level deeper.
21
17
  */
22
18
 
23
19
  const textStyleOptions = {
@@ -46,37 +42,6 @@ const makeModel = (copyrightStyle: Record<string, unknown>): BrandPageModel =>
46
42
  pageFooterMobile: { copyrightStyle: { ...copyrightStyle } },
47
43
  }) as unknown as BrandPageModel;
48
44
 
49
- const makeModelWithLinks = (
50
- links: { id?: string; imageUrl: string; url: string }[],
51
- ): BrandPageModel =>
52
- ({
53
- styles: { textStyles: { textStyleOptions } },
54
- pageFooterDesktop: {
55
- copyrightStyle: {},
56
- socialLinks: links.map((l) => ({
57
- id: l.id,
58
- imageUrl: l.imageUrl,
59
- url: l.url,
60
- imageTitle: '',
61
- width: 0,
62
- height: 0,
63
- imageLink: '',
64
- })),
65
- },
66
- pageFooterMobile: {
67
- copyrightStyle: {},
68
- socialLinks: links.map((l) => ({
69
- id: l.id,
70
- imageUrl: l.imageUrl,
71
- url: l.url,
72
- imageTitle: '',
73
- width: 0,
74
- height: 0,
75
- imageLink: '',
76
- })),
77
- },
78
- }) as unknown as BrandPageModel;
79
-
80
45
  const run = (args: UpdateFooterArguments, model: BrandPageModel) =>
81
46
  new FooterBuilder({
82
47
  id: 'f1',
@@ -84,10 +49,6 @@ const run = (args: UpdateFooterArguments, model: BrandPageModel) =>
84
49
  payload: args,
85
50
  }).buildAsync(model);
86
51
 
87
- // ---------------------------------------------------------------------------
88
- // Copyright font (BP-5420)
89
- // ---------------------------------------------------------------------------
90
-
91
52
  describe('FooterBuilder — copyright font vs global style', () => {
92
53
  it('detaches from the global preset: applies the font, clears textStyleType, retains preset values', async () => {
93
54
  const model = makeModel({
@@ -113,262 +74,3 @@ describe('FooterBuilder — copyright font vs global style', () => {
113
74
  expect(style.textAlign).toBe('center');
114
75
  });
115
76
  });
116
-
117
- // ---------------------------------------------------------------------------
118
- // Social links — id-based mutations (BP-5603)
119
- // ---------------------------------------------------------------------------
120
-
121
- describe('FooterBuilder — social links id-based mutations', () => {
122
- it('backfills ids on legacy links that have none', async () => {
123
- // Links without ids (legacy persisted data)
124
- const model = makeModelWithLinks([
125
- { imageUrl: 'facebook', url: 'https://fb.com' },
126
- { imageUrl: 'instagram', url: 'https://ig.com' },
127
- ]);
128
-
129
- const { model: out } = await run({}, model);
130
-
131
- const links = out.pageFooterDesktop!.socialLinks;
132
-
133
- expect(links[0].id).toBeTruthy();
134
- expect(links[1].id).toBeTruthy();
135
- expect(links[0].id).not.toBe(links[1].id);
136
- });
137
-
138
- it('updates an existing link in place by item_id', async () => {
139
- const model = makeModelWithLinks([
140
- { id: 'link-1', imageUrl: 'facebook', url: 'https://fb.com' },
141
- { id: 'link-2', imageUrl: 'instagram', url: 'https://ig.com' },
142
- ]);
143
-
144
- const { model: out } = await run(
145
- {
146
- social_links: [
147
- { item_id: 'link-1', url: 'https://facebook.com/mybrand' },
148
- ],
149
- },
150
- model,
151
- );
152
-
153
- const links = out.pageFooterDesktop!.socialLinks;
154
-
155
- expect(links).toHaveLength(2);
156
- expect(links[0].url).toBe('https://facebook.com/mybrand');
157
- expect(links[0].id).toBe('link-1');
158
- // untouched link unchanged
159
- expect(links[1].id).toBe('link-2');
160
- expect(links[1].url).toBe('https://ig.com');
161
- });
162
-
163
- it('updates social_link_icon on an existing link', async () => {
164
- const model = makeModelWithLinks([
165
- { id: 'link-1', imageUrl: 'facebook', url: 'https://fb.com' },
166
- ]);
167
-
168
- const { model: out } = await run(
169
- {
170
- social_links: [{ item_id: 'link-1', social_link_icon: 'facebook-v2' }],
171
- },
172
- model,
173
- );
174
-
175
- expect(out.pageFooterDesktop!.socialLinks[0].imageUrl).toBe('facebook-v2');
176
- });
177
-
178
- it('skips unknown item_id silently', async () => {
179
- const model = makeModelWithLinks([
180
- { id: 'link-1', imageUrl: 'facebook', url: 'https://fb.com' },
181
- ]);
182
-
183
- const { model: out } = await run(
184
- {
185
- social_links: [{ item_id: 'no-such-id', url: 'https://nowhere.com' }],
186
- },
187
- model,
188
- );
189
-
190
- const links = out.pageFooterDesktop!.socialLinks;
191
-
192
- expect(links).toHaveLength(1);
193
- expect(links[0].url).toBe('https://fb.com');
194
- });
195
-
196
- it('inserts a new link (no item_id) appended by default', async () => {
197
- const model = makeModelWithLinks([
198
- { id: 'link-1', imageUrl: 'facebook', url: 'https://fb.com' },
199
- ]);
200
-
201
- const { model: out } = await run(
202
- {
203
- social_links: [
204
- { social_link_icon: 'instagram', url: 'https://ig.com' },
205
- ],
206
- },
207
- model,
208
- );
209
-
210
- const links = out.pageFooterDesktop!.socialLinks;
211
-
212
- expect(links).toHaveLength(2);
213
- expect(links[1].imageUrl).toBe('instagram');
214
- expect(links[1].id).toBeTruthy();
215
- });
216
-
217
- it('inserts a new link before an anchor via move_before_item_id', async () => {
218
- const model = makeModelWithLinks([
219
- { id: 'link-1', imageUrl: 'facebook', url: 'https://fb.com' },
220
- { id: 'link-2', imageUrl: 'instagram', url: 'https://ig.com' },
221
- ]);
222
-
223
- const { model: out } = await run(
224
- {
225
- social_links: [
226
- {
227
- social_link_icon: 'x',
228
- url: 'https://x.com',
229
- move_before_item_id: 'link-2',
230
- },
231
- ],
232
- },
233
- model,
234
- );
235
-
236
- const links = out.pageFooterDesktop!.socialLinks;
237
-
238
- expect(links).toHaveLength(3);
239
- expect(links[0].id).toBe('link-1');
240
- expect(links[1].imageUrl).toBe('x');
241
- expect(links[2].id).toBe('link-2');
242
- });
243
-
244
- it('inserts a new link at end via move_before_item_id "__end__"', async () => {
245
- const model = makeModelWithLinks([
246
- { id: 'link-1', imageUrl: 'facebook', url: 'https://fb.com' },
247
- { id: 'link-2', imageUrl: 'instagram', url: 'https://ig.com' },
248
- ]);
249
-
250
- const { model: out } = await run(
251
- {
252
- social_links: [
253
- {
254
- social_link_icon: 'x',
255
- url: 'https://x.com',
256
- move_before_item_id: '__end__',
257
- },
258
- ],
259
- },
260
- model,
261
- );
262
-
263
- const links = out.pageFooterDesktop!.socialLinks;
264
-
265
- expect(links).toHaveLength(3);
266
- expect(links[2].imageUrl).toBe('x');
267
- });
268
-
269
- it('moves an existing link before an anchor via move_before_item_id', async () => {
270
- const model = makeModelWithLinks([
271
- { id: 'link-1', imageUrl: 'facebook', url: 'https://fb.com' },
272
- { id: 'link-2', imageUrl: 'instagram', url: 'https://ig.com' },
273
- { id: 'link-3', imageUrl: 'x', url: 'https://x.com' },
274
- ]);
275
-
276
- const { model: out } = await run(
277
- {
278
- social_links: [{ item_id: 'link-3', move_before_item_id: 'link-1' }],
279
- },
280
- model,
281
- );
282
-
283
- const links = out.pageFooterDesktop!.socialLinks;
284
-
285
- expect(links.map((l) => l.id)).toEqual(['link-3', 'link-1', 'link-2']);
286
- });
287
-
288
- it('removes links by id', async () => {
289
- const model = makeModelWithLinks([
290
- { id: 'link-1', imageUrl: 'facebook', url: 'https://fb.com' },
291
- { id: 'link-2', imageUrl: 'instagram', url: 'https://ig.com' },
292
- { id: 'link-3', imageUrl: 'x', url: 'https://x.com' },
293
- ]);
294
-
295
- const { model: out } = await run(
296
- { _remove_social_link_ids: ['link-1', 'link-3'] },
297
- model,
298
- );
299
-
300
- const links = out.pageFooterDesktop!.socialLinks;
301
-
302
- expect(links).toHaveLength(1);
303
- expect(links[0].id).toBe('link-2');
304
- });
305
-
306
- it('_replace_social_links rebuilds the entire list and assigns fresh ids', async () => {
307
- const model = makeModelWithLinks([
308
- { id: 'old-1', imageUrl: 'facebook', url: 'https://fb.com' },
309
- ]);
310
-
311
- const { model: out } = await run(
312
- {
313
- _replace_social_links: true,
314
- social_links: [
315
- { social_link_icon: 'instagram', url: 'https://ig.com' },
316
- { social_link_icon: 'x', url: 'https://x.com' },
317
- ],
318
- },
319
- model,
320
- );
321
-
322
- const links = out.pageFooterDesktop!.socialLinks;
323
-
324
- expect(links).toHaveLength(2);
325
- expect(links[0].imageUrl).toBe('instagram');
326
- expect(links[1].imageUrl).toBe('x');
327
- // fresh ids assigned, not the old one
328
- expect(links[0].id).toBeTruthy();
329
- expect(links[0].id).not.toBe('old-1');
330
- });
331
-
332
- it('_replace_social_links with empty social_links clears all', async () => {
333
- const model = makeModelWithLinks([
334
- { id: 'link-1', imageUrl: 'facebook', url: 'https://fb.com' },
335
- ]);
336
-
337
- const { model: out } = await run({ _replace_social_links: true }, model);
338
-
339
- expect(out.pageFooterDesktop!.socialLinks).toHaveLength(0);
340
- });
341
-
342
- it('normalises mailto: and tel: prefixes on new links', async () => {
343
- const model = makeModelWithLinks([]);
344
-
345
- const { model: out } = await run(
346
- {
347
- social_links: [
348
- { social_link_icon: 'email', url: 'mailto:me@example.com' },
349
- { social_link_icon: 'phone', url: 'tel:+1234567890' },
350
- ],
351
- },
352
- model,
353
- );
354
-
355
- const links = out.pageFooterDesktop!.socialLinks;
356
-
357
- expect(links[0].url).toBe('me@example.com');
358
- expect(links[1].url).toBe('+1234567890');
359
- });
360
-
361
- it('applies mutations to both desktop and mobile footers', async () => {
362
- const model = makeModelWithLinks([
363
- { id: 'link-1', imageUrl: 'facebook', url: 'https://fb.com' },
364
- ]);
365
-
366
- const { model: out } = await run(
367
- { _remove_social_link_ids: ['link-1'] },
368
- model,
369
- );
370
-
371
- expect(out.pageFooterDesktop!.socialLinks).toHaveLength(0);
372
- expect(out.pageFooterMobile!.socialLinks).toHaveLength(0);
373
- });
374
- });