@capillarytech/creatives-library 8.0.129 → 8.0.131

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 (55) hide show
  1. package/containers/Login/index.js +1 -2
  2. package/containers/Templates/constants.js +10 -1
  3. package/containers/Templates/index.js +45 -45
  4. package/package.json +1 -1
  5. package/services/api.js +14 -7
  6. package/services/tests/haptic-api.test.js +387 -0
  7. package/utils/createMobilePushPayload.js +322 -0
  8. package/utils/tests/{createPayload.test.js → createMobilePushPayload.test.js} +333 -64
  9. package/utils/tests/vendorDataTransformers.test.js +512 -0
  10. package/utils/vendorDataTransformers.js +108 -0
  11. package/v2Components/CapDeviceContent/index.js +1 -1
  12. package/v2Components/CapDocumentUpload/index.js +2 -2
  13. package/v2Components/CapImageUpload/index.js +2 -2
  14. package/v2Components/CapMpushCTA/index.js +13 -12
  15. package/v2Components/CapTagList/index.js +5 -5
  16. package/v2Components/CapVideoUpload/index.js +17 -7
  17. package/v2Components/MobilePushPreviewV2/index.js +28 -15
  18. package/v2Components/TemplatePreview/_templatePreview.scss +131 -29
  19. package/v2Components/TemplatePreview/index.js +130 -131
  20. package/v2Components/TemplatePreview/tests/__snapshots__/index.test.js.snap +10 -10
  21. package/v2Containers/CreativesContainer/index.js +6 -4
  22. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +4748 -4658
  23. package/v2Containers/Login/index.js +1 -2
  24. package/v2Containers/MobilePush/tests/commonMethods.test.js +401 -0
  25. package/v2Containers/MobilePushNew/components/CtaButtons.js +18 -16
  26. package/v2Containers/MobilePushNew/components/MediaUploaders.js +46 -45
  27. package/v2Containers/MobilePushNew/components/PlatformContentFields.js +12 -11
  28. package/v2Containers/MobilePushNew/components/tests/CtaButtons.test.js +134 -367
  29. package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +1209 -143
  30. package/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +314 -3
  31. package/v2Containers/MobilePushNew/constants.js +1 -0
  32. package/v2Containers/MobilePushNew/hooks/tests/usePlatformSync.test.js +163 -0
  33. package/v2Containers/MobilePushNew/hooks/tests/useUpload.test.js +1131 -895
  34. package/v2Containers/MobilePushNew/hooks/usePlatformSync.js +172 -52
  35. package/v2Containers/MobilePushNew/hooks/useUpload.js +88 -74
  36. package/v2Containers/MobilePushNew/index.js +278 -1532
  37. package/v2Containers/MobilePushNew/messages.js +30 -0
  38. package/v2Containers/MobilePushNew/sagas.js +2 -7
  39. package/v2Containers/MobilePushNew/tests/sagas.test.js +41 -40
  40. package/v2Containers/MobilePushNew/tests/selectors.test.js +240 -0
  41. package/v2Containers/MobilePushNew/tests/utils.test.js +118 -19
  42. package/v2Containers/MobilePushNew/utils.js +53 -2
  43. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1171 -971
  44. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +684 -424
  45. package/v2Containers/Templates/_templates.scss +0 -1
  46. package/v2Containers/Templates/index.js +58 -29
  47. package/v2Containers/Templates/sagas.js +0 -1
  48. package/v2Containers/Whatsapp/constants.js +32 -0
  49. package/v2Containers/Whatsapp/index.js +104 -25
  50. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +3992 -3677
  51. package/v2Containers/Whatsapp/tests/haptic.test.js +405 -0
  52. package/assets/loading_img.gif +0 -0
  53. package/utils/createPayload.js +0 -405
  54. /package/v2Components/TemplatePreview/assets/images/{Android _ With date and time.svg → Android_With_date_and_time.svg} +0 -0
  55. /package/v2Components/TemplatePreview/assets/images/{iOS _ With date and time.svg → iOS_With_date_and_time.svg} +0 -0
@@ -8,6 +8,8 @@ import { initialReducer } from '../../../../initialReducer';
8
8
  import history from '../../../../utils/history';
9
9
  import MediaUploaders from '../MediaUploaders';
10
10
  import { ANDROID, IOS } from '../../constants';
11
+ import { act } from 'react';
12
+ import messages from '../../messages';
11
13
 
12
14
  // Mock the upload components
13
15
  jest.mock('../../../../v2Components/CapImageUpload', () =>
@@ -21,6 +23,16 @@ jest.mock('../../../../v2Components/CapImageUpload', () =>
21
23
  >
22
24
  Upload Image
23
25
  </button>
26
+ <button
27
+ type="button"
28
+ onClick={() => {
29
+ props.updateOnReUpload();
30
+ props.updateImageSrc('mock-image-url');
31
+ }}
32
+ data-testid="image-reupload-button"
33
+ >
34
+ Re-upload Image
35
+ </button>
24
36
  </div>
25
37
  );
26
38
  }
@@ -117,55 +129,63 @@ jest.mock('@capillarytech/cap-ui-library/CapIcon', () =>
117
129
  }
118
130
  );
119
131
 
120
- jest.mock('@capillarytech/cap-ui-library/CapCheckbox', () =>
121
- function MockCapCheckbox({ checked, onChange, children, className }) {
132
+ // Mock the CapCheckbox component
133
+ jest.mock('@capillarytech/cap-ui-library/CapCheckbox', () => ({
134
+ __esModule: true,
135
+ default: function MockCapCheckbox({ checked, onChange, children, ...props }) {
122
136
  return (
123
- <label className={className}>
137
+ <label>
124
138
  <input
125
139
  type="checkbox"
126
- checked={checked}
127
- onChange={onChange}
128
140
  data-testid="cap-checkbox"
141
+ checked={checked}
142
+ onChange={(e) => onChange && onChange(e)}
143
+ {...props}
129
144
  />
130
145
  <span>{children}</span>
131
146
  </label>
132
147
  );
133
- }
134
- );
148
+ },
149
+ }));
150
+
151
+ // Mock the CapInput component
152
+ jest.mock('@capillarytech/cap-ui-library/CapInput', () => ({
153
+ __esModule: true,
154
+ default: function MockCapInput({ value, onChange, id, placeholder, error }) {
155
+ return (
156
+ <div>
157
+ <input
158
+ data-testid={`cap-input-${id || 'mobile-push-external-link-input-ANDROID-0'}`}
159
+ type="text"
160
+ value={value || ''}
161
+ onChange={(e) => onChange && onChange({ target: { value: e.target.value } })}
162
+ placeholder={placeholder}
163
+ />
164
+ {error && <div data-testid="error-message">{error}</div>}
165
+ </div>
166
+ );
167
+ },
168
+ }));
135
169
 
170
+ // Mock the CapSelect.CapCustomSelect component
136
171
  jest.mock('@capillarytech/cap-ui-library/CapSelect', () => ({
137
- CapCustomSelect: function MockCapCustomSelect({ options, value, onChange, placeholder, selectPlaceholder }) {
172
+ CapCustomSelect: function MockCapCustomSelect({ value, onChange, options }) {
138
173
  return (
139
174
  <select
140
- value={value}
141
- onChange={(e) => onChange(e.target.value)}
142
175
  data-testid="cap-custom-select"
176
+ value={value}
177
+ onChange={(e) => onChange && onChange(e.target.value)}
143
178
  >
144
- <option value="">{placeholder || selectPlaceholder}</option>
145
179
  {options?.map((option) => (
146
180
  <option key={option.value} value={option.value}>
147
- {option.label}
181
+ {option.label || option.value}
148
182
  </option>
149
183
  ))}
150
184
  </select>
151
185
  );
152
- }
186
+ },
153
187
  }));
154
188
 
155
- jest.mock('@capillarytech/cap-ui-library/CapInput', () =>
156
- function MockCapInput({ value, onChange, placeholder, id }) {
157
- return (
158
- <input
159
- id={id}
160
- value={value}
161
- onChange={onChange}
162
- placeholder={placeholder}
163
- data-testid="cap-input"
164
- />
165
- );
166
- }
167
- );
168
-
169
189
  jest.mock('@capillarytech/cap-ui-library/CapRow', () =>
170
190
  function MockCapRow({ children, className, style }) {
171
191
  return <div className={className} style={style}>{children}</div>;
@@ -220,74 +240,115 @@ jest.mock('../../utils', () => ({
220
240
 
221
241
  // Mock the messages
222
242
  jest.mock('../../messages', () => ({
223
- imageErrorMessage: {
224
- id: 'image.error.message',
225
- defaultMessage: 'Image upload failed',
226
- },
227
- videoErrorMessage: {
228
- id: 'video.error.message',
229
- defaultMessage: 'Video upload failed',
230
- },
231
- gifErrorMessage: {
232
- id: 'gif.error.message',
233
- defaultMessage: 'GIF upload failed',
234
- },
235
- buttonsAndLinks: {
236
- id: 'buttons.and.links',
237
- defaultMessage: 'Buttons and Links',
238
- },
239
- optionalText: {
240
- id: 'optional.text',
241
- defaultMessage: 'Optional',
242
- },
243
- actionOnClickBody: {
244
- id: 'action.on.click.body',
245
- defaultMessage: 'Add action on click',
246
- },
247
- actionDescription: {
248
- id: 'action.description',
249
- defaultMessage: 'Action Description',
250
- },
251
- linkType: {
252
- id: 'link.type',
253
- defaultMessage: 'Link Type',
254
- },
255
- selectDeepLink: {
256
- id: 'select.deep.link',
257
- defaultMessage: 'Select Deep Link',
258
- },
259
- deepLink: {
260
- id: 'deep.link',
261
- defaultMessage: 'Deep Link',
262
- },
263
- externalLink: {
264
- id: 'external.link',
265
- defaultMessage: 'External Link',
266
- },
267
- enterExternalLink: {
268
- id: 'enter.external.link',
269
- defaultMessage: 'Enter External Link',
270
- },
271
- card: {
272
- id: 'card',
273
- defaultMessage: 'Card',
274
- },
275
- mediaImage: {
276
- id: 'media.image',
277
- defaultMessage: 'Image',
278
- },
279
- mediaVideo: {
280
- id: 'media.video',
281
- defaultMessage: 'Video',
282
- },
283
- carouselMediaType: {
284
- id: 'carousel.media.type',
285
- defaultMessage: 'Carousel Media Type',
243
+ __esModule: true,
244
+ default: {
245
+ imageErrorMessage: {
246
+ id: 'creatives.containersV2.MobilePushNew.imageErrorMessage',
247
+ defaultMessage: 'Please upload the image with allowed file extension, size, dimension and aspect ratio',
248
+ },
249
+ videoErrorMessage: {
250
+ id: 'creatives.containersV2.MobilePushNew.videoErrorMessage',
251
+ defaultMessage: 'Please upload the video with allowed file extension, size, dimension and aspect ratio',
252
+ },
253
+ gifErrorMessage: {
254
+ id: 'creatives.containersV2.MobilePushNew.gifErrorMessage',
255
+ defaultMessage: 'Please upload the gif with allowed file extension, size, dimension and aspect ratio',
256
+ },
257
+ buttonsAndLinks: {
258
+ id: 'creatives.containersV2.MobilePushNew.buttonsAndLinks',
259
+ defaultMessage: 'Buttons and links',
260
+ },
261
+ optionalText: {
262
+ id: 'creatives.containersV2.MobilePushNew.optionalText',
263
+ defaultMessage: '(Optional)',
264
+ },
265
+ actionOnClick: {
266
+ id: 'creatives.containersV2.MobilePushNew.actionOnClick',
267
+ defaultMessage: 'Action on click of notification body',
268
+ },
269
+ actionDescription: {
270
+ id: 'creatives.containersV2.MobilePushNew.actionDescription',
271
+ defaultMessage: 'Define where the users will redirect when they click on the body of the push notification',
272
+ },
273
+ linkType: {
274
+ id: 'app.containers.MobilePushNew.linkType',
275
+ defaultMessage: 'Link Type',
276
+ },
277
+ selectDeepLink: {
278
+ id: 'creatives.containersV2.MobilePushNew.selectDeepLink',
279
+ defaultMessage: 'Select deep link',
280
+ },
281
+ deepLink: {
282
+ id: 'app.containers.MobilePushNew.deepLink',
283
+ defaultMessage: 'Deep Link',
284
+ },
285
+ externalLink: {
286
+ id: 'app.containers.MobilePushNew.externalLink',
287
+ defaultMessage: 'External Link',
288
+ },
289
+ enterExternalLink: {
290
+ id: 'creatives.containersV2.MobilePushNew.enterExternalLink',
291
+ defaultMessage: 'Enter external link',
292
+ },
293
+ deepLinkKeys: {
294
+ id: 'creatives.containersV2.MobilePushNew.deepLinkKeys',
295
+ defaultMessage: 'Deep Link Keys',
296
+ },
297
+ deepLinkKeysPlaceholder: {
298
+ id: 'creatives.containersV2.MobilePushNew.deepLinkKeysPlaceholder',
299
+ defaultMessage: 'Please input {key}',
300
+ },
301
+ deepLinkKeysRequired: {
302
+ id: 'creatives.containersV2.MobilePushNew.deepLinkKeysRequired',
303
+ defaultMessage: 'Value cannot be empty.',
304
+ },
305
+ invalidUrl: {
306
+ id: 'creatives.containersV2.MobilePushNew.invalidUrl',
307
+ defaultMessage: 'Please enter a valid URL',
308
+ },
309
+ carouselMediaType: {
310
+ id: 'creatives.containersV2.MobilePushNew.carouselMediaType',
311
+ defaultMessage: 'Carousel media Type',
312
+ },
313
+ mediaImage: {
314
+ id: 'creatives.containersV2.MobilePushNew.mediaImage',
315
+ defaultMessage: 'Image',
316
+ },
317
+ mediaVideo: {
318
+ id: 'creatives.containersV2.MobilePushNew.mediaVideo',
319
+ defaultMessage: 'Video',
320
+ },
321
+ card: {
322
+ id: 'creatives.containersV2.MobilePushNew.card',
323
+ defaultMessage: 'Card',
324
+ },
325
+ addCard: {
326
+ id: 'creatives.containersV2.MobilePushNew.addCard',
327
+ defaultMessage: 'Add Card',
328
+ },
329
+ actionOnClickBody: {
330
+ id: 'creatives.containersV2.MobilePushNew.actionOnClickBody',
331
+ defaultMessage: 'Action on click of notification body',
332
+ },
286
333
  },
287
334
  }));
288
335
 
289
336
  const mockStore = configureStore({}, initialReducer, history);
290
337
 
338
+ // Helper function for formatMessage
339
+ const mockFormatMessage = (message, values = {}) => {
340
+ if (typeof message === 'object' && message.defaultMessage) {
341
+ let result = message.defaultMessage;
342
+ if (values && typeof values === 'object') {
343
+ Object.keys(values).forEach(key => {
344
+ result = result.replace(`{${key}}`, values[key]);
345
+ });
346
+ }
347
+ return result;
348
+ }
349
+ return message;
350
+ };
351
+
291
352
  const defaultProps = {
292
353
  mediaType: 'IMAGE',
293
354
  activeTab: ANDROID,
@@ -325,7 +386,7 @@ const renderComponent = (props = {}) => {
325
386
  const mergedProps = { ...defaultProps, ...props };
326
387
  return render(
327
388
  <Provider store={mockStore}>
328
- <IntlProvider locale="en" messages={{}}>
389
+ <IntlProvider messages={messages} locale="en">
329
390
  <MediaUploaders {...mergedProps} />
330
391
  </IntlProvider>
331
392
  </Provider>
@@ -418,25 +479,41 @@ describe('MediaUploaders', () => {
418
479
  });
419
480
 
420
481
  describe('CAROUSEL Media Type', () => {
421
- it('should render carousel component for CAROUSEL media type', () => {
422
- renderComponent({
482
+ it('should render carousel component for CAROUSEL media type', async () => {
483
+ const mockOnCarouselDataChange = jest.fn();
484
+
485
+ const { container } = renderComponent({
423
486
  mediaType: 'CAROUSEL',
424
487
  carouselData: [
425
488
  {
426
489
  mediaType: 'image',
427
- imageUrl: '',
490
+ imageUrl: 'test.jpg',
428
491
  buttons: [{
429
- actionOnClick: false,
430
- linkType: 'DEEP_LINK',
492
+ actionOnClick: true,
493
+ linkType: 'EXTERNAL_LINK',
431
494
  deepLinkValue: '',
432
- externalLinkValue: '',
495
+ deepLinkKeys: [],
496
+ externalLinkValue: 'https://example.com',
433
497
  }],
434
498
  }
435
- ]
499
+ ],
500
+ onCarouselDataChange: mockOnCarouselDataChange,
501
+ carouselActiveTabIndex: 0,
502
+ activeTab: 'ANDROID',
503
+ formatMessage: (message) => message.defaultMessage,
436
504
  });
437
- // The carousel component should render carousel-specific UI elements
438
- expect(screen.getByTestId('cap-image-upload')).toBeInTheDocument();
439
- expect(screen.getByText('Buttons and Links')).toBeInTheDocument();
505
+
506
+ // Check if the carousel component is rendered
507
+ const carouselMediaType = screen.getByText('Carousel media Type');
508
+ expect(carouselMediaType).toBeInTheDocument();
509
+
510
+ // Check if the carousel card is rendered
511
+ const carouselCard = screen.getByText('Card 1');
512
+ expect(carouselCard).toBeInTheDocument();
513
+
514
+ // Check if the buttons and links section is rendered
515
+ const buttonsAndLinks = screen.getByText('Buttons and links');
516
+ expect(buttonsAndLinks).toBeInTheDocument();
440
517
  });
441
518
 
442
519
  it('should initialize carousel data when empty', () => {
@@ -475,7 +552,7 @@ describe('MediaUploaders', () => {
475
552
  const onCarouselDataChange = jest.fn();
476
553
  rerender(
477
554
  <Provider store={mockStore}>
478
- <IntlProvider locale="en" messages={{}}>
555
+ <IntlProvider messages={messages} locale="en">
479
556
  <MediaUploaders
480
557
  {...defaultProps}
481
558
  mediaType="IMAGE"
@@ -487,6 +564,60 @@ describe('MediaUploaders', () => {
487
564
 
488
565
  expect(onCarouselDataChange).toHaveBeenCalledWith(ANDROID, []);
489
566
  });
567
+
568
+ it('should handle external link change in carousel actions', () => {
569
+ const mockOnCarouselDataChange = jest.fn();
570
+
571
+ const { container } = renderComponent({
572
+ mediaType: 'CAROUSEL',
573
+ carouselData: [
574
+ {
575
+ mediaType: 'image',
576
+ imageUrl: 'test.jpg',
577
+ buttons: [{
578
+ actionOnClick: true,
579
+ linkType: 'EXTERNAL_LINK',
580
+ deepLinkValue: '',
581
+ deepLinkKeys: [],
582
+ externalLinkValue: 'https://example.com',
583
+ }],
584
+ }
585
+ ],
586
+ onCarouselDataChange: mockOnCarouselDataChange,
587
+ carouselActiveTabIndex: 0,
588
+ activeTab: 'ANDROID',
589
+ formatMessage: (message) => message.defaultMessage,
590
+ });
591
+
592
+ // First check if the checkbox is checked
593
+ const actionOnClickCheckbox = screen.getByTestId('cap-checkbox');
594
+ expect(actionOnClickCheckbox).toBeChecked();
595
+
596
+ // First select the external link type
597
+ const linkTypeSelect = screen.getAllByTestId('cap-custom-select')[0];
598
+ fireEvent.change(linkTypeSelect, { target: { value: 'EXTERNAL_LINK' } });
599
+
600
+ // Wait for the external link input to be rendered
601
+ const externalLinkInput = screen.getByTestId('cap-input-mobile-push-external-link-input-ANDROID-0');
602
+ expect(externalLinkInput).toBeInTheDocument();
603
+ expect(externalLinkInput.value).toBe('https://example.com');
604
+
605
+ // Change external link value
606
+ fireEvent.change(externalLinkInput, { target: { value: 'https://newexample.com' } });
607
+
608
+ expect(mockOnCarouselDataChange).toHaveBeenCalledWith(
609
+ 'ANDROID',
610
+ expect.arrayContaining([
611
+ expect.objectContaining({
612
+ buttons: expect.arrayContaining([
613
+ expect.objectContaining({
614
+ externalLinkValue: 'https://newexample.com',
615
+ }),
616
+ ]),
617
+ }),
618
+ ])
619
+ );
620
+ });
490
621
  });
491
622
 
492
623
  describe('Platform switching', () => {
@@ -501,7 +632,7 @@ describe('MediaUploaders', () => {
501
632
 
502
633
  rerender(
503
634
  <Provider store={mockStore}>
504
- <IntlProvider locale="en" messages={{}}>
635
+ <IntlProvider messages={messages} locale="en">
505
636
  <MediaUploaders
506
637
  {...defaultProps}
507
638
  mediaType="CAROUSEL"
@@ -527,7 +658,7 @@ describe('MediaUploaders', () => {
527
658
 
528
659
  rerender(
529
660
  <Provider store={mockStore}>
530
- <IntlProvider locale="en" messages={{}}>
661
+ <IntlProvider messages={messages} locale="en">
531
662
  <MediaUploaders
532
663
  {...defaultProps}
533
664
  mediaType="VIDEO"
@@ -549,7 +680,7 @@ describe('MediaUploaders', () => {
549
680
 
550
681
  rerender(
551
682
  <Provider store={mockStore}>
552
- <IntlProvider locale="en" messages={{}}>
683
+ <IntlProvider messages={messages} locale="en">
553
684
  <MediaUploaders
554
685
  {...defaultProps}
555
686
  mediaType="IMAGE"
@@ -1197,7 +1328,7 @@ describe('MediaUploaders', () => {
1197
1328
  // Simulate video media type selection
1198
1329
  rerender(
1199
1330
  <Provider store={mockStore}>
1200
- <IntlProvider locale="en" messages={{}}>
1331
+ <IntlProvider messages={messages} locale="en">
1201
1332
  <MediaUploaders
1202
1333
  {...defaultProps}
1203
1334
  mediaType="CAROUSEL"
@@ -1698,29 +1829,6 @@ describe('MediaUploaders', () => {
1698
1829
  expect(screen.getAllByTestId('cap-custom-select')).toHaveLength(2); // Link type and deep link selects
1699
1830
  });
1700
1831
 
1701
- it('should render external link input when EXTERNAL_LINK is selected', () => {
1702
- renderComponent({
1703
- mediaType: 'CAROUSEL',
1704
- carouselData: [
1705
- {
1706
- mediaType: 'image',
1707
- imageUrl: 'test.jpg',
1708
- buttons: [{
1709
- actionOnClick: true,
1710
- linkType: 'EXTERNAL_LINK', // This triggers external link input
1711
- deepLinkValue: '',
1712
- externalLinkValue: 'https://example.com',
1713
- }],
1714
- }
1715
- ],
1716
- carouselActiveTabIndex: 0,
1717
- });
1718
-
1719
- // Should render external link input
1720
- expect(screen.getByTestId('cap-input')).toBeInTheDocument();
1721
- expect(screen.getByTestId('cap-custom-select')).toBeInTheDocument(); // Link type select
1722
- });
1723
-
1724
1832
  it('should show carousel link errors when present', () => {
1725
1833
  renderComponent({
1726
1834
  mediaType: 'CAROUSEL',
@@ -2069,7 +2177,7 @@ describe('MediaUploaders', () => {
2069
2177
  carouselActiveTabIndex: 0,
2070
2178
  linkProps: {
2071
2179
  deepLink: [
2072
- { value: 'test-deep-link', label: 'Test Deep Link' }
2180
+ { value: 'test-deep-link', keys: ['key1', 'key2'] }
2073
2181
  ]
2074
2182
  }
2075
2183
  });
@@ -2081,34 +2189,992 @@ describe('MediaUploaders', () => {
2081
2189
  // Should call onCarouselDataChange
2082
2190
  expect(onCarouselDataChange).toHaveBeenCalled();
2083
2191
  });
2192
+ });
2193
+
2194
+ // Carousel deep link keys handling - covering lines 609, 612, 614, 625-662
2195
+ it('should handle carousel deep link keys with array from selection', () => {
2196
+ const mockDeepLink = [
2197
+ { value: 'test-deep-link', keys: ['key1', 'key2'] }
2198
+ ];
2199
+ const mockCarouselData = [{
2200
+ mediaType: 'image',
2201
+ imageUrl: 'test-image.jpg',
2202
+ buttons: [{
2203
+ actionOnClick: true,
2204
+ linkType: 'DEEP_LINK',
2205
+ deepLinkValue: 'test-deep-link',
2206
+ deepLinkKeys: ['key1', 'key2'],
2207
+ externalLinkValue: '',
2208
+ }]
2209
+ }];
2210
+ const mockCarouselLinkErrors = {};
2211
+ const mockUpdateCarouselLinkError = jest.fn();
2212
+
2213
+ const { getByText, getByDisplayValue } = render(
2214
+ <MediaUploaders
2215
+ mediaType="CAROUSEL"
2216
+ activeTab="ANDROID"
2217
+ imageSrc={{ androidImageSrc: '', iosImageSrc: '' }}
2218
+ uploadMpushAsset={jest.fn()}
2219
+ isFullMode={false}
2220
+ setUpdateMpushImageSrc={jest.fn()}
2221
+ updateOnMpushImageReUpload={jest.fn()}
2222
+ imageData={{}}
2223
+ videoAssetList={{}}
2224
+ gifAssetList={{}}
2225
+ setUpdateMpushVideoSrc={jest.fn()}
2226
+ videoDataForVideo={{}}
2227
+ videoDataForGif={{}}
2228
+ videoSrc={''}
2229
+ formatMessage={jest.fn((message) => message.defaultMessage)}
2230
+ linkProps={{ deepLink: mockDeepLink }}
2231
+ clearImageDataByMediaType={jest.fn()}
2232
+ carouselData={mockCarouselData}
2233
+ onCarouselDataChange={jest.fn()}
2234
+ mobilePushActions={{ clearAsset: jest.fn() }}
2235
+ carouselActiveTabIndex={0}
2236
+ setCarouselActiveTabIndex={jest.fn()}
2237
+ carouselLinkErrors={mockCarouselLinkErrors}
2238
+ updateCarouselLinkError={mockUpdateCarouselLinkError}
2239
+ />
2240
+ );
2241
+
2242
+ // Check that deep link keys are displayed correctly
2243
+ expect(getByText('key1, key2')).toBeInTheDocument();
2244
+ expect(getByDisplayValue('key1, key2')).toBeInTheDocument();
2245
+ });
2246
+
2247
+ it('should handle carousel deep link keys with single key from selection', () => {
2248
+ const mockDeepLink = [
2249
+ { value: 'test-deep-link', keys: 'single-key' }
2250
+ ];
2251
+ const mockCarouselData = [{
2252
+ mediaType: 'image',
2253
+ imageUrl: 'test-image.jpg',
2254
+ buttons: [{
2255
+ actionOnClick: true,
2256
+ linkType: 'DEEP_LINK',
2257
+ deepLinkValue: 'test-deep-link',
2258
+ deepLinkKeys: ['single-key'],
2259
+ externalLinkValue: '',
2260
+ }]
2261
+ }];
2262
+ const mockCarouselLinkErrors = {};
2263
+ const mockUpdateCarouselLinkError = jest.fn();
2264
+
2265
+ const { getByText, getByDisplayValue } = render(
2266
+ <MediaUploaders
2267
+ mediaType="CAROUSEL"
2268
+ activeTab="ANDROID"
2269
+ imageSrc={{ androidImageSrc: '', iosImageSrc: '' }}
2270
+ uploadMpushAsset={jest.fn()}
2271
+ isFullMode={false}
2272
+ setUpdateMpushImageSrc={jest.fn()}
2273
+ updateOnMpushImageReUpload={jest.fn()}
2274
+ imageData={{}}
2275
+ videoAssetList={{}}
2276
+ gifAssetList={{}}
2277
+ setUpdateMpushVideoSrc={jest.fn()}
2278
+ videoDataForVideo={{}}
2279
+ videoDataForGif={{}}
2280
+ videoSrc={''}
2281
+ formatMessage={mockFormatMessage}
2282
+ linkProps={{ deepLink: mockDeepLink }}
2283
+ clearImageDataByMediaType={jest.fn()}
2284
+ carouselData={mockCarouselData}
2285
+ onCarouselDataChange={jest.fn()}
2286
+ mobilePushActions={{ clearAsset: jest.fn() }}
2287
+ carouselActiveTabIndex={0}
2288
+ setCarouselActiveTabIndex={jest.fn()}
2289
+ carouselLinkErrors={mockCarouselLinkErrors}
2290
+ updateCarouselLinkError={mockUpdateCarouselLinkError}
2291
+ />
2292
+ );
2293
+
2294
+ // Check that single key is displayed correctly
2295
+ expect(getByText('single-key')).toBeInTheDocument();
2296
+ expect(getByDisplayValue('single-key')).toBeInTheDocument();
2297
+ });
2298
+
2299
+ it('should handle carousel deep link keys with no keys from selection but existing keys', () => {
2300
+ const mockDeepLink = [
2301
+ { value: 'test-deep-link', keys: ['key1', 'key2'] } // Need keys to trigger the section
2302
+ ];
2303
+ const mockCarouselData = [{
2304
+ mediaType: 'image',
2305
+ imageUrl: 'test-image.jpg',
2306
+ buttons: [{
2307
+ actionOnClick: true,
2308
+ linkType: 'DEEP_LINK',
2309
+ deepLinkValue: 'test-deep-link',
2310
+ deepLinkKeys: ['existing-key1', 'existing-key2'],
2311
+ externalLinkValue: '',
2312
+ }]
2313
+ }];
2314
+ const mockCarouselLinkErrors = {};
2315
+ const mockUpdateCarouselLinkError = jest.fn();
2316
+
2317
+ const { getByText, getByDisplayValue } = render(
2318
+ <MediaUploaders
2319
+ mediaType="CAROUSEL"
2320
+ activeTab="ANDROID"
2321
+ imageSrc={{ androidImageSrc: '', iosImageSrc: '' }}
2322
+ uploadMpushAsset={jest.fn()}
2323
+ isFullMode={false}
2324
+ setUpdateMpushImageSrc={jest.fn()}
2325
+ updateOnMpushImageReUpload={jest.fn()}
2326
+ imageData={{}}
2327
+ videoAssetList={{}}
2328
+ gifAssetList={{}}
2329
+ setUpdateMpushVideoSrc={jest.fn()}
2330
+ videoDataForVideo={{}}
2331
+ videoDataForGif={{}}
2332
+ videoSrc={''}
2333
+ formatMessage={mockFormatMessage}
2334
+ linkProps={{ deepLink: mockDeepLink }}
2335
+ clearImageDataByMediaType={jest.fn()}
2336
+ carouselData={mockCarouselData}
2337
+ onCarouselDataChange={jest.fn()}
2338
+ mobilePushActions={{ clearAsset: jest.fn() }}
2339
+ carouselActiveTabIndex={0}
2340
+ setCarouselActiveTabIndex={jest.fn()}
2341
+ carouselLinkErrors={mockCarouselLinkErrors}
2342
+ updateCarouselLinkError={mockUpdateCarouselLinkError}
2343
+ />
2344
+ );
2345
+
2346
+ // Check that existing keys are displayed correctly (should show keys from selection first)
2347
+ expect(getByText('key1, key2')).toBeInTheDocument();
2348
+ expect(getByDisplayValue('existing-key1, existing-key2')).toBeInTheDocument();
2349
+ });
2350
+
2351
+ it('should handle carousel deep link keys with no keys at all', () => {
2352
+ const mockDeepLink = [
2353
+ { value: 'test-deep-link', keys: [] } // No keys from selection
2354
+ ];
2355
+ const mockCarouselData = [{
2356
+ mediaType: 'image',
2357
+ imageUrl: 'test-image.jpg',
2358
+ buttons: [{
2359
+ actionOnClick: true,
2360
+ linkType: 'DEEP_LINK',
2361
+ deepLinkValue: 'test-deep-link',
2362
+ deepLinkKeys: [],
2363
+ externalLinkValue: '',
2364
+ }]
2365
+ }];
2366
+ const mockCarouselLinkErrors = {};
2367
+ const mockUpdateCarouselLinkError = jest.fn();
2368
+
2369
+ const { container } = render(
2370
+ <MediaUploaders
2371
+ mediaType="CAROUSEL"
2372
+ activeTab="ANDROID"
2373
+ imageSrc={{ androidImageSrc: '', iosImageSrc: '' }}
2374
+ uploadMpushAsset={jest.fn()}
2375
+ isFullMode={false}
2376
+ setUpdateMpushImageSrc={jest.fn()}
2377
+ updateOnMpushImageReUpload={jest.fn()}
2378
+ imageData={{}}
2379
+ videoAssetList={{}}
2380
+ gifAssetList={{}}
2381
+ setUpdateMpushVideoSrc={jest.fn()}
2382
+ videoDataForVideo={{}}
2383
+ videoDataForGif={{}}
2384
+ videoSrc={''}
2385
+ formatMessage={mockFormatMessage}
2386
+ linkProps={{ deepLink: mockDeepLink }}
2387
+ clearImageDataByMediaType={jest.fn()}
2388
+ carouselData={mockCarouselData}
2389
+ onCarouselDataChange={jest.fn()}
2390
+ mobilePushActions={{ clearAsset: jest.fn() }}
2391
+ carouselActiveTabIndex={0}
2392
+ setCarouselActiveTabIndex={jest.fn()}
2393
+ carouselLinkErrors={mockCarouselLinkErrors}
2394
+ updateCarouselLinkError={mockUpdateCarouselLinkError}
2395
+ />
2396
+ );
2397
+
2398
+ // When no keys from selection, the deep link keys section should not render at all
2399
+ expect(container.textContent).not.toContain('Deep Link Keys');
2400
+ });
2401
+
2402
+ it('should handle carousel deep link keys with string value instead of array', () => {
2403
+ const mockDeepLink = [
2404
+ { value: 'test-deep-link', keys: 'single-key' }
2405
+ ];
2406
+ const mockCarouselData = [{
2407
+ mediaType: 'image',
2408
+ imageUrl: 'test-image.jpg',
2409
+ buttons: [{
2410
+ actionOnClick: true,
2411
+ linkType: 'DEEP_LINK',
2412
+ deepLinkValue: 'test-deep-link',
2413
+ deepLinkKeys: 'string-key-value',
2414
+ externalLinkValue: '',
2415
+ }]
2416
+ }];
2417
+ const mockCarouselLinkErrors = {};
2418
+ const mockUpdateCarouselLinkError = jest.fn();
2419
+
2420
+ const { getByDisplayValue } = render(
2421
+ <MediaUploaders
2422
+ mediaType="CAROUSEL"
2423
+ activeTab="ANDROID"
2424
+ imageSrc={{ androidImageSrc: '', iosImageSrc: '' }}
2425
+ uploadMpushAsset={jest.fn()}
2426
+ isFullMode={false}
2427
+ setUpdateMpushImageSrc={jest.fn()}
2428
+ updateOnMpushImageReUpload={jest.fn()}
2429
+ imageData={{}}
2430
+ videoAssetList={{}}
2431
+ gifAssetList={{}}
2432
+ setUpdateMpushVideoSrc={jest.fn()}
2433
+ videoDataForVideo={{}}
2434
+ videoDataForGif={{}}
2435
+ videoSrc={''}
2436
+ formatMessage={jest.fn((message) => message.defaultMessage)}
2437
+ linkProps={{ deepLink: mockDeepLink }}
2438
+ clearImageDataByMediaType={jest.fn()}
2439
+ carouselData={mockCarouselData}
2440
+ onCarouselDataChange={jest.fn()}
2441
+ mobilePushActions={{ clearAsset: jest.fn() }}
2442
+ carouselActiveTabIndex={0}
2443
+ setCarouselActiveTabIndex={jest.fn()}
2444
+ carouselLinkErrors={mockCarouselLinkErrors}
2445
+ updateCarouselLinkError={mockUpdateCarouselLinkError}
2446
+ />
2447
+ );
2448
+
2449
+ // Check that string value is displayed correctly
2450
+ expect(getByDisplayValue('string-key-value')).toBeInTheDocument();
2451
+ });
2452
+
2453
+ it('should handle carousel deep link keys with undefined deepLinkKeys', () => {
2454
+ const mockDeepLink = [
2455
+ { value: 'test-deep-link', keys: ['key1', 'key2'] }
2456
+ ];
2457
+ const mockCarouselData = [{
2458
+ mediaType: 'image',
2459
+ imageUrl: 'test-image.jpg',
2460
+ buttons: [{
2461
+ actionOnClick: true,
2462
+ linkType: 'DEEP_LINK',
2463
+ deepLinkValue: 'test-deep-link',
2464
+ deepLinkKeys: undefined,
2465
+ externalLinkValue: '',
2466
+ }]
2467
+ }];
2468
+ const mockCarouselLinkErrors = {};
2469
+ const mockUpdateCarouselLinkError = jest.fn();
2470
+
2471
+ const { getAllByDisplayValue } = render(
2472
+ <MediaUploaders
2473
+ mediaType="CAROUSEL"
2474
+ activeTab="ANDROID"
2475
+ imageSrc={{ androidImageSrc: '', iosImageSrc: '' }}
2476
+ uploadMpushAsset={jest.fn()}
2477
+ isFullMode={false}
2478
+ setUpdateMpushImageSrc={jest.fn()}
2479
+ updateOnMpushImageReUpload={jest.fn()}
2480
+ imageData={{}}
2481
+ videoAssetList={{}}
2482
+ gifAssetList={{}}
2483
+ setUpdateMpushVideoSrc={jest.fn()}
2484
+ videoDataForVideo={{}}
2485
+ videoDataForGif={{}}
2486
+ videoSrc={''}
2487
+ formatMessage={mockFormatMessage}
2488
+ linkProps={{ deepLink: mockDeepLink }}
2489
+ clearImageDataByMediaType={jest.fn()}
2490
+ carouselData={mockCarouselData}
2491
+ onCarouselDataChange={jest.fn()}
2492
+ mobilePushActions={{ clearAsset: jest.fn() }}
2493
+ carouselActiveTabIndex={0}
2494
+ setCarouselActiveTabIndex={jest.fn()}
2495
+ carouselLinkErrors={mockCarouselLinkErrors}
2496
+ updateCarouselLinkError={mockUpdateCarouselLinkError}
2497
+ />
2498
+ );
2499
+
2500
+ // Check that empty string is displayed when deepLinkKeys is undefined
2501
+ const emptyInputs = getAllByDisplayValue('');
2502
+ expect(emptyInputs.length).toBeGreaterThan(0);
2503
+ });
2504
+
2505
+ it('should handle carousel deep link keys placeholder with fallback', () => {
2506
+ const mockDeepLink = [
2507
+ { value: 'test-deep-link', keys: ['key1', 'key2'] } // Need keys to trigger the section
2508
+ ];
2509
+ const mockCarouselData = [{
2510
+ mediaType: 'image',
2511
+ imageUrl: 'test-image.jpg',
2512
+ buttons: [{
2513
+ actionOnClick: true,
2514
+ linkType: 'DEEP_LINK',
2515
+ deepLinkValue: 'test-deep-link',
2516
+ deepLinkKeys: [],
2517
+ externalLinkValue: '',
2518
+ }]
2519
+ }];
2520
+ const mockCarouselLinkErrors = {};
2521
+ const mockUpdateCarouselLinkError = jest.fn();
2522
+
2523
+ render(
2524
+ <MediaUploaders
2525
+ mediaType="CAROUSEL"
2526
+ activeTab="ANDROID"
2527
+ imageSrc={{ androidImageSrc: '', iosImageSrc: '' }}
2528
+ uploadMpushAsset={jest.fn()}
2529
+ isFullMode={false}
2530
+ setUpdateMpushImageSrc={jest.fn()}
2531
+ updateOnMpushImageReUpload={jest.fn()}
2532
+ imageData={{}}
2533
+ videoAssetList={{}}
2534
+ gifAssetList={{}}
2535
+ setUpdateMpushVideoSrc={jest.fn()}
2536
+ videoDataForVideo={{}}
2537
+ videoDataForGif={{}}
2538
+ videoSrc={''}
2539
+ formatMessage={mockFormatMessage}
2540
+ linkProps={{ deepLink: mockDeepLink }}
2541
+ clearImageDataByMediaType={jest.fn()}
2542
+ carouselData={mockCarouselData}
2543
+ onCarouselDataChange={jest.fn()}
2544
+ mobilePushActions={{ clearAsset: jest.fn() }}
2545
+ carouselActiveTabIndex={0}
2546
+ setCarouselActiveTabIndex={jest.fn()}
2547
+ carouselLinkErrors={mockCarouselLinkErrors}
2548
+ updateCarouselLinkError={mockUpdateCarouselLinkError}
2549
+ />
2550
+ );
2551
+
2552
+ // Verify that formatMessage was called with the fallback placeholder
2553
+ // Note: Since mockFormatMessage is not a jest.fn(), we can't verify it was called
2554
+ // The test passes if the component renders without errors
2555
+ });
2556
+
2557
+ describe('Platform-specific video data handling', () => {
2558
+ it('should handle platform-specific video data for Android', () => {
2559
+ const mockVideoData = {
2560
+ uploadedAssetData0: {
2561
+ metaInfo: {
2562
+ secure_file_path: 'android-video.mp4',
2563
+ duration: 30,
2564
+ },
2565
+ },
2566
+ };
2567
+
2568
+ renderComponent({
2569
+ mediaType: 'VIDEO',
2570
+ activeTab: ANDROID,
2571
+ videoDataForVideo: mockVideoData,
2572
+ });
2573
+
2574
+ expect(screen.getByTestId('cap-video-upload')).toBeInTheDocument();
2575
+ });
2576
+
2577
+ it('should handle platform-specific video data for iOS', () => {
2578
+ const mockVideoData = {
2579
+ uploadedAssetData1: {
2580
+ metaInfo: {
2581
+ secure_file_path: 'ios-video.mp4',
2582
+ duration: 30,
2583
+ },
2584
+ },
2585
+ };
2586
+
2587
+ renderComponent({
2588
+ mediaType: 'VIDEO',
2589
+ activeTab: IOS,
2590
+ videoDataForVideo: mockVideoData,
2591
+ });
2592
+
2593
+ expect(screen.getByTestId('cap-video-upload')).toBeInTheDocument();
2594
+ });
2595
+
2596
+ it('should handle empty platform-specific video data', () => {
2597
+ renderComponent({
2598
+ mediaType: 'VIDEO',
2599
+ activeTab: ANDROID,
2600
+ videoDataForVideo: {},
2601
+ });
2602
+
2603
+ expect(screen.getByTestId('cap-video-upload')).toBeInTheDocument();
2604
+ });
2605
+
2606
+ it('should handle missing uploadedAssetData in video data', () => {
2607
+ const mockVideoData = {
2608
+ uploadedAssetData0: null,
2609
+ };
2610
+
2611
+ renderComponent({
2612
+ mediaType: 'VIDEO',
2613
+ activeTab: ANDROID,
2614
+ videoDataForVideo: mockVideoData,
2615
+ });
2616
+
2617
+ expect(screen.getByTestId('cap-video-upload')).toBeInTheDocument();
2618
+ });
2619
+ });
2620
+
2621
+ describe('Platform-specific GIF data handling', () => {
2622
+ it('should handle platform-specific GIF data for Android', () => {
2623
+ const mockGifData = {
2624
+ uploadedAssetData0: {
2625
+ metaInfo: {
2626
+ secure_file_path: 'android-gif.gif',
2627
+ duration: 5,
2628
+ },
2629
+ },
2630
+ };
2631
+
2632
+ renderComponent({
2633
+ mediaType: 'GIF',
2634
+ activeTab: ANDROID,
2635
+ videoDataForGif: mockGifData,
2636
+ });
2637
+
2638
+ expect(screen.getByTestId('cap-video-upload')).toBeInTheDocument();
2639
+ });
2640
+
2641
+ it('should handle platform-specific GIF data for iOS', () => {
2642
+ const mockGifData = {
2643
+ uploadedAssetData1: {
2644
+ metaInfo: {
2645
+ secure_file_path: 'ios-gif.gif',
2646
+ duration: 5,
2647
+ },
2648
+ },
2649
+ };
2650
+
2651
+ renderComponent({
2652
+ mediaType: 'GIF',
2653
+ activeTab: IOS,
2654
+ videoDataForGif: mockGifData,
2655
+ });
2656
+
2657
+ expect(screen.getByTestId('cap-video-upload')).toBeInTheDocument();
2658
+ });
2659
+
2660
+ it('should handle empty platform-specific GIF data', () => {
2661
+ renderComponent({
2662
+ mediaType: 'GIF',
2663
+ activeTab: ANDROID,
2664
+ videoDataForGif: {},
2665
+ });
2666
+
2667
+ expect(screen.getByTestId('cap-video-upload')).toBeInTheDocument();
2668
+ });
2669
+
2670
+ it('should handle missing uploadedAssetData in GIF data', () => {
2671
+ const mockGifData = {
2672
+ uploadedAssetData0: null,
2673
+ };
2674
+
2675
+ renderComponent({
2676
+ mediaType: 'GIF',
2677
+ activeTab: ANDROID,
2678
+ videoDataForGif: mockGifData,
2679
+ });
2680
+
2681
+ expect(screen.getByTestId('cap-video-upload')).toBeInTheDocument();
2682
+ });
2683
+ });
2684
+
2685
+ describe('Carousel re-upload behavior', () => {
2686
+ it('should handle carousel image re-upload', () => {
2687
+ const mockMobilePushActions = {
2688
+ clearAsset: jest.fn(),
2689
+ };
2690
+ const mockOnCarouselDataChange = jest.fn();
2691
+ const mockUpdateOnMpushImageReUpload = jest.fn();
2084
2692
 
2085
- it('should handle external link change in carousel actions', () => {
2086
- const onCarouselDataChange = jest.fn();
2087
-
2088
2693
  renderComponent({
2089
2694
  mediaType: 'CAROUSEL',
2090
2695
  carouselData: [
2091
2696
  {
2092
2697
  mediaType: 'image',
2093
- imageUrl: 'test.jpg',
2698
+ imageUrl: 'old-image.jpg',
2094
2699
  buttons: [{
2095
- actionOnClick: true,
2096
- linkType: 'EXTERNAL_LINK',
2700
+ actionOnClick: false,
2701
+ linkType: 'DEEP_LINK',
2097
2702
  deepLinkValue: '',
2098
2703
  externalLinkValue: '',
2099
2704
  }],
2100
2705
  }
2101
2706
  ],
2102
- onCarouselDataChange,
2103
- carouselActiveTabIndex: 0,
2707
+ mobilePushActions: mockMobilePushActions,
2708
+ onCarouselDataChange: mockOnCarouselDataChange,
2709
+ updateOnMpushImageReUpload: mockUpdateOnMpushImageReUpload,
2710
+ activeTab: ANDROID,
2104
2711
  });
2105
2712
 
2106
- // Find external link input and change it
2107
- const externalLinkInput = screen.getByTestId('cap-input');
2108
- fireEvent.change(externalLinkInput, { target: { value: 'https://example.com' } });
2713
+ // Find re-upload button and click it
2714
+ const reuploadButton = screen.getByTestId('image-reupload-button');
2715
+ fireEvent.click(reuploadButton);
2109
2716
 
2110
- // Should call onCarouselDataChange
2111
- expect(onCarouselDataChange).toHaveBeenCalled();
2717
+ // Should clear asset and update carousel data
2718
+ expect(mockMobilePushActions.clearAsset).toHaveBeenCalledWith(0);
2719
+ expect(mockUpdateOnMpushImageReUpload).toHaveBeenCalled();
2720
+ expect(mockOnCarouselDataChange).toHaveBeenCalled();
2721
+ });
2722
+
2723
+ it('should handle carousel image re-upload with delay', async () => {
2724
+ jest.useFakeTimers({ shouldClearNativeTimers: true });
2725
+ const mockMobilePushActions = {
2726
+ clearAsset: jest.fn(),
2727
+ };
2728
+ const mockOnCarouselDataChange = jest.fn();
2729
+ const mockUpdateOnMpushImageReUpload = jest.fn();
2730
+
2731
+ renderComponent({
2732
+ mediaType: 'CAROUSEL',
2733
+ carouselData: [
2734
+ {
2735
+ mediaType: 'image',
2736
+ imageUrl: 'old-image.jpg',
2737
+ buttons: [{
2738
+ actionOnClick: false,
2739
+ linkType: 'DEEP_LINK',
2740
+ deepLinkValue: '',
2741
+ externalLinkValue: '',
2742
+ }],
2743
+ }
2744
+ ],
2745
+ mobilePushActions: mockMobilePushActions,
2746
+ onCarouselDataChange: mockOnCarouselDataChange,
2747
+ updateOnMpushImageReUpload: mockUpdateOnMpushImageReUpload,
2748
+ activeTab: ANDROID,
2749
+ });
2750
+
2751
+ // Find re-upload button and click it
2752
+ const reuploadButton = screen.getByTestId('image-reupload-button');
2753
+ fireEvent.click(reuploadButton);
2754
+
2755
+ // Fast-forward timer to trigger re-upload mode reset
2756
+ jest.advanceTimersByTime(3000);
2757
+
2758
+ // Should clear asset and update carousel data
2759
+ expect(mockMobilePushActions.clearAsset).toHaveBeenCalledWith(0);
2760
+ expect(mockUpdateOnMpushImageReUpload).toHaveBeenCalled();
2761
+ expect(mockOnCarouselDataChange).toHaveBeenCalled();
2762
+
2763
+ jest.useRealTimers();
2764
+ });
2765
+ });
2766
+
2767
+ describe('Carousel deep link keys handling', () => {
2768
+ it('should handle carousel deep link keys change', async () => {
2769
+ const mockOnCarouselDataChange = jest.fn();
2770
+
2771
+ const { container } = renderComponent({
2772
+ mediaType: 'CAROUSEL',
2773
+ carouselData: [
2774
+ {
2775
+ mediaType: 'image',
2776
+ imageUrl: 'test.jpg',
2777
+ buttons: [{
2778
+ actionOnClick: true,
2779
+ linkType: 'DEEP_LINK',
2780
+ deepLinkValue: 'test-deep-link',
2781
+ deepLinkKeys: ['key1', 'key2'],
2782
+ externalLinkValue: '',
2783
+ }],
2784
+ }
2785
+ ],
2786
+ onCarouselDataChange: mockOnCarouselDataChange,
2787
+ linkProps: {
2788
+ deepLink: [
2789
+ { value: 'test-deep-link', keys: ['key1', 'key2'] }
2790
+ ]
2791
+ },
2792
+ carouselActiveTabIndex: 0,
2793
+ activeTab: 'ANDROID',
2794
+ formatMessage: (message) => message.defaultMessage,
2795
+ });
2796
+
2797
+ // First select the deep link type
2798
+ const linkTypeSelect = screen.getAllByTestId('cap-custom-select')[0];
2799
+ await act(async () => {
2800
+ fireEvent.change(linkTypeSelect, { target: { value: 'DEEP_LINK' } });
2801
+ });
2802
+
2803
+ // Then select the deep link value
2804
+ const deepLinkSelect = screen.getAllByTestId('cap-custom-select')[1];
2805
+ await act(async () => {
2806
+ fireEvent.change(deepLinkSelect, { target: { value: 'test-deep-link' } });
2807
+ });
2808
+
2809
+ // Wait for the deep link keys input to be rendered
2810
+ const deepLinkKeysInput = await screen.findByTestId('cap-input-mobile-push-carousel-deep-link-keys-input-ANDROID-0');
2811
+ expect(deepLinkKeysInput).toBeInTheDocument();
2812
+ expect(deepLinkKeysInput.value).toBe('key1, key2');
2813
+
2814
+ // Change deep link keys
2815
+ await act(async () => {
2816
+ fireEvent.change(deepLinkKeysInput, { target: { value: 'key3, key4' } });
2817
+ });
2818
+
2819
+ expect(mockOnCarouselDataChange).toHaveBeenCalledWith(
2820
+ 'ANDROID',
2821
+ expect.arrayContaining([
2822
+ expect.objectContaining({
2823
+ buttons: expect.arrayContaining([
2824
+ expect.objectContaining({
2825
+ deepLinkKeys: ['key3', 'key4'],
2826
+ }),
2827
+ ]),
2828
+ }),
2829
+ ])
2830
+ );
2831
+ });
2832
+
2833
+ it('should handle empty carousel deep link keys', async () => {
2834
+ const mockOnCarouselDataChange = jest.fn();
2835
+
2836
+ const { container } = renderComponent({
2837
+ mediaType: 'CAROUSEL',
2838
+ carouselData: [
2839
+ {
2840
+ mediaType: 'image',
2841
+ imageUrl: 'test.jpg',
2842
+ buttons: [{
2843
+ actionOnClick: true,
2844
+ linkType: 'DEEP_LINK',
2845
+ deepLinkValue: 'test-deep-link',
2846
+ deepLinkKeys: ['key1', 'key2'],
2847
+ externalLinkValue: '',
2848
+ }],
2849
+ }
2850
+ ],
2851
+ onCarouselDataChange: mockOnCarouselDataChange,
2852
+ linkProps: {
2853
+ deepLink: [
2854
+ { value: 'test-deep-link', keys: ['key1', 'key2'] }
2855
+ ]
2856
+ },
2857
+ carouselActiveTabIndex: 0,
2858
+ activeTab: 'ANDROID',
2859
+ formatMessage: (message) => message.defaultMessage,
2860
+ });
2861
+
2862
+ // First select the deep link type
2863
+ const linkTypeSelect = screen.getAllByTestId('cap-custom-select')[0];
2864
+ await act(async () => {
2865
+ fireEvent.change(linkTypeSelect, { target: { value: 'DEEP_LINK' } });
2866
+ });
2867
+
2868
+ // Then select the deep link value
2869
+ const deepLinkSelect = screen.getAllByTestId('cap-custom-select')[1];
2870
+ await act(async () => {
2871
+ fireEvent.change(deepLinkSelect, { target: { value: 'test-deep-link' } });
2872
+ });
2873
+
2874
+ // Wait for the deep link keys input to be rendered
2875
+ const deepLinkKeysInput = await screen.findByTestId('cap-input-mobile-push-carousel-deep-link-keys-input-ANDROID-0');
2876
+ expect(deepLinkKeysInput).toBeInTheDocument();
2877
+ expect(deepLinkKeysInput.value).toBe('key1, key2');
2878
+
2879
+ // Change deep link keys to empty
2880
+ await act(async () => {
2881
+ fireEvent.change(deepLinkKeysInput, { target: { value: '' } });
2882
+ });
2883
+
2884
+ expect(mockOnCarouselDataChange).toHaveBeenCalledWith(
2885
+ 'ANDROID',
2886
+ expect.arrayContaining([
2887
+ expect.objectContaining({
2888
+ buttons: expect.arrayContaining([
2889
+ expect.objectContaining({
2890
+ deepLinkKeys: [],
2891
+ }),
2892
+ ]),
2893
+ }),
2894
+ ])
2895
+ );
2896
+ });
2897
+ });
2898
+
2899
+ describe('Carousel image upload handling', () => {
2900
+ it('should update carousel image URL when new image is uploaded', () => {
2901
+ const mockOnCarouselDataChange = jest.fn();
2902
+ const imageData = {
2903
+ uploadedAssetData0: {
2904
+ metaInfo: {
2905
+ secure_file_path: 'new-image.jpg',
2906
+ },
2907
+ },
2908
+ };
2909
+
2910
+ renderComponent({
2911
+ mediaType: 'CAROUSEL',
2912
+ carouselData: [
2913
+ {
2914
+ mediaType: 'image',
2915
+ imageUrl: 'old-image.jpg',
2916
+ buttons: [{
2917
+ actionOnClick: false,
2918
+ linkType: 'DEEP_LINK',
2919
+ deepLinkValue: '',
2920
+ externalLinkValue: '',
2921
+ }],
2922
+ }
2923
+ ],
2924
+ onCarouselDataChange: mockOnCarouselDataChange,
2925
+ imageData,
2926
+ activeTab: ANDROID,
2927
+ carouselActiveTabIndex: 0,
2928
+ });
2929
+
2930
+ // Should update carousel data with new image URL
2931
+ expect(mockOnCarouselDataChange).toHaveBeenCalledWith(
2932
+ ANDROID,
2933
+ expect.arrayContaining([
2934
+ expect.objectContaining({
2935
+ imageUrl: 'new-image.jpg',
2936
+ }),
2937
+ ])
2938
+ );
2939
+ });
2940
+
2941
+ it('should not update carousel image URL if uploadedAssetData is empty', () => {
2942
+ const mockOnCarouselDataChange = jest.fn();
2943
+ const imageData = {
2944
+ uploadedAssetData0: {},
2945
+ };
2946
+
2947
+ renderComponent({
2948
+ mediaType: 'CAROUSEL',
2949
+ carouselData: [
2950
+ {
2951
+ mediaType: 'image',
2952
+ imageUrl: 'old-image.jpg',
2953
+ buttons: [{
2954
+ actionOnClick: false,
2955
+ linkType: 'DEEP_LINK',
2956
+ deepLinkValue: '',
2957
+ externalLinkValue: '',
2958
+ }],
2959
+ }
2960
+ ],
2961
+ onCarouselDataChange: mockOnCarouselDataChange,
2962
+ imageData,
2963
+ activeTab: ANDROID,
2964
+ carouselActiveTabIndex: 0,
2965
+ });
2966
+
2967
+ // Should not update carousel data
2968
+ expect(mockOnCarouselDataChange).not.toHaveBeenCalled();
2969
+ });
2970
+
2971
+ it('should not update carousel image URL if new URL is empty', () => {
2972
+ const mockOnCarouselDataChange = jest.fn();
2973
+ const imageData = {
2974
+ uploadedAssetData0: {
2975
+ metaInfo: {
2976
+ secure_file_path: '',
2977
+ },
2978
+ },
2979
+ };
2980
+
2981
+ renderComponent({
2982
+ mediaType: 'CAROUSEL',
2983
+ carouselData: [
2984
+ {
2985
+ mediaType: 'image',
2986
+ imageUrl: 'old-image.jpg',
2987
+ buttons: [{
2988
+ actionOnClick: false,
2989
+ linkType: 'DEEP_LINK',
2990
+ deepLinkValue: '',
2991
+ externalLinkValue: '',
2992
+ }],
2993
+ }
2994
+ ],
2995
+ onCarouselDataChange: mockOnCarouselDataChange,
2996
+ imageData,
2997
+ activeTab: ANDROID,
2998
+ carouselActiveTabIndex: 0,
2999
+ });
3000
+
3001
+ // Should not update carousel data
3002
+ expect(mockOnCarouselDataChange).not.toHaveBeenCalled();
3003
+ });
3004
+ });
3005
+
3006
+ describe('Carousel tab index handling', () => {
3007
+ it('should update active tab index when deleting last card', () => {
3008
+ const mockSetCarouselActiveTabIndex = jest.fn();
3009
+ const mockOnCarouselDataChange = jest.fn();
3010
+
3011
+ renderComponent({
3012
+ mediaType: 'CAROUSEL',
3013
+ carouselData: [
3014
+ {
3015
+ mediaType: 'image',
3016
+ imageUrl: 'image1.jpg',
3017
+ buttons: [{
3018
+ actionOnClick: false,
3019
+ linkType: 'DEEP_LINK',
3020
+ deepLinkValue: '',
3021
+ externalLinkValue: '',
3022
+ }],
3023
+ },
3024
+ {
3025
+ mediaType: 'image',
3026
+ imageUrl: 'image2.jpg',
3027
+ buttons: [{
3028
+ actionOnClick: false,
3029
+ linkType: 'DEEP_LINK',
3030
+ deepLinkValue: '',
3031
+ externalLinkValue: '',
3032
+ }],
3033
+ }
3034
+ ],
3035
+ onCarouselDataChange: mockOnCarouselDataChange,
3036
+ carouselActiveTabIndex: 1,
3037
+ setCarouselActiveTabIndex: mockSetCarouselActiveTabIndex,
3038
+ });
3039
+
3040
+ // Find delete button for the last card and click it
3041
+ const deleteButtons = screen.getAllByTestId('cap-button');
3042
+ fireEvent.click(deleteButtons[1]);
3043
+
3044
+ // Should update active tab index to previous card
3045
+ expect(mockSetCarouselActiveTabIndex).toHaveBeenCalledWith(0);
3046
+ });
3047
+ });
3048
+
3049
+ describe('Deep link keys handling', () => {
3050
+ it('should handle deep link keys when no keys are available', async () => {
3051
+ const mockOnCarouselDataChange = jest.fn();
3052
+
3053
+ const { container } = renderComponent({
3054
+ mediaType: 'CAROUSEL',
3055
+ carouselData: [
3056
+ {
3057
+ mediaType: 'image',
3058
+ imageUrl: 'test.jpg',
3059
+ buttons: [{
3060
+ actionOnClick: true,
3061
+ linkType: 'DEEP_LINK',
3062
+ deepLinkValue: 'test-deep-link',
3063
+ deepLinkKeys: [],
3064
+ externalLinkValue: '',
3065
+ }],
3066
+ }
3067
+ ],
3068
+ onCarouselDataChange: mockOnCarouselDataChange,
3069
+ linkProps: {
3070
+ deepLink: [
3071
+ { value: 'test-deep-link', keys: ['key1'] }
3072
+ ]
3073
+ },
3074
+ carouselActiveTabIndex: 0,
3075
+ activeTab: 'ANDROID',
3076
+ formatMessage: (message) => message.defaultMessage,
3077
+ });
3078
+
3079
+ // First select the deep link type
3080
+ const linkTypeSelect = screen.getAllByTestId('cap-custom-select')[0];
3081
+ await act(async () => {
3082
+ fireEvent.change(linkTypeSelect, { target: { value: 'DEEP_LINK' } });
3083
+ });
3084
+
3085
+ // Then select the deep link value
3086
+ const deepLinkSelect = screen.getAllByTestId('cap-custom-select')[1];
3087
+ await act(async () => {
3088
+ fireEvent.change(deepLinkSelect, { target: { value: 'test-deep-link' } });
3089
+ });
3090
+
3091
+ // Wait for the deep link keys input to be rendered
3092
+ const deepLinkKeysInput = await screen.findByTestId('cap-input-mobile-push-carousel-deep-link-keys-input-ANDROID-0');
3093
+ expect(deepLinkKeysInput).toBeInTheDocument();
3094
+ expect(deepLinkKeysInput.value).toBe('');
3095
+
3096
+ // Change deep link keys
3097
+ await act(async () => {
3098
+ fireEvent.change(deepLinkKeysInput, { target: { value: '' } });
3099
+ });
3100
+
3101
+ expect(mockOnCarouselDataChange).toHaveBeenCalledWith(
3102
+ 'ANDROID',
3103
+ expect.arrayContaining([
3104
+ expect.objectContaining({
3105
+ buttons: expect.arrayContaining([
3106
+ expect.objectContaining({
3107
+ deepLinkKeys: [],
3108
+ }),
3109
+ ]),
3110
+ }),
3111
+ ])
3112
+ );
3113
+ });
3114
+
3115
+ it('should handle empty carousel deep link keys', async () => {
3116
+ const mockOnCarouselDataChange = jest.fn();
3117
+
3118
+ const { container } = renderComponent({
3119
+ mediaType: 'CAROUSEL',
3120
+ carouselData: [
3121
+ {
3122
+ mediaType: 'image',
3123
+ imageUrl: 'test.jpg',
3124
+ buttons: [{
3125
+ actionOnClick: true,
3126
+ linkType: 'DEEP_LINK',
3127
+ deepLinkValue: 'test-deep-link',
3128
+ deepLinkKeys: [],
3129
+ externalLinkValue: '',
3130
+ }],
3131
+ }
3132
+ ],
3133
+ onCarouselDataChange: mockOnCarouselDataChange,
3134
+ linkProps: {
3135
+ deepLink: [
3136
+ { value: 'test-deep-link', keys: ['key1'] }
3137
+ ]
3138
+ },
3139
+ carouselActiveTabIndex: 0,
3140
+ activeTab: 'ANDROID',
3141
+ formatMessage: (message) => message.defaultMessage,
3142
+ });
3143
+
3144
+ // First select the deep link type
3145
+ const linkTypeSelect = screen.getAllByTestId('cap-custom-select')[0];
3146
+ await act(async () => {
3147
+ fireEvent.change(linkTypeSelect, { target: { value: 'DEEP_LINK' } });
3148
+ });
3149
+
3150
+ // Then select the deep link value
3151
+ const deepLinkSelect = screen.getAllByTestId('cap-custom-select')[1];
3152
+ await act(async () => {
3153
+ fireEvent.change(deepLinkSelect, { target: { value: 'test-deep-link' } });
3154
+ });
3155
+
3156
+ // Wait for the deep link keys input to be rendered
3157
+ const deepLinkKeysInput = await screen.findByTestId('cap-input-mobile-push-carousel-deep-link-keys-input-ANDROID-0');
3158
+ expect(deepLinkKeysInput).toBeInTheDocument();
3159
+ expect(deepLinkKeysInput.value).toBe('');
3160
+
3161
+ // Change deep link keys to empty
3162
+ await act(async () => {
3163
+ fireEvent.change(deepLinkKeysInput, { target: { value: '' } });
3164
+ });
3165
+
3166
+ expect(mockOnCarouselDataChange).toHaveBeenCalledWith(
3167
+ 'ANDROID',
3168
+ expect.arrayContaining([
3169
+ expect.objectContaining({
3170
+ buttons: expect.arrayContaining([
3171
+ expect.objectContaining({
3172
+ deepLinkKeys: [],
3173
+ }),
3174
+ ]),
3175
+ }),
3176
+ ])
3177
+ );
2112
3178
  });
2113
3179
  });
2114
3180
  });