@capillarytech/creatives-library 8.0.329 → 8.0.330-alpha.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 (89) hide show
  1. package/constants/unified.js +4 -0
  2. package/package.json +1 -1
  3. package/utils/commonUtils.js +19 -1
  4. package/utils/templateVarUtils.js +35 -6
  5. package/utils/tests/tagValidations.test.js +20 -0
  6. package/utils/tests/templateVarUtils.test.js +44 -0
  7. package/v2Components/CapActionButton/constants.js +7 -0
  8. package/v2Components/CapActionButton/index.js +167 -109
  9. package/v2Components/CapActionButton/index.scss +157 -6
  10. package/v2Components/CapActionButton/messages.js +19 -3
  11. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  12. package/v2Components/CapTagList/index.js +28 -23
  13. package/v2Components/CapTagList/style.scss +29 -0
  14. package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
  15. package/v2Components/CapTagListWithInput/index.js +4 -0
  16. package/v2Components/CapWhatsappCTA/index.js +2 -0
  17. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +1 -0
  18. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +160 -15
  19. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +18 -0
  20. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +323 -77
  21. package/v2Components/CommonTestAndPreview/index.js +49 -57
  22. package/v2Components/CommonTestAndPreview/messages.js +8 -0
  23. package/v2Components/CommonTestAndPreview/reducer.js +3 -1
  24. package/v2Components/CommonTestAndPreview/sagas.js +2 -1
  25. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  26. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  27. package/v2Components/FormBuilder/index.js +1 -0
  28. package/v2Components/HtmlEditor/HTMLEditor.js +6 -1
  29. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  30. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +927 -2
  31. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +3 -0
  32. package/v2Components/SmsFallback/smsFallbackUtils.js +14 -3
  33. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +16 -0
  34. package/v2Components/TemplatePreview/_templatePreview.scss +33 -23
  35. package/v2Components/TemplatePreview/constants.js +2 -0
  36. package/v2Components/TemplatePreview/index.js +143 -28
  37. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  38. package/v2Components/TestAndPreviewSlidebox/index.js +5 -0
  39. package/v2Components/mockdata.js +1 -0
  40. package/v2Containers/BeeEditor/index.js +19 -1
  41. package/v2Containers/CreativesContainer/SlideBoxContent.js +28 -1
  42. package/v2Containers/CreativesContainer/index.js +9 -3
  43. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +5 -0
  44. package/v2Containers/Email/index.js +78 -39
  45. package/v2Containers/Email/reducer.js +2 -2
  46. package/v2Containers/Email/sagas.js +3 -1
  47. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -2
  48. package/v2Containers/Email/tests/sagas.test.js +230 -0
  49. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +6 -1
  50. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
  51. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -2
  52. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
  53. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +3 -1
  54. package/v2Containers/EmailWrapper/index.js +4 -0
  55. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
  56. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +9 -0
  57. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +1 -0
  58. package/v2Containers/MobilePush/Create/index.js +2 -0
  59. package/v2Containers/MobilePush/Edit/index.js +2 -0
  60. package/v2Containers/MobilepushWrapper/index.js +3 -1
  61. package/v2Containers/Rcs/constants.js +85 -7
  62. package/v2Containers/Rcs/index.js +1592 -156
  63. package/v2Containers/Rcs/index.js.rej +1336 -0
  64. package/v2Containers/Rcs/index.scss +191 -0
  65. package/v2Containers/Rcs/index.scss.rej +74 -0
  66. package/v2Containers/Rcs/messages.js +28 -2
  67. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +20 -0
  68. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +69178 -117691
  69. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +128 -0
  70. package/v2Containers/Rcs/tests/index.test.js +132 -94
  71. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +67 -0
  72. package/v2Containers/Rcs/tests/utils.test.js +276 -38
  73. package/v2Containers/Rcs/utils.js +130 -7
  74. package/v2Containers/Sms/Edit/index.js +2 -0
  75. package/v2Containers/SmsTrai/Edit/index.js +27 -0
  76. package/v2Containers/SmsTrai/Edit/messages.js +5 -0
  77. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
  78. package/v2Containers/SmsWrapper/index.js +2 -0
  79. package/v2Containers/TagList/index.js +73 -20
  80. package/v2Containers/TagList/messages.js +4 -0
  81. package/v2Containers/TagList/tests/TagList.test.js +124 -20
  82. package/v2Containers/TagList/tests/mockdata.js +17 -0
  83. package/v2Containers/Templates/_templates.scss +99 -0
  84. package/v2Containers/Templates/index.js +29 -14
  85. package/v2Containers/Viber/index.js +3 -0
  86. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -2
  87. package/v2Containers/WebPush/Create/index.js +10 -2
  88. package/v2Containers/Whatsapp/index.js +5 -0
  89. package/v2Containers/Zalo/index.js +2 -0
@@ -59,6 +59,7 @@ const CodeEditorPaneComponent = ({
59
59
  injectedTags = {},
60
60
  location,
61
61
  eventContextTags = [],
62
+ waitEventContextTags = {},
62
63
  selectedOfferDetails = [],
63
64
  channel,
64
65
  userLocale = 'en',
@@ -289,6 +290,7 @@ const CodeEditorPaneComponent = ({
289
290
  location={location}
290
291
  selectedOfferDetails={selectedOfferDetails}
291
292
  eventContextTags={eventContextTags}
293
+ waitEventContextTags={waitEventContextTags}
292
294
  popoverPlacement="rightTop"
293
295
  />
294
296
  </CapRow>
@@ -327,6 +329,7 @@ CodeEditorPane.propTypes = {
327
329
  injectedTags: PropTypes.object,
328
330
  location: PropTypes.object,
329
331
  eventContextTags: PropTypes.array,
332
+ waitEventContextTags: PropTypes.object,
330
333
  selectedOfferDetails: PropTypes.array,
331
334
  channel: PropTypes.string,
332
335
  userLocale: PropTypes.string,
@@ -1,5 +1,6 @@
1
1
  import get from 'lodash/get';
2
2
  import { getFallbackResolvedContentForCardDisplay } from '../../utils/templateVarUtils';
3
+ import { extractRegisteredSenderIdsFromSmsFallbackRecord } from '../../utils/commonUtils';
3
4
  import { SMS_CATEGORY_FILTERS } from './constants';
4
5
 
5
6
  export function buildFallbackDataFromTemplate(template) {
@@ -16,9 +17,14 @@ export function buildFallbackDataFromTemplate(template) {
16
17
  const content = smsEditor;
17
18
  const headerList = Array.isArray(base.header) ? base.header : [];
18
19
  const senderId = (headerList[0]) || base.senderId || '';
20
+ const templateName =
21
+ template.name
22
+ || base.template_name
23
+ || base['template-name']
24
+ || '';
19
25
  return {
20
26
  smsTemplateId: template._id || '',
21
- templateName: template.name || '',
27
+ templateName,
22
28
  content,
23
29
  templateContent,
24
30
  senderId,
@@ -35,14 +41,19 @@ export function mapFallbackValueToEditTemplateData(source) {
35
41
  rcsSmsFallbackVarMappedFromSource != null
36
42
  && typeof rcsSmsFallbackVarMappedFromSource === 'object'
37
43
  && Object.keys(rcsSmsFallbackVarMappedFromSource).length > 0;
44
+ const registeredSenderIdsForHeader =
45
+ extractRegisteredSenderIdsFromSmsFallbackRecord(source);
38
46
  return {
39
47
  _id: source?.smsTemplateId,
40
48
  name: source?.templateName,
41
49
  versions: {
42
50
  base: {
43
51
  'sms-editor': source?.templateContent || source?.content,
44
- ...(Array.isArray(source?.registeredSenderIds) && source.registeredSenderIds.length
45
- ? { header: source.registeredSenderIds }
52
+ ...(source?.templateName != null && String(source.templateName).trim() !== ''
53
+ ? { template_name: String(source.templateName).trim() }
54
+ : {}),
55
+ ...(Array.isArray(registeredSenderIdsForHeader) && registeredSenderIdsForHeader.length
56
+ ? { header: registeredSenderIdsForHeader }
46
57
  : {}),
47
58
  ...(typeof source?.unicodeValidity === 'boolean'
48
59
  ? { 'unicode-validity': source.unicodeValidity }
@@ -50,6 +50,21 @@ describe('smsFallbackUtils', () => {
50
50
  };
51
51
  expect(buildFallbackDataFromTemplate(template).unicodeValidity).toBe(true);
52
52
  });
53
+
54
+ it('uses versions.base.template_name when root name is empty (DLT detail shape)', () => {
55
+ const template = {
56
+ _id: 'dlt1',
57
+ name: '',
58
+ versions: {
59
+ base: {
60
+ template_name: 'DLT Registered Name',
61
+ 'sms-editor': 'Hi',
62
+ header: ['H1'],
63
+ },
64
+ },
65
+ };
66
+ expect(buildFallbackDataFromTemplate(template).templateName).toBe('DLT Registered Name');
67
+ });
53
68
  });
54
69
 
55
70
  describe('getSmsFallbackCardDisplayContent', () => {
@@ -101,6 +116,7 @@ describe('smsFallbackUtils', () => {
101
116
  versions: {
102
117
  base: {
103
118
  'sms-editor': 'body',
119
+ template_name: 'N',
104
120
  header: ['h1'],
105
121
  'unicode-validity': false,
106
122
  },
@@ -537,20 +537,21 @@
537
537
  .unicode-disabled{
538
538
  font-size: 16px;
539
539
  }
540
+ position: absolute;
541
+ overflow: auto;
542
+ top: 0;
543
+ left: 17%;
544
+ white-space: pre-wrap;
545
+ word-break: break-word;
546
+ max-height: 100%;
547
+ line-height: 14px;
548
+ font-size: 10px;
549
+ font-family: 'open-sans';
550
+
540
551
  &.sms {
541
- max-height: 100%;
542
- position: absolute;
543
552
  // width: initial;
544
- overflow: auto;
545
- top: 0;
546
- left: 17%;
547
- white-space: pre-wrap;
548
- word-break: break-word;
549
- line-height: 14px;
550
553
  padding: 0 8px 8px 8px;
551
- font-size: 10px;
552
554
  width: 100%;
553
- font-family: 'open-sans';
554
555
  .rcs-image{
555
556
  width: 100%;
556
557
  height: 123px;
@@ -582,22 +583,12 @@
582
583
  &:not(.sms){
583
584
  padding: 4px;
584
585
  background: #3F51B5;
585
- position: absolute;
586
586
  // width: initial;
587
- overflow: auto;
588
- top: 0;
589
- left: 17%;
590
- white-space: pre-wrap;
591
- word-break: break-word;
592
587
  border-radius: 4px;
593
- max-height: 100%;
594
588
  color: #FFFFFF;
595
589
  width: 70%;
596
590
  min-height: 26px;
597
- line-height: 14px;
598
591
  padding: 8px;
599
- font-size: 10px;
600
- font-family: 'open-sans';
601
592
  }
602
593
  &.message-pop-carousel {
603
594
  position: relative;
@@ -842,6 +833,27 @@
842
833
  }
843
834
  }
844
835
 
836
+ .shell-v2.rcs-preview {
837
+ // Override `.message-pop:not(.sms)` (blue background) for RCS carousel cards.
838
+ .msg-container.sms {
839
+ .message-pop.sms {
840
+ .rcs-carousel-card {
841
+ background: $CAP_WHITE;
842
+ color: $CAP_G01;
843
+ width: 10.4rem;
844
+ left: 0;
845
+ flex-shrink: 0;
846
+ padding: $CAP_SPACE_04 0 $CAP_SPACE_08;
847
+ border-radius: 0.428rem;
848
+
849
+ .carousel-title {
850
+ font-weight: 700 !important;
851
+ }
852
+ }
853
+ }
854
+ }
855
+ }
856
+
845
857
  .align-center {
846
858
  text-align: center;
847
859
  }
@@ -1012,9 +1024,7 @@
1012
1024
  top: 0;
1013
1025
  }
1014
1026
  .video-icon {
1015
- position: absolute;
1016
- right: -17px;
1017
- bottom: -17px;
1027
+ position: sticky;
1018
1028
  }
1019
1029
 
1020
1030
  .zalo-preview-container {
@@ -0,0 +1,2 @@
1
+ /** Matches {{ varName }} placeholders (alphanumeric + underscore) */
2
+ export const TEMPLATE_VAR_REGEX = /\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g;
@@ -52,6 +52,7 @@ import { QUICK_REPLY, WHATSAPP_CATEGORIES, PHONE_NUMBER } from '../../v2Containe
52
52
  import { RCS_BUTTON_TYPES, LEFT, HORIZONTAL, VERTICAL, RIGHT} from '../../v2Containers/Rcs/constants';
53
53
  import { ANDROID, INAPP_MESSAGE_LAYOUT_TYPES } from '../../v2Containers/InApp/constants';
54
54
  import { CAROUSEL } from '../../v2Containers/MobilePushNew/constants';
55
+ import { TEMPLATE_VAR_REGEX } from './constants';
55
56
 
56
57
  const wechatBodyNew = require('./assets/images/wechat_mobile_android.svg');
57
58
  const smsMobileAndroid = require('./assets/images/sms_mobile_android.svg');
@@ -234,7 +235,23 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
234
235
  let content = channel && channel.toLowerCase() === 'sms' ? [this.props.content] : this.props.content;
235
236
  const { formatMessage } = intl;
236
237
  const { rcsPreviewContent, inAppPreviewContent, viberPreviewContent, isBeeFreeTemplate } = content || {};
237
- const { rcsImageSrc, rcsVideoSrc, rcsTitle, rcsDesc, rcsSuggestions } = rcsPreviewContent || {};
238
+ const normalizedRcsPreviewContent = Array.isArray(rcsPreviewContent)
239
+ ? { carouselData: rcsPreviewContent }
240
+ : (rcsPreviewContent || {});
241
+ const {
242
+ rcsImageSrc,
243
+ rcsVideoSrc,
244
+ rcsTitle,
245
+ rcsDesc,
246
+ rcsSuggestions,
247
+ carouselData: rcsCarouselData,
248
+ rcsCarouselData: rcsCarouselDataAlt,
249
+ cardVarMapped: rcsCardVarMapped,
250
+ } = normalizedRcsPreviewContent;
251
+ const resolvedRcsCarouselData =
252
+ Array.isArray(rcsCarouselData) && rcsCarouselData.length > 0
253
+ ? rcsCarouselData
254
+ : rcsCarouselDataAlt;
238
255
  const {
239
256
  videoParams,
240
257
  imageURL,
@@ -315,6 +332,18 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
315
332
  'flex-shrink': 0,
316
333
  'left': 0,
317
334
  };
335
+
336
+ const resolveVarsWithMap = (input, varMap) => {
337
+ if (input === null || input === undefined) return '';
338
+ const str = typeof input === 'string' ? input : String(input);
339
+ return str.replace(TEMPLATE_VAR_REGEX, (token, varName) => {
340
+ const mappedValue = varMap?.[varName];
341
+ if (mappedValue === null || mappedValue === undefined || String(mappedValue) === '') {
342
+ return token;
343
+ }
344
+ return String(mappedValue);
345
+ });
346
+ };
318
347
  const getVideoContent = ({
319
348
  video,
320
349
  actionUrl,
@@ -471,13 +500,15 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
471
500
  const whatsappUpdatedAccountName = whatsappAccountName || templateData?.versions?.base?.content?.whatsapp?.accountName || '';
472
501
  const whatsappUpdatedLen = whatsappContentLen !== undefined ? whatsappContentLen : content?.charCount;
473
502
 
474
- const renderRcsSuggestionsPreview = () => {
503
+ const renderRcsSuggestionsPreview = (suggestionsArg) => {
475
504
  const renderArray = [];
476
- (rcsSuggestions || []).forEach((suggestion) => {
477
- renderArray.push(<CapDivider className="whatsapp-divider" />);
505
+ const suggestions = Array.isArray(suggestionsArg) ? suggestionsArg : (rcsSuggestions || []);
506
+ (suggestions || []).forEach((suggestion, idx) => {
507
+ const suggestionKey = `${suggestion?.type || 'unknown'}-${suggestion?.text || ''}-${idx}`;
508
+ renderArray.push(<CapDivider key={`rcs-divider-${suggestionKey}`} className="whatsapp-divider" />);
478
509
  if (suggestion.type === RCS_BUTTON_TYPES.QUICK_REPLY) {
479
510
  renderArray.push(
480
- <CapLabel type="label21" className="rcs-cta-preview">
511
+ <CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
481
512
  <CapIcon
482
513
  type='small-link'
483
514
  size="xs"
@@ -487,14 +518,14 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
487
518
  );
488
519
  } else if (suggestion.type === RCS_BUTTON_TYPES.CTA) {
489
520
  renderArray.push(
490
- <CapLabel type="label21" className="rcs-cta-preview">
521
+ <CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
491
522
  <CapIcon type="launch" size="xs" />
492
523
  {suggestion.text}
493
524
  </CapLabel>,
494
525
  );
495
526
  } else if (suggestion.type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
496
527
  renderArray.push(
497
- <CapLabel type="label21" className="rcs-cta-preview">
528
+ <CapLabel key={`rcs-suggestion-${suggestionKey}`} type="label21" className="rcs-cta-preview">
498
529
  <CapIcon type="call" size="xs" />
499
530
  {suggestion.text}
500
531
  </CapLabel>,
@@ -513,7 +544,7 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
513
544
  className="message-pop-item align-left rcs-content"
514
545
  fontWeight="bold"
515
546
  >
516
- {rcsTitle}
547
+ {resolveVarsWithMap(rcsTitle, rcsCardVarMapped)}
517
548
  </CapLabel>
518
549
  <CapDivider className="whatsapp-divider" />
519
550
  </>
@@ -523,7 +554,7 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
523
554
  type="label5"
524
555
  className="message-pop-item align-left rcs-desc rcs-content"
525
556
  >
526
- {rcsDesc}
557
+ {resolveVarsWithMap(rcsDesc, rcsCardVarMapped)}
527
558
  </CapLabel>
528
559
  )}
529
560
  {renderRcsSuggestionsPreview()}
@@ -540,50 +571,134 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
540
571
  />
541
572
  )}
542
573
  {rcsVideoSrc && (
543
- <div className="video-preview">
574
+ <CapRow className="video-preview">
544
575
  <CapImage
545
576
  src={rcsVideoSrc}
546
577
  className="rcs-image"
547
578
  alt={formatMessage(messages.previewGenerated)}
548
579
  />
549
- <div className="icon-position">
580
+ <CapRow className="icon-position">
550
581
  <CapImage
551
582
  className="video-icon"
552
583
  src={videoPlay}
553
584
  />
554
- </div>
555
- </div>
585
+ </CapRow>
586
+ </CapRow>
556
587
  )}
557
588
  </>
558
589
  );
559
590
 
560
591
  const renderRcsPreviewContent = () => {
592
+ const carouselCards = Array.isArray(resolvedRcsCarouselData) ? resolvedRcsCarouselData : [];
593
+ if (carouselCards.length > 0) {
594
+ return (
595
+ <CapRow className="msg-container sms">
596
+ <CapRow className="message-pop sms">
597
+ <CapRow className="msg-container-carousel">
598
+ <CapRow className="scroll-container">
599
+ {carouselCards.map((card, idx) => {
600
+ const key = `rcs-carousel-${idx}-${card?.bodyText || card?.imageSrc || card?.videoPreviewImg || ''}`;
601
+ const isVideo = (card?.mediaType || '').toLowerCase() === 'video';
602
+ const cardSuggestions = Array.isArray(card?.suggestions) ? card.suggestions : [];
603
+ const effectiveCardVarMap = card?.cardVarMapped || rcsCardVarMapped;
604
+ const suggestionsNode = cardSuggestions.length > 0
605
+ ? renderRcsSuggestionsPreview(cardSuggestions)
606
+ : null;
607
+
608
+ const resolvedCardTitle = resolveVarsWithMap(card?.title, effectiveCardVarMap);
609
+ const resolvedCardBodyText = resolveVarsWithMap(card?.bodyText, effectiveCardVarMap);
610
+
611
+ return (
612
+ <CapRow
613
+ key={key}
614
+ className="message-pop align-left message-pop-carousel rcs-carousel-card"
615
+ >
616
+ <CapRow className="whatsapp-content">
617
+ {!isVideo && (
618
+ <CapImage
619
+ src={card?.imageSrc ? card.imageSrc : whatsappImageEmptyPreview}
620
+ className="whatsapp-image"
621
+ alt={formatMessage(messages.previewGenerated)}
622
+ />
623
+ )}
624
+ {isVideo && (
625
+ <CapTooltip title={formatMessage(messages.videoPreviewTooltip)}>
626
+ <CapRow className="video-preview">
627
+ <CapImage
628
+ src={card?.videoPreviewImg ? card.videoPreviewImg : whatsappVideoEmptyPreview}
629
+ className="whatsapp-image"
630
+ alt={formatMessage(messages.previewGenerated)}
631
+ />
632
+ <CapRow className="icon-position">
633
+ <CapImage className="video-icon" src={videoPlay} alt="Play" />
634
+ </CapRow>
635
+ </CapRow>
636
+ </CapTooltip>
637
+ )}
638
+
639
+ {(resolvedCardTitle || resolvedCardBodyText) && (
640
+ <CapRow className="carousel-content">
641
+ {!!resolvedCardTitle && (
642
+ <CapLabel
643
+ type={card?.titleLabelType || 'label1'}
644
+ className="carousel-title"
645
+ >
646
+ {resolvedCardTitle}
647
+ </CapLabel>
648
+ )}
649
+ {!!resolvedCardBodyText && (
650
+ <CapLabel
651
+ type={card?.bodyLabelType || 'label2'}
652
+ className="carousel-message"
653
+ >
654
+ {resolvedCardBodyText}
655
+ </CapLabel>
656
+ )}
657
+ </CapRow>
658
+ )}
659
+
660
+ {!!suggestionsNode && (
661
+ <>
662
+ {suggestionsNode}
663
+ </>
664
+ )}
665
+ </CapRow>
666
+ </CapRow>
667
+ );
668
+ })}
669
+ </CapRow>
670
+ </CapRow>
671
+ </CapRow>
672
+ </CapRow>
673
+ );
674
+ }
675
+
561
676
  if (rcsOrientation === HORIZONTAL) {
562
677
  return rcsType === RIGHT ? (
563
- <div className="msg-container sms">
564
- <div className="message-pop sms horizontal">
678
+ <CapRow className="msg-container sms">
679
+ <CapRow className="message-pop sms horizontal">
565
680
  <CapColumn className="rcs-preview-text" span={12}>{renderTextPreviewContent()}</CapColumn>
566
681
  <CapColumn span={12}>{renderMediaPreviewContent()}</CapColumn>
567
- </div>
568
- </div>
682
+ </CapRow>
683
+ </CapRow>
569
684
 
570
685
  ) : (
571
- <div className="msg-container sms">
572
- <div className="message-pop sms horizontal">
686
+ <CapRow className="msg-container sms">
687
+ <CapRow className="message-pop sms horizontal">
573
688
  <CapColumn span={12}>{renderMediaPreviewContent()}</CapColumn>
574
689
  <CapColumn className="rcs-preview-text" span={12}>{renderTextPreviewContent()}</CapColumn>
575
- </div>
576
- </div>
690
+ </CapRow>
691
+ </CapRow>
577
692
  );
578
693
  }
579
694
 
580
695
  return (
581
- <div className="msg-container sms">
582
- <div className="message-pop sms">
696
+ <CapRow className="msg-container sms">
697
+ <CapRow className="message-pop sms">
583
698
  {renderMediaPreviewContent()}
584
699
  {renderTextPreviewContent()}
585
- </div>
586
- </div>
700
+ </CapRow>
701
+ </CapRow>
587
702
  );
588
703
  };
589
704
 
@@ -1291,14 +1406,14 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
1291
1406
  ""
1292
1407
  )}
1293
1408
  {channel?.toUpperCase() === RCS && (
1294
- <div className="shell-v2 align-center rcs-preview">
1409
+ <CapRow className="shell-v2 align-center rcs-preview">
1295
1410
  <CapImage
1296
1411
  className="preview-image"
1297
1412
  src={rcsIosPreview ? smsMobileIos : smsMobileAndroid}
1298
1413
  alt={formatMessage(messages.previewGenerated)}
1299
1414
  />
1300
1415
  {renderRcsPreviewContent()}
1301
- </div>
1416
+ </CapRow>
1302
1417
 
1303
1418
  )}
1304
1419
  {channel?.toUpperCase() === ZALO && (
@@ -1499,4 +1614,4 @@ TemplatePreview.propTypes = {
1499
1614
  rcsOrientation: PropTypes.string,
1500
1615
  };
1501
1616
 
1502
- export default (injectIntl(TemplatePreview));
1617
+ export default (injectIntl(TemplatePreview));
@@ -2,6 +2,9 @@ import React from 'react';
2
2
  import { shallowWithIntl } from '../../../helpers/intl-enzym-test-helpers';
3
3
 
4
4
  import { TemplatePreview } from '../index';
5
+ import { RCS } from '../../../v2Containers/CreativesContainer/constants';
6
+ import whatsappImageEmptyPreview from '../assets/images/empty_image_preview.svg';
7
+ import whatsappVideoEmptyPreview from '../assets/images/empty_video_preview.svg';
5
8
 
6
9
  describe('Test Templates container', () => {
7
10
  let renderedComponent;
@@ -69,4 +72,143 @@ describe('Test Templates container', () => {
69
72
  });
70
73
  expect(renderedComponent).toMatchSnapshot();
71
74
  });
75
+
76
+ describe('RCS carousel preview branches', () => {
77
+ const buildRcsContent = (cards) => ({
78
+ rcsPreviewContent: {
79
+ carouselData: cards,
80
+ },
81
+ });
82
+
83
+ const getNodesByClassName = (className) =>
84
+ renderedComponent.findWhere((node) => node.prop('className') === className);
85
+
86
+ it('renders image and video cards with proper media-specific markup', () => {
87
+ renderFunction(
88
+ RCS,
89
+ buildRcsContent([
90
+ {
91
+ mediaType: 'image',
92
+ imageSrc: 'https://example.com/image-card.jpg',
93
+ bodyText: 'image body',
94
+ suggestions: [],
95
+ },
96
+ {
97
+ mediaType: 'video',
98
+ videoPreviewImg: 'https://example.com/video-thumb.jpg',
99
+ bodyText: 'video body',
100
+ suggestions: [],
101
+ },
102
+ ]),
103
+ '',
104
+ 0,
105
+ );
106
+
107
+ // Video card should render tooltip/video icon path; image-only card should not.
108
+ expect(renderedComponent.find('CapTooltip')).toHaveLength(1);
109
+ expect(
110
+ renderedComponent.findWhere((node) => node.prop('className') === 'video-icon')
111
+ ).toHaveLength(1);
112
+
113
+ // Both cards render media containers.
114
+ expect(
115
+ renderedComponent.findWhere((node) => node.prop('className') === 'whatsapp-image')
116
+ ).toHaveLength(2);
117
+ });
118
+
119
+ it('renders per-card suggestions only for cards that provide suggestions', () => {
120
+ renderFunction(
121
+ RCS,
122
+ buildRcsContent([
123
+ {
124
+ mediaType: 'image',
125
+ imageSrc: 'https://example.com/with-suggestions.jpg',
126
+ bodyText: 'has suggestions',
127
+ suggestions: [
128
+ { type: 'QUICK_REPLY', text: 'Reply 1' },
129
+ { type: 'CTA', text: 'Visit' },
130
+ ],
131
+ },
132
+ {
133
+ mediaType: 'image',
134
+ imageSrc: 'https://example.com/no-suggestions.jpg',
135
+ bodyText: 'no suggestions',
136
+ suggestions: [],
137
+ },
138
+ ]),
139
+ '',
140
+ 0,
141
+ );
142
+
143
+ // Suggestion labels/icons should be rendered only from first card.
144
+ expect(getNodesByClassName('rcs-cta-preview')).toHaveLength(2);
145
+ expect(getNodesByClassName('whatsapp-divider')).not.toHaveLength(0);
146
+ });
147
+
148
+ it('falls back to empty preview assets when image/video media src is missing', () => {
149
+ renderFunction(
150
+ RCS,
151
+ buildRcsContent([
152
+ {
153
+ mediaType: 'image',
154
+ bodyText: 'fallback image',
155
+ suggestions: [],
156
+ },
157
+ {
158
+ mediaType: 'video',
159
+ bodyText: 'fallback video',
160
+ suggestions: [],
161
+ },
162
+ ]),
163
+ '',
164
+ 0,
165
+ );
166
+
167
+ const mediaImageNodes = renderedComponent.findWhere(
168
+ (node) => node.name() === 'CapImage' && node.prop('className') === 'whatsapp-image'
169
+ );
170
+ const mediaSrcs = mediaImageNodes.map((node) => node.prop('src'));
171
+
172
+ expect(mediaSrcs).toContain(whatsappImageEmptyPreview);
173
+ expect(mediaSrcs).toContain(whatsappVideoEmptyPreview);
174
+ });
175
+
176
+ it('generates unique carousel keys using rcs-carousel-<idx>-<bodyText|imageSrc|videoPreviewImg>', () => {
177
+ renderFunction(
178
+ RCS,
179
+ buildRcsContent([
180
+ {
181
+ mediaType: 'image',
182
+ imageSrc: 'https://example.com/one.jpg',
183
+ bodyText: 'same body',
184
+ suggestions: [],
185
+ },
186
+ {
187
+ mediaType: 'image',
188
+ imageSrc: 'https://example.com/two.jpg',
189
+ bodyText: 'same body',
190
+ suggestions: [],
191
+ },
192
+ {
193
+ mediaType: 'video',
194
+ videoPreviewImg: 'https://example.com/three-thumb.jpg',
195
+ bodyText: 'same body',
196
+ suggestions: [],
197
+ },
198
+ ]),
199
+ '',
200
+ 0,
201
+ );
202
+
203
+ const cardNodes = getNodesByClassName('message-pop align-left message-pop-carousel rcs-carousel-card');
204
+ const keys = cardNodes.map((node) => node.key());
205
+
206
+ expect(keys).toEqual([
207
+ 'rcs-carousel-0-same body',
208
+ 'rcs-carousel-1-same body',
209
+ 'rcs-carousel-2-same body',
210
+ ]);
211
+ expect(new Set(keys).size).toBe(keys.length);
212
+ });
213
+ });
72
214
  });
@@ -84,6 +84,10 @@ TestAndPreviewSlidebox.propTypes = {
84
84
  templateName: PropTypes.string,
85
85
  [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: PropTypes.object,
86
86
  }),
87
+ /** Passed to CommonTestAndPreview for RCS test-meta resolution (slot semantics vs full-mode). */
88
+ rcsTestPreviewOptions: PropTypes.shape({
89
+ isLibraryMode: PropTypes.bool,
90
+ }),
87
91
  // Redux props are passed through
88
92
  actions: PropTypes.object.isRequired,
89
93
  extractedTags: PropTypes.array.isRequired,
@@ -115,6 +119,7 @@ TestAndPreviewSlidebox.defaultProps = {
115
119
  currentTab: 1,
116
120
  messageMetaConfigId: null,
117
121
  prefilledValues: {},
122
+ rcsTestPreviewOptions: undefined,
118
123
  senderDetailsByChannel: {},
119
124
  wecrmAccounts: [],
120
125
  isLoadingSenderDetails: false,
@@ -94,3 +94,4 @@ export default {
94
94
  totalCount: 7,
95
95
  },
96
96
  };
97
+