@capillarytech/creatives-library 8.0.353-alpha.6 → 8.0.354
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 +0 -29
- package/index.html +1 -0
- package/package.json +1 -1
- package/services/tests/api.test.js +20 -35
- package/utils/cdnTransformation.js +63 -3
- package/utils/commonUtils.js +1 -19
- package/utils/tests/cdnTransformation.test.js +111 -0
- package/v2Components/CapActionButton/constants.js +0 -7
- package/v2Components/CapActionButton/index.js +108 -166
- package/v2Components/CapActionButton/index.scss +6 -157
- package/v2Components/CapActionButton/messages.js +3 -19
- package/v2Components/CapActionButton/tests/index.test.js +17 -41
- package/v2Components/CapTagList/index.js +0 -10
- package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -72
- package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
- package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -213
- package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
- package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
- package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
- package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
- package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +15 -157
- package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +76 -346
- package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
- package/v2Components/CommonTestAndPreview/constants.js +2 -38
- package/v2Components/CommonTestAndPreview/index.js +186 -691
- package/v2Components/CommonTestAndPreview/messages.js +3 -45
- package/v2Components/CommonTestAndPreview/sagas.js +6 -25
- package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -308
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
- package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +1 -8
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +283 -281
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
- package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -132
- package/v2Components/CommonTestAndPreview/tests/sagas.test.js +26 -36
- package/v2Components/FormBuilder/index.js +6 -11
- package/v2Components/TemplatePreview/_templatePreview.scss +23 -38
- package/v2Components/TemplatePreview/index.js +31 -143
- package/v2Components/TemplatePreview/tests/index.test.js +0 -142
- package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
- package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
- package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
- package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
- package/v2Containers/CreativesContainer/constants.js +0 -9
- package/v2Containers/CreativesContainer/index.js +103 -322
- package/v2Containers/CreativesContainer/index.scss +1 -51
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -20
- package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
- package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
- package/v2Containers/Rcs/constants.js +10 -119
- package/v2Containers/Rcs/index.js +818 -2450
- package/v2Containers/Rcs/index.scss +8 -280
- package/v2Containers/Rcs/messages.js +3 -34
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +70073 -98018
- package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
- package/v2Containers/Rcs/tests/index.test.js +121 -152
- package/v2Containers/Rcs/tests/mockData.js +0 -38
- package/v2Containers/Rcs/tests/utils.test.js +30 -646
- package/v2Containers/Rcs/utils.js +11 -478
- package/v2Containers/Sms/Create/index.js +40 -106
- package/v2Containers/SmsTrai/Create/index.js +4 -9
- package/v2Containers/SmsTrai/Edit/constants.js +0 -2
- package/v2Containers/SmsTrai/Edit/index.js +130 -640
- package/v2Containers/SmsTrai/Edit/messages.js +4 -14
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2296 -4249
- package/v2Containers/SmsWrapper/index.js +8 -37
- package/v2Containers/TagList/index.js +0 -6
- package/v2Containers/Templates/_templates.scss +9 -166
- package/v2Containers/Templates/actions.js +0 -11
- package/v2Containers/Templates/constants.js +0 -2
- package/v2Containers/Templates/index.js +52 -120
- package/v2Containers/Templates/sagas.js +18 -57
- package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1017 -1062
- package/v2Containers/Templates/tests/sagas.test.js +39 -205
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
- package/v2Containers/TemplatesV2/index.js +23 -86
- package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
- package/v2Containers/Whatsapp/index.js +20 -3
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
- package/utils/rcsPayloadUtils.js +0 -92
- package/utils/templateVarUtils.js +0 -201
- package/utils/tests/rcsPayloadUtils.test.js +0 -226
- package/utils/tests/templateVarUtils.test.js +0 -204
- package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
- package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
- package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -91
- package/v2Components/SmsFallback/constants.js +0 -73
- package/v2Components/SmsFallback/index.js +0 -956
- package/v2Components/SmsFallback/index.scss +0 -265
- package/v2Components/SmsFallback/messages.js +0 -78
- package/v2Components/SmsFallback/smsFallbackUtils.js +0 -119
- package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
- package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
- package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
- package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -223
- package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -309
- package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
- package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
- package/v2Components/TemplatePreview/constants.js +0 -2
- package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
- package/v2Components/VarSegmentMessageEditor/index.js +0 -125
- package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -79
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
- package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
- package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -225
- package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
- package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
- package/v2Containers/SmsTrai/Edit/index.scss +0 -121
- package/v2Containers/Templates/TemplatesActionBar.js +0 -101
- package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
- package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
- package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
- package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import isEmpty from 'lodash/isEmpty';
|
|
2
|
-
import get from 'lodash/get';
|
|
3
|
-
import { RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
|
|
4
|
-
import {
|
|
5
|
-
RCS_NUMERIC_VAR_NAME_REGEX,
|
|
6
|
-
RCS_CARD_VAR_MAPPED_SEMANTIC_KEY_REGEX,
|
|
7
|
-
} from './constants';
|
|
8
|
-
|
|
9
|
-
/** RCS card `cardVarMapped` / `rcsCardVarMapped` only (SMS fallback slot keys stay on `smsFallBackContent`). */
|
|
10
|
-
export function pickRcsCardVarMappedEntries(record) {
|
|
11
|
-
if (record == null || typeof record !== 'object' || Array.isArray(record)) return {};
|
|
12
|
-
return Object.fromEntries(
|
|
13
|
-
Object.entries(record).filter(([k]) => {
|
|
14
|
-
const key = String(k);
|
|
15
|
-
return (
|
|
16
|
-
RCS_NUMERIC_VAR_NAME_REGEX.test(key)
|
|
17
|
-
|| RCS_CARD_VAR_MAPPED_SEMANTIC_KEY_REGEX.test(key)
|
|
18
|
-
);
|
|
19
|
-
}),
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Nested `versions…smsFallBackContent` and root `smsFallBackContent` (CreativesContainer mirror)
|
|
25
|
-
* can diverge. Merge explicitly (avoid `get(..., {}) || root` — `{}` is truthy and blocks fallback).
|
|
26
|
-
*
|
|
27
|
-
* When both exist, **nested wins on overlap**: the canonical RCS path is what `createPayload` / submit
|
|
28
|
-
* update; the root mirror can lag behind campaign parent state and would otherwise keep old
|
|
29
|
-
* `message` / `rcsSmsFallbackVarMapped` in hydration and approval payloads.
|
|
30
|
-
*/
|
|
31
|
-
export function mergeRcsSmsFallBackContentFromDetails(details) {
|
|
32
|
-
if (!details || typeof details !== 'object') return {};
|
|
33
|
-
const nestedSmsFallback = details?.versions?.base?.content?.RCS?.smsFallBackContent;
|
|
34
|
-
const rootSmsFallback = details?.smsFallBackContent;
|
|
35
|
-
const nestedRecord =
|
|
36
|
-
nestedSmsFallback != null && typeof nestedSmsFallback === 'object' ? nestedSmsFallback : {};
|
|
37
|
-
const rootRecord =
|
|
38
|
-
rootSmsFallback != null && typeof rootSmsFallback === 'object' ? rootSmsFallback : {};
|
|
39
|
-
return { ...rootRecord, ...nestedRecord };
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Merge SMS fallback slot maps from API/templateData (`apiShape`) and editor state (`localShape`).
|
|
44
|
-
* Spreading `{ ...api, ...local }` lets `rcsSmsFallbackVarMapped: {}` on local wipe a populated API map,
|
|
45
|
-
* which keeps Done disabled in DLT campaigns until fixed.
|
|
46
|
-
*/
|
|
47
|
-
export function mergeRcsSmsFallbackVarMapLayers(apiShape = {}, localShape = {}) {
|
|
48
|
-
const readMap = (o) => {
|
|
49
|
-
if (!o || typeof o !== 'object') return {};
|
|
50
|
-
const camel = o.rcsSmsFallbackVarMapped;
|
|
51
|
-
const kebab = o['rcs-sms-fallback-var-mapped'];
|
|
52
|
-
const src =
|
|
53
|
-
camel && typeof camel === 'object'
|
|
54
|
-
? camel
|
|
55
|
-
: kebab && typeof kebab === 'object'
|
|
56
|
-
? kebab
|
|
57
|
-
: {};
|
|
58
|
-
return { ...src };
|
|
59
|
-
};
|
|
60
|
-
return { ...readMap(apiShape), ...readMap(localShape) };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* First non-empty trimmed string for SMS fallback body used in DLT slot checks / payload `templateContent`.
|
|
65
|
-
* Prefer **raw** template fields before resolved `message` so `{#…#}` / `{{…}}` tokens are not lost
|
|
66
|
-
* when `message` is consumer-resolved text (campaigns).
|
|
67
|
-
*/
|
|
68
|
-
/**
|
|
69
|
-
* Whether an SMS fallback object carries template body, name, or var-map data (same rule as Rcs `createPayload`).
|
|
70
|
-
*/
|
|
71
|
-
export function hasMeaningfulSmsFallbackShape(s) {
|
|
72
|
-
return !!(
|
|
73
|
-
s
|
|
74
|
-
&& (
|
|
75
|
-
String(
|
|
76
|
-
s.content
|
|
77
|
-
|| s.templateContent
|
|
78
|
-
|| s.message
|
|
79
|
-
|| s.smsContent
|
|
80
|
-
|| s.smsTemplateContent
|
|
81
|
-
|| '',
|
|
82
|
-
).trim() !== ''
|
|
83
|
-
|| String(s.templateName || s.smsTemplateName || '').trim() !== ''
|
|
84
|
-
|| (s.rcsSmsFallbackVarMapped && Object.keys(s.rcsSmsFallbackVarMapped).length > 0)
|
|
85
|
-
|| (s[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
|
|
86
|
-
&& Object.keys(s[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0)
|
|
87
|
-
)
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Library `templateData`: merged root + nested `smsFallBackContent` (nested wins), matching `createPayload`.
|
|
93
|
-
*/
|
|
94
|
-
export function getLibrarySmsFallbackApiBaselineFromTemplateData(templateData) {
|
|
95
|
-
const smsFromTemplateRoot = get(
|
|
96
|
-
templateData,
|
|
97
|
-
'versions.base.content.RCS.smsFallBackContent',
|
|
98
|
-
);
|
|
99
|
-
return {
|
|
100
|
-
...(templateData?.smsFallBackContent && typeof templateData.smsFallBackContent === 'object'
|
|
101
|
-
? templateData.smsFallBackContent
|
|
102
|
-
: {}),
|
|
103
|
-
...(smsFromTemplateRoot && typeof smsFromTemplateRoot === 'object'
|
|
104
|
-
? smsFromTemplateRoot
|
|
105
|
-
: {}),
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export { extractRegisteredSenderIdsFromSmsFallbackRecord } from '../../utils/commonUtils';
|
|
110
|
-
|
|
111
|
-
export function pickFirstSmsFallbackTemplateString(sms = {}) {
|
|
112
|
-
if (!sms || typeof sms !== 'object') return '';
|
|
113
|
-
const keys = [
|
|
114
|
-
'templateContent',
|
|
115
|
-
'smsTemplateContent',
|
|
116
|
-
'content',
|
|
117
|
-
'smsContent',
|
|
118
|
-
'message',
|
|
119
|
-
];
|
|
120
|
-
for (let i = 0; i < keys.length; i += 1) {
|
|
121
|
-
const v = sms[keys[i]];
|
|
122
|
-
if (v == null) continue;
|
|
123
|
-
const s = String(v).trim();
|
|
124
|
-
if (s) return s;
|
|
125
|
-
}
|
|
126
|
-
return '';
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Campaign reopen: payloads often keep tag/label values on numeric keys (`"1"`, `"2"`, …) while
|
|
131
|
-
* semantic keys (`user_name`, …) stay `""`. Copy non-empty slot values onto empty semantic keys so
|
|
132
|
-
* VarSegment editors prepopulate after Done → Edit without changing global resolve behavior.
|
|
133
|
-
*/
|
|
134
|
-
export function syncCardVarMappedSemanticsFromSlots(
|
|
135
|
-
cardVarMappedInput,
|
|
136
|
-
templateTitle,
|
|
137
|
-
templateDesc,
|
|
138
|
-
rcsVarRegex,
|
|
139
|
-
) {
|
|
140
|
-
const cardVarMappedSynced =
|
|
141
|
-
cardVarMappedInput != null && typeof cardVarMappedInput === 'object'
|
|
142
|
-
? { ...cardVarMappedInput }
|
|
143
|
-
: {};
|
|
144
|
-
const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
|
|
145
|
-
const templateVarTokens = [
|
|
146
|
-
...(templateTitle?.match(rcsVarRegex) ?? []),
|
|
147
|
-
...(templateDesc?.match(rcsVarRegex) ?? []),
|
|
148
|
-
];
|
|
149
|
-
templateVarTokens.forEach((token, slotIndexZeroBased) => {
|
|
150
|
-
const semanticVarName = getVarNameFromToken(token);
|
|
151
|
-
if (!semanticVarName) return;
|
|
152
|
-
const numericSlotKey = String(slotIndexZeroBased + 1);
|
|
153
|
-
const semanticValueTrimmed = String(cardVarMappedSynced[semanticVarName] ?? '').trim();
|
|
154
|
-
const numericSlotValueTrimmed = String(cardVarMappedSynced[numericSlotKey] ?? '').trim();
|
|
155
|
-
if (!semanticValueTrimmed && numericSlotValueTrimmed) {
|
|
156
|
-
cardVarMappedSynced[semanticVarName] = cardVarMappedSynced[numericSlotKey];
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
return cardVarMappedSynced;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Maps resolved campaign values back to `{{semanticKey}}` using `cardVarMapped` keys.
|
|
164
|
-
* Used when hydrating library/journey RCS edit from payloads without existing `{{…}}` tokens.
|
|
165
|
-
*/
|
|
166
|
-
export function getUnmappedDesc(str, mapping) {
|
|
167
|
-
if (!str) return '';
|
|
168
|
-
if (!mapping || Object.keys(mapping).length === 0) return str;
|
|
169
|
-
let result = str;
|
|
170
|
-
const replacements = [];
|
|
171
|
-
Object.entries(mapping).forEach(([key, value]) => {
|
|
172
|
-
const raw = (value ?? '').toString();
|
|
173
|
-
if (!raw || raw?.trim?.() === '') return;
|
|
174
|
-
const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
|
|
175
|
-
replacements.push({ key, needle: raw });
|
|
176
|
-
if (braced !== raw) replacements.push({ key, needle: braced });
|
|
177
|
-
});
|
|
178
|
-
const seen = new Set();
|
|
179
|
-
const uniq = replacements
|
|
180
|
-
.filter(({ key, needle }) => {
|
|
181
|
-
const id = `${key}::${needle}`;
|
|
182
|
-
if (seen.has(id)) return false;
|
|
183
|
-
seen.add(id);
|
|
184
|
-
return true;
|
|
185
|
-
})
|
|
186
|
-
.sort((a, b) => (b.needle.length - a.needle.length));
|
|
187
|
-
|
|
188
|
-
uniq.forEach(({ key, needle }) => {
|
|
189
|
-
if (!needle) return;
|
|
190
|
-
const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
191
|
-
const regex = new RegExp(escaped, 'g');
|
|
192
|
-
result = result.replace(regex, `{{${key}}}`);
|
|
193
|
-
});
|
|
194
|
-
return result;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export function hasRcsVarTokens(s, rcsVarRegex) {
|
|
198
|
-
return (s?.match(rcsVarRegex) ?? []).length > 0;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Library / journey: only run `getUnmappedDesc` when the loaded copy has no `{{…}}` tokens
|
|
203
|
-
* (e.g. fully resolved campaign text). If the API already has numeric or named slots, keep as-is.
|
|
204
|
-
*/
|
|
205
|
-
export function normalizeLibraryLoadedTitleDesc({
|
|
206
|
-
loadedTitle,
|
|
207
|
-
loadedDesc,
|
|
208
|
-
isFullMode,
|
|
209
|
-
cardVarMappedAfterHydration,
|
|
210
|
-
rcsVarRegex,
|
|
211
|
-
}) {
|
|
212
|
-
const normalizedTitle =
|
|
213
|
-
!isFullMode
|
|
214
|
-
&& !isEmpty(cardVarMappedAfterHydration)
|
|
215
|
-
&& !hasRcsVarTokens(loadedTitle, rcsVarRegex)
|
|
216
|
-
? getUnmappedDesc(loadedTitle, cardVarMappedAfterHydration)
|
|
217
|
-
: loadedTitle;
|
|
218
|
-
const normalizedDesc =
|
|
219
|
-
!isFullMode
|
|
220
|
-
&& !isEmpty(cardVarMappedAfterHydration)
|
|
221
|
-
&& !hasRcsVarTokens(loadedDesc, rcsVarRegex)
|
|
222
|
-
? getUnmappedDesc(loadedDesc, cardVarMappedAfterHydration)
|
|
223
|
-
: loadedDesc;
|
|
224
|
-
return { normalizedTitle, normalizedDesc };
|
|
225
|
-
}
|
|
@@ -1,318 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared SMS FormBuilder formData helpers for Sms/Create (and any embedded host).
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @param {object} formData FormBuilder state (same shape as Sms/Create `this.state.formData`)
|
|
7
|
-
* @param {number} currentTab 1-based tab index
|
|
8
|
-
* @returns {string} Raw message body or ''
|
|
9
|
-
*/
|
|
10
|
-
export function getSmsMessageFromFormData(formData, currentTab) {
|
|
11
|
-
if (formData == null || typeof formData !== 'object') {
|
|
12
|
-
return '';
|
|
13
|
-
}
|
|
14
|
-
const tab = currentTab != null && currentTab > 0 ? currentTab : 1;
|
|
15
|
-
const currentTabData = formData[tab - 1];
|
|
16
|
-
if (currentTabData && typeof currentTabData === 'object') {
|
|
17
|
-
const versionedKey = tab > 1 ? `sms-editor${tab}` : 'sms-editor';
|
|
18
|
-
if (Object.prototype.hasOwnProperty.call(currentTabData, versionedKey)) {
|
|
19
|
-
const v = currentTabData[versionedKey];
|
|
20
|
-
// Key exists — commit to this version's value rather than falling through to base.
|
|
21
|
-
// Treat null/undefined as empty so a cleared version correctly reports as empty.
|
|
22
|
-
return (v != null && v !== '') ? String(v) : '';
|
|
23
|
-
}
|
|
24
|
-
if (currentTabData['sms-editor'] != null) {
|
|
25
|
-
return String(currentTabData['sms-editor']);
|
|
26
|
-
}
|
|
27
|
-
const activeTab = currentTabData.activeTab || 'base';
|
|
28
|
-
if (currentTabData[activeTab]?.['sms-editor'] != null) {
|
|
29
|
-
return String(currentTabData[activeTab]['sms-editor']);
|
|
30
|
-
}
|
|
31
|
-
if (currentTabData.base?.['sms-editor'] != null) {
|
|
32
|
-
return String(currentTabData.base['sms-editor']);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
const rootBase = formData.base;
|
|
36
|
-
if (rootBase && typeof rootBase === 'object' && rootBase['sms-editor'] != null) {
|
|
37
|
-
return String(rootBase['sms-editor']);
|
|
38
|
-
}
|
|
39
|
-
return '';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @param {number} [tabCount] Total number of versions/tabs. When >1 all versions are checked.
|
|
44
|
-
* @returns {{ isTemplateNameEmpty: boolean, isMessageEmpty: boolean }}
|
|
45
|
-
*/
|
|
46
|
-
export function getSmsEmbeddedFooterValidity(formData, tabCount) {
|
|
47
|
-
const rawName = formData?.['template-name'];
|
|
48
|
-
const name = rawName != null && rawName !== '' ? String(rawName).trim() : '';
|
|
49
|
-
|
|
50
|
-
// Check ALL versions: if any version's message is empty, Done should be disabled.
|
|
51
|
-
// With a single version this is equivalent to the previous single-tab check.
|
|
52
|
-
const count = tabCount != null && tabCount > 1 ? tabCount : 1;
|
|
53
|
-
let isMessageEmpty = false;
|
|
54
|
-
for (let i = 1; i <= count; i++) {
|
|
55
|
-
const content = getSmsMessageFromFormData(formData, i);
|
|
56
|
-
const msg = content != null && content !== '' ? String(content).trim() : '';
|
|
57
|
-
if (!msg) {
|
|
58
|
-
isMessageEmpty = true;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return {
|
|
64
|
-
isTemplateNameEmpty: !name,
|
|
65
|
-
isMessageEmpty,
|
|
66
|
-
};
|
|
67
|
-
}
|