@capillarytech/creatives-library 8.0.345-alpha.13 → 8.0.345-alpha.15

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 (138) hide show
  1. package/constants/unified.js +29 -0
  2. package/package.json +1 -1
  3. package/services/api.js +0 -20
  4. package/services/tests/api.test.js +13 -59
  5. package/utils/commonUtils.js +19 -1
  6. package/utils/rcsPayloadUtils.js +92 -0
  7. package/utils/templateVarUtils.js +201 -0
  8. package/utils/tests/templateVarUtils.test.js +204 -0
  9. package/v2Components/CapActionButton/constants.js +7 -0
  10. package/v2Components/CapActionButton/index.js +167 -109
  11. package/v2Components/CapActionButton/index.scss +157 -6
  12. package/v2Components/CapActionButton/messages.js +19 -3
  13. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  14. package/v2Components/CapCustomSkeleton/index.js +1 -1
  15. package/v2Components/CapCustomSkeleton/tests/__snapshots__/index.test.js.snap +12 -12
  16. package/v2Components/CapTagList/index.js +10 -0
  17. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
  22. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  24. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  25. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +160 -15
  26. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +18 -0
  27. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +341 -76
  28. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
  29. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  30. package/v2Components/CommonTestAndPreview/constants.js +38 -2
  31. package/v2Components/CommonTestAndPreview/index.js +676 -186
  32. package/v2Components/CommonTestAndPreview/messages.js +49 -3
  33. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  34. package/v2Components/CommonTestAndPreview/sagas.js +15 -6
  35. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
  36. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  37. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  38. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  39. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  40. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  41. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  42. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  43. package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
  44. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  45. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  46. package/v2Components/FormBuilder/index.js +8 -10
  47. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
  48. package/v2Components/SmsFallback/constants.js +73 -0
  49. package/v2Components/SmsFallback/index.js +955 -0
  50. package/v2Components/SmsFallback/index.scss +265 -0
  51. package/v2Components/SmsFallback/messages.js +78 -0
  52. package/v2Components/SmsFallback/smsFallbackUtils.js +118 -0
  53. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  54. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  55. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  56. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
  57. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +277 -0
  58. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  59. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  60. package/v2Components/TemplatePreview/_templatePreview.scss +33 -23
  61. package/v2Components/TemplatePreview/constants.js +2 -0
  62. package/v2Components/TemplatePreview/index.js +143 -28
  63. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  64. package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
  65. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  66. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  67. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  68. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  69. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  70. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  71. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  72. package/v2Containers/CreativesContainer/SlideBoxFooter.js +11 -4
  73. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  74. package/v2Containers/CreativesContainer/constants.js +9 -0
  75. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
  76. package/v2Containers/CreativesContainer/index.js +300 -108
  77. package/v2Containers/CreativesContainer/index.scss +51 -1
  78. package/v2Containers/CreativesContainer/messages.js +0 -4
  79. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  80. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +78 -34
  81. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  82. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  83. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
  84. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -18
  85. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  86. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  87. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  88. package/v2Containers/Rcs/constants.js +119 -8
  89. package/v2Containers/Rcs/index.js +2379 -807
  90. package/v2Containers/Rcs/index.js.rej +1336 -0
  91. package/v2Containers/Rcs/index.scss +276 -6
  92. package/v2Containers/Rcs/index.scss.rej +74 -0
  93. package/v2Containers/Rcs/messages.js +38 -3
  94. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
  95. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98018 -70073
  96. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  97. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +128 -0
  98. package/v2Containers/Rcs/tests/index.test.js +152 -121
  99. package/v2Containers/Rcs/tests/mockData.js +38 -0
  100. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  101. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  102. package/v2Containers/Rcs/utils.js +478 -11
  103. package/v2Containers/Sms/Create/index.js +100 -40
  104. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  105. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  106. package/v2Containers/SmsTrai/Create/index.js +9 -4
  107. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  108. package/v2Containers/SmsTrai/Edit/index.js +636 -130
  109. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  110. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  111. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
  112. package/v2Containers/SmsWrapper/index.js +37 -8
  113. package/v2Containers/TagList/index.js +6 -0
  114. package/v2Containers/Templates/ChannelTypeIllustration.js +6 -23
  115. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  116. package/v2Containers/Templates/_templates.scss +181 -126
  117. package/v2Containers/Templates/actions.js +11 -36
  118. package/v2Containers/Templates/constants.js +2 -23
  119. package/v2Containers/Templates/index.js +142 -333
  120. package/v2Containers/Templates/messages.js +0 -68
  121. package/v2Containers/Templates/reducer.js +0 -68
  122. package/v2Containers/Templates/sagas.js +55 -98
  123. package/v2Containers/Templates/selectors.js +0 -12
  124. package/v2Containers/Templates/tests/ChannelTypeIllustration.test.js +0 -12
  125. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  126. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1042 -1256
  127. package/v2Containers/Templates/tests/index.test.js +0 -6
  128. package/v2Containers/Templates/tests/reducer.test.js +0 -178
  129. package/v2Containers/Templates/tests/sagas.test.js +200 -436
  130. package/v2Containers/Templates/tests/selector.test.js +0 -32
  131. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  132. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  133. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  134. package/v2Containers/TemplatesV2/index.js +86 -23
  135. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  136. package/v2Containers/Whatsapp/index.js +3 -20
  137. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
  138. package/v2Containers/Assets/images/archive_Empty_Illustration.svg +0 -9
@@ -8,7 +8,7 @@ import React from 'react';
8
8
  import { render, screen } from '@testing-library/react';
9
9
  import { IntlProvider, injectIntl } from 'react-intl';
10
10
  import RcsPreviewContent from '../../UnifiedPreview/RcsPreviewContent';
11
- import { ANDROID, IOS } from '../../constants';
11
+ import { ANDROID, IOS, MEDIA_TYPE_VIDEO } from '../../constants';
12
12
  import messages from '../../messages';
13
13
 
14
14
  // Mock constants
@@ -42,6 +42,13 @@ const TestWrapper = ({ children }) => (
42
42
  </IntlProvider>
43
43
  );
44
44
 
45
+ const renderRcsPreview = (props) =>
46
+ render(
47
+ <TestWrapper>
48
+ <ComponentToRender {...props} />
49
+ </TestWrapper>,
50
+ );
51
+
45
52
  describe('RcsPreviewContent', () => {
46
53
  const defaultProps = {
47
54
  content: {
@@ -60,33 +67,10 @@ describe('RcsPreviewContent', () => {
60
67
  });
61
68
 
62
69
  describe('Loading State', () => {
63
- it('should render loading state when isUpdating is true', () => {
64
- const props = {
65
- ...defaultProps,
66
- isUpdating: true,
67
- };
68
-
69
- render(
70
- <TestWrapper>
71
- <ComponentToRender {...props} />
72
- </TestWrapper>
73
- );
74
-
70
+ it('should render loading state and spinner when isUpdating is true', () => {
71
+ const props = { ...defaultProps, isUpdating: true };
72
+ const { container } = renderRcsPreview(props);
75
73
  expect(screen.getByText('Updating preview with the latest changes')).toBeTruthy();
76
- });
77
-
78
- it('should show loading spinner', () => {
79
- const props = {
80
- ...defaultProps,
81
- isUpdating: true,
82
- };
83
-
84
- const { container } = render(
85
- <TestWrapper>
86
- <ComponentToRender {...props} />
87
- </TestWrapper>
88
- );
89
-
90
74
  expect(container.querySelector('.ant-spin')).toBeTruthy();
91
75
  });
92
76
  });
@@ -98,50 +82,16 @@ describe('RcsPreviewContent', () => {
98
82
  error: 'Failed to load preview',
99
83
  };
100
84
 
101
- render(
102
- <TestWrapper>
103
- <ComponentToRender {...props} />
104
- </TestWrapper>
105
- );
85
+ renderRcsPreview(props);
106
86
 
107
87
  expect(screen.getAllByText('Failed to load preview')).toHaveLength(2);
108
88
  });
109
89
  });
110
90
 
111
91
  describe('Text Preview Content', () => {
112
- it('should render templateHeader as title', () => {
113
- const props = {
114
- ...defaultProps,
115
- content: {
116
- templateHeader: 'RCS Title',
117
- templateMessage: 'RCS Description',
118
- },
119
- };
120
-
121
- render(
122
- <TestWrapper>
123
- <ComponentToRender {...props} />
124
- </TestWrapper>
125
- );
126
-
92
+ it('should render templateHeader and templateMessage (default text content)', () => {
93
+ renderRcsPreview(defaultProps);
127
94
  expect(screen.getByText('RCS Title')).toBeTruthy();
128
- });
129
-
130
- it('should render templateMessage as description', () => {
131
- const props = {
132
- ...defaultProps,
133
- content: {
134
- templateHeader: 'RCS Title',
135
- templateMessage: 'RCS Description',
136
- },
137
- };
138
-
139
- render(
140
- <TestWrapper>
141
- <ComponentToRender {...props} />
142
- </TestWrapper>
143
- );
144
-
145
95
  expect(screen.getByText('RCS Description')).toBeTruthy();
146
96
  });
147
97
 
@@ -154,11 +104,7 @@ describe('RcsPreviewContent', () => {
154
104
  },
155
105
  };
156
106
 
157
- render(
158
- <TestWrapper>
159
- <ComponentToRender {...props} />
160
- </TestWrapper>
161
- );
107
+ renderRcsPreview(props);
162
108
 
163
109
  expect(screen.getByText('Fallback Title')).toBeTruthy();
164
110
  });
@@ -172,30 +118,13 @@ describe('RcsPreviewContent', () => {
172
118
  },
173
119
  };
174
120
 
175
- render(
176
- <TestWrapper>
177
- <ComponentToRender {...props} />
178
- </TestWrapper>
179
- );
121
+ renderRcsPreview(props);
180
122
 
181
123
  expect(screen.getByText('Fallback Description')).toBeTruthy();
182
124
  });
183
125
 
184
126
  it('should render divider after title', () => {
185
- const props = {
186
- ...defaultProps,
187
- content: {
188
- templateHeader: 'Title',
189
- templateMessage: 'Description',
190
- },
191
- };
192
-
193
- const { container } = render(
194
- <TestWrapper>
195
- <ComponentToRender {...props} />
196
- </TestWrapper>
197
- );
198
-
127
+ const { container } = renderRcsPreview(defaultProps);
199
128
  expect(container.querySelector('.whatsapp-divider')).toBeTruthy();
200
129
  });
201
130
 
@@ -207,11 +136,7 @@ describe('RcsPreviewContent', () => {
207
136
  },
208
137
  };
209
138
 
210
- render(
211
- <TestWrapper>
212
- <ComponentToRender {...props} />
213
- </TestWrapper>
214
- );
139
+ renderRcsPreview(props);
215
140
 
216
141
  expect(screen.queryByText(/Title/)).toBeFalsy();
217
142
  });
@@ -226,11 +151,7 @@ describe('RcsPreviewContent', () => {
226
151
  },
227
152
  };
228
153
 
229
- const { container } = render(
230
- <TestWrapper>
231
- <ComponentToRender {...props} />
232
- </TestWrapper>
233
- );
154
+ const { container } = renderRcsPreview(props);
234
155
 
235
156
  const image = container.querySelector('.rcs-image');
236
157
  expect(image).toBeTruthy();
@@ -246,11 +167,7 @@ describe('RcsPreviewContent', () => {
246
167
  },
247
168
  };
248
169
 
249
- const { container } = render(
250
- <TestWrapper>
251
- <ComponentToRender {...props} />
252
- </TestWrapper>
253
- );
170
+ const { container } = renderRcsPreview(props);
254
171
 
255
172
  expect(container.querySelector('.video-preview')).toBeTruthy();
256
173
  expect(container.querySelector('.video-icon')).toBeTruthy();
@@ -264,11 +181,7 @@ describe('RcsPreviewContent', () => {
264
181
  },
265
182
  };
266
183
 
267
- const { container } = render(
268
- <TestWrapper>
269
- <ComponentToRender {...props} />
270
- </TestWrapper>
271
- );
184
+ const { container } = renderRcsPreview(props);
272
185
 
273
186
  const image = container.querySelector('.rcs-image');
274
187
  expect(image.getAttribute('src')).toBe('https://video.url');
@@ -284,198 +197,334 @@ describe('RcsPreviewContent', () => {
284
197
  },
285
198
  };
286
199
 
287
- render(
288
- <TestWrapper>
289
- <ComponentToRender {...props} />
290
- </TestWrapper>
291
- );
200
+ renderRcsPreview(props);
292
201
 
293
202
  expect(screen.getByText('Title')).toBeTruthy();
294
203
  expect(screen.getByText('Description')).toBeTruthy();
295
204
  });
296
205
  });
297
206
 
298
- describe('RCS Suggestions', () => {
299
- it('should render QUICK_REPLY suggestion', () => {
207
+ describe('Carousel Preview Content', () => {
208
+ it('should render carousel cards when carouselData is present', () => {
300
209
  const props = {
301
210
  ...defaultProps,
302
211
  content: {
303
- templateHeader: 'Title',
304
- templateMessage: 'Description',
305
- suggestions: [
306
- { type: 'QUICK_REPLY', text: 'Yes' },
212
+ carouselData: [
213
+ {
214
+ mediaType: 'image',
215
+ imageSrc: 'https://image.1/url',
216
+ title: 'Card 1',
217
+ bodyText: 'Body 1',
218
+ suggestions: [{ type: 'CTA', text: 'Visit' }],
219
+ },
220
+ {
221
+ mediaType: 'video',
222
+ videoPreviewImg: 'https://thumb.2/url',
223
+ title: 'Card 2',
224
+ bodyText: 'Body 2',
225
+ suggestions: [{ type: 'QUICK_REPLY', text: 'Yes' }],
226
+ },
307
227
  ],
308
228
  },
309
229
  };
310
230
 
311
- const { container } = render(
312
- <TestWrapper>
313
- <ComponentToRender {...props} />
314
- </TestWrapper>
315
- );
231
+ const { container } = renderRcsPreview(props);
232
+
233
+ expect(container.querySelectorAll('.message-pop-carousel').length).toBe(2);
234
+ expect(container.querySelector('.scroll-container')).toBeTruthy();
235
+ expect(screen.getByText('Card 1')).toBeTruthy();
236
+ expect(screen.getByText('Card 2')).toBeTruthy();
237
+ expect(container.querySelector('.video-preview')).toBeTruthy();
238
+ expect(container.querySelector('.video-icon')).toBeTruthy();
239
+ expect(screen.getByText('Visit')).toBeTruthy();
316
240
  expect(screen.getByText('Yes')).toBeTruthy();
317
- expect(container.querySelector('.cap-icon-v2-small-link')).toBeTruthy();
241
+ expect(container.querySelectorAll('.rcs-carousel-cta-stack').length).toBe(2);
242
+ expect(container.querySelector('.rcs-cta-preview--carousel-bar')).toBeTruthy();
318
243
  });
319
244
 
320
- it('should render CTA suggestion', () => {
245
+ it('should apply carouselPreviewDimensions as aspect-ratio on image and video media wraps', () => {
321
246
  const props = {
322
247
  ...defaultProps,
323
248
  content: {
324
- templateHeader: 'Title',
325
- templateMessage: 'Description',
326
- suggestions: [
327
- { type: 'CTA', text: 'Visit Website' },
249
+ carouselPreviewDimensions: {
250
+ imageWidth: 1160,
251
+ imageHeight: 720,
252
+ videoThumbWidth: 718,
253
+ videoThumbHeight: 448,
254
+ },
255
+ carouselData: [
256
+ {
257
+ mediaType: 'image',
258
+ imageSrc: 'https://image.1/url',
259
+ title: 'Card 1',
260
+ bodyText: 'Body 1',
261
+ suggestions: [],
262
+ },
263
+ {
264
+ mediaType: 'video',
265
+ videoPreviewImg: 'https://thumb.2/url',
266
+ title: 'Card 2',
267
+ bodyText: 'Body 2',
268
+ suggestions: [],
269
+ },
328
270
  ],
329
271
  },
330
272
  };
273
+ const { container } = renderRcsPreview(props);
274
+ const wraps = container.querySelectorAll('.whatsapp-image.rcs-carousel-media-wrap');
275
+ expect(wraps.length).toBe(2);
276
+ expect(wraps[0].style.aspectRatio).toBe('1160 / 720');
277
+ expect(wraps[1].style.aspectRatio).toBe('718 / 448');
278
+ });
331
279
 
332
- const { container } = render(
333
- <TestWrapper>
334
- <ComponentToRender {...props} />
335
- </TestWrapper>
336
- );
280
+ it('should use text preview when carouselData is not an array', () => {
281
+ const props = {
282
+ ...defaultProps,
283
+ content: {
284
+ carouselData: { invalid: true },
285
+ templateHeader: 'Fallback title',
286
+ templateMessage: 'Fallback body',
287
+ },
288
+ };
289
+ const { container } = renderRcsPreview(props);
290
+ expect(container.querySelector('.scroll-container')).toBeFalsy();
291
+ expect(screen.getByText('Fallback title')).toBeTruthy();
292
+ expect(screen.getByText('Fallback body')).toBeTruthy();
293
+ });
337
294
 
338
- expect(screen.getByText('Visit Website')).toBeTruthy();
295
+ it('should use text preview when carouselData is an empty array', () => {
296
+ const props = {
297
+ ...defaultProps,
298
+ content: {
299
+ carouselData: [],
300
+ templateHeader: 'No cards',
301
+ templateMessage: 'Use text',
302
+ },
303
+ };
304
+ const { container } = renderRcsPreview(props);
305
+ expect(container.querySelector('.scroll-container')).toBeFalsy();
306
+ expect(screen.getByText('No cards')).toBeTruthy();
339
307
  });
340
308
 
341
- it('should render PHONE_NUMBER suggestion', () => {
309
+ it('should render image branch when mediaType is undefined (not video)', () => {
342
310
  const props = {
343
311
  ...defaultProps,
344
312
  content: {
345
- templateHeader: 'Title',
346
- templateMessage: 'Description',
347
- suggestions: [
348
- { type: 'PHONE_NUMBER', text: 'Call Us' },
313
+ carouselData: [
314
+ {
315
+ title: 'T',
316
+ bodyText: 'B',
317
+ imageSrc: '',
318
+ suggestions: [],
319
+ },
349
320
  ],
350
321
  },
351
322
  };
352
-
353
- const { container } = render(
354
- <TestWrapper>
355
- <ComponentToRender {...props} />
356
- </TestWrapper>
357
- );
358
-
359
- expect(screen.getByText('Call Us')).toBeTruthy();
323
+ const { container } = renderRcsPreview(props);
324
+ expect(container.querySelector('.video-preview')).toBeFalsy();
325
+ const img = container.querySelector('.rcs-carousel-img');
326
+ expect(img).toBeTruthy();
327
+ expect(img.getAttribute('src')).toBeTruthy();
360
328
  });
361
329
 
362
- it('should render multiple suggestions', () => {
330
+ it('should treat MEDIA_TYPE_VIDEO as video regardless of case', () => {
363
331
  const props = {
364
332
  ...defaultProps,
365
333
  content: {
366
- templateHeader: 'Title',
367
- templateMessage: 'Description',
368
- suggestions: [
369
- { type: 'QUICK_REPLY', text: 'Yes' },
370
- { type: 'CTA', text: 'Visit' },
371
- { type: 'PHONE_NUMBER', text: 'Call' },
334
+ carouselData: [
335
+ {
336
+ mediaType: MEDIA_TYPE_VIDEO,
337
+ videoPreviewImg: 'https://thumb/video',
338
+ title: 'Vid',
339
+ bodyText: 'Clip',
340
+ suggestions: [],
341
+ },
372
342
  ],
373
343
  },
374
344
  };
375
-
376
- render(
377
- <TestWrapper>
378
- <ComponentToRender {...props} />
379
- </TestWrapper>
345
+ const { container } = renderRcsPreview(props);
346
+ expect(container.querySelector('.video-preview')).toBeTruthy();
347
+ expect(container.querySelector('.whatsapp-image.rcs-carousel-media-wrap .rcs-carousel-img')?.getAttribute('src')).toBe(
348
+ 'https://thumb/video',
380
349
  );
381
-
382
- expect(screen.getByText('Yes')).toBeTruthy();
383
- expect(screen.getByText('Visit')).toBeTruthy();
384
- expect(screen.getByText('Call')).toBeTruthy();
385
350
  });
386
351
 
387
- it('should render dividers between suggestions', () => {
352
+ it('should use empty video placeholder when video card has no videoPreviewImg', () => {
388
353
  const props = {
389
354
  ...defaultProps,
390
355
  content: {
391
- templateHeader: 'Title',
392
- templateMessage: 'Description',
393
- suggestions: [
394
- { type: 'QUICK_REPLY', text: 'Yes' },
395
- { type: 'CTA', text: 'Visit' },
356
+ carouselData: [
357
+ {
358
+ mediaType: 'video',
359
+ title: 'V',
360
+ bodyText: 'No thumb',
361
+ suggestions: [],
362
+ },
396
363
  ],
397
364
  },
398
365
  };
399
-
400
- const { container } = render(
401
- <TestWrapper>
402
- <ComponentToRender {...props} />
403
- </TestWrapper>
404
- );
405
-
406
- const dividers = container.querySelectorAll('.whatsapp-divider');
407
- expect(dividers.length).toBeGreaterThan(0);
366
+ const { container } = renderRcsPreview(props);
367
+ const vidImg = container.querySelector('.video-preview .rcs-carousel-img');
368
+ expect(vidImg).toBeTruthy();
369
+ expect(vidImg.getAttribute('src')).toBeTruthy();
408
370
  });
409
371
 
410
- it('should not render suggestions when array is empty', () => {
372
+ it('should not render per-card suggestions block when suggestions is null or non-array', () => {
411
373
  const props = {
412
374
  ...defaultProps,
413
375
  content: {
414
- templateHeader: 'Title',
415
- templateMessage: 'Description',
416
- suggestions: [],
376
+ carouselData: [
377
+ {
378
+ mediaType: 'image',
379
+ imageSrc: 'https://x.png',
380
+ title: 'A',
381
+ bodyText: 'B',
382
+ suggestions: null,
383
+ },
384
+ {
385
+ mediaType: 'image',
386
+ imageSrc: 'https://y.png',
387
+ title: 'C',
388
+ bodyText: 'D',
389
+ suggestions: 'not-an-array',
390
+ },
391
+ ],
417
392
  },
418
393
  };
419
-
420
- render(
421
- <TestWrapper>
422
- <ComponentToRender {...props} />
423
- </TestWrapper>
424
- );
425
-
426
- expect(screen.getByText('Title')).toBeTruthy();
394
+ const { container } = renderRcsPreview(props);
395
+ expect(container.querySelectorAll('.message-pop-carousel').length).toBe(2);
396
+ const ctas = container.querySelectorAll('.rcs-cta-preview');
397
+ expect(ctas.length).toBe(0);
427
398
  });
428
399
 
429
- it('should handle suggestion without text', () => {
400
+ it('should render title-only card with body placeholder', () => {
430
401
  const props = {
431
402
  ...defaultProps,
432
403
  content: {
433
- templateHeader: 'Title',
434
- templateMessage: 'Description',
435
- suggestions: [
436
- { type: 'QUICK_REPLY', text: null },
404
+ carouselData: [
405
+ {
406
+ mediaType: 'image',
407
+ imageSrc: 'https://only-title.png',
408
+ title: 'OnlyTitle',
409
+ suggestions: [],
410
+ },
437
411
  ],
438
412
  },
439
413
  };
414
+ renderRcsPreview(props);
415
+ expect(screen.getByText('OnlyTitle')).toBeTruthy();
416
+ expect(screen.getByText('Card description')).toBeTruthy();
417
+ expect(document.querySelector('.carousel-message.rcs-carousel-field-placeholder')).toBeTruthy();
418
+ });
440
419
 
441
- render(
442
- <TestWrapper>
443
- <ComponentToRender {...props} />
444
- </TestWrapper>
445
- );
446
-
447
- // Should not crash
448
- expect(screen.getByText('Title')).toBeTruthy();
420
+ it('should render body-only card with title placeholder', () => {
421
+ const props = {
422
+ ...defaultProps,
423
+ content: {
424
+ carouselData: [
425
+ {
426
+ mediaType: 'image',
427
+ imageSrc: 'https://only-body.png',
428
+ bodyText: 'OnlyBody',
429
+ suggestions: [],
430
+ },
431
+ ],
432
+ },
433
+ };
434
+ renderRcsPreview(props);
435
+ expect(screen.getByText('OnlyBody')).toBeTruthy();
436
+ expect(screen.getByText('Card title')).toBeTruthy();
437
+ expect(document.querySelector('.carousel-title.rcs-carousel-field-placeholder')).toBeTruthy();
449
438
  });
450
- });
451
439
 
452
- describe('Device Handling', () => {
453
- it('should use Android device image for ANDROID device', () => {
440
+ it('should show title and body placeholders when card has no title or body', () => {
454
441
  const props = {
455
442
  ...defaultProps,
456
- device: ANDROID,
443
+ content: {
444
+ carouselData: [
445
+ {
446
+ mediaType: 'image',
447
+ imageSrc: 'https://silent.png',
448
+ suggestions: [],
449
+ },
450
+ ],
451
+ },
457
452
  };
453
+ const { container } = renderRcsPreview(props);
454
+ expect(container.querySelector('.carousel-content')).toBeTruthy();
455
+ expect(screen.getByText('Card title')).toBeTruthy();
456
+ expect(screen.getByText('Card description')).toBeTruthy();
457
+ expect(container.querySelectorAll('.rcs-carousel-field-placeholder').length).toBe(2);
458
+ });
459
+ });
458
460
 
459
- const { container } = render(
460
- <TestWrapper>
461
- <ComponentToRender {...props} />
462
- </TestWrapper>
463
- );
461
+ describe('RCS Suggestions', () => {
462
+ const withTextBodySuggestions = (suggestions) => ({
463
+ ...defaultProps,
464
+ content: { templateHeader: 'Title', templateMessage: 'Description', suggestions },
465
+ });
464
466
 
465
- expect(container.querySelector('.sms-device-image')).toBeTruthy();
467
+ it.each([
468
+ ['QUICK_REPLY', 'Yes', true],
469
+ ['CTA', 'Visit Website', false],
470
+ ['PHONE_NUMBER', 'Call Us', false],
471
+ ])('should render %s suggestion', (type, text, expectQuickReplyIcon) => {
472
+ const props = withTextBodySuggestions([{ type, text }]);
473
+ const { container } = renderRcsPreview(props);
474
+ expect(screen.getByText(text)).toBeTruthy();
475
+ if (expectQuickReplyIcon) {
476
+ expect(container.querySelector('.cap-icon-v2-small-link')).toBeTruthy();
477
+ }
466
478
  });
467
479
 
480
+ it('should render multiple suggestions', () => {
481
+ const props = withTextBodySuggestions([
482
+ { type: 'QUICK_REPLY', text: 'Yes' },
483
+ { type: 'CTA', text: 'Visit' },
484
+ { type: 'PHONE_NUMBER', text: 'Call' },
485
+ ]);
486
+
487
+ renderRcsPreview(props);
488
+
489
+ expect(screen.getByText('Yes')).toBeTruthy();
490
+ expect(screen.getByText('Visit')).toBeTruthy();
491
+ expect(screen.getByText('Call')).toBeTruthy();
492
+ });
493
+
494
+ it('should render dividers between suggestions', () => {
495
+ const props = withTextBodySuggestions([
496
+ { type: 'QUICK_REPLY', text: 'Yes' },
497
+ { type: 'CTA', text: 'Visit' },
498
+ ]);
499
+
500
+ const { container } = renderRcsPreview(props);
501
+
502
+ const dividers = container.querySelectorAll('.whatsapp-divider');
503
+ expect(dividers.length).toBeGreaterThan(0);
504
+ });
505
+
506
+ it('should not render suggestions when array is empty', () => {
507
+ renderRcsPreview(withTextBodySuggestions([]));
508
+
509
+ expect(screen.getByText('Title')).toBeTruthy();
510
+ });
511
+
512
+ it('should handle suggestion without text', () => {
513
+ renderRcsPreview(withTextBodySuggestions([{ type: 'QUICK_REPLY', text: null }]));
514
+
515
+ // Should not crash
516
+ expect(screen.getByText('Title')).toBeTruthy();
517
+ });
518
+ });
519
+
520
+ describe('Device Handling', () => {
468
521
  it('should use iOS device image for IOS device', () => {
469
522
  const props = {
470
523
  ...defaultProps,
471
524
  device: IOS,
472
525
  };
473
526
 
474
- const { container } = render(
475
- <TestWrapper>
476
- <ComponentToRender {...props} />
477
- </TestWrapper>
478
- );
527
+ const { container } = renderRcsPreview(props);
479
528
 
480
529
  expect(container.querySelector('.sms-device-image')).toBeTruthy();
481
530
  });
@@ -488,11 +537,7 @@ describe('RcsPreviewContent', () => {
488
537
  senderId: 'MY_SENDER_ID',
489
538
  };
490
539
 
491
- render(
492
- <TestWrapper>
493
- <ComponentToRender {...props} />
494
- </TestWrapper>
495
- );
540
+ renderRcsPreview(props);
496
541
 
497
542
  expect(screen.getByText('MY_SENDER_ID')).toBeTruthy();
498
543
  });
@@ -503,11 +548,7 @@ describe('RcsPreviewContent', () => {
503
548
  senderId: null,
504
549
  };
505
550
 
506
- render(
507
- <TestWrapper>
508
- <ComponentToRender {...props} />
509
- </TestWrapper>
510
- );
551
+ renderRcsPreview(props);
511
552
 
512
553
  expect(screen.getByText('Sender ID')).toBeTruthy();
513
554
  });
@@ -515,11 +556,7 @@ describe('RcsPreviewContent', () => {
515
556
 
516
557
  describe('Timestamp', () => {
517
558
  it('should display timestamp', () => {
518
- render(
519
- <TestWrapper>
520
- <ComponentToRender {...defaultProps} />
521
- </TestWrapper>
522
- );
559
+ renderRcsPreview(defaultProps);
523
560
 
524
561
  // Timestamp format: "H:MM AM/PM"
525
562
  const timestamp = screen.getByText(/\d{1,2}:\d{2} (AM|PM)/);
@@ -537,44 +574,17 @@ describe('RcsPreviewContent', () => {
537
574
  },
538
575
  };
539
576
 
540
- const { container } = render(
541
- <TestWrapper>
542
- <ComponentToRender {...props} />
543
- </TestWrapper>
544
- );
577
+ const { container } = renderRcsPreview(props);
545
578
 
546
579
  // Should prioritize video when both are present
547
580
  expect(container.querySelector('.video-preview')).toBeTruthy();
548
581
  });
549
582
 
550
- it('should handle empty content object', () => {
551
- const props = {
552
- ...defaultProps,
553
- content: {},
554
- };
555
-
556
- render(
557
- <TestWrapper>
558
- <ComponentToRender {...props} />
559
- </TestWrapper>
560
- );
561
-
562
- // Should render structure
563
- expect(screen.getByText('Today')).toBeTruthy();
564
- });
565
-
566
- it('should handle null content', () => {
567
- const props = {
568
- ...defaultProps,
569
- content: null,
570
- };
571
-
572
- render(
573
- <TestWrapper>
574
- <ComponentToRender {...props} />
575
- </TestWrapper>
576
- );
577
-
583
+ it.each([
584
+ ['empty object', {}],
585
+ ['null', null],
586
+ ])('should render structure when content is %s', (_, contentValue) => {
587
+ renderRcsPreview({ ...defaultProps, content: contentValue });
578
588
  expect(screen.getByText('Today')).toBeTruthy();
579
589
  });
580
590
 
@@ -590,11 +600,7 @@ describe('RcsPreviewContent', () => {
590
600
  },
591
601
  };
592
602
 
593
- render(
594
- <TestWrapper>
595
- <ComponentToRender {...props} />
596
- </TestWrapper>
597
- );
603
+ renderRcsPreview(props);
598
604
 
599
605
  // Should not crash, but may not render the suggestion
600
606
  expect(screen.getByText('Title')).toBeTruthy();
@@ -607,11 +613,7 @@ describe('RcsPreviewContent', () => {
607
613
  formatMessage: jest.fn((msg) => msg.defaultMessage || msg.id),
608
614
  };
609
615
 
610
- render(
611
- <TestWrapper>
612
- <ComponentToRender {...minimalProps} />
613
- </TestWrapper>
614
- );
616
+ renderRcsPreview(minimalProps);
615
617
 
616
618
  expect(screen.getByText('Today')).toBeTruthy();
617
619
  });
@@ -621,11 +623,7 @@ describe('RcsPreviewContent', () => {
621
623
  it('should accept all required props without warnings', () => {
622
624
  const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
623
625
 
624
- render(
625
- <TestWrapper>
626
- <ComponentToRender {...defaultProps} />
627
- </TestWrapper>
628
- );
626
+ renderRcsPreview(defaultProps);
629
627
 
630
628
  expect(consoleSpy).not.toHaveBeenCalled();
631
629
  consoleSpy.mockRestore();