@capillarytech/creatives-library 9.0.36 → 9.0.37-alpha.0
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.
- package/AppRoot.js +124 -0
- package/app-config.js +61 -0
- package/app.js +10 -175
- package/bootstrap.js +185 -0
- package/entry.js +67 -1
- package/mfe-exposed-components.js +2 -4
- package/package.json +2 -2
- package/services/api.js +9 -1
- package/styles/containers/layout/_layoutPage.scss +8 -6
- package/translations/en.json +2 -2
- package/utils/getDataLayer.js +15 -0
- package/utils/gtmTrackers/gtmEvents/creativeDetails.js +2 -1
- package/utils/mfeDetect.js +1 -0
- package/utils/mfeFirstPaintReady.js +29 -0
- package/utils/mfeHistory.js +62 -0
- package/utils/permissions.js +59 -0
- package/utils/rcsPayloadUtils.js +5 -2
- package/utils/tests/permissions.test.js +116 -0
- package/utils/tests/rcsPayloadUtils.test.js +54 -2
- package/v2Components/AccessForbidden/_accessForbidden.scss +14 -0
- package/v2Components/AccessForbidden/index.js +5 -3
- package/v2Components/AccessForbidden/messages.js +3 -2
- package/v2Components/CapActionButton/constants.js +1 -0
- package/v2Components/CapActionButton/index.js +77 -9
- package/v2Components/CapActionButton/index.scss +13 -0
- package/v2Components/CapActionButton/messages.js +13 -0
- package/v2Components/CapActionButton/tests/index.test.js +32 -1
- package/v2Components/CommonTestAndPreview/index.js +44 -15
- package/v2Components/CommonTestAndPreview/tests/index.test.js +78 -0
- package/v2Components/CommonTestAndPreview/utils.js +34 -0
- package/v2Components/NavigationBar/index.js +9 -7
- package/v2Components/NavigationBar/mfeModuleHeader.config.js +16 -0
- package/v2Components/NavigationBar/tests/index.test.js +39 -19
- package/v2Components/NoPermissionWrapper/_noPermissionWrapper.scss +12 -0
- package/v2Components/NoPermissionWrapper/index.js +26 -0
- package/v2Components/SmsFallback/index.js +6 -0
- package/v2Containers/App/constants.js +5 -0
- package/v2Containers/Assets/Gallery/_gallery.scss +6 -0
- package/v2Containers/Assets/Gallery/index.js +34 -10
- package/v2Containers/Cap/constants.js +1 -0
- package/v2Containers/Cap/index.js +62 -29
- package/v2Containers/Cap/messages.js +4 -0
- package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +2 -2
- package/v2Containers/CreativesContainer/index.js +11 -8
- package/v2Containers/CreativesContainer/tests/index.test.js +53 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +6 -6
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +4 -4
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +32 -32
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +36 -36
- package/v2Containers/Rcs/components/CarouselCard.js +5 -0
- package/v2Containers/Rcs/components/CarouselCardButtons.js +20 -0
- package/v2Containers/Rcs/index.js +154 -79
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +55 -4
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +404 -118
- package/v2Containers/Rcs/tests/carouselUtils.test.js +13 -17
- package/v2Containers/Rcs/tests/index.test.js +141 -6
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +110 -0
- package/v2Containers/Rcs/tests/utils.test.js +36 -0
- package/v2Containers/Rcs/utils.js +16 -4
- package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +8 -8
- package/v2Containers/SmsTrai/Edit/index.js +35 -24
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +18 -18
- package/v2Containers/Templates/ChannelTypeIllustration.js +10 -1
- package/v2Containers/Templates/_templates.scss +7 -1
- package/v2Containers/Templates/index.js +141 -99
- package/v2Containers/Templates/tests/ChannelTypeIllustration.test.js +7 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +173 -115
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +8 -3
- package/v2Containers/TemplatesV2/index.js +38 -14
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +268 -268
|
@@ -554,15 +554,13 @@ describe('buildCarouselCardsForPreview', () => {
|
|
|
554
554
|
expect(
|
|
555
555
|
buildCarouselCardsForPreview(undefined, {
|
|
556
556
|
isFullMode: false,
|
|
557
|
-
|
|
558
|
-
resolveTemplateWithMap: jest.fn(),
|
|
557
|
+
resolveCarouselTemplateWithMap: jest.fn(),
|
|
559
558
|
}),
|
|
560
559
|
).toEqual([]);
|
|
561
560
|
expect(
|
|
562
561
|
buildCarouselCardsForPreview([], {
|
|
563
562
|
isFullMode: false,
|
|
564
|
-
|
|
565
|
-
resolveTemplateWithMap: jest.fn(),
|
|
563
|
+
resolveCarouselTemplateWithMap: jest.fn(),
|
|
566
564
|
}),
|
|
567
565
|
).toEqual([]);
|
|
568
566
|
});
|
|
@@ -571,19 +569,18 @@ describe('buildCarouselCardsForPreview', () => {
|
|
|
571
569
|
expect(
|
|
572
570
|
buildCarouselCardsForPreview(null, {
|
|
573
571
|
isFullMode: false,
|
|
574
|
-
|
|
575
|
-
resolveTemplateWithMap: jest.fn(),
|
|
572
|
+
resolveCarouselTemplateWithMap: jest.fn(),
|
|
576
573
|
}),
|
|
577
574
|
).toEqual([]);
|
|
578
575
|
});
|
|
579
576
|
|
|
580
|
-
it('passes title/description through unresolved in full mode without calling
|
|
581
|
-
const
|
|
577
|
+
it('passes title/description through unresolved in full mode without calling resolveCarouselTemplateWithMap', () => {
|
|
578
|
+
const resolveCarouselTemplateWithMap = jest.fn();
|
|
582
579
|
const out = buildCarouselCardsForPreview(
|
|
583
580
|
[{ title: 'Hi {{a}}', description: 'Bye {{b}}' }],
|
|
584
|
-
{ isFullMode: true,
|
|
581
|
+
{ isFullMode: true, resolveCarouselTemplateWithMap },
|
|
585
582
|
);
|
|
586
|
-
expect(
|
|
583
|
+
expect(resolveCarouselTemplateWithMap).not.toHaveBeenCalled();
|
|
587
584
|
expect(out[0].title).toBe('Hi {{a}}');
|
|
588
585
|
expect(out[0].bodyText).toBe('Bye {{b}}');
|
|
589
586
|
});
|
|
@@ -595,7 +592,7 @@ describe('buildCarouselCardsForPreview', () => {
|
|
|
595
592
|
{ title: 'Hi {{a}}', description: 'Bye {{b}} {{c}}' },
|
|
596
593
|
{ title: 'Solo {{d}}', description: 'none' },
|
|
597
594
|
],
|
|
598
|
-
{ isFullMode: false,
|
|
595
|
+
{ isFullMode: false, resolveCarouselTemplateWithMap },
|
|
599
596
|
);
|
|
600
597
|
expect(resolveCarouselTemplateWithMap).toHaveBeenNthCalledWith(1, 'Hi {{a}}', 0, false);
|
|
601
598
|
expect(resolveCarouselTemplateWithMap).toHaveBeenNthCalledWith(2, 'Bye {{b}} {{c}}', 0, true);
|
|
@@ -610,8 +607,7 @@ describe('buildCarouselCardsForPreview', () => {
|
|
|
610
607
|
it('defaults missing card fields, including a default {} for a missing array item', () => {
|
|
611
608
|
const out = buildCarouselCardsForPreview([undefined], {
|
|
612
609
|
isFullMode: true,
|
|
613
|
-
|
|
614
|
-
resolveTemplateWithMap: jest.fn(),
|
|
610
|
+
resolveCarouselTemplateWithMap: jest.fn(),
|
|
615
611
|
});
|
|
616
612
|
expect(out[0]).toEqual({
|
|
617
613
|
mediaType: '',
|
|
@@ -635,7 +631,7 @@ describe('buildCarouselCardsForPreview', () => {
|
|
|
635
631
|
description: '',
|
|
636
632
|
},
|
|
637
633
|
],
|
|
638
|
-
{ isFullMode: true,
|
|
634
|
+
{ isFullMode: true, resolveCarouselTemplateWithMap: jest.fn() },
|
|
639
635
|
);
|
|
640
636
|
expect(out[0].mediaType).toBe('image');
|
|
641
637
|
expect(out[0].imageSrc).toBe('img.png');
|
|
@@ -651,7 +647,7 @@ describe('buildCarouselCardsForPreview', () => {
|
|
|
651
647
|
videoAsset: { videoSrc: 'v.mp4', videoThumbnail: 'other-thumb.png' },
|
|
652
648
|
},
|
|
653
649
|
],
|
|
654
|
-
{ isFullMode: true,
|
|
650
|
+
{ isFullMode: true, resolveCarouselTemplateWithMap: jest.fn() },
|
|
655
651
|
);
|
|
656
652
|
expect(out[0].videoPreviewImg).toBe('thumb.png');
|
|
657
653
|
expect(out[0].videoSrc).toBe('v.mp4');
|
|
@@ -665,7 +661,7 @@ describe('buildCarouselCardsForPreview', () => {
|
|
|
665
661
|
videoAsset: { videoSrc: 'v.mp4', videoThumbnail: 'other-thumb.png' },
|
|
666
662
|
},
|
|
667
663
|
],
|
|
668
|
-
{ isFullMode: true,
|
|
664
|
+
{ isFullMode: true, resolveCarouselTemplateWithMap: jest.fn() },
|
|
669
665
|
);
|
|
670
666
|
expect(out[0].videoPreviewImg).toBe('other-thumb.png');
|
|
671
667
|
});
|
|
@@ -673,7 +669,7 @@ describe('buildCarouselCardsForPreview', () => {
|
|
|
673
669
|
it('defaults videoPreviewImg and videoSrc to empty string when nothing is set', () => {
|
|
674
670
|
const out = buildCarouselCardsForPreview(
|
|
675
671
|
[{ mediaType: RCS_MEDIA_TYPES.IMAGE }],
|
|
676
|
-
{ isFullMode: true,
|
|
672
|
+
{ isFullMode: true, resolveCarouselTemplateWithMap: jest.fn() },
|
|
677
673
|
);
|
|
678
674
|
expect(out[0].videoPreviewImg).toBe('');
|
|
679
675
|
expect(out[0].videoSrc).toBe('');
|
|
@@ -570,8 +570,8 @@ describe('RCS carousel label resolution in non-full mode', () => {
|
|
|
570
570
|
expect(card0NameAreas.length).toBe(2);
|
|
571
571
|
|
|
572
572
|
act(() => {
|
|
573
|
-
const slotId =
|
|
574
|
-
|
|
573
|
+
const slotId = card0NameAreas.at(0).prop('id') || card0NameAreas.at(0).key();
|
|
574
|
+
card0NameAreas.at(0).props().onChange({ target: { id: slotId, value: 'madhavi' } });
|
|
575
575
|
});
|
|
576
576
|
wrapper.update();
|
|
577
577
|
|
|
@@ -1189,6 +1189,130 @@ describe('RCS createPayload', () => {
|
|
|
1189
1189
|
expect(Object.prototype.hasOwnProperty.call(card, 'cardVarMapped')).toBe(true);
|
|
1190
1190
|
});
|
|
1191
1191
|
|
|
1192
|
+
it('disables Done in non-full mode when a CTA button URL still holds the unresolved {{1}} placeholder', () => {
|
|
1193
|
+
const templateData = {
|
|
1194
|
+
name: 'PromoCard',
|
|
1195
|
+
type: 'RCS',
|
|
1196
|
+
versions: {
|
|
1197
|
+
base: {
|
|
1198
|
+
content: {
|
|
1199
|
+
RCS: {
|
|
1200
|
+
rcsContent: {
|
|
1201
|
+
cardType: 'STANDALONE',
|
|
1202
|
+
cardSettings: { cardOrientation: 'VERTICAL' },
|
|
1203
|
+
cardContent: [
|
|
1204
|
+
{
|
|
1205
|
+
title: 'Title',
|
|
1206
|
+
description: 'Desc',
|
|
1207
|
+
mediaType: 'VIDEO',
|
|
1208
|
+
media: {
|
|
1209
|
+
mediaUrl: 'https://cdn.example.com/video.mp4',
|
|
1210
|
+
thumbnailUrl: 'https://cdn.example.com/video-thumb.jpg',
|
|
1211
|
+
},
|
|
1212
|
+
suggestions: [
|
|
1213
|
+
{ type: 'CTA', text: 'Visit here', url: 'https://example.com{{1}}' },
|
|
1214
|
+
],
|
|
1215
|
+
},
|
|
1216
|
+
],
|
|
1217
|
+
contentType: 'RICHCARD',
|
|
1218
|
+
},
|
|
1219
|
+
},
|
|
1220
|
+
},
|
|
1221
|
+
},
|
|
1222
|
+
},
|
|
1223
|
+
};
|
|
1224
|
+
|
|
1225
|
+
const wrapper = mountWithIntl(
|
|
1226
|
+
<Provider store={store}>
|
|
1227
|
+
<Rcs
|
|
1228
|
+
actions={{
|
|
1229
|
+
clearCreateResponse: jest.fn(),
|
|
1230
|
+
getTemplateDetails: jest.fn(),
|
|
1231
|
+
uploadRcsAsset: jest.fn(),
|
|
1232
|
+
clearRcsMediaAsset: jest.fn(),
|
|
1233
|
+
editTemplate: jest.fn(),
|
|
1234
|
+
clearEditResponse: jest.fn(),
|
|
1235
|
+
}}
|
|
1236
|
+
globalActions={{ fetchSchemaForEntity }}
|
|
1237
|
+
onCreateComplete={onCreateComplete}
|
|
1238
|
+
handleClose={handleClose}
|
|
1239
|
+
intl={{ formatMessage }}
|
|
1240
|
+
location={{ pathname: '/rcs/create', query: { type: false, module: 'default' }, search: '' }}
|
|
1241
|
+
params={params}
|
|
1242
|
+
templateData={templateData}
|
|
1243
|
+
rcsData={{}}
|
|
1244
|
+
isFullMode={false}
|
|
1245
|
+
isEditFlow={false}
|
|
1246
|
+
loadingTags={false}
|
|
1247
|
+
metaEntities={[]}
|
|
1248
|
+
isDltEnabled={false}
|
|
1249
|
+
smsRegister={'DLT'}
|
|
1250
|
+
getFormData={jest.fn()}
|
|
1251
|
+
/>
|
|
1252
|
+
</Provider>,
|
|
1253
|
+
);
|
|
1254
|
+
|
|
1255
|
+
const doneBtn = wrapper.find('CapButton.rcs-done-btn').at(0);
|
|
1256
|
+
expect(doneBtn.exists()).toBe(true);
|
|
1257
|
+
expect(doneBtn.prop('disabled')).toBe(true);
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
it('disables Send for Approval in full-mode creation when a CTA button URL still holds the unresolved {{1}} placeholder', () => {
|
|
1261
|
+
const wrapper = mountWithIntl(
|
|
1262
|
+
<Provider store={store}>
|
|
1263
|
+
<Rcs
|
|
1264
|
+
actions={{
|
|
1265
|
+
clearCreateResponse: jest.fn(),
|
|
1266
|
+
getTemplateDetails: jest.fn(),
|
|
1267
|
+
uploadRcsAsset: jest.fn(),
|
|
1268
|
+
clearRcsMediaAsset: jest.fn(),
|
|
1269
|
+
editTemplate: jest.fn(),
|
|
1270
|
+
clearEditResponse: jest.fn(),
|
|
1271
|
+
}}
|
|
1272
|
+
globalActions={{ fetchSchemaForEntity }}
|
|
1273
|
+
onCreateComplete={onCreateComplete}
|
|
1274
|
+
handleClose={handleClose}
|
|
1275
|
+
intl={{ formatMessage }}
|
|
1276
|
+
location={{ pathname: '/rcs/create', query: { type: false, module: 'default' }, search: '' }}
|
|
1277
|
+
params={params}
|
|
1278
|
+
templateData={{}}
|
|
1279
|
+
rcsData={{}}
|
|
1280
|
+
isFullMode
|
|
1281
|
+
isEditFlow={false}
|
|
1282
|
+
loadingTags={false}
|
|
1283
|
+
metaEntities={[]}
|
|
1284
|
+
isDltEnabled={false}
|
|
1285
|
+
smsRegister={'DLT'}
|
|
1286
|
+
getFormData={jest.fn()}
|
|
1287
|
+
/>
|
|
1288
|
+
</Provider>,
|
|
1289
|
+
);
|
|
1290
|
+
|
|
1291
|
+
act(() => {
|
|
1292
|
+
wrapper.find('#rcs_template_name_input').at(0).simulate('change', { target: { value: 'PromoCard' } });
|
|
1293
|
+
});
|
|
1294
|
+
wrapper.update();
|
|
1295
|
+
|
|
1296
|
+
act(() => {
|
|
1297
|
+
wrapper.find('#rcs_template_message_textarea').at(0).simulate('change', { target: { value: 'Hello there' } });
|
|
1298
|
+
});
|
|
1299
|
+
wrapper.update();
|
|
1300
|
+
|
|
1301
|
+
const actionButton = wrapper.find('.cap-action-button-mock').at(0);
|
|
1302
|
+
expect(actionButton.exists()).toBe(true);
|
|
1303
|
+
act(() => {
|
|
1304
|
+
actionButton.prop('updateButtonChange')(
|
|
1305
|
+
{ type: 'CTA', text: 'Visit here', url: 'https://example.com{{1}}' },
|
|
1306
|
+
0,
|
|
1307
|
+
);
|
|
1308
|
+
});
|
|
1309
|
+
wrapper.update();
|
|
1310
|
+
|
|
1311
|
+
const doneBtn = wrapper.find('CapButton.rcs-done-btn').at(0);
|
|
1312
|
+
expect(doneBtn.exists()).toBe(true);
|
|
1313
|
+
expect(doneBtn.prop('disabled')).toBe(true);
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1192
1316
|
it('should sync duplicate placeholders via cardVarMapped in non-full mode (typing once updates all occurrences)', () => {
|
|
1193
1317
|
const templateData = {
|
|
1194
1318
|
name: 'DupVarsTemplate',
|
|
@@ -1286,7 +1410,12 @@ describe('RCS createPayload', () => {
|
|
|
1286
1410
|
cardContent: [
|
|
1287
1411
|
{
|
|
1288
1412
|
title: 'Hello {{user_name}} and {{user_name}}',
|
|
1289
|
-
|
|
1413
|
+
// Distinct from the title's var name: VarSegmentMessageEditor ids are
|
|
1414
|
+
// `${token}_${localSegmentIndex}`, not scoped per-field, so reusing
|
|
1415
|
+
// {{user_name}} here would collide with the title's own id and get
|
|
1416
|
+
// picked up by the `{{user_name}}_` filter below once the tag-select
|
|
1417
|
+
// renames the title's first occurrence.
|
|
1418
|
+
description: 'Hi {{other_var}}',
|
|
1290
1419
|
mediaType: 'IMAGE',
|
|
1291
1420
|
media: { mediaUrl: '', thumbnailUrl: '' },
|
|
1292
1421
|
cardVarMapped: {},
|
|
@@ -1352,12 +1481,18 @@ describe('RCS createPayload', () => {
|
|
|
1352
1481
|
});
|
|
1353
1482
|
wrapper.update();
|
|
1354
1483
|
|
|
1355
|
-
|
|
1484
|
+
// The focused occurrence's own token is replaced with the picked tag, while the untouched
|
|
1485
|
+
// duplicate keeps its original {{user_name}} token but syncs to the same resolved value.
|
|
1486
|
+
const renamedVarArea = wrapper.find('CapInputTextArea').filterWhere((n) => {
|
|
1487
|
+
const id = n.prop('id') || '';
|
|
1488
|
+
return id.includes('{{first_name}}_');
|
|
1489
|
+
});
|
|
1490
|
+
const duplicateVarArea = wrapper.find('CapInputTextArea').filterWhere((n) => {
|
|
1356
1491
|
const id = n.prop('id') || '';
|
|
1357
1492
|
return id.includes('{{user_name}}_');
|
|
1358
1493
|
});
|
|
1359
|
-
expect(
|
|
1360
|
-
expect(
|
|
1494
|
+
expect(renamedVarArea.at(0).prop('value')).toBe('{{first_name}}');
|
|
1495
|
+
expect(duplicateVarArea.at(0).prop('value')).toBe('{{first_name}}');
|
|
1361
1496
|
});
|
|
1362
1497
|
|
|
1363
1498
|
it('should keep two tags + freetext inside the variable textarea in non-full mode edit (not merged into static text)', () => {
|
|
@@ -364,6 +364,14 @@ describe('rcsLibraryHydrationUtils', () => {
|
|
|
364
364
|
expect(out).toEqual({ 1: 'x', user_name: 'y' });
|
|
365
365
|
});
|
|
366
366
|
|
|
367
|
+
it('keeps function-style tag keys with parentheses (voucher/offer/badge tags)', () => {
|
|
368
|
+
const input = {
|
|
369
|
+
'voucher(1190738)': 'SAVE20',
|
|
370
|
+
'badges_enroll_no_expiry(123)': 'Gold',
|
|
371
|
+
};
|
|
372
|
+
expect(pickRcsCardVarMappedEntries(input)).toEqual(input);
|
|
373
|
+
});
|
|
374
|
+
|
|
367
375
|
it('returns {} for an empty object', () => {
|
|
368
376
|
expect(pickRcsCardVarMappedEntries({})).toEqual({});
|
|
369
377
|
});
|
|
@@ -439,6 +447,7 @@ describe('rcsLibraryHydrationUtils', () => {
|
|
|
439
447
|
template: 'Hello',
|
|
440
448
|
templateName: 'T1',
|
|
441
449
|
registeredSenderIds: ['S1'],
|
|
450
|
+
templateVariableMapping: { '{#a#}_0': { data: 'x', count: 1 } },
|
|
442
451
|
},
|
|
443
452
|
},
|
|
444
453
|
});
|
|
@@ -497,6 +506,7 @@ describe('rcsLibraryHydrationUtils', () => {
|
|
|
497
506
|
expect(out.smsFallBackContent.templateConfigs).toEqual({
|
|
498
507
|
templateName: 'ConfigName',
|
|
499
508
|
registeredSenderIds: ['H1'],
|
|
509
|
+
templateVariableMapping: { a: { data: '1', count: 1 } },
|
|
500
510
|
});
|
|
501
511
|
expect(out.smsFallBackContent.rcsSmsFallbackVarMapped).toEqual({ a: '1' });
|
|
502
512
|
});
|
|
@@ -537,6 +547,25 @@ describe('rcsLibraryHydrationUtils', () => {
|
|
|
537
547
|
});
|
|
538
548
|
});
|
|
539
549
|
|
|
550
|
+
it('non-full mode: DLT campaign with registeredSenderIds carries the var map into templateConfigs.templateVariableMapping (so it survives normalizeRcsMessageContentForApi stripping rcsSmsFallbackVarMapped)', () => {
|
|
551
|
+
enableTraiDlt();
|
|
552
|
+
const out = buildSmsFallBackContentForPayload({
|
|
553
|
+
smsFallbackData: {
|
|
554
|
+
message: 'Hi {#var#} your OTP is {#var#}.Capillary Tech.',
|
|
555
|
+
templateName: 'TEST_FALLBACK',
|
|
556
|
+
registeredSenderIds: ['CPLRYT'],
|
|
557
|
+
rcsSmsFallbackVarMapped: { '{#var#}_1': 'test1', '{#var#}_3': 'test2' },
|
|
558
|
+
},
|
|
559
|
+
templateData: {},
|
|
560
|
+
isFullMode: false,
|
|
561
|
+
smsRegister: 'DLT',
|
|
562
|
+
});
|
|
563
|
+
expect(out.smsFallBackContent.templateConfigs.templateVariableMapping).toEqual({
|
|
564
|
+
'{#var#}_1': { data: 'test1', count: 1 },
|
|
565
|
+
'{#var#}_3': { data: 'test2', count: 2 },
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
|
|
540
569
|
it('non-full mode: DLT campaign but no derivable templateConfigs omits templateConfigs', () => {
|
|
541
570
|
enableTraiDlt();
|
|
542
571
|
const out = buildSmsFallBackContentForPayload({
|
|
@@ -656,6 +685,45 @@ describe('rcsLibraryHydrationUtils', () => {
|
|
|
656
685
|
expect(warnSpy).toHaveBeenCalled();
|
|
657
686
|
});
|
|
658
687
|
|
|
688
|
+
it('falls back to templateConfigs.templateName when root-level name fields are absent', () => {
|
|
689
|
+
const out = resolveSmsFallbackHydrationFromDetails({
|
|
690
|
+
smsFallBackContent: {
|
|
691
|
+
templateConfigs: { templateName: 'ConfigTemplateName' },
|
|
692
|
+
},
|
|
693
|
+
});
|
|
694
|
+
expect(out.nextSmsState.templateName).toBe('ConfigTemplateName');
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
it('falls back to templateConfigs.name when no other name field is present anywhere', () => {
|
|
698
|
+
const out = resolveSmsFallbackHydrationFromDetails({
|
|
699
|
+
smsFallBackContent: {
|
|
700
|
+
templateConfigs: { name: 'ConfigName' },
|
|
701
|
+
},
|
|
702
|
+
});
|
|
703
|
+
expect(out.nextSmsState.templateName).toBe('ConfigName');
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it('falls back to the root-level templateName when templateConfigs has no name field', () => {
|
|
707
|
+
const out = resolveSmsFallbackHydrationFromDetails({
|
|
708
|
+
smsFallBackContent: {
|
|
709
|
+
templateName: 'RootName',
|
|
710
|
+
templateConfigs: { registeredSenderIds: ['123'] },
|
|
711
|
+
},
|
|
712
|
+
});
|
|
713
|
+
expect(out.nextSmsState.templateName).toBe('RootName');
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
it('prefers the root templateName over a conflicting nested templateConfigs.name', () => {
|
|
717
|
+
const out = resolveSmsFallbackHydrationFromDetails({
|
|
718
|
+
smsFallBackContent: {
|
|
719
|
+
templateName: 'RootName',
|
|
720
|
+
smsTemplateName: '',
|
|
721
|
+
templateConfigs: { name: 'NestedName', header: ['H1'] },
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
expect(out.nextSmsState.templateName).toBe('RootName');
|
|
725
|
+
});
|
|
726
|
+
|
|
659
727
|
it('is meaningful from var map alone, with no template name or message text', () => {
|
|
660
728
|
const out = resolveSmsFallbackHydrationFromDetails({
|
|
661
729
|
smsFallBackContent: { rcsSmsFallbackVarMapped: { '{#a#}_0': 'v' } },
|
|
@@ -705,6 +773,48 @@ describe('rcsLibraryHydrationUtils', () => {
|
|
|
705
773
|
).toBe('Direct Template');
|
|
706
774
|
});
|
|
707
775
|
|
|
776
|
+
it('prefers templateConfigs.template (raw, {#var#} intact) over smsContent/message for DLT', () => {
|
|
777
|
+
const out = resolveSmsFallbackHydrationFromDetails({
|
|
778
|
+
smsFallBackContent: {
|
|
779
|
+
smsContent: 'hey user this is a test',
|
|
780
|
+
message: 'hey user this is a test',
|
|
781
|
+
templateConfigs: {
|
|
782
|
+
templateName: 'RCSDLT2',
|
|
783
|
+
template: 'hey{#var#}test',
|
|
784
|
+
templateVariableMapping: {
|
|
785
|
+
'{#var#}_1': { data: ' user this is a ', count: 1 },
|
|
786
|
+
},
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
});
|
|
790
|
+
expect(out.nextSmsState.content).toBe('hey{#var#}test');
|
|
791
|
+
expect(out.nextSmsState.templateContent).toBe('hey{#var#}test');
|
|
792
|
+
expect(out.nextSmsState.rcsSmsFallbackVarMapped).toEqual({ '{#var#}_1': ' user this is a ' });
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it('merges templateVariableMapping-derived slots with an explicit rcsSmsFallbackVarMapped (explicit wins)', () => {
|
|
796
|
+
const out = resolveSmsFallbackHydrationFromDetails({
|
|
797
|
+
smsFallBackContent: {
|
|
798
|
+
smsContent: 'hey user this is a test',
|
|
799
|
+
rcsSmsFallbackVarMapped: { '{#var#}_1': 'explicit override' },
|
|
800
|
+
templateConfigs: {
|
|
801
|
+
template: 'hey{#var#}test',
|
|
802
|
+
templateVariableMapping: {
|
|
803
|
+
'{#var#}_1': { data: ' user this is a ', count: 1 },
|
|
804
|
+
},
|
|
805
|
+
},
|
|
806
|
+
},
|
|
807
|
+
});
|
|
808
|
+
expect(out.nextSmsState.rcsSmsFallbackVarMapped).toEqual({ '{#var#}_1': 'explicit override' });
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
it('falls back to smsContent when templateConfigs.template is absent (non-DLT)', () => {
|
|
812
|
+
const out = resolveSmsFallbackHydrationFromDetails({
|
|
813
|
+
smsFallBackContent: { smsContent: 'Hi {{first_name}}, your OTP is 1234' },
|
|
814
|
+
});
|
|
815
|
+
expect(out.nextSmsState.content).toBe('Hi {{first_name}}, your OTP is 1234');
|
|
816
|
+
});
|
|
817
|
+
|
|
708
818
|
it('joins an updated-sms-editor array into the fallback message', () => {
|
|
709
819
|
const out = resolveSmsFallbackHydrationFromDetails({
|
|
710
820
|
smsFallBackContent: {
|
|
@@ -781,6 +781,42 @@ describe('RCS utils', () => {
|
|
|
781
781
|
expect(rcsTitle).toBe('');
|
|
782
782
|
expect(rcsDesc).toBe('Visit Store for {{loyalty_points}} discount');
|
|
783
783
|
});
|
|
784
|
+
|
|
785
|
+
it('carousel card 1+: cardSlotOffset param resolves against its own global slots, not card 0\'s', () => {
|
|
786
|
+
// Full, correctly-populated carousel-wide map: slot 1 = card 0 title (user_id_b64), slot 2 =
|
|
787
|
+
// card 0 desc (voucher(1190738)), slot 3 = card 1 title (user_id_b64), slot 4 = card 1 desc
|
|
788
|
+
// (first_name).
|
|
789
|
+
const fullCarouselWideMap = {
|
|
790
|
+
1: '{{user_id_b64}}',
|
|
791
|
+
2: '{{voucher(1190738)}}',
|
|
792
|
+
3: '{{user_id_b64}}',
|
|
793
|
+
4: '{{first_name}}',
|
|
794
|
+
};
|
|
795
|
+
// Card 1's own title/description, called with cardSlotOffset=2 (card 0 contributed 2 slots) —
|
|
796
|
+
// the same way mapRcsCardContentForConsumerWithResolvedTags now calls it per card.
|
|
797
|
+
const { rcsTitle, rcsDesc } = resolveRcsCardPreviewStrings(
|
|
798
|
+
'This is a park {{3}}',
|
|
799
|
+
'This park has a cat and a bird {{4}}',
|
|
800
|
+
fullCarouselWideMap,
|
|
801
|
+
true,
|
|
802
|
+
false,
|
|
803
|
+
false,
|
|
804
|
+
2,
|
|
805
|
+
);
|
|
806
|
+
expect(rcsTitle).toBe('This is a park {{user_id_b64}}');
|
|
807
|
+
expect(rcsDesc).toBe('This park has a cat and a bird {{first_name}}');
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
it('omitting cardSlotOffset defaults to 0 (single-card / standalone callers unaffected)', () => {
|
|
811
|
+
const { rcsTitle, rcsDesc } = resolveRcsCardPreviewStrings(
|
|
812
|
+
'Hi {{1}}',
|
|
813
|
+
'Pts {{2}}',
|
|
814
|
+
{ 1: '{{first_name}}', 2: '{{loyalty_points}}' },
|
|
815
|
+
true,
|
|
816
|
+
);
|
|
817
|
+
expect(rcsTitle).toBe('Hi {{first_name}}');
|
|
818
|
+
expect(rcsDesc).toBe('Pts {{loyalty_points}}');
|
|
819
|
+
});
|
|
784
820
|
});
|
|
785
821
|
|
|
786
822
|
describe('sanitizeCardVarMappedValue', () => {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
RCS_NUMERIC_VAR_NAME_REGEX,
|
|
7
7
|
REGEX_SPECIAL_CHARS_ESCAPE_PATTERN,
|
|
8
8
|
RCS_STRIP_MUSTACHE_DELIMITERS_REGEX,
|
|
9
|
+
RCS_MUSTACHE_WRAPPED_REGEX,
|
|
9
10
|
} from './constants';
|
|
10
11
|
import './index.scss';
|
|
11
12
|
// import { formatMessage } from '../../../utils/intl';
|
|
@@ -341,6 +342,8 @@ export function resolveRcsCardPreviewStrings(
|
|
|
341
342
|
cardVarMapped,
|
|
342
343
|
isLibraryMode = false,
|
|
343
344
|
textOnlyCard = false,
|
|
345
|
+
preserveLiteralValueTokens = false,
|
|
346
|
+
cardSlotOffset = 0,
|
|
344
347
|
) {
|
|
345
348
|
const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
|
|
346
349
|
const getVarNameFromToken = (token = '') =>
|
|
@@ -389,8 +392,8 @@ export function resolveRcsCardPreviewStrings(
|
|
|
389
392
|
? 0
|
|
390
393
|
: (effectiveTitle.match(rcsVarRegex) || []).length;
|
|
391
394
|
return {
|
|
392
|
-
rcsTitle: textOnlyCard ? '' : resolveTemplateWithMap(effectiveTitle,
|
|
393
|
-
rcsDesc: resolveTemplateWithMap(String(description || ''), titleVarCount),
|
|
395
|
+
rcsTitle: textOnlyCard ? '' : resolveTemplateWithMap(effectiveTitle, cardSlotOffset),
|
|
396
|
+
rcsDesc: resolveTemplateWithMap(String(description || ''), cardSlotOffset + titleVarCount),
|
|
394
397
|
};
|
|
395
398
|
}
|
|
396
399
|
|
|
@@ -411,6 +414,7 @@ export function mapRcsCardContentForConsumerWithResolvedTags(
|
|
|
411
414
|
: {};
|
|
412
415
|
const list = Array.isArray(cardContentArray) ? cardContentArray : [];
|
|
413
416
|
const isLibraryMode = isFullMode !== true;
|
|
417
|
+
let carouselSlotOffset = 0;
|
|
414
418
|
return list.map((card) => {
|
|
415
419
|
if (!card || typeof card !== 'object') return card;
|
|
416
420
|
const nested =
|
|
@@ -421,13 +425,21 @@ export function mapRcsCardContentForConsumerWithResolvedTags(
|
|
|
421
425
|
const nestedClean = pickRcsCardVarMappedEntries(nested);
|
|
422
426
|
const merged = { ...rootClean, ...nestedClean };
|
|
423
427
|
const textOnly = isRcsTextOnlyCardMediaType(card.mediaType);
|
|
428
|
+
const cardTitle = card.title ?? '';
|
|
429
|
+
const cardDescription = card.description ?? '';
|
|
424
430
|
const { rcsTitle, rcsDesc } = resolveRcsCardPreviewStrings(
|
|
425
|
-
|
|
426
|
-
|
|
431
|
+
cardTitle,
|
|
432
|
+
cardDescription,
|
|
427
433
|
merged,
|
|
428
434
|
isLibraryMode,
|
|
429
435
|
textOnly,
|
|
436
|
+
true,
|
|
437
|
+
carouselSlotOffset,
|
|
430
438
|
);
|
|
439
|
+
const effectiveTitleForCount = textOnly ? '' : String(cardTitle);
|
|
440
|
+
const titleVarCount = (effectiveTitleForCount.match(rcsVarRegex) || []).length;
|
|
441
|
+
const descVarCount = (String(cardDescription).match(rcsVarRegex) || []).length;
|
|
442
|
+
carouselSlotOffset += titleVarCount + descVarCount;
|
|
431
443
|
const { cardVarMapped: _drop, ...cardRest } = card;
|
|
432
444
|
|
|
433
445
|
return {
|
|
@@ -112,8 +112,8 @@ exports[`Creatives SmsTraiCreate test/> done button should be disabled if blank
|
|
|
112
112
|
"app.v2containers.SmsTrai.Edit.traiEditTitle": "Message",
|
|
113
113
|
"app.v2containers.SmsTrai.Edit.unicodeLabel": "Allow unicode characters",
|
|
114
114
|
"app.v2containers.SmsTrai.Edit.unsupportedTagsValidationError": "Unsupported tags: {unsupportedTags}. Please remove them from this message.",
|
|
115
|
-
"creatives.components.AccessForbidden.forbiddenDesc": "
|
|
116
|
-
"creatives.components.AccessForbidden.forbiddenHeader": "
|
|
115
|
+
"creatives.components.AccessForbidden.forbiddenDesc": "Please contact your organisation administrator to request access.",
|
|
116
|
+
"creatives.components.AccessForbidden.forbiddenHeader": "You don't have permission to view this page.",
|
|
117
117
|
"creatives.components.BreadCrumbs.header": "This is the BreadCrumbs component !",
|
|
118
118
|
"creatives.components.CapTagList.Cancel": "Cancel",
|
|
119
119
|
"creatives.components.CapTagList.Ok": "Ok",
|
|
@@ -3273,8 +3273,8 @@ exports[`Creatives SmsTraiCreate test/> invalid file drop 1`] = `
|
|
|
3273
3273
|
"app.v2containers.SmsTrai.Edit.traiEditTitle": "Message",
|
|
3274
3274
|
"app.v2containers.SmsTrai.Edit.unicodeLabel": "Allow unicode characters",
|
|
3275
3275
|
"app.v2containers.SmsTrai.Edit.unsupportedTagsValidationError": "Unsupported tags: {unsupportedTags}. Please remove them from this message.",
|
|
3276
|
-
"creatives.components.AccessForbidden.forbiddenDesc": "
|
|
3277
|
-
"creatives.components.AccessForbidden.forbiddenHeader": "
|
|
3276
|
+
"creatives.components.AccessForbidden.forbiddenDesc": "Please contact your organisation administrator to request access.",
|
|
3277
|
+
"creatives.components.AccessForbidden.forbiddenHeader": "You don't have permission to view this page.",
|
|
3278
3278
|
"creatives.components.BreadCrumbs.header": "This is the BreadCrumbs component !",
|
|
3279
3279
|
"creatives.components.CapTagList.Cancel": "Cancel",
|
|
3280
3280
|
"creatives.components.CapTagList.Ok": "Ok",
|
|
@@ -6417,8 +6417,8 @@ exports[`Creatives SmsTraiCreate test/> renders UI 1`] = `
|
|
|
6417
6417
|
"app.v2containers.SmsTrai.Edit.traiEditTitle": "Message",
|
|
6418
6418
|
"app.v2containers.SmsTrai.Edit.unicodeLabel": "Allow unicode characters",
|
|
6419
6419
|
"app.v2containers.SmsTrai.Edit.unsupportedTagsValidationError": "Unsupported tags: {unsupportedTags}. Please remove them from this message.",
|
|
6420
|
-
"creatives.components.AccessForbidden.forbiddenDesc": "
|
|
6421
|
-
"creatives.components.AccessForbidden.forbiddenHeader": "
|
|
6420
|
+
"creatives.components.AccessForbidden.forbiddenDesc": "Please contact your organisation administrator to request access.",
|
|
6421
|
+
"creatives.components.AccessForbidden.forbiddenHeader": "You don't have permission to view this page.",
|
|
6422
6422
|
"creatives.components.BreadCrumbs.header": "This is the BreadCrumbs component !",
|
|
6423
6423
|
"creatives.components.CapTagList.Cancel": "Cancel",
|
|
6424
6424
|
"creatives.components.CapTagList.Ok": "Ok",
|
|
@@ -9578,8 +9578,8 @@ exports[`Creatives SmsTraiCreate test/> valid file drop 1`] = `
|
|
|
9578
9578
|
"app.v2containers.SmsTrai.Edit.traiEditTitle": "Message",
|
|
9579
9579
|
"app.v2containers.SmsTrai.Edit.unicodeLabel": "Allow unicode characters",
|
|
9580
9580
|
"app.v2containers.SmsTrai.Edit.unsupportedTagsValidationError": "Unsupported tags: {unsupportedTags}. Please remove them from this message.",
|
|
9581
|
-
"creatives.components.AccessForbidden.forbiddenDesc": "
|
|
9582
|
-
"creatives.components.AccessForbidden.forbiddenHeader": "
|
|
9581
|
+
"creatives.components.AccessForbidden.forbiddenDesc": "Please contact your organisation administrator to request access.",
|
|
9582
|
+
"creatives.components.AccessForbidden.forbiddenHeader": "You don't have permission to view this page.",
|
|
9583
9583
|
"creatives.components.BreadCrumbs.header": "This is the BreadCrumbs component !",
|
|
9584
9584
|
"creatives.components.CapTagList.Cancel": "Cancel",
|
|
9585
9585
|
"creatives.components.CapTagList.Ok": "Ok",
|