@capillarytech/creatives-library 9.0.29 → 9.0.30

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 (147) 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 +102 -0
  7. package/utils/templateVarUtils.js +198 -0
  8. package/utils/tests/rcsPayloadUtils.test.js +295 -0
  9. package/utils/tests/templateVarUtils.test.js +204 -0
  10. package/v2Components/CapActionButton/constants.js +21 -1
  11. package/v2Components/CapActionButton/index.js +261 -144
  12. package/v2Components/CapActionButton/index.scss +245 -14
  13. package/v2Components/CapActionButton/messages.js +32 -3
  14. package/v2Components/CapActionButton/tests/index.test.js +74 -19
  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 +322 -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/MobilePush/Create/test/saga.test.js +2 -2
  96. package/v2Containers/Rcs/carouselUtils.js +224 -0
  97. package/v2Containers/Rcs/components/CarouselCard.js +317 -0
  98. package/v2Containers/Rcs/components/CarouselCardButtons.js +113 -0
  99. package/v2Containers/Rcs/components/CarouselCardMedia.js +136 -0
  100. package/v2Containers/Rcs/components/CarouselCharacterCount.js +31 -0
  101. package/v2Containers/Rcs/components/CarouselDimensionSelection.js +80 -0
  102. package/v2Containers/Rcs/constants.js +132 -16
  103. package/v2Containers/Rcs/index.js +1822 -946
  104. package/v2Containers/Rcs/index.scss +443 -8
  105. package/v2Containers/Rcs/messages.js +45 -22
  106. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +494 -0
  107. package/v2Containers/Rcs/tests/CarouselCard.test.js +464 -0
  108. package/v2Containers/Rcs/tests/CarouselCardButtons.test.js +211 -0
  109. package/v2Containers/Rcs/tests/CarouselCardMedia.test.js +160 -0
  110. package/v2Containers/Rcs/tests/CarouselCharacterCount.test.js +50 -0
  111. package/v2Containers/Rcs/tests/CarouselDimensionSelection.test.js +119 -0
  112. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +74440 -39387
  113. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +10 -74
  114. package/v2Containers/Rcs/tests/carouselUtils.test.js +916 -0
  115. package/v2Containers/Rcs/tests/index.test.js +275 -40
  116. package/v2Containers/Rcs/tests/mockData.js +38 -0
  117. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +912 -0
  118. package/v2Containers/Rcs/tests/utils.test.js +682 -30
  119. package/v2Containers/Rcs/utils.js +514 -12
  120. package/v2Containers/Sms/Create/index.js +115 -48
  121. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  122. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  123. package/v2Containers/SmsTrai/Create/index.js +9 -4
  124. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  125. package/v2Containers/SmsTrai/Edit/index.js +678 -169
  126. package/v2Containers/SmsTrai/Edit/index.scss +126 -0
  127. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  128. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +5615 -3014
  129. package/v2Containers/SmsWrapper/index.js +37 -8
  130. package/v2Containers/TagList/index.js +5 -0
  131. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  132. package/v2Containers/Templates/_templates.scss +196 -12
  133. package/v2Containers/Templates/actions.js +11 -0
  134. package/v2Containers/Templates/constants.js +2 -0
  135. package/v2Containers/Templates/index.js +131 -59
  136. package/v2Containers/Templates/sagas.js +57 -13
  137. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +129 -0
  138. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1060 -1015
  139. package/v2Containers/Templates/tests/sagas.test.js +199 -16
  140. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  141. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  142. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  143. package/v2Containers/TemplatesV2/index.js +88 -25
  144. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  145. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  146. package/v2Containers/Whatsapp/index.js +3 -20
  147. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
@@ -55,6 +55,33 @@ describe('parseSenderDetailsResponse', () => {
55
55
  expect(result.domains[0].cdmaSenders[0].value).toBe('CDMA1');
56
56
  });
57
57
 
58
+ it('should populate SMS senders when channel argument is lowercase (normalized for branches)', () => {
59
+ const response = {
60
+ entity: {
61
+ SMS: [
62
+ {
63
+ id: 10,
64
+ priority: 1,
65
+ domainProperties: {
66
+ domainName: 'SMS Domain 1',
67
+ id: 100,
68
+ contactInfo: [
69
+ { type: 'gsm_sender_id', valid: true, value: 'GSM1', default: true },
70
+ { type: 'cdma_sender_id', valid: true, value: 'CDMA1' },
71
+ ],
72
+ connectionProperties: {},
73
+ },
74
+ },
75
+ ],
76
+ },
77
+ };
78
+ const result = parseSenderDetailsResponse('sms', response);
79
+ expect(result.domains).toHaveLength(1);
80
+ expect(result.domains[0].gsmSenders).toHaveLength(1);
81
+ expect(result.domains[0].gsmSenders[0].value).toBe('GSM1');
82
+ expect(result.domains[0].cdmaSenders).toHaveLength(1);
83
+ });
84
+
58
85
  it('should parse EMAIL channel entity array', () => {
59
86
  const response = {
60
87
  entity: {
@@ -231,5 +258,319 @@ describe('parseSenderDetailsResponse', () => {
231
258
  const result = parseSenderDetailsResponse('WHATSAPP', response);
232
259
  expect(result.domains[0].sourceAccountIdentifier).toBe('waba-456');
233
260
  });
261
+
262
+ it('should map hostName to domainName when domainName missing (RCS)', () => {
263
+ const response = {
264
+ entity: {
265
+ RCS: [
266
+ {
267
+ id: 1,
268
+ priority: 0,
269
+ domainProperties: {
270
+ hostName: 'rcs-host',
271
+ id: 42,
272
+ contactInfo: [
273
+ { type: 'gsm_sender_id', valid: true, value: 'S1', default: true },
274
+ ],
275
+ },
276
+ },
277
+ ],
278
+ },
279
+ };
280
+ const result = parseSenderDetailsResponse('RCS', response);
281
+ expect(result.domains[0].domainName).toBe('rcs-host');
282
+ expect(result.domains[0].domainId).toBe(42);
283
+ expect(result.domains[0].gsmSenders[0].value).toBe('S1');
284
+ });
285
+
286
+ it('should unwrap result.entity when top-level entity is absent', () => {
287
+ const response = {
288
+ result: {
289
+ entity: {
290
+ SMS: [
291
+ {
292
+ id: 1,
293
+ priority: 0,
294
+ domainProperties: {
295
+ domainName: 'ViaResult',
296
+ id: 9,
297
+ contactInfo: [{ type: 'gsm_sender_id', valid: true, value: 'G' }],
298
+ },
299
+ },
300
+ ],
301
+ },
302
+ },
303
+ };
304
+ const result = parseSenderDetailsResponse('SMS', response);
305
+ expect(result.domains[0].domainName).toBe('ViaResult');
306
+ });
307
+
308
+ it('should merge rcs_sender_id into gsmSenders for RCS', () => {
309
+ const response = {
310
+ entity: {
311
+ RCS: [
312
+ {
313
+ id: 1,
314
+ priority: 0,
315
+ domainProperties: {
316
+ domainName: 'RCS Domain',
317
+ id: 1,
318
+ contactInfo: [
319
+ { type: 'rcs_sender_id', valid: true, value: 'RCS123', default: true },
320
+ ],
321
+ },
322
+ },
323
+ ],
324
+ },
325
+ };
326
+ const result = parseSenderDetailsResponse('RCS', response);
327
+ expect(result.domains[0].gsmSenders).toHaveLength(1);
328
+ expect(result.domains[0].gsmSenders[0].value).toBe('RCS123');
329
+ });
330
+
331
+ it('should unwrap data.entity when top-level entity is absent', () => {
332
+ const response = {
333
+ data: {
334
+ entity: {
335
+ SMS: [
336
+ {
337
+ id: 1,
338
+ priority: 0,
339
+ domainProperties: {
340
+ domainName: 'ViaData',
341
+ id: 9,
342
+ contactInfo: [{ type: 'gsm_sender_id', valid: true, value: 'G' }],
343
+ },
344
+ },
345
+ ],
346
+ },
347
+ },
348
+ };
349
+ const result = parseSenderDetailsResponse('SMS', response);
350
+ expect(result.domains[0].domainName).toBe('ViaData');
351
+ });
352
+
353
+ it('should return empty domains when entity is explicitly null', () => {
354
+ expect(parseSenderDetailsResponse('SMS', { entity: null })).toEqual({ domains: [] });
355
+ });
356
+
357
+ it('should return empty when channel data is a non-domain object', () => {
358
+ expect(parseSenderDetailsResponse('SMS', { entity: { notAnArray: true } })).toEqual({ domains: [] });
359
+ });
360
+
361
+ it('should use userid when sourceAccountIdentifier and wabaId are missing (WHATSAPP)', () => {
362
+ const response = {
363
+ entity: {
364
+ WHATSAPP: [
365
+ {
366
+ id: 1,
367
+ priority: 0,
368
+ domainProperties: {
369
+ domainName: 'W',
370
+ id: 1,
371
+ connectionProperties: { userid: 'user-99' },
372
+ },
373
+ },
374
+ ],
375
+ },
376
+ };
377
+ const result = parseSenderDetailsResponse('WHATSAPP', response);
378
+ expect(result.domains[0].sourceAccountIdentifier).toBe('user-99');
379
+ });
380
+
381
+ it('should dedupe duplicate GSM values for RCS (merged contact types)', () => {
382
+ const response = {
383
+ entity: {
384
+ RCS: [
385
+ {
386
+ id: 1,
387
+ priority: 0,
388
+ domainProperties: {
389
+ domainName: 'R',
390
+ id: 1,
391
+ contactInfo: [
392
+ { type: 'gsm_sender_id', valid: true, value: 'SAME', default: true },
393
+ { type: 'rcs_sender_id', valid: true, value: 'SAME' },
394
+ ],
395
+ },
396
+ },
397
+ ],
398
+ },
399
+ };
400
+ const result = parseSenderDetailsResponse('RCS', response);
401
+ expect(result.domains[0].gsmSenders.filter((r) => r.value === 'SAME')).toHaveLength(1);
402
+ });
403
+
404
+ it('should return empty domains when response is not an object', () => {
405
+ expect(parseSenderDetailsResponse('SMS', 42)).toEqual({ domains: [] });
406
+ expect(parseSenderDetailsResponse('SMS', 'x')).toEqual({ domains: [] });
407
+ });
408
+
409
+ it('should return empty domains when entity is empty string', () => {
410
+ expect(parseSenderDetailsResponse('SMS', { entity: '' })).toEqual({ domains: [] });
411
+ });
412
+
413
+ it('should normalize hyphenated contactInfo.type to match gsm_sender_id', () => {
414
+ const response = {
415
+ entity: {
416
+ SMS: [
417
+ {
418
+ id: 1,
419
+ domainProperties: {
420
+ domainName: 'D',
421
+ id: 1,
422
+ contactInfo: [
423
+ { type: 'gsm-sender-id', valid: true, value: 'HYPH' },
424
+ ],
425
+ },
426
+ },
427
+ ],
428
+ },
429
+ };
430
+ const result = parseSenderDetailsResponse('SMS', response);
431
+ expect(result.domains[0].gsmSenders[0].value).toBe('HYPH');
432
+ });
433
+
434
+ it('should skip whitespace-only GSM values when merging RCS contact types', () => {
435
+ const response = {
436
+ entity: {
437
+ RCS: [
438
+ {
439
+ id: 1,
440
+ domainProperties: {
441
+ domainName: 'R',
442
+ id: 1,
443
+ contactInfo: [
444
+ { type: 'gsm_sender_id', valid: true, value: ' ' },
445
+ { type: 'gsm_sender_id', valid: true, value: 'OK' },
446
+ ],
447
+ },
448
+ },
449
+ ],
450
+ },
451
+ };
452
+ const values = parseSenderDetailsResponse('RCS', response).domains[0].gsmSenders.map((g) => g.value);
453
+ expect(values).toEqual(['OK']);
454
+ });
455
+
456
+ it('should use default priority 0 when priority is null', () => {
457
+ const response = {
458
+ entity: {
459
+ SMS: [
460
+ {
461
+ id: 1,
462
+ priority: null,
463
+ domainProperties: {
464
+ domainName: 'P',
465
+ id: 9,
466
+ contactInfo: [],
467
+ },
468
+ },
469
+ ],
470
+ },
471
+ };
472
+ expect(parseSenderDetailsResponse('SMS', response).domains[0].priority).toBe(0);
473
+ });
474
+
475
+ it('should unwrap entity from raw response object when entity key is absent', () => {
476
+ const response = { misc: 1 };
477
+ expect(parseSenderDetailsResponse('SMS', response)).toEqual({ domains: [] });
478
+ });
479
+ });
480
+
481
+ describe('unwrapEntity edge cases', () => {
482
+ it('should return empty domains when entity key is explicitly null', () => {
483
+ // unwrapEntity: response.entity === null → returns null → parseSenderDetailsResponse returns { domains: [] }
484
+ expect(parseSenderDetailsResponse('SMS', { entity: null })).toEqual({ domains: [] });
485
+ });
486
+ });
487
+
488
+ describe('null or empty channel', () => {
489
+ it('should return empty domains when channel is null', () => {
490
+ const response = { entity: { SMS: [{ id: 1, domainProperties: { domainName: 'D', id: 10, contactInfo: [] } }] } };
491
+ expect(parseSenderDetailsResponse(null, response)).toEqual({ domains: [] });
492
+ });
493
+
494
+ it('should return empty domains when channel is empty string', () => {
495
+ const response = { entity: { SMS: [{ id: 1, domainProperties: { domainName: 'D', id: 10, contactInfo: [] } }] } };
496
+ expect(parseSenderDetailsResponse('', response)).toEqual({ domains: [] });
497
+ });
498
+ });
499
+
500
+ describe('single non-array domain object', () => {
501
+ it('should wrap single domain with domainProperties in an array and return one domain', () => {
502
+ const response = {
503
+ entity: {
504
+ SMS: {
505
+ id: 5,
506
+ domainProperties: {
507
+ domainName: 'SingleDomain',
508
+ id: 99,
509
+ contactInfo: [{ type: 'gsm_sender_id', valid: true, value: 'GSMSINGLE' }],
510
+ },
511
+ },
512
+ },
513
+ };
514
+ const result = parseSenderDetailsResponse('SMS', response);
515
+ expect(result.domains).toHaveLength(1);
516
+ expect(result.domains[0].domainName).toBe('SingleDomain');
517
+ expect(result.domains[0].gsmSenders[0].value).toBe('GSMSINGLE');
518
+ });
519
+
520
+ it('should return empty domains for single non-array domain without domainProperties', () => {
521
+ const response = {
522
+ entity: { SMS: { id: 5, someProp: 'value' } },
523
+ };
524
+ expect(parseSenderDetailsResponse('SMS', response)).toEqual({ domains: [] });
525
+ });
526
+ });
527
+
528
+ describe('deduplication by domainId when domainName is null', () => {
529
+ it('should deduplicate two entries sharing the same domainId when domainName is null', () => {
530
+ const response = {
531
+ entity: {
532
+ SMS: [
533
+ {
534
+ id: 10,
535
+ priority: 1,
536
+ domainProperties: { domainName: null, id: 42, contactInfo: [] },
537
+ },
538
+ {
539
+ id: 11,
540
+ priority: 2,
541
+ domainProperties: { domainName: null, id: 42, contactInfo: [] },
542
+ },
543
+ ],
544
+ },
545
+ };
546
+ const result = parseSenderDetailsResponse('SMS', response);
547
+ expect(result.domains).toHaveLength(1);
548
+ expect(result.domains[0].domainId).toBe(42);
549
+ });
550
+ });
551
+
552
+ describe('typeMatches null-type filtering', () => {
553
+ it('should exclude contactInfo items with null type from gsmSenders', () => {
554
+ const response = {
555
+ entity: {
556
+ SMS: [
557
+ {
558
+ id: 1,
559
+ domainProperties: {
560
+ domainName: 'D',
561
+ id: 1,
562
+ contactInfo: [
563
+ { type: null, valid: true, value: 'EXCLUDED' },
564
+ { type: 'gsm_sender_id', valid: true, value: 'INCLUDED' },
565
+ ],
566
+ },
567
+ },
568
+ ],
569
+ },
570
+ };
571
+ const result = parseSenderDetailsResponse('SMS', response);
572
+ expect(result.domains[0].gsmSenders).toHaveLength(1);
573
+ expect(result.domains[0].gsmSenders[0].value).toBe('INCLUDED');
574
+ });
234
575
  });
235
576
  });
@@ -8,7 +8,7 @@ import React from 'react';
8
8
  import { render, screen } from '@testing-library/react';
9
9
  import PreviewSection from '../PreviewSection';
10
10
 
11
- // Mock UnifiedPreview component
11
+ // Mock UnifiedPreview component (lightweight DOM for this suite only).
12
12
  const mockUnifiedPreview = jest.fn();
13
13
  jest.mock('../UnifiedPreview', () => {
14
14
  // eslint-disable-next-line global-require, import/no-extraneous-dependencies
@@ -232,3 +232,10 @@ describe('PreviewSection', () => {
232
232
  });
233
233
  });
234
234
  });
235
+
236
+ // This suite mocks ../UnifiedPreview. resetModules() alone is not enough — Jest still applies the
237
+ // manual mock on the next load. Unmock + reset so later test files in this worker get the real module.
238
+ afterAll(() => {
239
+ jest.unmock('../UnifiedPreview');
240
+ jest.resetModules();
241
+ });
@@ -16,25 +16,38 @@ import isEmpty from 'lodash/isEmpty';
16
16
  jest.mock('@capillarytech/cap-ui-library/CapRow', () => ({ children, ...rest }) => <div {...rest}>{children}</div>);
17
17
  jest.mock('@capillarytech/cap-ui-library/CapButton', () => ({ children, ...rest }) => <button type="button" {...rest}>{children}</button>);
18
18
  jest.mock('@capillarytech/cap-ui-library/CapHeader', () => ({ title, description }) => <div><span>{title}</span>{description}</div>);
19
- jest.mock('@capillarytech/cap-ui-library/CapTreeSelect', () => ({ treeData, onChange, value, placeholder }) => (
20
- <div data-testid="cap-tree-select"><input onChange={(e) => onChange && onChange(e.target.value)} placeholder={placeholder} /></div>
19
+ jest.mock('@capillarytech/cap-ui-library/CapTreeSelect', () => ({ treeData, onChange, value, placeholder, getPopupContainer }) => (
20
+ <div data-testid="cap-tree-select">
21
+ <input onChange={(e) => onChange && onChange(e.target.value)} placeholder={placeholder} />
22
+ {getPopupContainer && (
23
+ <button
24
+ type="button"
25
+ data-testid="cap-tree-select-popup-container"
26
+ onClick={() => getPopupContainer({ closest: () => null })}
27
+ >
28
+ resolve popup container
29
+ </button>
30
+ )}
31
+ </div>
21
32
  ));
22
33
 
23
34
  import SendTestMessage from '../SendTestMessage';
24
35
 
25
36
  // Mock DeliverySettings to assert props
26
37
  jest.mock('../DeliverySettings', () => function MockDeliverySettings(props) {
27
- return (
38
+ const smsRows = props.senderDetailsByChannel?.SMS || [];
39
+ return (
28
40
  <div data-testid="delivery-settings" data-props={JSON.stringify({
29
41
  channel: props.channel,
30
42
  hasDeliverySettings: !!props.deliverySettings,
31
- senderDetailsLength: (props.senderDetailsOptions || []).length,
43
+ senderDetailsLength: smsRows.length,
32
44
  wecrmAccountsLength: (props.wecrmAccounts || []).length,
33
45
  hasOnSave: typeof props.onSaveDeliverySettings === 'function',
34
46
  isLoadingSenderDetails: props.isLoadingSenderDetails,
35
47
  smsTraiDltEnabled: props.smsTraiDltEnabled,
36
48
  registeredSenderIds: props.registeredSenderIds,
37
49
  whatsappAccountFromForm: props.whatsappAccountFromForm,
50
+ isChannelSmsFallbackPreviewEnabled: props.isChannelSmsFallbackPreviewEnabled,
38
51
  })}
39
52
  />
40
53
  );
@@ -129,7 +142,7 @@ describe('SendTestMessage', () => {
129
142
  formatMessage: jest.fn((msg) => msg.defaultMessage || msg.id),
130
143
  channel: 'EMAIL',
131
144
  deliverySettings: {},
132
- senderDetailsOptions: [],
145
+ senderDetailsByChannel: {},
133
146
  wecrmAccounts: [],
134
147
  onSaveDeliverySettings: jest.fn(),
135
148
  isLoadingSenderDetails: false,
@@ -187,6 +200,17 @@ describe('SendTestMessage', () => {
187
200
  });
188
201
  });
189
202
 
203
+ it('should fall back to document.body when getPopupContainer finds no matching ancestor', () => {
204
+ render(
205
+ <TestWrapper>
206
+ <SendTestMessage {...defaultProps} />
207
+ </TestWrapper>
208
+ );
209
+
210
+ fireEvent.click(screen.getByTestId('cap-tree-select-popup-container'));
211
+ // No throw + no assertion target means the fallback branch (`|| document.body`) executed safely.
212
+ });
213
+
190
214
  it('should render send button', async () => {
191
215
  render(
192
216
  <TestWrapper>
@@ -233,6 +257,22 @@ describe('SendTestMessage', () => {
233
257
  expect(screen.getByTestId('delivery-settings')).toBeTruthy();
234
258
  });
235
259
 
260
+ it('should render DeliverySettings when channel is RCS and pass SMS fallback preview flag', () => {
261
+ render(
262
+ <TestWrapper>
263
+ <SendTestMessage
264
+ {...defaultProps}
265
+ channel="RCS"
266
+ isChannelSmsFallbackPreviewEnabled
267
+ />
268
+ </TestWrapper>
269
+ );
270
+ const el = screen.getByTestId('delivery-settings');
271
+ const data = JSON.parse(el.getAttribute('data-props'));
272
+ expect(data.channel).toBe('RCS');
273
+ expect(data.isChannelSmsFallbackPreviewEnabled).toBe(true);
274
+ });
275
+
236
276
  it('should not render DeliverySettings when channel is INAPP', () => {
237
277
  render(
238
278
  <TestWrapper>
@@ -280,7 +320,9 @@ describe('SendTestMessage', () => {
280
320
  {...defaultProps}
281
321
  channel="SMS"
282
322
  deliverySettings={{ domainId: 1 }}
283
- senderDetailsOptions={[{ domainId: 1, domainName: 'SMS Dom' }]}
323
+ senderDetailsByChannel={{
324
+ SMS: [{ domainId: 1, domainName: 'SMS Dom' }],
325
+ }}
284
326
  wecrmAccounts={[]}
285
327
  onSaveDeliverySettings={onSave}
286
328
  isLoadingSenderDetails={true}
@@ -668,21 +710,72 @@ describe('SendTestMessage', () => {
668
710
  ...defaultProps,
669
711
  renderAddTestCustomerButton,
670
712
  };
671
- render(
713
+ const { container } = render(
672
714
  <TestWrapper>
673
715
  <SendTestMessage {...props} />
674
716
  </TestWrapper>
675
717
  );
676
- // Assert component wiring: renderAddTestCustomerButton must have been called during render.
677
- expect(renderAddTestCustomerButton).toHaveBeenCalled();
678
- // Assert the returned element has the expected shape.
679
- const notFoundContent = renderAddTestCustomerButton.mock.results[0].value;
718
+ // notFoundContent is passed to TreeSelect and shown when dropdown is open with no match.
719
+ // We assert the render function returns a button that can be clicked.
720
+ const notFoundContent = renderAddTestCustomerButton.mock.results[0]?.value;
680
721
  expect(notFoundContent).toBeTruthy();
681
- expect(notFoundContent.type).toBe('button');
682
722
  expect(notFoundContent.props['data-testid']).toBe('add-test-customer-btn');
683
- // Assert clicking the button triggers the handler.
684
- notFoundContent.props.onClick();
685
- expect(onAddClick).toHaveBeenCalled();
723
+ expect(notFoundContent.type).toBe('button');
724
+ // Simulate click on the returned element's onClick
725
+ if (notFoundContent.props.onClick) {
726
+ notFoundContent.props.onClick();
727
+ expect(onAddClick).toHaveBeenCalled();
728
+ }
729
+ });
730
+
731
+ it('should render without notFoundContent when renderAddTestCustomerButton is not provided', () => {
732
+ const { renderAddTestCustomerButton, ...propsWithoutRenderButton } = defaultProps;
733
+ render(
734
+ <TestWrapper>
735
+ <SendTestMessage {...propsWithoutRenderButton} />
736
+ </TestWrapper>
737
+ );
738
+ expect(screen.getByTestId('cap-tree-select')).toBeTruthy();
739
+ });
740
+ });
741
+
742
+ describe('Default value fallbacks', () => {
743
+ it('should fall back to an empty object for deliverySettings when explicitly null', () => {
744
+ render(
745
+ <TestWrapper>
746
+ <SendTestMessage {...defaultProps} channel="SMS" deliverySettings={null} />
747
+ </TestWrapper>
748
+ );
749
+ const el = screen.getByTestId('delivery-settings');
750
+ const data = JSON.parse(el.getAttribute('data-props'));
751
+ // deliverySettings={null} hits the `deliverySettings || {}` fallback, so the
752
+ // downstream prop ends up as a truthy empty object rather than null.
753
+ expect(data.hasDeliverySettings).toBe(true);
754
+ });
755
+
756
+ it('should fall back to an empty array for wecrmAccounts when explicitly null', () => {
757
+ render(
758
+ <TestWrapper>
759
+ <SendTestMessage {...defaultProps} channel="SMS" wecrmAccounts={null} />
760
+ </TestWrapper>
761
+ );
762
+ const el = screen.getByTestId('delivery-settings');
763
+ const data = JSON.parse(el.getAttribute('data-props'));
764
+ expect(data.wecrmAccountsLength).toBe(0);
765
+ });
766
+
767
+ it('falls back to an empty object for senderDetailsByChannel when the function is invoked directly without React defaultProps resolution', () => {
768
+ // React only merges Component.defaultProps in when an element is created via
769
+ // JSX/React.createElement. Rendering <SendTestMessage /> without the prop (or with
770
+ // it explicitly undefined) always resolves through defaultProps to {} before the
771
+ // function body runs, so the inline destructuring default
772
+ // `senderDetailsByChannel = {}` on the function signature can never fire that way.
773
+ // Invoking the component as a plain function bypasses that defaultProps merge,
774
+ // letting the destructuring default itself execute.
775
+ const { senderDetailsByChannel, ...propsWithoutSenderDetailsByChannel } = defaultProps;
776
+ const element = SendTestMessage({ ...propsWithoutSenderDetailsByChannel, channel: 'SMS' });
777
+ render(<TestWrapper>{element}</TestWrapper>);
778
+ expect(screen.getByTestId('delivery-settings')).toBeTruthy();
686
779
  });
687
780
  });
688
781
  });