@capillarytech/creatives-library 8.0.353-alpha.5 → 8.0.353-alpha.6

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 (136) hide show
  1. package/constants/unified.js +29 -0
  2. package/package.json +1 -1
  3. package/services/tests/api.test.js +35 -20
  4. package/utils/commonUtils.js +19 -1
  5. package/utils/rcsPayloadUtils.js +92 -0
  6. package/utils/templateVarUtils.js +201 -0
  7. package/utils/tests/rcsPayloadUtils.test.js +226 -0
  8. package/utils/tests/templateVarUtils.test.js +204 -0
  9. package/v2Components/CapActionButton/constants.js +7 -0
  10. package/v2Components/CapActionButton/index.js +166 -108
  11. package/v2Components/CapActionButton/index.scss +157 -6
  12. package/v2Components/CapActionButton/messages.js +19 -3
  13. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  14. package/v2Components/CapTagList/index.js +10 -0
  15. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +72 -49
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +213 -21
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  22. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  23. package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +0 -17
  24. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +157 -15
  25. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +346 -146
  26. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +138 -48
  27. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  28. package/v2Components/CommonTestAndPreview/constants.js +38 -4
  29. package/v2Components/CommonTestAndPreview/index.js +691 -235
  30. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  31. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  32. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  33. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
  34. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  35. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  36. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  37. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  38. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  39. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/PreviewHeader.test.js +0 -159
  40. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  41. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -256
  42. package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -2
  43. package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -198
  44. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  45. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +36 -26
  46. package/v2Components/FormBuilder/index.js +11 -6
  47. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  48. package/v2Components/SmsFallback/constants.js +73 -0
  49. package/v2Components/SmsFallback/index.js +956 -0
  50. package/v2Components/SmsFallback/index.scss +265 -0
  51. package/v2Components/SmsFallback/messages.js +78 -0
  52. package/v2Components/SmsFallback/smsFallbackUtils.js +119 -0
  53. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  54. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  55. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  56. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +223 -0
  57. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  58. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  59. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  60. package/v2Components/TemplatePreview/_templatePreview.scss +38 -23
  61. package/v2Components/TemplatePreview/constants.js +2 -0
  62. package/v2Components/TemplatePreview/index.js +143 -31
  63. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  64. package/v2Components/TestAndPreviewSlidebox/index.js +15 -3
  65. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  66. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  67. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  68. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  69. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  70. package/v2Containers/App/constants.js +0 -3
  71. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  72. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  73. package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
  74. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  75. package/v2Containers/CreativesContainer/constants.js +9 -0
  76. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
  77. package/v2Containers/CreativesContainer/index.js +322 -103
  78. package/v2Containers/CreativesContainer/index.scss +51 -1
  79. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  80. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +78 -34
  81. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  82. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  83. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
  84. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  85. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  86. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  87. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  88. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  89. package/v2Containers/Rcs/constants.js +119 -10
  90. package/v2Containers/Rcs/index.js +2445 -813
  91. package/v2Containers/Rcs/index.scss +280 -8
  92. package/v2Containers/Rcs/messages.js +34 -3
  93. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
  94. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98018 -70073
  95. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  96. package/v2Containers/Rcs/tests/index.test.js +152 -121
  97. package/v2Containers/Rcs/tests/mockData.js +38 -0
  98. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  99. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  100. package/v2Containers/Rcs/utils.js +478 -11
  101. package/v2Containers/Sms/Create/index.js +106 -40
  102. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  103. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  104. package/v2Containers/SmsTrai/Create/index.js +9 -4
  105. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  106. package/v2Containers/SmsTrai/Edit/index.js +640 -130
  107. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  108. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  109. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
  110. package/v2Containers/SmsWrapper/index.js +37 -8
  111. package/v2Containers/TagList/index.js +6 -0
  112. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  113. package/v2Containers/Templates/_templates.scss +166 -9
  114. package/v2Containers/Templates/actions.js +11 -0
  115. package/v2Containers/Templates/constants.js +2 -0
  116. package/v2Containers/Templates/index.js +122 -120
  117. package/v2Containers/Templates/sagas.js +56 -12
  118. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  119. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1062 -1017
  120. package/v2Containers/Templates/tests/sagas.test.js +199 -16
  121. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  122. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  123. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  124. package/v2Containers/TemplatesV2/index.js +86 -23
  125. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  126. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  127. package/v2Containers/WebPush/Create/index.js +8 -91
  128. package/v2Containers/WebPush/Create/index.scss +0 -7
  129. package/v2Containers/Whatsapp/index.js +3 -20
  130. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
  131. package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +0 -169
  132. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +0 -522
  133. package/v2Containers/App/tests/constants.test.js +0 -61
  134. package/v2Containers/Templates/tests/webpush.test.js +0 -375
  135. package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +0 -338
  136. package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +0 -325
@@ -0,0 +1,318 @@
1
+ import {
2
+ getUnmappedDesc,
3
+ hasRcsVarTokens,
4
+ normalizeLibraryLoadedTitleDesc,
5
+ mergeRcsSmsFallBackContentFromDetails,
6
+ mergeRcsSmsFallbackVarMapLayers,
7
+ pickFirstSmsFallbackTemplateString,
8
+ syncCardVarMappedSemanticsFromSlots,
9
+ hasMeaningfulSmsFallbackShape,
10
+ getLibrarySmsFallbackApiBaselineFromTemplateData,
11
+ extractRegisteredSenderIdsFromSmsFallbackRecord,
12
+ pickRcsCardVarMappedEntries,
13
+ } from '../rcsLibraryHydrationUtils';
14
+ import { rcsVarRegex } from '../constants';
15
+
16
+ describe('rcsLibraryHydrationUtils', () => {
17
+ describe('mergeRcsSmsFallBackContentFromDetails', () => {
18
+ it('returns empty object for missing details', () => {
19
+ expect(mergeRcsSmsFallBackContentFromDetails(null)).toEqual({});
20
+ expect(mergeRcsSmsFallBackContentFromDetails(undefined)).toEqual({});
21
+ });
22
+
23
+ it('merges nested RCS smsFallBackContent with root mirror; nested wins on overlap', () => {
24
+ const details = {
25
+ versions: {
26
+ base: {
27
+ content: {
28
+ RCS: {
29
+ smsFallBackContent: {
30
+ smsTemplateName: 'OldName',
31
+ message: 'from nested',
32
+ smsContent: 'nested body',
33
+ },
34
+ },
35
+ },
36
+ },
37
+ },
38
+ smsFallBackContent: {
39
+ message: 'from root',
40
+ },
41
+ };
42
+ const out = mergeRcsSmsFallBackContentFromDetails(details);
43
+ expect(out.message).toBe('from nested');
44
+ expect(out.smsContent).toBe('nested body');
45
+ expect(out.smsTemplateName).toBe('OldName');
46
+ });
47
+
48
+ it('uses root when nested path is absent', () => {
49
+ const details = {
50
+ smsFallBackContent: { message: 'only root' },
51
+ };
52
+ expect(mergeRcsSmsFallBackContentFromDetails(details)).toEqual({ message: 'only root' });
53
+ });
54
+ });
55
+
56
+ describe('mergeRcsSmsFallbackVarMapLayers', () => {
57
+ it('merges API map with local so empty local object does not wipe API slots', () => {
58
+ const api = { rcsSmsFallbackVarMapped: { '{#a#}_0': 'x' } };
59
+ const local = { rcsSmsFallbackVarMapped: {} };
60
+ expect(mergeRcsSmsFallbackVarMapLayers(api, local)).toEqual({ '{#a#}_0': 'x' });
61
+ });
62
+
63
+ it('local slot values win on same key', () => {
64
+ const api = { rcsSmsFallbackVarMapped: { '{#a#}_0': 'old' } };
65
+ const local = { rcsSmsFallbackVarMapped: { '{#a#}_0': 'new' } };
66
+ expect(mergeRcsSmsFallbackVarMapLayers(api, local)).toEqual({ '{#a#}_0': 'new' });
67
+ });
68
+
69
+ it('reads kebab-case var map from a layer when camel is absent', () => {
70
+ const api = { 'rcs-sms-fallback-var-mapped': { '{#b#}_1': 'y' } };
71
+ expect(mergeRcsSmsFallbackVarMapLayers(api, {})).toEqual({ '{#b#}_1': 'y' });
72
+ });
73
+ });
74
+
75
+ describe('hasMeaningfulSmsFallbackShape', () => {
76
+ it('is false for empty / missing', () => {
77
+ expect(hasMeaningfulSmsFallbackShape(null)).toBe(false);
78
+ expect(hasMeaningfulSmsFallbackShape({})).toBe(false);
79
+ });
80
+
81
+ it('is true when template body or name or var map exists', () => {
82
+ expect(hasMeaningfulSmsFallbackShape({ message: 'hi' })).toBe(true);
83
+ expect(hasMeaningfulSmsFallbackShape({ smsTemplateName: 'T' })).toBe(true);
84
+ expect(hasMeaningfulSmsFallbackShape({ rcsSmsFallbackVarMapped: { '{#a#}_0': 'x' } })).toBe(true);
85
+ });
86
+ });
87
+
88
+ describe('getLibrarySmsFallbackApiBaselineFromTemplateData', () => {
89
+ it('merges root and nested; nested wins on overlap', () => {
90
+ const templateData = {
91
+ smsFallBackContent: { message: 'root' },
92
+ versions: {
93
+ base: {
94
+ content: {
95
+ RCS: {
96
+ smsFallBackContent: { message: 'nested', smsTemplateName: 'N' },
97
+ },
98
+ },
99
+ },
100
+ },
101
+ };
102
+ const out = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
103
+ expect(out.message).toBe('nested');
104
+ expect(out.smsTemplateName).toBe('N');
105
+ });
106
+ });
107
+
108
+ describe('extractRegisteredSenderIdsFromSmsFallbackRecord', () => {
109
+ it('returns null for missing / empty', () => {
110
+ expect(extractRegisteredSenderIdsFromSmsFallbackRecord(null)).toBe(null);
111
+ expect(extractRegisteredSenderIdsFromSmsFallbackRecord({})).toBe(null);
112
+ expect(
113
+ extractRegisteredSenderIdsFromSmsFallbackRecord({ registeredSenderIds: [] }),
114
+ ).toBe(null);
115
+ });
116
+
117
+ it('prefers top-level registeredSenderIds', () => {
118
+ expect(
119
+ extractRegisteredSenderIdsFromSmsFallbackRecord({
120
+ registeredSenderIds: ['A'],
121
+ templateConfigs: { registeredSenderIds: ['B'] },
122
+ }),
123
+ ).toEqual(['A']);
124
+ });
125
+
126
+ it('reads templateConfigs.registeredSenderIds when top-level absent', () => {
127
+ expect(
128
+ extractRegisteredSenderIdsFromSmsFallbackRecord({
129
+ templateConfigs: { registeredSenderIds: ['DLT1', 'DLT2'] },
130
+ }),
131
+ ).toEqual(['DLT1', 'DLT2']);
132
+ });
133
+
134
+ it('falls back to templateConfigs.header array', () => {
135
+ expect(
136
+ extractRegisteredSenderIdsFromSmsFallbackRecord({
137
+ templateConfigs: { header: ['H1'] },
138
+ }),
139
+ ).toEqual(['H1']);
140
+ });
141
+ });
142
+
143
+ describe('pickFirstSmsFallbackTemplateString', () => {
144
+ it('prefers smsTemplateContent over resolved message so DLT tokens stay in the string', () => {
145
+ const sms = {
146
+ message: 'Hello user',
147
+ smsTemplateContent: 'Hello {#name#}',
148
+ };
149
+ expect(pickFirstSmsFallbackTemplateString(sms)).toBe('Hello {#name#}');
150
+ });
151
+
152
+ it('skips whitespace-only templateContent and uses next field', () => {
153
+ expect(
154
+ pickFirstSmsFallbackTemplateString({
155
+ templateContent: ' ',
156
+ message: 'x',
157
+ }),
158
+ ).toBe('x');
159
+ });
160
+ });
161
+
162
+ describe('getUnmappedDesc', () => {
163
+ it('returns empty string for falsy str', () => {
164
+ expect(getUnmappedDesc('', { a: '1' })).toBe('');
165
+ expect(getUnmappedDesc(null, { a: '1' })).toBe('');
166
+ });
167
+
168
+ it('returns str when mapping is empty or missing', () => {
169
+ expect(getUnmappedDesc('hello', null)).toBe('hello');
170
+ expect(getUnmappedDesc('hello', {})).toBe('hello');
171
+ });
172
+
173
+ it('replaces literal values with {{key}} placeholders', () => {
174
+ expect(getUnmappedDesc('Hi Alice', { user_name: 'Alice' })).toBe('Hi {{user_name}}');
175
+ });
176
+
177
+ it('skips mapping entries with empty or whitespace-only values', () => {
178
+ expect(getUnmappedDesc('x', { a: '', b: ' ' })).toBe('x');
179
+ });
180
+
181
+ it('treats already-braced raw values without duplicating braced needle', () => {
182
+ expect(getUnmappedDesc('v {{user_id}}', { user_id: '{{user_id}}' })).toContain('{{user_id}}');
183
+ });
184
+
185
+ it('adds braced needle when resolved value is not already in {{…}} form', () => {
186
+ const out = getUnmappedDesc('Hello Bob', { nick: 'Bob' });
187
+ expect(out).toBe('Hello {{nick}}');
188
+ });
189
+
190
+ it('ignores undefined mapping values', () => {
191
+ expect(getUnmappedDesc('x', { a: undefined })).toBe('x');
192
+ });
193
+
194
+ it('matches longer needles first to avoid partial replaces', () => {
195
+ const out = getUnmappedDesc('ab', { x: 'a', y: 'ab' });
196
+ expect(out).toBe('{{y}}');
197
+ });
198
+
199
+ it('escapes regex metacharacters in resolved values', () => {
200
+ expect(getUnmappedDesc('price 9.99', { p: '9.99' })).toBe('price {{p}}');
201
+ });
202
+ });
203
+
204
+ describe('hasRcsVarTokens', () => {
205
+ it('is false for empty or non-matching strings', () => {
206
+ expect(hasRcsVarTokens('', rcsVarRegex)).toBe(false);
207
+ expect(hasRcsVarTokens('plain', rcsVarRegex)).toBe(false);
208
+ });
209
+
210
+ it('is true when string contains {{word}} tokens', () => {
211
+ expect(hasRcsVarTokens('{{1}} hi', rcsVarRegex)).toBe(true);
212
+ expect(hasRcsVarTokens('{{user_id}}', rcsVarRegex)).toBe(true);
213
+ expect(hasRcsVarTokens('{{dynamic_expiry_date_after_3_days.FORMAT_1}}', rcsVarRegex)).toBe(true);
214
+ });
215
+ });
216
+
217
+ describe('normalizeLibraryLoadedTitleDesc', () => {
218
+ const cardVarMappedAfterHydration = { user_id: '123' };
219
+
220
+ it('returns originals in full mode', () => {
221
+ const out = normalizeLibraryLoadedTitleDesc({
222
+ loadedTitle: 'Hello 123',
223
+ loadedDesc: 'Desc',
224
+ isFullMode: true,
225
+ cardVarMappedAfterHydration,
226
+ rcsVarRegex,
227
+ });
228
+ expect(out).toEqual({ normalizedTitle: 'Hello 123', normalizedDesc: 'Desc' });
229
+ });
230
+
231
+ it('unmaps when library mode, map present, and no mustache tokens', () => {
232
+ const out = normalizeLibraryLoadedTitleDesc({
233
+ loadedTitle: 'Hello 123',
234
+ loadedDesc: 'World 123',
235
+ isFullMode: false,
236
+ cardVarMappedAfterHydration,
237
+ rcsVarRegex,
238
+ });
239
+ expect(out.normalizedTitle).toBe('Hello {{user_id}}');
240
+ expect(out.normalizedDesc).toBe('World {{user_id}}');
241
+ });
242
+
243
+ it('keeps {{numeric}} slots when tokens already exist', () => {
244
+ const out = normalizeLibraryLoadedTitleDesc({
245
+ loadedTitle: 'Hi {{1}}',
246
+ loadedDesc: 'Body 123',
247
+ isFullMode: false,
248
+ cardVarMappedAfterHydration,
249
+ rcsVarRegex,
250
+ });
251
+ expect(out.normalizedTitle).toBe('Hi {{1}}');
252
+ expect(out.normalizedDesc).toBe('Body {{user_id}}');
253
+ });
254
+
255
+ it('does not unmap when cardVarMappedAfterHydration is empty', () => {
256
+ const out = normalizeLibraryLoadedTitleDesc({
257
+ loadedTitle: 'Hello',
258
+ loadedDesc: 'D',
259
+ isFullMode: false,
260
+ cardVarMappedAfterHydration: {},
261
+ rcsVarRegex,
262
+ });
263
+ expect(out).toEqual({ normalizedTitle: 'Hello', normalizedDesc: 'D' });
264
+ });
265
+ });
266
+
267
+ describe('syncCardVarMappedSemanticsFromSlots', () => {
268
+ it('copies numeric slot values onto empty semantic keys (campaign round-trip)', () => {
269
+ const out = syncCardVarMappedSemanticsFromSlots(
270
+ { 1: '[FirstName]', user_name: '', 2: 'x', other: '' },
271
+ 'Hi {{user_name}}',
272
+ 'More {{other}}',
273
+ rcsVarRegex,
274
+ );
275
+ expect(out.user_name).toBe('[FirstName]');
276
+ expect(out.other).toBe('x');
277
+ });
278
+
279
+ it('does not overwrite non-empty semantic keys', () => {
280
+ const out = syncCardVarMappedSemanticsFromSlots(
281
+ { 1: 'from-slot', user_name: 'kept' },
282
+ 'Hello {{user_name}}',
283
+ '',
284
+ rcsVarRegex,
285
+ );
286
+ expect(out.user_name).toBe('kept');
287
+ });
288
+ });
289
+
290
+ describe('pickRcsCardVarMappedEntries', () => {
291
+ it('returns {} for null, undefined, and array inputs', () => {
292
+ expect(pickRcsCardVarMappedEntries(null)).toEqual({});
293
+ expect(pickRcsCardVarMappedEntries(undefined)).toEqual({});
294
+ expect(pickRcsCardVarMappedEntries(['{#v#}_0', 'x'])).toEqual({});
295
+ });
296
+
297
+ it('keeps numeric slot keys', () => {
298
+ expect(pickRcsCardVarMappedEntries({ 1: 'a', 2: 'b' })).toEqual({ 1: 'a', 2: 'b' });
299
+ });
300
+
301
+ it('keeps semantic word keys', () => {
302
+ expect(pickRcsCardVarMappedEntries({ user_name: 'Alice', gt: '{{tag}}' })).toEqual({
303
+ user_name: 'Alice',
304
+ gt: '{{tag}}',
305
+ });
306
+ });
307
+
308
+ it('strips SMS fallback slot keys like {#var#}_0', () => {
309
+ const input = { 1: 'x', user_name: 'y', '{#promo#}_0': 'z', '{{name}}_1': 'w' };
310
+ const out = pickRcsCardVarMappedEntries(input);
311
+ expect(out).toEqual({ 1: 'x', user_name: 'y' });
312
+ });
313
+
314
+ it('returns {} for an empty object', () => {
315
+ expect(pickRcsCardVarMappedEntries({})).toEqual({});
316
+ });
317
+ });
318
+ });