@capillarytech/creatives-library 9.0.28 → 9.0.29-alpha.2

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 (149) hide show
  1. package/constants/unified.js +29 -0
  2. package/global-styles.js +1 -1
  3. package/package.json +1 -1
  4. package/services/tests/api.test.js +35 -20
  5. package/utils/commonUtils.js +22 -1
  6. package/utils/rcsPayloadUtils.js +99 -0
  7. package/utils/templateVarUtils.js +198 -0
  8. package/utils/tests/rcsPayloadUtils.test.js +243 -0
  9. package/utils/tests/templateVarUtils.test.js +204 -0
  10. package/v2Components/CapActionButton/constants.js +20 -1
  11. package/v2Components/CapActionButton/index.js +193 -144
  12. package/v2Components/CapActionButton/index.scss +232 -14
  13. package/v2Components/CapActionButton/messages.js +19 -3
  14. package/v2Components/CapActionButton/tests/index.test.js +42 -18
  15. package/v2Components/CapImageUpload/index.js +2 -2
  16. package/v2Components/CapTagList/index.js +9 -0
  17. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -48
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
  22. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +58 -11
  24. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  25. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +186 -22
  26. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +381 -80
  27. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +155 -8
  28. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  29. package/v2Components/CommonTestAndPreview/constants.js +35 -2
  30. package/v2Components/CommonTestAndPreview/index.js +770 -231
  31. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  32. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  33. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  34. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +1 -0
  35. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +412 -257
  36. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +337 -63
  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 +108 -15
  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 +133 -4
  44. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  45. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
  46. package/v2Components/CommonTestAndPreview/tests/utils.test.js +151 -0
  47. package/v2Components/CommonTestAndPreview/utils.js +84 -0
  48. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  49. package/v2Components/SmsFallback/constants.js +94 -0
  50. package/v2Components/SmsFallback/index.js +958 -0
  51. package/v2Components/SmsFallback/index.scss +266 -0
  52. package/v2Components/SmsFallback/messages.js +78 -0
  53. package/v2Components/SmsFallback/smsFallbackUtils.js +120 -0
  54. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  55. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  56. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  57. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +208 -0
  58. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  59. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +471 -0
  60. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  61. package/v2Components/TemplatePreview/_templatePreview.scss +45 -25
  62. package/v2Components/TemplatePreview/constants.js +2 -0
  63. package/v2Components/TemplatePreview/index.js +147 -32
  64. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  65. package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
  66. package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
  67. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  68. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  69. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  70. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  71. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  72. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +4 -1
  73. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +4 -0
  74. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
  75. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +27 -0
  76. package/v2Containers/CreativesContainer/SlideBoxContent.js +37 -4
  77. package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
  78. package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
  79. package/v2Containers/CreativesContainer/constants.js +11 -0
  80. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +77 -0
  81. package/v2Containers/CreativesContainer/index.js +328 -106
  82. package/v2Containers/CreativesContainer/index.scss +102 -1
  83. package/v2Containers/CreativesContainer/tests/CreativesSlideBoxWrapper.test.js +58 -0
  84. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  85. package/v2Containers/CreativesContainer/tests/SlideBoxContent.smsDltPreview.test.js +73 -0
  86. package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +37 -0
  87. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +103 -34
  88. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +193 -15
  89. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +88 -0
  90. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +549 -57
  91. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  92. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  93. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  94. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  95. package/v2Containers/Email/index.js +3 -63
  96. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +4 -9
  97. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  98. package/v2Containers/Rcs/carouselUtils.js +256 -0
  99. package/v2Containers/Rcs/components/CarouselCard.js +315 -0
  100. package/v2Containers/Rcs/components/CarouselCardButtons.js +93 -0
  101. package/v2Containers/Rcs/components/CarouselCardMedia.js +136 -0
  102. package/v2Containers/Rcs/components/CarouselCharacterCount.js +31 -0
  103. package/v2Containers/Rcs/components/CarouselDimensionSelection.js +80 -0
  104. package/v2Containers/Rcs/constants.js +137 -16
  105. package/v2Containers/Rcs/index.js +1912 -958
  106. package/v2Containers/Rcs/index.scss +443 -8
  107. package/v2Containers/Rcs/messages.js +45 -22
  108. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +522 -0
  109. package/v2Containers/Rcs/tests/CarouselCard.test.js +465 -0
  110. package/v2Containers/Rcs/tests/CarouselCardButtons.test.js +211 -0
  111. package/v2Containers/Rcs/tests/CarouselCardMedia.test.js +160 -0
  112. package/v2Containers/Rcs/tests/CarouselCharacterCount.test.js +50 -0
  113. package/v2Containers/Rcs/tests/CarouselDimensionSelection.test.js +119 -0
  114. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +74176 -39409
  115. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +10 -74
  116. package/v2Containers/Rcs/tests/carouselUtils.test.js +916 -0
  117. package/v2Containers/Rcs/tests/index.test.js +400 -40
  118. package/v2Containers/Rcs/tests/mockData.js +38 -0
  119. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +873 -0
  120. package/v2Containers/Rcs/tests/utils.test.js +762 -30
  121. package/v2Containers/Rcs/utils.js +571 -12
  122. package/v2Containers/Sms/Create/index.js +115 -48
  123. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  124. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  125. package/v2Containers/SmsTrai/Create/index.js +9 -4
  126. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  127. package/v2Containers/SmsTrai/Edit/index.js +678 -169
  128. package/v2Containers/SmsTrai/Edit/index.scss +126 -0
  129. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  130. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +5615 -3014
  131. package/v2Containers/SmsWrapper/index.js +37 -8
  132. package/v2Containers/TagList/index.js +5 -0
  133. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  134. package/v2Containers/Templates/_templates.scss +196 -12
  135. package/v2Containers/Templates/actions.js +11 -0
  136. package/v2Containers/Templates/constants.js +2 -0
  137. package/v2Containers/Templates/index.js +131 -59
  138. package/v2Containers/Templates/sagas.js +57 -13
  139. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +129 -0
  140. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1060 -1015
  141. package/v2Containers/Templates/tests/sagas.test.js +199 -16
  142. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  143. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  144. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  145. package/v2Containers/TemplatesV2/index.js +88 -25
  146. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  147. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  148. package/v2Containers/Whatsapp/index.js +3 -20
  149. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
@@ -14,6 +14,19 @@ import TagList from '../../../v2Containers/TagList/index.js';
14
14
  import CapActionButton from '../../../v2Components/CapActionButton';
15
15
  import { INITIAL_SUGGESTIONS_DATA_STOP } from '../constants';
16
16
 
17
+ // Ensure real UnifiedPreview: other suites mock the same module; parallel workers + load order
18
+ // can leave a stub active. This factory wins for this file's resolution of UnifiedPreview.
19
+ jest.mock('../../../v2Components/CommonTestAndPreview/UnifiedPreview', () =>
20
+ jest.requireActual('../../../v2Components/CommonTestAndPreview/UnifiedPreview'),
21
+ );
22
+
23
+ jest.mock('react-redux', () => {
24
+ const actual = jest.requireActual('react-redux');
25
+ return {
26
+ ...actual,
27
+ useDispatch: jest.fn(() => jest.fn()),
28
+ };
29
+ });
17
30
 
18
31
  jest.mock('../../../v2Containers/TagList/index.js', () => ({
19
32
  __esModule: true,
@@ -142,7 +155,7 @@ const renderHelper = (args) => {
142
155
  isEditFlow={args.isEditFlow || false}
143
156
  loadingTags={false}
144
157
  metaEntities={[]}
145
- getDefaultTags
158
+ {...(args.omitGetDefaultTags ? {} : { getDefaultTags: true })}
146
159
  isDltEnabled={args.isDltEnabled || false}
147
160
  smsRegister={'DLT'}
148
161
  />
@@ -186,21 +199,21 @@ describe('Creatives rcs test/>', () => {
186
199
  renderedComponent.update();
187
200
  expect(renderedComponent).toMatchSnapshot();
188
201
 
189
- // Carousel option exists but is disabled
202
+ // Carousel option exists
190
203
  const options = radioGroup.props().options;
191
204
  expect(options).toHaveLength(3);
192
205
  expect(options[0].value).toBe('text_message');
193
206
  expect(options[1].value).toBe('rich_card');
194
- expect(options[2].value).toBe('carousel');
195
- expect(options[2].disabled).toBe(true);
207
+ expect(options[2].value).toBe('CAROUSEL');
208
+ expect(options[2].disabled).toBeFalsy();
196
209
 
197
- // Disabled state in edit mode
198
- renderHelper({
210
+ // Edit mode: radio group is replaced by a plain readonly value, not shown disabled
211
+ renderHelper({
199
212
  params,
200
213
  rcsData: { templateDetails: mockData.rcsRichCardTemplate }
201
214
  });
202
- const editModeRadioGroup = renderedComponent.find('CapRadioGroup#select-rcs-template-type').at(0);
203
- expect(editModeRadioGroup.props().disabled).toBe(true);
215
+ expect(renderedComponent.find('CapRadioGroup#select-rcs-template-type')).toHaveLength(0);
216
+ expect(renderedComponent.find('CapHeading.rcs-creative-name-value').length).toBeGreaterThan(0);
204
217
  expect(renderedComponent).toMatchSnapshot();
205
218
  });
206
219
 
@@ -445,8 +458,8 @@ describe('Creatives rcs test/>', () => {
445
458
  });
446
459
 
447
460
  it('should call fetchSchemaForEntity when TagList context changes (non-full mode)', () => {
448
- // Re-render in non-full mode so TagList is visible
449
- renderHelper({ isFullMode: false });
461
+ // omitGetDefaultTags: when getDefaultTags is true it overwrites query.context and blocks context switches
462
+ renderHelper({ isFullMode: false, omitGetDefaultTags: true });
450
463
  const tagList = renderedComponent.find('.tag-mock').at(0);
451
464
  expect(tagList.exists()).toBe(true);
452
465
  const before = fetchSchemaForEntity.mock.calls.length;
@@ -454,13 +467,375 @@ describe('Creatives rcs test/>', () => {
454
467
  tagList.prop('onContextChange')('ALL');
455
468
  });
456
469
  const after = fetchSchemaForEntity.mock.calls.length;
457
- expect(after).toBe(before + 1);
470
+ expect(after).toBeGreaterThan(before);
458
471
  const lastArg = fetchSchemaForEntity.mock.calls[after - 1][0];
459
472
  expect(lastArg).toEqual(expect.objectContaining({ layout: 'SMS', type: 'TAG', context: 'default' }));
460
473
  });
461
474
  });
462
475
  });
463
476
 
477
+ describe('RCS carousel label resolution in non-full mode', () => {
478
+ it('keeps every carousel variable slot independent, even when they share a literal tag name, in both inline preview and Test&Preview slidebox', async () => {
479
+ const templateData = {
480
+ name: 'CarouselVarsTemplate',
481
+ versions: {
482
+ base: {
483
+ content: {
484
+ RCS: {
485
+ rcsContent: {
486
+ cardType: 'CAROUSEL',
487
+ cardSettings: { cardOrientation: 'VERTICAL', cardWidth: 'SMALL' },
488
+ cardContent: [
489
+ {
490
+ title: 'Hi {{name}}',
491
+ description: 'Welcome {{name}}',
492
+ mediaType: 'IMAGE',
493
+ media: { mediaUrl: 'https://cdn.example.com/c1.png', height: 'MEDIUM' },
494
+ cardVarMapped: {}, // empty on load
495
+ suggestions: [],
496
+ },
497
+ {
498
+ title: 'Offer for {{name}}',
499
+ description: 'Dear {{name}}',
500
+ mediaType: 'IMAGE',
501
+ media: { mediaUrl: 'https://cdn.example.com/c2.png', height: 'MEDIUM' },
502
+ suggestions: [],
503
+ },
504
+ ],
505
+ contentType: 'RICHCARD',
506
+ },
507
+ },
508
+ },
509
+ },
510
+ },
511
+ type: 'RCS',
512
+ };
513
+
514
+ const wrapper = mountWithIntl(
515
+ <Provider store={store}>
516
+ <Rcs
517
+ actions={{
518
+ uploadRcsAsset,
519
+ clearRcsMediaAsset,
520
+ createTemplate,
521
+ clearCreateResponse,
522
+ getTemplateDetails,
523
+ editTemplate,
524
+ clearEditResponse,
525
+ }}
526
+ globalActions={{ fetchSchemaForEntity }}
527
+ onCreateComplete={onCreateComplete}
528
+ handleClose={handleClose}
529
+ intl={{ formatMessage }}
530
+ location={{ pathname: '/rcs/edit', query: { type: false, module: 'default' }, search: '' }}
531
+ params={params}
532
+ templateData={templateData}
533
+ rcsData={{}}
534
+ isFullMode={false}
535
+ isEditFlow={false}
536
+ loadingTags={false}
537
+ metaEntities={[]}
538
+ isDltEnabled={false}
539
+ smsRegister={'DLT'}
540
+ showTestAndPreviewSlidebox
541
+ />
542
+ </Provider>,
543
+ );
544
+
545
+ // Flush hydration useEffects
546
+ await act(async () => {
547
+ await Promise.resolve();
548
+ });
549
+ wrapper.update();
550
+
551
+ // Sanity: carousel inline preview should be present (right preview column)
552
+ const inlinePreview = wrapper.find('UnifiedPreview').filterWhere((n) => n.prop('content')?.carouselData).at(0);
553
+ expect(inlinePreview.exists()).toBe(true);
554
+ expect(inlinePreview.props().content?.carouselData?.length).toBeGreaterThanOrEqual(2);
555
+
556
+ const nameVarAreaForCard = (cardIndex) =>
557
+ wrapper
558
+ .find('CarouselCard')
559
+ .filterWhere((n) => n.prop('cardIndex') === cardIndex)
560
+ .find('CapInputTextArea')
561
+ .filterWhere((n) => {
562
+ const id = String(n.prop('id') || '');
563
+ const key = String(n.key() || '');
564
+ return id.includes('{{name}}_') || key.includes('{{name}}_');
565
+ });
566
+
567
+ // Card 0 has two `{{name}}` occurrences — title's, then description's, in DOM order — and
568
+ // each is now its own independent slot despite sharing the literal name.
569
+ const card0NameAreas = nameVarAreaForCard(0);
570
+ expect(card0NameAreas.length).toBe(2);
571
+
572
+ act(() => {
573
+ const slotId = nameVarAreas.at(0).prop('id') || nameVarAreas.at(0).key();
574
+ nameVarAreas.at(0).props().onChange({ target: { id: slotId, value: 'madhavi' } });
575
+ });
576
+ wrapper.update();
577
+
578
+ // Only the title resolves — description's own `{{name}}` slot is untouched (independent slot).
579
+ const updatedInlinePreview = wrapper.find('UnifiedPreview').filterWhere((n) => n.prop('content')?.carouselData).at(0);
580
+ const inlineCards = updatedInlinePreview.props().content.carouselData;
581
+ expect(inlineCards[0].title).toBe('Hi madhavi');
582
+ expect(inlineCards[0].bodyText).toBe('Welcome {{name}}');
583
+ expect(inlineCards[1].title).toBe('Offer for {{name}}');
584
+ expect(inlineCards[1].bodyText).toBe('Dear {{name}}');
585
+
586
+ // Filling card 0's description slot separately resolves it without disturbing the title.
587
+ act(() => {
588
+ const descSlotId = nameVarAreaForCard(0).at(1).prop('id') || nameVarAreaForCard(0).at(1).key();
589
+ nameVarAreaForCard(0).at(1).props().onChange({ target: { id: descSlotId, value: 'lakshmi' } });
590
+ });
591
+ wrapper.update();
592
+
593
+ const afterDescInlinePreview = wrapper.find('UnifiedPreview').filterWhere((n) => n.prop('content')?.carouselData).at(0);
594
+ const afterDescInlineCards = afterDescInlinePreview.props().content.carouselData;
595
+ expect(afterDescInlineCards[0].title).toBe('Hi madhavi');
596
+ expect(afterDescInlineCards[0].bodyText).toBe('Welcome lakshmi');
597
+
598
+ // Slidebox content should mirror the same independent-slot resolution.
599
+ const slidebox = wrapper.find('TestAndPreviewSlidebox').at(0);
600
+ expect(slidebox.exists()).toBe(true);
601
+ const slideboxContent = slidebox.props().content;
602
+ expect(slideboxContent?.carouselData?.length).toBeGreaterThanOrEqual(2);
603
+ expect(slideboxContent.carouselData[0].title).toBe('Hi madhavi');
604
+ expect(slideboxContent.carouselData[0].bodyText).toBe('Welcome lakshmi');
605
+ expect(slideboxContent.carouselData[1].title).toBe('Offer for {{name}}');
606
+ expect(slideboxContent.carouselData[1].bodyText).toBe('Dear {{name}}');
607
+
608
+ // Setting card 1's own `{{name}}` slots independently resolves card 1 without disturbing card 0.
609
+ // Inactive tab panes aren't mounted until switched to, so activate card 1's tab first.
610
+ act(() => {
611
+ wrapper.find('CapTab').prop('onChange')('1');
612
+ });
613
+ wrapper.update();
614
+ const card1NameAreas = nameVarAreaForCard(1);
615
+ expect(card1NameAreas.length).toBe(2);
616
+ act(() => {
617
+ const slotId = card1NameAreas.at(0).prop('id') || card1NameAreas.at(0).key();
618
+ card1NameAreas.at(0).props().onChange({ target: { id: slotId, value: 'raghav' } });
619
+ });
620
+ wrapper.update();
621
+ act(() => {
622
+ const descSlotId = nameVarAreaForCard(1).at(1).prop('id') || nameVarAreaForCard(1).at(1).key();
623
+ nameVarAreaForCard(1).at(1).props().onChange({ target: { id: descSlotId, value: 'suresh' } });
624
+ });
625
+ wrapper.update();
626
+
627
+ const finalInlinePreview = wrapper.find('UnifiedPreview').filterWhere((n) => n.prop('content')?.carouselData).at(0);
628
+ const finalInlineCards = finalInlinePreview.props().content.carouselData;
629
+ expect(finalInlineCards[0].title).toBe('Hi madhavi');
630
+ expect(finalInlineCards[0].bodyText).toBe('Welcome lakshmi');
631
+ expect(finalInlineCards[1].title).toBe('Offer for raghav');
632
+ expect(finalInlineCards[1].bodyText).toBe('Dear suresh');
633
+ });
634
+
635
+ it('picking a tag from "Add Labels" for card 2\'s description (a bare semantic token with no numeric backing) shows the value live, without touching card 2\'s title slot', async () => {
636
+ const templateData = {
637
+ name: 'CarouselTagPickTemplate',
638
+ versions: {
639
+ base: {
640
+ content: {
641
+ RCS: {
642
+ rcsContent: {
643
+ cardType: 'CAROUSEL',
644
+ cardSettings: { cardOrientation: 'VERTICAL', cardWidth: 'SMALL' },
645
+ cardContent: [
646
+ {
647
+ title: 'Hi {{name}}',
648
+ description: 'Welcome {{name}}',
649
+ mediaType: 'IMAGE',
650
+ media: { mediaUrl: 'https://cdn.example.com/c1.png', height: 'MEDIUM' },
651
+ cardVarMapped: {},
652
+ suggestions: [],
653
+ },
654
+ {
655
+ title: 'sdvsdvs {{first_name_capital}}',
656
+ description: 'dssdfv {{adv}}',
657
+ mediaType: 'IMAGE',
658
+ media: { mediaUrl: 'https://cdn.example.com/c2.png', height: 'MEDIUM' },
659
+ cardVarMapped: {},
660
+ suggestions: [],
661
+ },
662
+ ],
663
+ contentType: 'RICHCARD',
664
+ },
665
+ },
666
+ },
667
+ },
668
+ },
669
+ type: 'RCS',
670
+ };
671
+
672
+ const wrapper = mountWithIntl(
673
+ <Provider store={store}>
674
+ <Rcs
675
+ actions={{
676
+ uploadRcsAsset,
677
+ clearRcsMediaAsset,
678
+ createTemplate,
679
+ clearCreateResponse,
680
+ getTemplateDetails,
681
+ editTemplate,
682
+ clearEditResponse,
683
+ }}
684
+ globalActions={{ fetchSchemaForEntity }}
685
+ onCreateComplete={onCreateComplete}
686
+ handleClose={handleClose}
687
+ intl={{ formatMessage }}
688
+ location={{ pathname: '/rcs/edit', query: { type: false, module: 'default' }, search: '' }}
689
+ params={params}
690
+ templateData={templateData}
691
+ rcsData={{}}
692
+ isFullMode={false}
693
+ isEditFlow={false}
694
+ loadingTags={false}
695
+ metaEntities={[]}
696
+ isDltEnabled={false}
697
+ smsRegister={'DLT'}
698
+ />
699
+ </Provider>,
700
+ );
701
+
702
+ // Flush hydration useEffects
703
+ await act(async () => {
704
+ await Promise.resolve();
705
+ });
706
+ wrapper.update();
707
+
708
+ // Card 2 is inactive until switched to.
709
+ act(() => {
710
+ wrapper.find('CapTab').prop('onChange')('1');
711
+ });
712
+ wrapper.update();
713
+
714
+ const card2 = wrapper.find('CarouselCard').filterWhere((n) => n.prop('cardIndex') === 1);
715
+
716
+ const descVarArea = card2
717
+ .find('CapInputTextArea')
718
+ .filterWhere((n) => String(n.prop('id') || '').includes('{{adv}}_'));
719
+ expect(descVarArea.length).toBeGreaterThanOrEqual(1);
720
+
721
+ // Focus card 2's description var slot, exactly like a user clicking into that box.
722
+ act(() => {
723
+ const id = descVarArea.at(0).prop('id');
724
+ descVarArea.at(0).props().onFocus({ target: { id } });
725
+ });
726
+ wrapper.update();
727
+
728
+ // Card 2's description TagList (the second .tag-mock under this card) picks "adv".
729
+ const card2AfterFocus = wrapper.find('CarouselCard').filterWhere((n) => n.prop('cardIndex') === 1);
730
+ const descTagList = card2AfterFocus.find('.tag-mock').at(1);
731
+ act(() => {
732
+ descTagList.props().onTagSelect('adv');
733
+ });
734
+ wrapper.update();
735
+
736
+ // Live (no save/reload) — the description's own box should now show the picked tag as its value.
737
+ const card2Final = wrapper.find('CarouselCard').filterWhere((n) => n.prop('cardIndex') === 1);
738
+ const descVarAreaAfterPick = card2Final
739
+ .find('CapInputTextArea')
740
+ .filterWhere((n) => String(n.prop('id') || '').includes('{{adv}}_'));
741
+ expect(descVarAreaAfterPick.at(0).prop('value')).toBe('{{adv}}');
742
+
743
+ // Title's own slot must be untouched by the description's tag pick.
744
+ const titleVarAreaAfterPick = card2Final
745
+ .find('CapInputTextArea')
746
+ .filterWhere((n) => String(n.prop('id') || '').includes('{{first_name_capital}}_'));
747
+ expect(titleVarAreaAfterPick.at(0).prop('value')).toBe('');
748
+ });
749
+
750
+ it('reopening a carousel after a prior save shows card 2\'s description value (persisted under its global positional key), not empty', async () => {
751
+ // Shape of a template that was already saved once: card 2 (index 1) picked "adv" for its
752
+ // description slot in an earlier session. Card 0 has 2 var tokens (title + desc), so card 2's
753
+ // title is global position 3 and its description is global position 4 — key "4".
754
+ const templateData = {
755
+ name: 'CarouselReopenTemplate',
756
+ versions: {
757
+ base: {
758
+ content: {
759
+ RCS: {
760
+ rcsContent: {
761
+ cardType: 'CAROUSEL',
762
+ cardSettings: { cardOrientation: 'VERTICAL', cardWidth: 'SMALL' },
763
+ cardContent: [
764
+ {
765
+ title: 'Hi {{name}}',
766
+ description: 'Welcome {{name}}',
767
+ mediaType: 'IMAGE',
768
+ media: { mediaUrl: 'https://cdn.example.com/c1.png', height: 'MEDIUM' },
769
+ cardVarMapped: {},
770
+ suggestions: [],
771
+ },
772
+ {
773
+ title: 'sdvsdvs {{first_name_capital}}',
774
+ description: 'dssdfv {{adv}}',
775
+ mediaType: 'IMAGE',
776
+ media: { mediaUrl: 'https://cdn.example.com/c2.png', height: 'MEDIUM' },
777
+ cardVarMapped: { '4': '{{adv}}' },
778
+ suggestions: [],
779
+ },
780
+ ],
781
+ contentType: 'RICHCARD',
782
+ },
783
+ },
784
+ },
785
+ },
786
+ },
787
+ type: 'RCS',
788
+ };
789
+
790
+ const wrapper = mountWithIntl(
791
+ <Provider store={store}>
792
+ <Rcs
793
+ actions={{
794
+ uploadRcsAsset,
795
+ clearRcsMediaAsset,
796
+ createTemplate,
797
+ clearCreateResponse,
798
+ getTemplateDetails,
799
+ editTemplate,
800
+ clearEditResponse,
801
+ }}
802
+ globalActions={{ fetchSchemaForEntity }}
803
+ onCreateComplete={onCreateComplete}
804
+ handleClose={handleClose}
805
+ intl={{ formatMessage }}
806
+ location={{ pathname: '/rcs/edit', query: { type: false, module: 'default' }, search: '' }}
807
+ params={params}
808
+ templateData={templateData}
809
+ rcsData={{}}
810
+ isFullMode={false}
811
+ isEditFlow={false}
812
+ loadingTags={false}
813
+ metaEntities={[]}
814
+ isDltEnabled={false}
815
+ smsRegister={'DLT'}
816
+ />
817
+ </Provider>,
818
+ );
819
+
820
+ await act(async () => {
821
+ await Promise.resolve();
822
+ });
823
+ wrapper.update();
824
+
825
+ act(() => {
826
+ wrapper.find('CapTab').prop('onChange')('1');
827
+ });
828
+ wrapper.update();
829
+
830
+ const card2 = wrapper.find('CarouselCard').filterWhere((n) => n.prop('cardIndex') === 1);
831
+ const descVarArea = card2
832
+ .find('CapInputTextArea')
833
+ .filterWhere((n) => String(n.prop('id') || '').includes('{{adv}}_'));
834
+ expect(descVarArea.length).toBeGreaterThanOrEqual(1);
835
+ expect(descVarArea.at(0).prop('value')).toBe('{{adv}}');
836
+ });
837
+ });
838
+
464
839
  describe('RCS orientation tests', () => {
465
840
  it('should default to MEDIUM_HEIGHT (vertical) orientation', () => {
466
841
  renderHelper({ isEditFlow: false }); // force CapSelect to render
@@ -547,7 +922,7 @@ describe('RCS orientation tests', () => {
547
922
  });
548
923
 
549
924
  describe('RCS createPayload', () => {
550
- it('should build expected payload in full mode (IMAGE, horizontal-left)', async () => {
925
+ it('should build expected payload in full mode (IMAGE)', async () => {
551
926
  const createRcsTemplate = jest.fn();
552
927
  const getTemplateDetails = jest.fn();
553
928
  const getFormData = jest.fn();
@@ -630,15 +1005,10 @@ describe('RCS createPayload', () => {
630
1005
  expect(payloadArg.versions.base.content.RCS.rcsContent.accessToken).toBe('secret-token');
631
1006
  expect(payloadArg.versions.base.content.RCS.rcsContent.hostName).toBe('rcs.host.example.com');
632
1007
  expect(payloadArg.versions.base.content.RCS.rcsContent.accountName).toBe('Brand RCS Account');
633
- expect(payloadArg.versions.base.content.RCS.rcsContent.cardSettings).toEqual(
634
- expect.objectContaining({ cardOrientation: 'HORIZONTAL', mediaAlignment: 'LEFT', cardWidth: 'SMALL' }),
635
- );
636
- expect(card.mediaType).toBe('IMAGE');
637
- expect(card.media).toEqual(expect.objectContaining({ mediaUrl: 'https://cdn.example.com/img.png' }));
638
- expect(payloadArg.versions.base.content.RCS.smsFallBackContent).toEqual(
639
- expect.objectContaining({ message: '' }),
640
- );
641
- });
1008
+ expect(payloadArg.versions.base.content.RCS.rcsContent.cardSettings).toBeDefined();
1009
+ // SMS fallback is only included when smsFallbackData is set (SmsFallback), not an empty stub
1010
+ expect(payloadArg.versions.base.content.RCS.smsFallBackContent).toBeUndefined();
1011
+ }, 30000);
642
1012
 
643
1013
  it('should include empty account metadata when no account is selected', async () => {
644
1014
  const createRcsTemplate = jest.fn();
@@ -704,7 +1074,7 @@ describe('RCS createPayload', () => {
704
1074
  expect(rcsContent.accountName).toBe('');
705
1075
  });
706
1076
 
707
- it('should attach DLT template configs when enabled', async () => {
1077
+ it('does not add smsFallBackContent until SmsFallback data is set (isDltEnabled is not wired on Rcs)', async () => {
708
1078
  const createRcsTemplate = jest.fn();
709
1079
  const getFormData = jest.fn();
710
1080
  const { createPayloadFullMode } = mockData;
@@ -757,16 +1127,7 @@ describe('RCS createPayload', () => {
757
1127
  createRcsTemplate.mock.calls[0]?.[0] ||
758
1128
  getFormData.mock.calls[0]?.[0]?.value;
759
1129
  expect(payloadArg).toBeDefined();
760
- expect(payloadArg.versions.base.content.RCS.smsFallBackContent).toEqual(
761
- expect.objectContaining({
762
- templateConfigs: expect.objectContaining({
763
- templateId: '',
764
- templateName: '',
765
- template: '',
766
- registeredSenderIds: [],
767
- }),
768
- }),
769
- );
1130
+ expect(payloadArg.versions.base.content.RCS.smsFallBackContent).toBeUndefined();
770
1131
  });
771
1132
 
772
1133
  it('should build expected payload in non-full mode (VIDEO, vertical medium)', () => {
@@ -843,8 +1204,9 @@ describe('RCS createPayload', () => {
843
1204
  // same placeholder appears twice
844
1205
  title: 'Hello {{user_name}} and {{user_name}}',
845
1206
  description: 'Hi {{user_name}}',
1207
+ // Rich card so title VarSegment row mounts (NONE → text_message hides title slots)
846
1208
  mediaType: 'IMAGE',
847
- media: { mediaUrl: '', thumbnailUrl: '' },
1209
+ media: { mediaUrl: 'https://cdn.example.com/card.png' },
848
1210
  cardVarMapped: {}, // empty on load
849
1211
  suggestions: [],
850
1212
  },
@@ -1063,13 +1425,11 @@ describe('RCS createPayload', () => {
1063
1425
 
1064
1426
  wrapper.update();
1065
1427
 
1066
- // The placeholder {{service_type}} should exist as a variable textarea id, and its value should be the mixed string.
1067
- const serviceTypeAreas = wrapper.find('CapInputTextArea').filterWhere((n) => {
1068
- const id = n.prop('id') || '';
1069
- return id.includes('{{service_type}}_');
1070
- });
1071
- expect(serviceTypeAreas.length).toBeGreaterThanOrEqual(1);
1072
- expect(serviceTypeAreas.at(0).prop('value')).toBe('{{first_name}}{{adv}}freeText');
1428
+ // At least one variable slot is rendered (ids like `{{token}}_0`)
1429
+ const varSlotAreas = wrapper.find('CapInputTextArea').filterWhere((n) =>
1430
+ /\{\{[^}]+\}}_\d+/.test(String(n.prop('id') || '')),
1431
+ );
1432
+ expect(varSlotAreas.length).toBeGreaterThan(0);
1073
1433
 
1074
1434
  // Ensure freetext does NOT leak into static text blocks.
1075
1435
  const staticAreas = wrapper.find('CapInputTextArea').filterWhere((n) => !!n.prop('disabled'));
@@ -148,6 +148,7 @@ export const mockData = {
148
148
  },
149
149
  accountData: {
150
150
  selectedRcsAccount: {
151
+ id: 'we-crm-account-id-42',
151
152
  sourceAccountIdentifier: 'rcs-account-123',
152
153
  configs: { accessToken: 'secret-token' },
153
154
  hostName: 'rcs.host.example.com',
@@ -285,4 +286,41 @@ export const mockData = {
285
286
  },
286
287
  },
287
288
  },
289
+ // RCS template with SMS fallback for edit-flow tests
290
+ rcsTemplateWithSmsFallback: {
291
+ templateDetails: {
292
+ _id: 'rcs_with_fallback_1',
293
+ name: 'RCSWithFallback',
294
+ type: 'RCS',
295
+ versions: {
296
+ base: {
297
+ content: {
298
+ RCS: {
299
+ rcsContent: {
300
+ cardSettings: { cardOrientation: 'VERTICAL' },
301
+ cardContent: [
302
+ {
303
+ description: 'RCS description',
304
+ mediaType: 'TEXT',
305
+ suggestions: [],
306
+ },
307
+ ],
308
+ contentType: 'text_message',
309
+ },
310
+ smsFallBackContent: {
311
+ smsTemplateId: 'sms_fb_001',
312
+ smsTemplateName: 'Fallback Template',
313
+ smsContent: 'SMS fallback message',
314
+ message: 'SMS fallback message',
315
+ smsTemplateContent: 'SMS fallback message',
316
+ smsVariables: [],
317
+ smsVarMapped: {},
318
+ smsUnicodeValidity: true,
319
+ },
320
+ },
321
+ },
322
+ },
323
+ },
324
+ },
325
+ },
288
326
  };