@capillarytech/creatives-library 8.0.359-alpha.0 → 8.0.359-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 (147) hide show
  1. package/constants/unified.js +29 -0
  2. package/index.html +1 -0
  3. package/package.json +1 -1
  4. package/services/tests/api.test.js +35 -20
  5. package/utils/cdnTransformation.js +75 -3
  6. package/utils/commonUtils.js +19 -1
  7. package/utils/rcsPayloadUtils.js +92 -0
  8. package/utils/templateVarUtils.js +201 -0
  9. package/utils/tests/cdnTransformation.test.js +127 -0
  10. package/utils/tests/rcsPayloadUtils.test.js +226 -0
  11. package/utils/tests/templateVarUtils.test.js +204 -0
  12. package/v2Components/CapActionButton/constants.js +7 -0
  13. package/v2Components/CapActionButton/index.js +166 -108
  14. package/v2Components/CapActionButton/index.scss +157 -6
  15. package/v2Components/CapActionButton/messages.js +19 -3
  16. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  17. package/v2Components/CapImageUpload/index.js +2 -2
  18. package/v2Components/CapTagList/index.js +10 -0
  19. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +72 -49
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
  22. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
  24. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  25. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  26. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  27. package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +16 -0
  28. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +157 -15
  29. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +14 -132
  30. package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +169 -0
  31. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +400 -239
  32. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +202 -10
  33. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  34. package/v2Components/CommonTestAndPreview/constants.js +40 -2
  35. package/v2Components/CommonTestAndPreview/index.js +887 -453
  36. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  37. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  38. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  39. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
  40. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  41. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  42. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  43. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  44. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  45. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/PreviewHeader.test.js +163 -0
  46. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  47. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +0 -364
  48. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +522 -0
  49. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +454 -1
  50. package/v2Components/CommonTestAndPreview/tests/constants.test.js +2 -1
  51. package/v2Components/CommonTestAndPreview/tests/index.test.js +327 -4
  52. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  53. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
  54. package/v2Components/FormBuilder/index.js +167 -56
  55. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  56. package/v2Components/SmsFallback/constants.js +73 -0
  57. package/v2Components/SmsFallback/index.js +956 -0
  58. package/v2Components/SmsFallback/index.scss +265 -0
  59. package/v2Components/SmsFallback/messages.js +78 -0
  60. package/v2Components/SmsFallback/smsFallbackUtils.js +119 -0
  61. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  62. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  63. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  64. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +223 -0
  65. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  66. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  67. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  68. package/v2Components/TemplatePreview/_templatePreview.scss +37 -22
  69. package/v2Components/TemplatePreview/constants.js +2 -0
  70. package/v2Components/TemplatePreview/index.js +143 -31
  71. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  72. package/v2Components/TestAndPreviewSlidebox/index.js +15 -3
  73. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  74. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  75. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  76. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  77. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  78. package/v2Containers/App/constants.js +3 -0
  79. package/v2Containers/App/tests/constants.test.js +61 -0
  80. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +17 -0
  81. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  82. package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
  83. package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
  84. package/v2Containers/CreativesContainer/constants.js +9 -0
  85. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
  86. package/v2Containers/CreativesContainer/index.js +382 -127
  87. package/v2Containers/CreativesContainer/index.scss +83 -1
  88. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  89. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +79 -34
  90. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  91. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  92. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
  93. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  94. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  95. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  96. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  97. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  98. package/v2Containers/Rcs/constants.js +120 -11
  99. package/v2Containers/Rcs/index.js +2577 -812
  100. package/v2Containers/Rcs/index.scss +281 -8
  101. package/v2Containers/Rcs/messages.js +34 -3
  102. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
  103. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98036 -70145
  104. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  105. package/v2Containers/Rcs/tests/index.test.js +152 -121
  106. package/v2Containers/Rcs/tests/mockData.js +38 -0
  107. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  108. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  109. package/v2Containers/Rcs/utils.js +478 -11
  110. package/v2Containers/Sms/Create/index.js +106 -40
  111. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  112. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  113. package/v2Containers/SmsTrai/Create/index.js +9 -4
  114. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  115. package/v2Containers/SmsTrai/Edit/index.js +640 -130
  116. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  117. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  118. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
  119. package/v2Containers/SmsWrapper/index.js +37 -8
  120. package/v2Containers/TagList/index.js +6 -0
  121. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  122. package/v2Containers/Templates/_templates.scss +166 -86
  123. package/v2Containers/Templates/actions.js +11 -0
  124. package/v2Containers/Templates/constants.js +2 -0
  125. package/v2Containers/Templates/index.js +203 -145
  126. package/v2Containers/Templates/sagas.js +62 -13
  127. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  128. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1062 -1017
  129. package/v2Containers/Templates/tests/sagas.test.js +222 -22
  130. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  131. package/v2Containers/Templates/tests/webpush.test.js +375 -0
  132. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  133. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  134. package/v2Containers/TemplatesV2/index.js +86 -23
  135. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  136. package/v2Containers/Viber/constants.js +0 -19
  137. package/v2Containers/Viber/index.js +47 -714
  138. package/v2Containers/Viber/index.scss +0 -148
  139. package/v2Containers/Viber/messages.js +0 -116
  140. package/v2Containers/Viber/tests/index.test.js +0 -80
  141. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  142. package/v2Containers/WebPush/Create/index.js +91 -8
  143. package/v2Containers/WebPush/Create/index.scss +7 -0
  144. package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +348 -0
  145. package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +325 -0
  146. package/v2Containers/Whatsapp/index.js +3 -20
  147. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
@@ -0,0 +1,226 @@
1
+ import { normalizeRcsMessageContentForApi } from '../rcsPayloadUtils';
2
+
3
+ describe('normalizeRcsMessageContentForApi', () => {
4
+ it('defaults missing rcsContent to empty object and still normalizes root smsFallBackContent', () => {
5
+ const item = {
6
+ smsFallBackContent: { message: 'root-only' },
7
+ };
8
+ normalizeRcsMessageContentForApi(item);
9
+ expect(item.rcsContent).toEqual({});
10
+ expect(item.smsFallBackContent).toEqual({ message: 'root-only' });
11
+ });
12
+
13
+ it('moves nested smsFallBackContent out of rcsContent and merges onto root', () => {
14
+ const item = {
15
+ rcsContent: {
16
+ contentType: 'RICHCARD',
17
+ smsFallBackContent: {
18
+ message: 'nested-msg',
19
+ templateConfigs: { registeredSenderIds: ['H1'] },
20
+ },
21
+ },
22
+ smsFallBackContent: { message: 'root-msg' },
23
+ };
24
+ normalizeRcsMessageContentForApi(item);
25
+ expect(item.rcsContent).toEqual({ contentType: 'RICHCARD' });
26
+ expect(item.smsFallBackContent.message).toBe('root-msg');
27
+ expect(item.smsFallBackContent.templateConfigs).toEqual({
28
+ registeredSenderIds: ['H1'],
29
+ });
30
+ });
31
+
32
+ it('nested message merges when root has no message key', () => {
33
+ const item = {
34
+ rcsContent: {
35
+ smsFallBackContent: { message: 'from-nested' },
36
+ },
37
+ };
38
+ normalizeRcsMessageContentForApi(item);
39
+ expect(item.rcsContent).toEqual({});
40
+ expect(item.smsFallBackContent).toEqual({ message: 'from-nested' });
41
+ });
42
+
43
+ it('removes smsFallBackContent when nothing to serialize', () => {
44
+ const item = {
45
+ rcsContent: { smsFallBackContent: {} },
46
+ smsFallBackContent: {},
47
+ };
48
+ normalizeRcsMessageContentForApi(item);
49
+ expect(item.rcsContent).toEqual({});
50
+ expect(item.smsFallBackContent).toBeUndefined();
51
+ });
52
+
53
+ it('does not add templateConfigs when empty', () => {
54
+ const item = {
55
+ rcsContent: {},
56
+ smsFallBackContent: { message: 'x', templateConfigs: {} },
57
+ };
58
+ normalizeRcsMessageContentForApi(item);
59
+ expect(item.smsFallBackContent).toEqual({ message: 'x' });
60
+ });
61
+
62
+ it('serializes templateConfigs only when message is absent but templateConfigs has data', () => {
63
+ const item = {
64
+ rcsContent: {
65
+ smsFallBackContent: {
66
+ templateConfigs: { registeredSenderIds: ['Z'] },
67
+ },
68
+ },
69
+ };
70
+ normalizeRcsMessageContentForApi(item);
71
+ expect(item.smsFallBackContent).toEqual({
72
+ templateConfigs: { registeredSenderIds: ['Z'] },
73
+ });
74
+ });
75
+
76
+ it('includes message in api payload when key exists with empty string', () => {
77
+ const item = {
78
+ smsFallBackContent: { message: '', templateConfigs: { id: 't1' } },
79
+ };
80
+ normalizeRcsMessageContentForApi(item);
81
+ expect(item.smsFallBackContent).toEqual({
82
+ message: '',
83
+ templateConfigs: { id: 't1' },
84
+ });
85
+ });
86
+
87
+ it('merges when legacy nested is null-coalesced', () => {
88
+ const item = {
89
+ rcsContent: {
90
+ contentType: 'TEXT',
91
+ smsFallBackContent: null,
92
+ },
93
+ smsFallBackContent: { message: 'from-root' },
94
+ };
95
+ normalizeRcsMessageContentForApi(item);
96
+ expect(item.rcsContent).toEqual({ contentType: 'TEXT' });
97
+ expect(item.smsFallBackContent).toEqual({ message: 'from-root' });
98
+ });
99
+
100
+ it('folds rcsSmsFallbackVarMapped into message and omits var map from API shape', () => {
101
+ const msg = '{{optout}} {{fullname}} test SMS';
102
+ const varMapped = {
103
+ '{{optout}}_0': '{{city}}',
104
+ '{{fullname}}_2': '{{fullname}}',
105
+ };
106
+ const item = {
107
+ rcsContent: { contentType: 'RICHCARD' },
108
+ smsFallBackContent: {
109
+ message: msg,
110
+ rcsSmsFallbackVarMapped: varMapped,
111
+ },
112
+ };
113
+ normalizeRcsMessageContentForApi(item);
114
+ expect(item.smsFallBackContent).toEqual({
115
+ message: '{{city}} {{fullname}} test SMS',
116
+ });
117
+ expect(item.smsFallBackContent.rcsSmsFallbackVarMapped).toBeUndefined();
118
+ });
119
+
120
+ it('merges nested rcsSmsFallbackVarMapped with root message; root message wins on key clash', () => {
121
+ const nestedVar = { '{{a}}_0': '{{nested}}' };
122
+ const item = {
123
+ rcsContent: {
124
+ contentType: 'RICHCARD',
125
+ smsFallBackContent: {
126
+ message: '{{a}} nested-tail',
127
+ rcsSmsFallbackVarMapped: nestedVar,
128
+ },
129
+ },
130
+ smsFallBackContent: {
131
+ message: '{{a}} tail',
132
+ rcsSmsFallbackVarMapped: { '{{a}}_0': '{{root}}' },
133
+ },
134
+ };
135
+ normalizeRcsMessageContentForApi(item);
136
+ expect(item.smsFallBackContent.message).toBe('{{root}} tail');
137
+ expect(item.smsFallBackContent.rcsSmsFallbackVarMapped).toBeUndefined();
138
+ });
139
+
140
+ it('non-slot var map keys do not change message but var map is still stripped', () => {
141
+ const varMapped = { 0: { label: 'City', tag: '{{city}}' } };
142
+ const item = {
143
+ rcsContent: { contentType: 'RICHCARD' },
144
+ smsFallBackContent: {
145
+ message: 'Hello',
146
+ rcsSmsFallbackVarMapped: varMapped,
147
+ },
148
+ };
149
+ normalizeRcsMessageContentForApi(item);
150
+ expect(item.smsFallBackContent).toEqual({ message: 'Hello' });
151
+ });
152
+
153
+ it('omits rcsSmsFallbackVarMapped when merged value is empty object', () => {
154
+ const item = {
155
+ rcsContent: {
156
+ smsFallBackContent: { rcsSmsFallbackVarMapped: {} },
157
+ },
158
+ smsFallBackContent: { message: 'only-msg' },
159
+ };
160
+ normalizeRcsMessageContentForApi(item);
161
+ expect(item.smsFallBackContent).toEqual({ message: 'only-msg' });
162
+ expect(item.smsFallBackContent.rcsSmsFallbackVarMapped).toBeUndefined();
163
+ });
164
+
165
+ it('removes root-level templateConfigs from RCS message content item', () => {
166
+ const item = {
167
+ rcsContent: { cardType: 'STANDALONE' },
168
+ templateConfigs: {
169
+ templateId: '69b3d33934a7bd0db5bada8e',
170
+ template: '{{registered_store_name}} Its a test message',
171
+ },
172
+ };
173
+ normalizeRcsMessageContentForApi(item);
174
+ expect(item.templateConfigs).toBeUndefined();
175
+ expect(item.rcsContent).toEqual({ cardType: 'STANDALONE' });
176
+ });
177
+
178
+ it('removes templateConfigs nested inside rcsContent', () => {
179
+ const item = {
180
+ rcsContent: {
181
+ cardType: 'STANDALONE',
182
+ templateConfigs: {
183
+ templateId: '69b3d33934a7bd0db5bada8e',
184
+ template: '{{registered_store_name}} Its a test message',
185
+ },
186
+ cardContent: [{ title: 'Card' }],
187
+ },
188
+ };
189
+ normalizeRcsMessageContentForApi(item);
190
+ expect(item.templateConfigs).toBeUndefined();
191
+ expect(item.rcsContent.templateConfigs).toBeUndefined();
192
+ expect(item.rcsContent.cardContent).toEqual([{ title: 'Card' }]);
193
+ });
194
+
195
+ it('strips isSaved from each suggestion in cardContent', () => {
196
+ const item = {
197
+ rcsContent: {
198
+ cardContent: [
199
+ {
200
+ title: 'Card 1',
201
+ suggestions: [
202
+ { type: 'QUICK_REPLY', text: 'Yes', isSaved: true },
203
+ { type: 'CTA', text: 'Go', url: 'https://example.com', isSaved: false },
204
+ ],
205
+ },
206
+ ],
207
+ },
208
+ };
209
+ normalizeRcsMessageContentForApi(item);
210
+ expect(item.rcsContent.cardContent[0].suggestions).toEqual([
211
+ { type: 'QUICK_REPLY', text: 'Yes' },
212
+ { type: 'CTA', text: 'Go', url: 'https://example.com' },
213
+ ]);
214
+ });
215
+
216
+ it('handles cardContent with no suggestions gracefully', () => {
217
+ const item = {
218
+ rcsContent: {
219
+ cardContent: [{ title: 'Card 1' }, { title: 'Card 2', suggestions: [] }],
220
+ },
221
+ };
222
+ normalizeRcsMessageContentForApi(item);
223
+ expect(item.rcsContent.cardContent[0].suggestions).toBeUndefined();
224
+ expect(item.rcsContent.cardContent[1].suggestions).toEqual([]);
225
+ });
226
+ });
@@ -0,0 +1,204 @@
1
+ import {
2
+ extractTemplateVariables,
3
+ splitTemplateVarString,
4
+ splitContentByOrderedVarTokens,
5
+ getFallbackResolvedContent,
6
+ getFallbackResolvedContentForCardDisplay,
7
+ isDltHashVarToken,
8
+ isAnyTemplateVarToken,
9
+ } from '../templateVarUtils';
10
+
11
+ describe('templateVarUtils', () => {
12
+ describe('splitContentByOrderedVarTokens', () => {
13
+ it('pushes remainder when next token is not found in string', () => {
14
+ expect(splitContentByOrderedVarTokens(['{{b}}'], 'hello')).toEqual(['hello']);
15
+ });
16
+
17
+ it('handles empty token queue by pushing remainder', () => {
18
+ expect(splitContentByOrderedVarTokens([], 'rest')).toEqual(['rest']);
19
+ });
20
+
21
+ it('handles null matchedVariableTokens as empty queue', () => {
22
+ expect(splitContentByOrderedVarTokens(null, 'abc')).toEqual(['abc']);
23
+ });
24
+
25
+ it('stops and pushes remainder when next token in queue is empty string', () => {
26
+ expect(splitContentByOrderedVarTokens(['{{a}}', ''], 'x{{a}}y')).toEqual(['x', '{{a}}', 'y']);
27
+ });
28
+
29
+ it('splits multiple ordered tokens across content', () => {
30
+ expect(splitContentByOrderedVarTokens(['{{a}}', '{{b}}'], 'p{{a}}q{{b}}r')).toEqual([
31
+ 'p',
32
+ '{{a}}',
33
+ 'q',
34
+ '{{b}}',
35
+ 'r',
36
+ ]);
37
+ });
38
+
39
+ it('returns empty segment list when content is null and queue is empty', () => {
40
+ expect(splitContentByOrderedVarTokens([], null)).toEqual([]);
41
+ });
42
+ });
43
+
44
+ describe('splitTemplateVarString', () => {
45
+ it('returns empty array for falsy string', () => {
46
+ expect(splitTemplateVarString('')).toEqual([]);
47
+ expect(splitTemplateVarString(null)).toEqual([]);
48
+ });
49
+
50
+ it('splits mustache segments', () => {
51
+ const parts = splitTemplateVarString('Hi {{name}}!');
52
+ expect(parts.some((p) => p.includes('name'))).toBe(true);
53
+ });
54
+
55
+ it('omits empty segments after filtering', () => {
56
+ expect(splitTemplateVarString('a{{x}}b')).toEqual(['a', '{{x}}', 'b']);
57
+ });
58
+ });
59
+
60
+ describe('getFallbackResolvedContent', () => {
61
+ it('replaces var segments from varMapData', () => {
62
+ const out = getFallbackResolvedContent('{{a}}', { '{{a}}_0': 'X' });
63
+ expect(out).toBe('X');
64
+ });
65
+
66
+ it('keeps DLT token string when slot empty', () => {
67
+ const out = getFallbackResolvedContent('{#v#}', {});
68
+ expect(out).toContain('#');
69
+ });
70
+
71
+ it('returns empty string for mustache slot when map has no value', () => {
72
+ const out = getFallbackResolvedContent('Hi {{missing}}', {});
73
+ expect(out).toBe('Hi ');
74
+ });
75
+
76
+ it('treats null as empty; explicit empty string is kept (cleared input)', () => {
77
+ expect(getFallbackResolvedContent('{{a}}', { '{{a}}_0': null })).toBe('');
78
+ expect(getFallbackResolvedContent('{{a}}', { '{{a}}_0': '' })).toBe('');
79
+ });
80
+
81
+ it('keeps DLT token in preview when slot map has empty string (no tag yet)', () => {
82
+ expect(getFallbackResolvedContent('{#v#}', { '{#v#}_0': '' })).toBe('{#v#}');
83
+ });
84
+
85
+ it('keeps DLT token in preview when slot map has null', () => {
86
+ expect(getFallbackResolvedContent('{#v#}', { '{#v#}_0': null })).toBe('{#v#}');
87
+ });
88
+
89
+ it('uses slot replacement when value is non-empty string', () => {
90
+ expect(getFallbackResolvedContent('A{{b}}C', { '{{b}}_1': 'ok' })).toBe('AokC');
91
+ });
92
+ });
93
+
94
+ describe('getFallbackResolvedContentForCardDisplay', () => {
95
+ it('shows raw tokens when map is empty or slots unset', () => {
96
+ expect(getFallbackResolvedContentForCardDisplay('Hi {{name}}', {})).toBe('Hi {{name}}');
97
+ expect(getFallbackResolvedContentForCardDisplay('{#var#}', {})).toBe('{#var#}');
98
+ });
99
+
100
+ it('shows raw token when slot key exists but value is empty (no labels)', () => {
101
+ expect(getFallbackResolvedContentForCardDisplay('{#var#}', { '{#var#}_0': '' })).toBe('{#var#}');
102
+ expect(getFallbackResolvedContentForCardDisplay('{{a}}', { '{{a}}_0': '' })).toBe('{{a}}');
103
+ });
104
+
105
+ it('replaces when slot has non-empty value', () => {
106
+ expect(getFallbackResolvedContentForCardDisplay('Hi {{name}}', { '{{name}}_1': 'Pat' })).toBe('Hi Pat');
107
+ });
108
+ });
109
+
110
+ describe('isDltHashVarToken / isAnyTemplateVarToken', () => {
111
+ it('detects DLT hash token', () => {
112
+ expect(isDltHashVarToken('{#x#}')).toBe(true);
113
+ expect(isDltHashVarToken('plain')).toBe(false);
114
+ });
115
+
116
+ it('isAnyTemplateVarToken combines mustache and DLT', () => {
117
+ expect(isAnyTemplateVarToken('{{a}}')).toBe(true);
118
+ expect(isAnyTemplateVarToken('{#x#}')).toBe(true);
119
+ });
120
+ });
121
+
122
+ describe('extractTemplateVariables', () => {
123
+ it('returns empty array for empty or missing template', () => {
124
+ expect(extractTemplateVariables('')).toEqual([]);
125
+ expect(extractTemplateVariables()).toEqual([]);
126
+ });
127
+
128
+ it('includes DLT {#var#} names when no custom capture regex', () => {
129
+ const vars = extractTemplateVariables('Hello {#promo#} and {{name}}');
130
+ expect(vars).toEqual(expect.arrayContaining(['promo', 'name']));
131
+ });
132
+
133
+ it('skips DLT scan when captureRegex is provided', () => {
134
+ const re = /\{\{([^}]+)\}\}/g;
135
+ expect(extractTemplateVariables('{#x#} {{y}}', re)).toEqual(['y']);
136
+ });
137
+
138
+ it('dedupes duplicate variable names', () => {
139
+ expect(extractTemplateVariables('{{a}} {{a}}')).toEqual(['a']);
140
+ });
141
+
142
+ it('uses default name var when DLT body capture is empty', () => {
143
+ const vars = extractTemplateVariables('{##}');
144
+ expect(vars).toContain('var');
145
+ });
146
+ });
147
+
148
+ describe('extractTemplateVariables (regex global)', () => {
149
+ it('extracts all vars when caller passes a non-global capture regex (avoids infinite exec loop)', () => {
150
+ const nonGlobal = /\{\{([^}]+)\}\}/;
151
+ expect(nonGlobal.global).toBe(false);
152
+ expect(extractTemplateVariables('{{a}} and {{b}}', nonGlobal)).toEqual(['a', 'b']);
153
+ });
154
+
155
+ it('still works when capture regex already has the global flag', () => {
156
+ const globalRe = /\{\{([^}]+)\}\}/g;
157
+ expect(extractTemplateVariables('{{x}} {{y}}', globalRe)).toEqual(['x', 'y']);
158
+ });
159
+ });
160
+
161
+ describe('getFallbackResolvedContent — userVarMap (third param)', () => {
162
+ it('falls back to userVarMap for mustache slot absent from varMapData', () => {
163
+ expect(getFallbackResolvedContent('Hi {{name}}', {}, { name: 'Alice' })).toBe('Hi Alice');
164
+ });
165
+
166
+ it('falls back to userVarMap for DLT slot absent from varMapData', () => {
167
+ expect(getFallbackResolvedContent('{#city#}', {}, { city: 'Mumbai' })).toBe('Mumbai');
168
+ });
169
+
170
+ it('falls back to userVarMap when mustache slot is present but blank', () => {
171
+ expect(getFallbackResolvedContent('{{x}}', { '{{x}}_0': '' }, { x: 'val' })).toBe('val');
172
+ });
173
+
174
+ it('falls back to userVarMap when DLT slot is present but blank', () => {
175
+ expect(getFallbackResolvedContent('{#x#}', { '{#x#}_0': '' }, { x: 'val' })).toBe('val');
176
+ });
177
+
178
+ it('slot wins over userVarMap when mustache slot is non-empty', () => {
179
+ expect(getFallbackResolvedContent('{{x}}', { '{{x}}_0': 'slot-val' }, { x: 'user-val' })).toBe('slot-val');
180
+ });
181
+
182
+ it('slot wins over userVarMap when DLT slot is non-empty', () => {
183
+ expect(getFallbackResolvedContent('{#x#}', { '{#x#}_0': 'slot' }, { x: 'user' })).toBe('slot');
184
+ });
185
+
186
+ it('resolves dot-path suffix key from userVarMap', () => {
187
+ expect(
188
+ getFallbackResolvedContent('{{FORMAT_1}}', {}, { 'expiry.FORMAT_1': 'Dec 2025' }),
189
+ ).toBe('Dec 2025');
190
+ });
191
+
192
+ it('userVarMap null behaves identically to the two-argument form', () => {
193
+ expect(getFallbackResolvedContent('{{a}}', { '{{a}}_0': 'x' }, null)).toBe('x');
194
+ });
195
+
196
+ it('blank userVarMap value leaves mustache token as empty string', () => {
197
+ expect(getFallbackResolvedContent('{{a}}', {}, { a: '' })).toBe('');
198
+ });
199
+
200
+ it('blank userVarMap value leaves DLT token as raw token', () => {
201
+ expect(getFallbackResolvedContent('{#a#}', {}, { a: '' })).toBe('{#a#}');
202
+ });
203
+ });
204
+ });
@@ -32,6 +32,13 @@ export const CTA_OPTIONS = [
32
32
  ];
33
33
 
34
34
  export const invalidVarRegex = /{{(.*?)}}/g;
35
+
36
+ /** URL CTA subtype (Figma: URL type dropdown). */
37
+ export const RCS_CTA_URL_TYPE = {
38
+ STATIC: 'STATIC',
39
+ DYNAMIC: 'DYNAMIC',
40
+ };
41
+
35
42
  export const BTN_MAX_LENGTH = 25;
36
43
  export const PHONE_NUMBER_MAX_LENGTH = 15;
37
44
  export const URL_MAX_LENGTH = 2000;