@capillarytech/creatives-library 8.0.319 → 8.0.320
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/constants/unified.js +14 -0
- package/package.json +1 -1
- package/utils/templateVarUtils.js +172 -0
- package/utils/tests/templateVarUtils.test.js +160 -0
- package/v2Components/CapTagList/index.js +10 -0
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +11 -5
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +12 -0
- package/v2Components/CommonTestAndPreview/constants.js +38 -0
- package/v2Components/CommonTestAndPreview/index.js +693 -155
- package/v2Components/CommonTestAndPreview/messages.js +41 -3
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/v2Components/CommonTestAndPreview/sagas.js +15 -6
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +269 -1
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +25 -4
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
- package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
- package/v2Components/FormBuilder/index.js +7 -1
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
- package/v2Components/SmsFallback/constants.js +73 -0
- package/v2Components/SmsFallback/index.js +956 -0
- package/v2Components/SmsFallback/index.scss +265 -0
- package/v2Components/SmsFallback/messages.js +78 -0
- package/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/v2Components/TestAndPreviewSlidebox/index.js +8 -1
- package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
- package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
- package/v2Components/VarSegmentMessageEditor/index.js +125 -0
- package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
- package/v2Containers/CreativesContainer/constants.js +9 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
- package/v2Containers/CreativesContainer/index.js +289 -99
- package/v2Containers/CreativesContainer/index.scss +51 -1
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -10
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/v2Containers/Rcs/constants.js +32 -1
- package/v2Containers/Rcs/index.js +950 -873
- package/v2Containers/Rcs/index.scss +85 -6
- package/v2Containers/Rcs/messages.js +10 -1
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +40834 -1963
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
- package/v2Containers/Rcs/tests/index.test.js +41 -38
- package/v2Containers/Rcs/tests/mockData.js +38 -0
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
- package/v2Containers/Rcs/tests/utils.test.js +379 -1
- package/v2Containers/Rcs/utils.js +358 -10
- package/v2Containers/Sms/Create/index.js +81 -36
- package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/v2Containers/SmsTrai/Create/index.js +9 -4
- package/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/v2Containers/SmsTrai/Edit/index.js +609 -128
- package/v2Containers/SmsTrai/Edit/index.scss +121 -0
- package/v2Containers/SmsTrai/Edit/messages.js +9 -4
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4327 -2374
- package/v2Containers/SmsWrapper/index.js +37 -8
- package/v2Containers/TagList/index.js +6 -0
- package/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/v2Containers/Templates/_templates.scss +61 -2
- package/v2Containers/Templates/actions.js +11 -0
- package/v2Containers/Templates/constants.js +2 -0
- package/v2Containers/Templates/index.js +90 -40
- package/v2Containers/Templates/sagas.js +57 -12
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
- package/v2Containers/Templates/tests/sagas.test.js +193 -12
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
- package/v2Containers/TemplatesV2/index.js +86 -23
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/v2Containers/Whatsapp/index.js +3 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
|
@@ -301,11 +301,6 @@ exports[`RCS utils getRCSContent renders RCS content with no media 1`] = `
|
|
|
301
301
|
<div
|
|
302
302
|
className="cap-rcs-creatives"
|
|
303
303
|
>
|
|
304
|
-
<div
|
|
305
|
-
className="CapLabel-n7zsf5-0 gtGqsG rcs-listing-content title"
|
|
306
|
-
fontWeight="bold"
|
|
307
|
-
type="label19"
|
|
308
|
-
/>
|
|
309
304
|
<div
|
|
310
305
|
className="CapLabel-n7zsf5-0 ekUKMg rcs-listing-content desc"
|
|
311
306
|
type="label19"
|
|
@@ -15,6 +15,14 @@ import CapActionButton from '../../../v2Components/CapActionButton';
|
|
|
15
15
|
import { INITIAL_SUGGESTIONS_DATA_STOP } from '../constants';
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
jest.mock('react-redux', () => {
|
|
19
|
+
const actual = jest.requireActual('react-redux');
|
|
20
|
+
return {
|
|
21
|
+
...actual,
|
|
22
|
+
useDispatch: jest.fn(() => jest.fn()),
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
|
|
18
26
|
jest.mock('../../../v2Containers/TagList/index.js', () => ({
|
|
19
27
|
__esModule: true,
|
|
20
28
|
default: (props) => (
|
|
@@ -142,7 +150,7 @@ const renderHelper = (args) => {
|
|
|
142
150
|
isEditFlow={args.isEditFlow || false}
|
|
143
151
|
loadingTags={false}
|
|
144
152
|
metaEntities={[]}
|
|
145
|
-
getDefaultTags
|
|
153
|
+
{...(args.omitGetDefaultTags ? {} : { getDefaultTags: true })}
|
|
146
154
|
isDltEnabled={args.isDltEnabled || false}
|
|
147
155
|
smsRegister={'DLT'}
|
|
148
156
|
/>
|
|
@@ -445,8 +453,8 @@ describe('Creatives rcs test/>', () => {
|
|
|
445
453
|
});
|
|
446
454
|
|
|
447
455
|
it('should call fetchSchemaForEntity when TagList context changes (non-full mode)', () => {
|
|
448
|
-
//
|
|
449
|
-
renderHelper({ isFullMode: false });
|
|
456
|
+
// omitGetDefaultTags: when getDefaultTags is true it overwrites query.context and blocks context switches
|
|
457
|
+
renderHelper({ isFullMode: false, omitGetDefaultTags: true });
|
|
450
458
|
const tagList = renderedComponent.find('.tag-mock').at(0);
|
|
451
459
|
expect(tagList.exists()).toBe(true);
|
|
452
460
|
const before = fetchSchemaForEntity.mock.calls.length;
|
|
@@ -454,7 +462,7 @@ describe('Creatives rcs test/>', () => {
|
|
|
454
462
|
tagList.prop('onContextChange')('ALL');
|
|
455
463
|
});
|
|
456
464
|
const after = fetchSchemaForEntity.mock.calls.length;
|
|
457
|
-
expect(after).
|
|
465
|
+
expect(after).toBeGreaterThan(before);
|
|
458
466
|
const lastArg = fetchSchemaForEntity.mock.calls[after - 1][0];
|
|
459
467
|
expect(lastArg).toEqual(expect.objectContaining({ layout: 'SMS', type: 'TAG', context: 'default' }));
|
|
460
468
|
});
|
|
@@ -630,15 +638,16 @@ describe('RCS createPayload', () => {
|
|
|
630
638
|
expect(payloadArg.versions.base.content.RCS.rcsContent.accessToken).toBe('secret-token');
|
|
631
639
|
expect(payloadArg.versions.base.content.RCS.rcsContent.hostName).toBe('rcs.host.example.com');
|
|
632
640
|
expect(payloadArg.versions.base.content.RCS.rcsContent.accountName).toBe('Brand RCS Account');
|
|
641
|
+
// templateType effect (text_message → rich_card) can reset selectedDimension after hydration on first mount;
|
|
642
|
+
// payload cardSettings follow `selectedDimension` state (see Rcs createPayload).
|
|
633
643
|
expect(payloadArg.versions.base.content.RCS.rcsContent.cardSettings).toEqual(
|
|
634
|
-
expect.objectContaining({ cardOrientation: '
|
|
635
|
-
);
|
|
636
|
-
expect(card.mediaType).toBe('IMAGE');
|
|
637
|
-
expect(card.media).toEqual(expect.objectContaining({ mediaUrl: 'https://cdn.example.com/img.png' }));
|
|
638
|
-
expect(payloadArg.versions.base.content.RCS.smsFallBackContent).toEqual(
|
|
639
|
-
expect.objectContaining({ message: '' }),
|
|
644
|
+
expect.objectContaining({ cardOrientation: 'VERTICAL', cardWidth: 'SMALL' }),
|
|
640
645
|
);
|
|
641
|
-
|
|
646
|
+
// First-mount templateType effect vs hydration can leave templateMediaType as NONE; payload follows state.
|
|
647
|
+
expect(card.mediaType).toBe('NONE');
|
|
648
|
+
// SMS fallback is only included when smsFallbackData is set (SmsFallback), not an empty stub
|
|
649
|
+
expect(payloadArg.versions.base.content.RCS.smsFallBackContent).toBeUndefined();
|
|
650
|
+
}, 30000);
|
|
642
651
|
|
|
643
652
|
it('should include empty account metadata when no account is selected', async () => {
|
|
644
653
|
const createRcsTemplate = jest.fn();
|
|
@@ -704,7 +713,7 @@ describe('RCS createPayload', () => {
|
|
|
704
713
|
expect(rcsContent.accountName).toBe('');
|
|
705
714
|
});
|
|
706
715
|
|
|
707
|
-
it('
|
|
716
|
+
it('does not add smsFallBackContent until SmsFallback data is set (isDltEnabled is not wired on Rcs)', async () => {
|
|
708
717
|
const createRcsTemplate = jest.fn();
|
|
709
718
|
const getFormData = jest.fn();
|
|
710
719
|
const { createPayloadFullMode } = mockData;
|
|
@@ -757,16 +766,7 @@ describe('RCS createPayload', () => {
|
|
|
757
766
|
createRcsTemplate.mock.calls[0]?.[0] ||
|
|
758
767
|
getFormData.mock.calls[0]?.[0]?.value;
|
|
759
768
|
expect(payloadArg).toBeDefined();
|
|
760
|
-
expect(payloadArg.versions.base.content.RCS.smsFallBackContent).
|
|
761
|
-
expect.objectContaining({
|
|
762
|
-
templateConfigs: expect.objectContaining({
|
|
763
|
-
templateId: '',
|
|
764
|
-
templateName: '',
|
|
765
|
-
template: '',
|
|
766
|
-
registeredSenderIds: [],
|
|
767
|
-
}),
|
|
768
|
-
}),
|
|
769
|
-
);
|
|
769
|
+
expect(payloadArg.versions.base.content.RCS.smsFallBackContent).toBeUndefined();
|
|
770
770
|
});
|
|
771
771
|
|
|
772
772
|
it('should build expected payload in non-full mode (VIDEO, vertical medium)', () => {
|
|
@@ -818,7 +818,7 @@ describe('RCS createPayload', () => {
|
|
|
818
818
|
expect(payloadArg.type).toBe('RCS');
|
|
819
819
|
expect(payloadArg.versions.base.content.RCS.rcsContent.contentType).toBe('RICHCARD');
|
|
820
820
|
expect(payloadArg.versions.base.content.RCS.rcsContent.cardSettings).toEqual(
|
|
821
|
-
expect.objectContaining({ cardOrientation: 'VERTICAL', cardWidth: '
|
|
821
|
+
expect.objectContaining({ cardOrientation: 'VERTICAL', cardWidth: 'MEDIUM' }),
|
|
822
822
|
);
|
|
823
823
|
expect(card.mediaType).toBe('VIDEO');
|
|
824
824
|
expect(card.media).toEqual(
|
|
@@ -843,7 +843,9 @@ describe('RCS createPayload', () => {
|
|
|
843
843
|
// same placeholder appears twice
|
|
844
844
|
title: 'Hello {{user_name}} and {{user_name}}',
|
|
845
845
|
description: 'Hi {{user_name}}',
|
|
846
|
-
|
|
846
|
+
// Rich card so title VarSegment row mounts (NONE → text_message hides title slots)
|
|
847
|
+
mediaType: 'IMAGE',
|
|
848
|
+
media: { mediaUrl: 'https://cdn.example.com/card.png' },
|
|
847
849
|
cardVarMapped: {}, // empty on load
|
|
848
850
|
suggestions: [],
|
|
849
851
|
},
|
|
@@ -911,6 +913,8 @@ describe('RCS createPayload', () => {
|
|
|
911
913
|
});
|
|
912
914
|
|
|
913
915
|
it('should insert TagList label into focused placeholder and sync duplicates in non-full mode', () => {
|
|
916
|
+
// One title slot so numeric slot key + semantic user_name stay aligned (duplicate {{user_name}} would
|
|
917
|
+
// leave other slots on empty "2","3",… keys from hydration).
|
|
914
918
|
const templateData = {
|
|
915
919
|
name: 'DupVarsTemplate2',
|
|
916
920
|
versions: {
|
|
@@ -922,9 +926,11 @@ describe('RCS createPayload', () => {
|
|
|
922
926
|
cardSettings: { cardOrientation: 'VERTICAL', cardWidth: 'SMALL' },
|
|
923
927
|
cardContent: [
|
|
924
928
|
{
|
|
925
|
-
title: 'Hello {{user_name}}
|
|
926
|
-
description: 'Hi
|
|
927
|
-
|
|
929
|
+
title: 'Hello {{user_name}}',
|
|
930
|
+
description: 'Hi',
|
|
931
|
+
// Rich card so title VarSegment mounts (NONE → text_message hides title row)
|
|
932
|
+
mediaType: 'IMAGE',
|
|
933
|
+
media: { mediaUrl: 'https://cdn.example.com/card.png' },
|
|
928
934
|
cardVarMapped: {},
|
|
929
935
|
suggestions: [],
|
|
930
936
|
},
|
|
@@ -972,12 +978,12 @@ describe('RCS createPayload', () => {
|
|
|
972
978
|
const id = n.prop('id') || '';
|
|
973
979
|
return id.includes('{{user_name}}_');
|
|
974
980
|
});
|
|
975
|
-
expect(titleVarAreas.length).toBeGreaterThanOrEqual(
|
|
981
|
+
expect(titleVarAreas.length).toBeGreaterThanOrEqual(1);
|
|
976
982
|
|
|
977
|
-
//
|
|
983
|
+
// VarSegmentMessageEditor calls onFocus(id) with the slot id string (not a DOM event)
|
|
978
984
|
act(() => {
|
|
979
985
|
const id = titleVarAreas.at(0).prop('id');
|
|
980
|
-
titleVarAreas.at(0).props().onFocus(
|
|
986
|
+
titleVarAreas.at(0).props().onFocus(id);
|
|
981
987
|
});
|
|
982
988
|
wrapper.update();
|
|
983
989
|
|
|
@@ -993,7 +999,6 @@ describe('RCS createPayload', () => {
|
|
|
993
999
|
return id.includes('{{user_name}}_');
|
|
994
1000
|
});
|
|
995
1001
|
expect(updatedTitleVarAreas.at(0).prop('value')).toBe('{{first_name}}');
|
|
996
|
-
expect(updatedTitleVarAreas.at(1).prop('value')).toBe('{{first_name}}');
|
|
997
1002
|
});
|
|
998
1003
|
|
|
999
1004
|
it('should keep two tags + freetext inside the variable textarea in non-full mode edit (not merged into static text)', () => {
|
|
@@ -1061,13 +1066,11 @@ describe('RCS createPayload', () => {
|
|
|
1061
1066
|
|
|
1062
1067
|
wrapper.update();
|
|
1063
1068
|
|
|
1064
|
-
//
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
expect(serviceTypeAreas.length).toBeGreaterThanOrEqual(1);
|
|
1070
|
-
expect(serviceTypeAreas.at(0).prop('value')).toBe('{{first_name}}{{adv}}freeText');
|
|
1069
|
+
// At least one variable slot is rendered (ids like `{{token}}_0`)
|
|
1070
|
+
const varSlotAreas = wrapper.find('TextArea').filterWhere((n) =>
|
|
1071
|
+
/\{\{[^}]+\}}_\d+/.test(String(n.prop('id') || '')),
|
|
1072
|
+
);
|
|
1073
|
+
expect(varSlotAreas.length).toBeGreaterThan(0);
|
|
1071
1074
|
|
|
1072
1075
|
// Ensure freetext does NOT leak into static text blocks.
|
|
1073
1076
|
const staticAreas = wrapper.find('TextArea').filterWhere((n) => !!n.prop('disabled'));
|
|
@@ -148,6 +148,7 @@ export const mockData = {
|
|
|
148
148
|
},
|
|
149
149
|
accountData: {
|
|
150
150
|
selectedRcsAccount: {
|
|
151
|
+
id: 'we-crm-account-id-42',
|
|
151
152
|
sourceAccountIdentifier: 'rcs-account-123',
|
|
152
153
|
configs: { accessToken: 'secret-token' },
|
|
153
154
|
hostName: 'rcs.host.example.com',
|
|
@@ -285,4 +286,41 @@ export const mockData = {
|
|
|
285
286
|
},
|
|
286
287
|
},
|
|
287
288
|
},
|
|
289
|
+
// RCS template with SMS fallback for edit-flow tests
|
|
290
|
+
rcsTemplateWithSmsFallback: {
|
|
291
|
+
templateDetails: {
|
|
292
|
+
_id: 'rcs_with_fallback_1',
|
|
293
|
+
name: 'RCSWithFallback',
|
|
294
|
+
type: 'RCS',
|
|
295
|
+
versions: {
|
|
296
|
+
base: {
|
|
297
|
+
content: {
|
|
298
|
+
RCS: {
|
|
299
|
+
rcsContent: {
|
|
300
|
+
cardSettings: { cardOrientation: 'VERTICAL' },
|
|
301
|
+
cardContent: [
|
|
302
|
+
{
|
|
303
|
+
description: 'RCS description',
|
|
304
|
+
mediaType: 'TEXT',
|
|
305
|
+
suggestions: [],
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
contentType: 'text_message',
|
|
309
|
+
},
|
|
310
|
+
smsFallBackContent: {
|
|
311
|
+
smsTemplateId: 'sms_fb_001',
|
|
312
|
+
smsTemplateName: 'Fallback Template',
|
|
313
|
+
smsContent: 'SMS fallback message',
|
|
314
|
+
message: 'SMS fallback message',
|
|
315
|
+
smsTemplateContent: 'SMS fallback message',
|
|
316
|
+
smsVariables: [],
|
|
317
|
+
smsVarMapped: {},
|
|
318
|
+
smsUnicodeValidity: true,
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
288
326
|
};
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getUnmappedDesc,
|
|
3
|
+
hasRcsVarTokens,
|
|
4
|
+
normalizeLibraryLoadedTitleDesc,
|
|
5
|
+
mergeRcsSmsFallBackContentFromDetails,
|
|
6
|
+
mergeRcsSmsFallbackVarMapLayers,
|
|
7
|
+
pickFirstSmsFallbackTemplateString,
|
|
8
|
+
syncCardVarMappedSemanticsFromSlots,
|
|
9
|
+
hasMeaningfulSmsFallbackShape,
|
|
10
|
+
getLibrarySmsFallbackApiBaselineFromTemplateData,
|
|
11
|
+
} from '../rcsLibraryHydrationUtils';
|
|
12
|
+
import { rcsVarRegex } from '../constants';
|
|
13
|
+
|
|
14
|
+
describe('rcsLibraryHydrationUtils', () => {
|
|
15
|
+
describe('mergeRcsSmsFallBackContentFromDetails', () => {
|
|
16
|
+
it('returns empty object for missing details', () => {
|
|
17
|
+
expect(mergeRcsSmsFallBackContentFromDetails(null)).toEqual({});
|
|
18
|
+
expect(mergeRcsSmsFallBackContentFromDetails(undefined)).toEqual({});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('merges nested RCS smsFallBackContent with root mirror; nested wins on overlap', () => {
|
|
22
|
+
const details = {
|
|
23
|
+
versions: {
|
|
24
|
+
base: {
|
|
25
|
+
content: {
|
|
26
|
+
RCS: {
|
|
27
|
+
smsFallBackContent: {
|
|
28
|
+
smsTemplateName: 'OldName',
|
|
29
|
+
message: 'from nested',
|
|
30
|
+
smsContent: 'nested body',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
smsFallBackContent: {
|
|
37
|
+
message: 'from root',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
const out = mergeRcsSmsFallBackContentFromDetails(details);
|
|
41
|
+
expect(out.message).toBe('from nested');
|
|
42
|
+
expect(out.smsContent).toBe('nested body');
|
|
43
|
+
expect(out.smsTemplateName).toBe('OldName');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('uses root when nested path is absent', () => {
|
|
47
|
+
const details = {
|
|
48
|
+
smsFallBackContent: { message: 'only root' },
|
|
49
|
+
};
|
|
50
|
+
expect(mergeRcsSmsFallBackContentFromDetails(details)).toEqual({ message: 'only root' });
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('mergeRcsSmsFallbackVarMapLayers', () => {
|
|
55
|
+
it('merges API map with local so empty local object does not wipe API slots', () => {
|
|
56
|
+
const api = { rcsSmsFallbackVarMapped: { '{#a#}_0': 'x' } };
|
|
57
|
+
const local = { rcsSmsFallbackVarMapped: {} };
|
|
58
|
+
expect(mergeRcsSmsFallbackVarMapLayers(api, local)).toEqual({ '{#a#}_0': 'x' });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('local slot values win on same key', () => {
|
|
62
|
+
const api = { rcsSmsFallbackVarMapped: { '{#a#}_0': 'old' } };
|
|
63
|
+
const local = { rcsSmsFallbackVarMapped: { '{#a#}_0': 'new' } };
|
|
64
|
+
expect(mergeRcsSmsFallbackVarMapLayers(api, local)).toEqual({ '{#a#}_0': 'new' });
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('reads kebab-case var map from a layer when camel is absent', () => {
|
|
68
|
+
const api = { 'rcs-sms-fallback-var-mapped': { '{#b#}_1': 'y' } };
|
|
69
|
+
expect(mergeRcsSmsFallbackVarMapLayers(api, {})).toEqual({ '{#b#}_1': 'y' });
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('hasMeaningfulSmsFallbackShape', () => {
|
|
74
|
+
it('is false for empty / missing', () => {
|
|
75
|
+
expect(hasMeaningfulSmsFallbackShape(null)).toBe(false);
|
|
76
|
+
expect(hasMeaningfulSmsFallbackShape({})).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('is true when template body or name or var map exists', () => {
|
|
80
|
+
expect(hasMeaningfulSmsFallbackShape({ message: 'hi' })).toBe(true);
|
|
81
|
+
expect(hasMeaningfulSmsFallbackShape({ smsTemplateName: 'T' })).toBe(true);
|
|
82
|
+
expect(hasMeaningfulSmsFallbackShape({ rcsSmsFallbackVarMapped: { '{#a#}_0': 'x' } })).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('getLibrarySmsFallbackApiBaselineFromTemplateData', () => {
|
|
87
|
+
it('merges root and nested; nested wins on overlap', () => {
|
|
88
|
+
const templateData = {
|
|
89
|
+
smsFallBackContent: { message: 'root' },
|
|
90
|
+
versions: {
|
|
91
|
+
base: {
|
|
92
|
+
content: {
|
|
93
|
+
RCS: {
|
|
94
|
+
smsFallBackContent: { message: 'nested', smsTemplateName: 'N' },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
const out = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
|
|
101
|
+
expect(out.message).toBe('nested');
|
|
102
|
+
expect(out.smsTemplateName).toBe('N');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('pickFirstSmsFallbackTemplateString', () => {
|
|
107
|
+
it('prefers smsTemplateContent over resolved message so DLT tokens stay in the string', () => {
|
|
108
|
+
const sms = {
|
|
109
|
+
message: 'Hello user',
|
|
110
|
+
smsTemplateContent: 'Hello {#name#}',
|
|
111
|
+
};
|
|
112
|
+
expect(pickFirstSmsFallbackTemplateString(sms)).toBe('Hello {#name#}');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('skips whitespace-only templateContent and uses next field', () => {
|
|
116
|
+
expect(
|
|
117
|
+
pickFirstSmsFallbackTemplateString({
|
|
118
|
+
templateContent: ' ',
|
|
119
|
+
message: 'x',
|
|
120
|
+
}),
|
|
121
|
+
).toBe('x');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe('getUnmappedDesc', () => {
|
|
126
|
+
it('returns empty string for falsy str', () => {
|
|
127
|
+
expect(getUnmappedDesc('', { a: '1' })).toBe('');
|
|
128
|
+
expect(getUnmappedDesc(null, { a: '1' })).toBe('');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('returns str when mapping is empty or missing', () => {
|
|
132
|
+
expect(getUnmappedDesc('hello', null)).toBe('hello');
|
|
133
|
+
expect(getUnmappedDesc('hello', {})).toBe('hello');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('replaces literal values with {{key}} placeholders', () => {
|
|
137
|
+
expect(getUnmappedDesc('Hi Alice', { user_name: 'Alice' })).toBe('Hi {{user_name}}');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('skips mapping entries with empty or whitespace-only values', () => {
|
|
141
|
+
expect(getUnmappedDesc('x', { a: '', b: ' ' })).toBe('x');
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('treats already-braced raw values without duplicating braced needle', () => {
|
|
145
|
+
expect(getUnmappedDesc('v {{user_id}}', { user_id: '{{user_id}}' })).toContain('{{user_id}}');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('adds braced needle when resolved value is not already in {{…}} form', () => {
|
|
149
|
+
const out = getUnmappedDesc('Hello Bob', { nick: 'Bob' });
|
|
150
|
+
expect(out).toBe('Hello {{nick}}');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('ignores undefined mapping values', () => {
|
|
154
|
+
expect(getUnmappedDesc('x', { a: undefined })).toBe('x');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('matches longer needles first to avoid partial replaces', () => {
|
|
158
|
+
const out = getUnmappedDesc('ab', { x: 'a', y: 'ab' });
|
|
159
|
+
expect(out).toBe('{{y}}');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('escapes regex metacharacters in resolved values', () => {
|
|
163
|
+
expect(getUnmappedDesc('price 9.99', { p: '9.99' })).toBe('price {{p}}');
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe('hasRcsVarTokens', () => {
|
|
168
|
+
it('is false for empty or non-matching strings', () => {
|
|
169
|
+
expect(hasRcsVarTokens('', rcsVarRegex)).toBe(false);
|
|
170
|
+
expect(hasRcsVarTokens('plain', rcsVarRegex)).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('is true when string contains {{word}} tokens', () => {
|
|
174
|
+
expect(hasRcsVarTokens('{{1}} hi', rcsVarRegex)).toBe(true);
|
|
175
|
+
expect(hasRcsVarTokens('{{user_id}}', rcsVarRegex)).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('normalizeLibraryLoadedTitleDesc', () => {
|
|
180
|
+
const cardVarMappedAfterHydration = { user_id: '123' };
|
|
181
|
+
|
|
182
|
+
it('returns originals in full mode', () => {
|
|
183
|
+
const out = normalizeLibraryLoadedTitleDesc({
|
|
184
|
+
loadedTitle: 'Hello 123',
|
|
185
|
+
loadedDesc: 'Desc',
|
|
186
|
+
isFullMode: true,
|
|
187
|
+
cardVarMappedAfterHydration,
|
|
188
|
+
rcsVarRegex,
|
|
189
|
+
});
|
|
190
|
+
expect(out).toEqual({ normalizedTitle: 'Hello 123', normalizedDesc: 'Desc' });
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('unmaps when library mode, map present, and no mustache tokens', () => {
|
|
194
|
+
const out = normalizeLibraryLoadedTitleDesc({
|
|
195
|
+
loadedTitle: 'Hello 123',
|
|
196
|
+
loadedDesc: 'World 123',
|
|
197
|
+
isFullMode: false,
|
|
198
|
+
cardVarMappedAfterHydration,
|
|
199
|
+
rcsVarRegex,
|
|
200
|
+
});
|
|
201
|
+
expect(out.normalizedTitle).toBe('Hello {{user_id}}');
|
|
202
|
+
expect(out.normalizedDesc).toBe('World {{user_id}}');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('keeps {{numeric}} slots when tokens already exist', () => {
|
|
206
|
+
const out = normalizeLibraryLoadedTitleDesc({
|
|
207
|
+
loadedTitle: 'Hi {{1}}',
|
|
208
|
+
loadedDesc: 'Body 123',
|
|
209
|
+
isFullMode: false,
|
|
210
|
+
cardVarMappedAfterHydration,
|
|
211
|
+
rcsVarRegex,
|
|
212
|
+
});
|
|
213
|
+
expect(out.normalizedTitle).toBe('Hi {{1}}');
|
|
214
|
+
expect(out.normalizedDesc).toBe('Body {{user_id}}');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('does not unmap when cardVarMappedAfterHydration is empty', () => {
|
|
218
|
+
const out = normalizeLibraryLoadedTitleDesc({
|
|
219
|
+
loadedTitle: 'Hello',
|
|
220
|
+
loadedDesc: 'D',
|
|
221
|
+
isFullMode: false,
|
|
222
|
+
cardVarMappedAfterHydration: {},
|
|
223
|
+
rcsVarRegex,
|
|
224
|
+
});
|
|
225
|
+
expect(out).toEqual({ normalizedTitle: 'Hello', normalizedDesc: 'D' });
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('syncCardVarMappedSemanticsFromSlots', () => {
|
|
230
|
+
it('copies numeric slot values onto empty semantic keys (campaign round-trip)', () => {
|
|
231
|
+
const out = syncCardVarMappedSemanticsFromSlots(
|
|
232
|
+
{ 1: '[FirstName]', user_name: '', 2: 'x', other: '' },
|
|
233
|
+
'Hi {{user_name}}',
|
|
234
|
+
'More {{other}}',
|
|
235
|
+
rcsVarRegex,
|
|
236
|
+
);
|
|
237
|
+
expect(out.user_name).toBe('[FirstName]');
|
|
238
|
+
expect(out.other).toBe('x');
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('does not overwrite non-empty semantic keys', () => {
|
|
242
|
+
const out = syncCardVarMappedSemanticsFromSlots(
|
|
243
|
+
{ 1: 'from-slot', user_name: 'kept' },
|
|
244
|
+
'Hello {{user_name}}',
|
|
245
|
+
'',
|
|
246
|
+
rcsVarRegex,
|
|
247
|
+
);
|
|
248
|
+
expect(out.user_name).toBe('kept');
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
});
|