@capillarytech/creatives-library 8.0.316 → 8.0.317-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/constants/unified.js +15 -0
- package/package.json +1 -1
- package/services/api.js +6 -0
- package/services/tests/api.test.js +7 -0
- package/utils/common.js +6 -1
- package/utils/templateVarUtils.js +172 -0
- package/utils/tests/tagValidations.test.js +34 -0
- package/utils/tests/templateVarUtils.test.js +160 -0
- package/v2Components/CapTagList/index.js +25 -22
- package/v2Components/CapTagList/style.scss +48 -0
- package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
- package/v2Components/CapTagListWithInput/index.js +4 -0
- package/v2Components/CapWhatsappCTA/index.js +2 -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 +14 -1
- package/v2Components/HtmlEditor/HTMLEditor.js +6 -1
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +927 -2
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +3 -0
- 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/BeeEditor/index.js +3 -0
- package/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
- package/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
- package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
- package/v2Containers/CommunicationFlow/constants.js +200 -0
- package/v2Containers/CommunicationFlow/index.js +102 -0
- package/v2Containers/CommunicationFlow/messages.js +346 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
- package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
- package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
- package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
- package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
- package/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
- package/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
- package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +64 -5
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
- package/v2Containers/CreativesContainer/constants.js +12 -0
- package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
- package/v2Containers/CreativesContainer/index.js +289 -93
- 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/Email/index.js +1 -0
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +7 -1
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -2
- package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +3 -0
- package/v2Containers/EmailWrapper/index.js +4 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +9 -0
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +19 -0
- package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +3 -0
- package/v2Containers/InAppWrapper/index.js +3 -0
- package/v2Containers/MobilePush/Create/index.js +2 -0
- package/v2Containers/MobilePush/Edit/index.js +2 -0
- package/v2Containers/MobilepushWrapper/index.js +3 -1
- package/v2Containers/Rcs/constants.js +32 -1
- package/v2Containers/Rcs/index.js +951 -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 +83 -36
- package/v2Containers/Sms/Edit/index.js +2 -0
- 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 +611 -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 +39 -8
- package/v2Containers/TagList/index.js +47 -2
- package/v2Containers/TagList/messages.js +4 -0
- package/v2Containers/TagList/tests/TagList.test.js +122 -20
- package/v2Containers/TagList/tests/mockdata.js +17 -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/Viber/index.js +5 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -2
- package/v2Containers/WebPush/Create/index.js +9 -1
- package/v2Containers/Whatsapp/index.js +8 -20
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +598 -34
- package/v2Containers/Zalo/index.js +2 -0
|
@@ -24,6 +24,20 @@ export default defineMessages({
|
|
|
24
24
|
id: `${scope}.personalizationTags`,
|
|
25
25
|
defaultMessage: 'Personalization Tags',
|
|
26
26
|
},
|
|
27
|
+
rcsTagsSectionTitle: {
|
|
28
|
+
id: `${scope}.rcsTagsSectionTitle`,
|
|
29
|
+
defaultMessage: 'RCS Tags',
|
|
30
|
+
},
|
|
31
|
+
/** Primary SMS section when channel is SMS — `messages[`${channel}TagsSectionTitle`]` resolves to this. */
|
|
32
|
+
SMSTagsSectionTitle: {
|
|
33
|
+
id: `${scope}.SMSTagsSectionTitle`,
|
|
34
|
+
defaultMessage: 'SMS tags',
|
|
35
|
+
},
|
|
36
|
+
/** Fallback SMS under RCS — same label as SMS Test & Preview for consistent personalization UX. */
|
|
37
|
+
smsFallbackTagsSectionTitle: {
|
|
38
|
+
id: `${scope}.smsFallbackTagsSectionTitle`,
|
|
39
|
+
defaultMessage: 'SMS tags',
|
|
40
|
+
},
|
|
27
41
|
customValues: {
|
|
28
42
|
id: `${scope}.customValues`,
|
|
29
43
|
defaultMessage: 'Custom Values',
|
|
@@ -36,6 +50,14 @@ export default defineMessages({
|
|
|
36
50
|
id: `${scope}.discardCustomValues`,
|
|
37
51
|
defaultMessage: 'Discard custom values',
|
|
38
52
|
},
|
|
53
|
+
rcsSenderIdLabel: {
|
|
54
|
+
id: `${scope}.rcsSenderIdLabel`,
|
|
55
|
+
defaultMessage: 'RCS Sender ID',
|
|
56
|
+
},
|
|
57
|
+
fallbackSmsSenderIdLabel: {
|
|
58
|
+
id: `${scope}.fallbackSmsSenderIdLabel`,
|
|
59
|
+
defaultMessage: 'Fallback SMS Sender ID',
|
|
60
|
+
},
|
|
39
61
|
updatePreview: {
|
|
40
62
|
id: `${scope}.updatePreview`,
|
|
41
63
|
defaultMessage: 'Update Preview',
|
|
@@ -92,6 +114,14 @@ export default defineMessages({
|
|
|
92
114
|
id: `${scope}.previewTitle`,
|
|
93
115
|
defaultMessage: 'Preview',
|
|
94
116
|
},
|
|
117
|
+
rcsTab: {
|
|
118
|
+
id: `${scope}.rcsTab`,
|
|
119
|
+
defaultMessage: 'RCS',
|
|
120
|
+
},
|
|
121
|
+
smsFallbackTab: {
|
|
122
|
+
id: `${scope}.smsFallbackTab`,
|
|
123
|
+
defaultMessage: 'Fallback SMS',
|
|
124
|
+
},
|
|
95
125
|
previewPlaceholder: {
|
|
96
126
|
id: `${scope}.previewPlaceholder`,
|
|
97
127
|
defaultMessage: 'Click "Update Preview" to see the rendered email.',
|
|
@@ -180,14 +210,22 @@ export default defineMessages({
|
|
|
180
210
|
id: `${scope}.lastModified`,
|
|
181
211
|
defaultMessage: 'Last modified',
|
|
182
212
|
},
|
|
183
|
-
|
|
184
|
-
id: `${scope}.
|
|
185
|
-
defaultMessage: 'by',
|
|
213
|
+
byAuthor: {
|
|
214
|
+
id: `${scope}.byAuthor`,
|
|
215
|
+
defaultMessage: 'by {name}',
|
|
186
216
|
},
|
|
187
217
|
senderId: {
|
|
188
218
|
id: `${scope}.senderId`,
|
|
189
219
|
defaultMessage: 'Sender ID',
|
|
190
220
|
},
|
|
221
|
+
rcsSenderIdLabel: {
|
|
222
|
+
id: `${scope}.rcsSenderIdLabel`,
|
|
223
|
+
defaultMessage: 'RCS sender ID',
|
|
224
|
+
},
|
|
225
|
+
domainLabel: {
|
|
226
|
+
id: `${scope}.domainLabel`,
|
|
227
|
+
defaultMessage: 'Domain',
|
|
228
|
+
},
|
|
191
229
|
urlPreviewImage: {
|
|
192
230
|
id: `${scope}.urlPreviewImage`,
|
|
193
231
|
defaultMessage: 'URL Preview Image',
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers for Liquid `/preview` (updateEmailPreview) responses.
|
|
3
|
+
* The API may nest the payload under `data` or return fields at the top level.
|
|
4
|
+
* SMS responses often use `messageBody` instead of `resolvedBody`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { getFallbackResolvedContent } from '../../utils/templateVarUtils';
|
|
8
|
+
import { RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from './constants';
|
|
9
|
+
|
|
10
|
+
export function normalizePreviewApiPayload(raw) {
|
|
11
|
+
if (!raw || typeof raw !== 'object') return raw;
|
|
12
|
+
const next = { ...raw };
|
|
13
|
+
if (next.resolvedBody == null) {
|
|
14
|
+
const alt = next.messageBody ?? next.previewMessage ?? next.previewText;
|
|
15
|
+
if (alt != null) next.resolvedBody = alt;
|
|
16
|
+
}
|
|
17
|
+
return next;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns a normalized preview object suitable for Redux `previewData`, or null on error / empty.
|
|
22
|
+
*/
|
|
23
|
+
export function extractPreviewFromLiquidResponse(response) {
|
|
24
|
+
if (!response || response.error) return null;
|
|
25
|
+
if (response.errors && Array.isArray(response.errors) && response.errors.length > 0) return null;
|
|
26
|
+
|
|
27
|
+
let body;
|
|
28
|
+
if (response.data !== undefined && response.data !== null && typeof response.data === 'object') {
|
|
29
|
+
body = normalizePreviewApiPayload(response.data);
|
|
30
|
+
} else if (typeof response === 'object' && !Array.isArray(response)) {
|
|
31
|
+
body = normalizePreviewApiPayload(response);
|
|
32
|
+
} else {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!body) return null;
|
|
37
|
+
if (body.resolvedBody === undefined && body.messageBody === undefined) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return body;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* RCS SMS fallback: merge template (`templateContent` / `content`) with VarSegment
|
|
45
|
+
* `rcsSmsFallbackVarMapped` before Liquid /preview, tag extraction, or createMessageMeta.
|
|
46
|
+
* Raw template alone is stale when the user edits slot values.
|
|
47
|
+
*/
|
|
48
|
+
export function getSmsFallbackTextForTagExtraction(smsFallbackContext) {
|
|
49
|
+
const rawTemplateBody =
|
|
50
|
+
smsFallbackContext?.templateContent ?? smsFallbackContext?.content ?? '';
|
|
51
|
+
if (!rawTemplateBody) return '';
|
|
52
|
+
const rcsSmsFallbackVarMapped = smsFallbackContext?.[RCS_SMS_FALLBACK_VAR_MAPPED_PROP];
|
|
53
|
+
const hasRcsSmsFallbackVarMappedEntries =
|
|
54
|
+
Object.keys(rcsSmsFallbackVarMapped ?? {}).length > 0;
|
|
55
|
+
if (hasRcsSmsFallbackVarMappedEntries) {
|
|
56
|
+
return getFallbackResolvedContent(rawTemplateBody, rcsSmsFallbackVarMapped ?? {});
|
|
57
|
+
}
|
|
58
|
+
return rawTemplateBody;
|
|
59
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
call, put, takeLatest, all,
|
|
6
|
+
call, put, takeLatest, takeEvery, all,
|
|
7
7
|
} from 'redux-saga/effects';
|
|
8
8
|
import get from 'lodash/get';
|
|
9
9
|
import isEmpty from 'lodash/isEmpty';
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
CHANNELS,
|
|
46
46
|
} from './constants';
|
|
47
47
|
import { parseSenderDetailsResponse } from './DeliverySettings/utils/parseSenderDetailsResponse';
|
|
48
|
+
import { extractPreviewFromLiquidResponse } from './previewApiUtils';
|
|
48
49
|
|
|
49
50
|
// Search Customers Saga
|
|
50
51
|
export function* searchCustomersSaga(action) {
|
|
@@ -91,11 +92,12 @@ export function* updatePreviewSaga(action) {
|
|
|
91
92
|
const customValues = action.payload.resolvedTags;
|
|
92
93
|
|
|
93
94
|
const response = yield call(Api.updateEmailPreview, action.payload);
|
|
94
|
-
|
|
95
|
+
const previewPayload = extractPreviewFromLiquidResponse(response);
|
|
96
|
+
if (previewPayload) {
|
|
95
97
|
yield put({
|
|
96
98
|
type: UPDATE_PREVIEW_SUCCESS,
|
|
97
99
|
payload: {
|
|
98
|
-
previewData:
|
|
100
|
+
previewData: previewPayload,
|
|
99
101
|
customValues, // Pass custom values to be preserved
|
|
100
102
|
},
|
|
101
103
|
});
|
|
@@ -236,8 +238,13 @@ export function* createMessageMetaSaga(action) {
|
|
|
236
238
|
export function* getPrefilledValuesSaga(action) {
|
|
237
239
|
try {
|
|
238
240
|
const response = yield call(Api.updateEmailPreview, action.payload);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
+
const body =
|
|
242
|
+
response?.data !== undefined && response?.data !== null
|
|
243
|
+
? response.data
|
|
244
|
+
: response;
|
|
245
|
+
const resolvedTagValues = body?.resolvedTagValues;
|
|
246
|
+
if (resolvedTagValues != null) {
|
|
247
|
+
yield put({ type: GET_PREFILLED_VALUES_SUCCESS, payload: { values: resolvedTagValues } });
|
|
241
248
|
} else {
|
|
242
249
|
// Pass all errors from API response to state
|
|
243
250
|
yield put({
|
|
@@ -337,7 +344,9 @@ export function* getWeCrmAccountsSaga(action) {
|
|
|
337
344
|
}
|
|
338
345
|
|
|
339
346
|
export function* watchGetSenderDetails() {
|
|
340
|
-
|
|
347
|
+
// takeEvery: RCS test & preview dispatches RCS then SMS back-to-back; takeLatest would
|
|
348
|
+
// cancel the RCS fetch so senderDetailsByChannel.RCS stayed empty while SMS loaded.
|
|
349
|
+
yield takeEvery(GET_SENDER_DETAILS_REQUESTED, getSenderDetailsSaga);
|
|
341
350
|
}
|
|
342
351
|
|
|
343
352
|
export function* watchGetWeCrmAccounts() {
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jest-environment jsdom
|
|
3
|
+
*/
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
6
|
+
import '@testing-library/jest-dom';
|
|
7
|
+
import { IntlProvider } from 'react-intl';
|
|
8
|
+
import CustomValuesEditor from '../CustomValuesEditor';
|
|
9
|
+
|
|
10
|
+
jest.mock('@capillarytech/cap-ui-library/CapRow', () => {
|
|
11
|
+
const React = require('react');
|
|
12
|
+
return function CapRow(props) {
|
|
13
|
+
return React.createElement('div', { className: props?.className, 'data-testid': 'cap-row' }, props?.children);
|
|
14
|
+
};
|
|
15
|
+
});
|
|
16
|
+
jest.mock('@capillarytech/cap-ui-library/CapSpin', () => {
|
|
17
|
+
const React = require('react');
|
|
18
|
+
return function CapSpin() {
|
|
19
|
+
return React.createElement('div', { 'data-testid': 'cap-spin' }, 'spin');
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
jest.mock('@capillarytech/cap-ui-library/CapSwitch', () => {
|
|
23
|
+
const React = require('react');
|
|
24
|
+
return function CapSwitch(props) {
|
|
25
|
+
return React.createElement('input', {
|
|
26
|
+
type: 'checkbox',
|
|
27
|
+
'data-testid': 'json-switch',
|
|
28
|
+
checked: props?.checked,
|
|
29
|
+
onChange: (e) => props?.onChange?.(e.target.checked),
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
jest.mock('@capillarytech/cap-ui-library/CapButton', () => {
|
|
34
|
+
const React = require('react');
|
|
35
|
+
return function CapButton(props) {
|
|
36
|
+
return React.createElement('button', {
|
|
37
|
+
type: 'button',
|
|
38
|
+
'data-testid': 'cap-button',
|
|
39
|
+
onClick: props?.onClick,
|
|
40
|
+
disabled: props?.disabled,
|
|
41
|
+
'data-loading': props?.loading,
|
|
42
|
+
}, props?.children);
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
jest.mock('@capillarytech/cap-ui-library/CapInput', () => {
|
|
46
|
+
const React = require('react');
|
|
47
|
+
return function CapInput(props) {
|
|
48
|
+
return React.createElement('input', {
|
|
49
|
+
'data-testid': 'tag-input',
|
|
50
|
+
value: props?.value,
|
|
51
|
+
placeholder: props?.placeholder,
|
|
52
|
+
onChange: props?.onChange,
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
jest.mock('@capillarytech/cap-ui-library/CapLabel', () => {
|
|
57
|
+
const React = require('react');
|
|
58
|
+
return function CapLabel(props) {
|
|
59
|
+
return React.createElement('div', { className: props?.className, 'data-testid': 'cap-label' }, props?.children);
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const formatMessage = (msg) => (typeof msg === 'object' && msg?.id ? msg.id : String(msg));
|
|
64
|
+
|
|
65
|
+
const baseProps = {
|
|
66
|
+
isExtractingTags: false,
|
|
67
|
+
isUpdatePreviewDisabled: false,
|
|
68
|
+
showJSON: false,
|
|
69
|
+
setShowJSON: jest.fn(),
|
|
70
|
+
customValues: { 'tag.a': 'v1' },
|
|
71
|
+
handleJSONTextChange: jest.fn(),
|
|
72
|
+
sections: [
|
|
73
|
+
{
|
|
74
|
+
key: 'sec1',
|
|
75
|
+
title: 'Section A',
|
|
76
|
+
requiredTags: [{ fullPath: 'tag.a', name: 'a' }],
|
|
77
|
+
optionalTags: [{ fullPath: 'tag.b', name: 'b' }],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
handleCustomValueChange: jest.fn(),
|
|
81
|
+
handleDiscardCustomValues: jest.fn(),
|
|
82
|
+
handleUpdatePreview: jest.fn(),
|
|
83
|
+
isUpdatingPreview: false,
|
|
84
|
+
formatMessage,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
describe('CustomValuesEditor', () => {
|
|
88
|
+
beforeEach(() => {
|
|
89
|
+
jest.clearAllMocks();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('shows loading state when isExtractingTags', () => {
|
|
93
|
+
render(
|
|
94
|
+
<IntlProvider locale="en" messages={{}}>
|
|
95
|
+
<CustomValuesEditor {...baseProps} isExtractingTags />
|
|
96
|
+
</IntlProvider>,
|
|
97
|
+
);
|
|
98
|
+
expect(screen.getByTestId('cap-spin')).toBeInTheDocument();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('shows values missing label when isUpdatePreviewDisabled', () => {
|
|
102
|
+
render(
|
|
103
|
+
<IntlProvider locale="en" messages={{}}>
|
|
104
|
+
<CustomValuesEditor {...baseProps} isUpdatePreviewDisabled />
|
|
105
|
+
</IntlProvider>,
|
|
106
|
+
);
|
|
107
|
+
expect(document.querySelector('.values-missing-message')).toBeTruthy();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('toggles JSON mode and calls setShowJSON', () => {
|
|
111
|
+
const setShowJSON = jest.fn();
|
|
112
|
+
render(
|
|
113
|
+
<IntlProvider locale="en" messages={{}}>
|
|
114
|
+
<CustomValuesEditor {...baseProps} setShowJSON={setShowJSON} />
|
|
115
|
+
</IntlProvider>,
|
|
116
|
+
);
|
|
117
|
+
fireEvent.click(screen.getByTestId('json-switch'));
|
|
118
|
+
expect(setShowJSON).toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('renders JSON textarea and fires handleJSONTextChange', () => {
|
|
122
|
+
render(
|
|
123
|
+
<IntlProvider locale="en" messages={{}}>
|
|
124
|
+
<CustomValuesEditor {...baseProps} showJSON />
|
|
125
|
+
</IntlProvider>,
|
|
126
|
+
);
|
|
127
|
+
const ta = document.querySelector('.json-textarea');
|
|
128
|
+
expect(ta).toBeTruthy();
|
|
129
|
+
fireEvent.change(ta, { target: { value: '{}' } });
|
|
130
|
+
expect(baseProps.handleJSONTextChange).toHaveBeenCalledWith('{}');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('renders required and optional tag inputs when not JSON', () => {
|
|
134
|
+
render(
|
|
135
|
+
<IntlProvider locale="en" messages={{}}>
|
|
136
|
+
<CustomValuesEditor {...baseProps} />
|
|
137
|
+
</IntlProvider>,
|
|
138
|
+
);
|
|
139
|
+
const inputs = screen.getAllByTestId('tag-input');
|
|
140
|
+
expect(inputs.length).toBeGreaterThan(0);
|
|
141
|
+
fireEvent.change(inputs[0], { target: { value: 'new' } });
|
|
142
|
+
expect(baseProps.handleCustomValueChange).toHaveBeenCalledWith('tag.a', 'new');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('calls discard and update handlers', () => {
|
|
146
|
+
render(
|
|
147
|
+
<IntlProvider locale="en" messages={{}}>
|
|
148
|
+
<CustomValuesEditor {...baseProps} />
|
|
149
|
+
</IntlProvider>,
|
|
150
|
+
);
|
|
151
|
+
const buttons = screen.getAllByTestId('cap-button');
|
|
152
|
+
fireEvent.click(buttons[0]);
|
|
153
|
+
fireEvent.click(buttons[buttons.length - 1]);
|
|
154
|
+
expect(baseProps.handleDiscardCustomValues).toHaveBeenCalled();
|
|
155
|
+
expect(baseProps.handleUpdatePreview).toHaveBeenCalled();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('skips sections with no tags', () => {
|
|
159
|
+
render(
|
|
160
|
+
<IntlProvider locale="en" messages={{}}>
|
|
161
|
+
<CustomValuesEditor
|
|
162
|
+
{...baseProps}
|
|
163
|
+
sections={[
|
|
164
|
+
{ key: 'empty', requiredTags: [], optionalTags: [] },
|
|
165
|
+
baseProps.sections[0],
|
|
166
|
+
]}
|
|
167
|
+
/>
|
|
168
|
+
</IntlProvider>,
|
|
169
|
+
);
|
|
170
|
+
expect(screen.getAllByTestId('tag-input').length).toBeGreaterThan(0);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('renders section title as FormattedMessage when title is an intl message object (not a string)', () => {
|
|
174
|
+
render(
|
|
175
|
+
<IntlProvider locale="en" messages={{ 'section.title.id': 'Intl Section Title' }}>
|
|
176
|
+
<CustomValuesEditor
|
|
177
|
+
{...baseProps}
|
|
178
|
+
sections={[
|
|
179
|
+
{
|
|
180
|
+
key: 'intl-sec',
|
|
181
|
+
title: { id: 'section.title.id', defaultMessage: 'Intl Section Title' },
|
|
182
|
+
requiredTags: [{ fullPath: 'tag.a', name: 'a' }],
|
|
183
|
+
optionalTags: [],
|
|
184
|
+
},
|
|
185
|
+
]}
|
|
186
|
+
/>
|
|
187
|
+
</IntlProvider>,
|
|
188
|
+
);
|
|
189
|
+
expect(screen.getByText('Intl Section Title')).toBeInTheDocument();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('renders section title as plain text when title is a string', () => {
|
|
193
|
+
render(
|
|
194
|
+
<IntlProvider locale="en" messages={{}}>
|
|
195
|
+
<CustomValuesEditor
|
|
196
|
+
{...baseProps}
|
|
197
|
+
sections={[
|
|
198
|
+
{
|
|
199
|
+
key: 'str-sec',
|
|
200
|
+
title: 'Plain String Title',
|
|
201
|
+
requiredTags: [{ fullPath: 'tag.a', name: 'a' }],
|
|
202
|
+
optionalTags: [],
|
|
203
|
+
},
|
|
204
|
+
]}
|
|
205
|
+
/>
|
|
206
|
+
</IntlProvider>,
|
|
207
|
+
);
|
|
208
|
+
expect(screen.getByText('Plain String Title')).toBeInTheDocument();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('renders nothing for section title when title is null (does not crash)', () => {
|
|
212
|
+
render(
|
|
213
|
+
<IntlProvider locale="en" messages={{}}>
|
|
214
|
+
<CustomValuesEditor
|
|
215
|
+
{...baseProps}
|
|
216
|
+
sections={[
|
|
217
|
+
{
|
|
218
|
+
key: 'no-title-sec',
|
|
219
|
+
title: null,
|
|
220
|
+
requiredTags: [{ fullPath: 'tag.a', name: 'a' }],
|
|
221
|
+
optionalTags: [],
|
|
222
|
+
},
|
|
223
|
+
]}
|
|
224
|
+
/>
|
|
225
|
+
</IntlProvider>,
|
|
226
|
+
);
|
|
227
|
+
const inputs = screen.getAllByTestId('tag-input');
|
|
228
|
+
expect(inputs.length).toBeGreaterThan(0);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('uses tag.name as column label when fullPath is absent', () => {
|
|
232
|
+
render(
|
|
233
|
+
<IntlProvider locale="en" messages={{}}>
|
|
234
|
+
<CustomValuesEditor
|
|
235
|
+
{...baseProps}
|
|
236
|
+
sections={[
|
|
237
|
+
{
|
|
238
|
+
key: 'name-only',
|
|
239
|
+
title: 'Section',
|
|
240
|
+
requiredTags: [{ name: 'myTag' }],
|
|
241
|
+
optionalTags: [],
|
|
242
|
+
},
|
|
243
|
+
]}
|
|
244
|
+
/>
|
|
245
|
+
</IntlProvider>,
|
|
246
|
+
);
|
|
247
|
+
expect(screen.getByText('myTag')).toBeInTheDocument();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('uses empty string as column label when both fullPath and name are absent', () => {
|
|
251
|
+
render(
|
|
252
|
+
<IntlProvider locale="en" messages={{}}>
|
|
253
|
+
<CustomValuesEditor
|
|
254
|
+
{...baseProps}
|
|
255
|
+
sections={[
|
|
256
|
+
{
|
|
257
|
+
key: 'no-label',
|
|
258
|
+
title: 'Section',
|
|
259
|
+
requiredTags: [{}],
|
|
260
|
+
optionalTags: [],
|
|
261
|
+
},
|
|
262
|
+
]}
|
|
263
|
+
/>
|
|
264
|
+
</IntlProvider>,
|
|
265
|
+
);
|
|
266
|
+
const inputs = screen.getAllByTestId('tag-input');
|
|
267
|
+
expect(inputs.length).toBeGreaterThan(0);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('renders optional-only section (no requiredTags) correctly', () => {
|
|
271
|
+
render(
|
|
272
|
+
<IntlProvider locale="en" messages={{}}>
|
|
273
|
+
<CustomValuesEditor
|
|
274
|
+
{...baseProps}
|
|
275
|
+
sections={[
|
|
276
|
+
{
|
|
277
|
+
key: 'optional-only',
|
|
278
|
+
title: 'Optional Section',
|
|
279
|
+
requiredTags: [],
|
|
280
|
+
optionalTags: [{ fullPath: 'tag.opt', name: 'opt' }],
|
|
281
|
+
},
|
|
282
|
+
]}
|
|
283
|
+
/>
|
|
284
|
+
</IntlProvider>,
|
|
285
|
+
);
|
|
286
|
+
expect(screen.getByText('Optional Section')).toBeInTheDocument();
|
|
287
|
+
const inputs = screen.getAllByTestId('tag-input');
|
|
288
|
+
expect(inputs.length).toBeGreaterThan(0);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('renders required-only section (no optionalTags) correctly', () => {
|
|
292
|
+
render(
|
|
293
|
+
<IntlProvider locale="en" messages={{}}>
|
|
294
|
+
<CustomValuesEditor
|
|
295
|
+
{...baseProps}
|
|
296
|
+
sections={[
|
|
297
|
+
{
|
|
298
|
+
key: 'required-only',
|
|
299
|
+
title: 'Required Section',
|
|
300
|
+
requiredTags: [{ fullPath: 'tag.req', name: 'req' }],
|
|
301
|
+
optionalTags: [],
|
|
302
|
+
},
|
|
303
|
+
]}
|
|
304
|
+
/>
|
|
305
|
+
</IntlProvider>,
|
|
306
|
+
);
|
|
307
|
+
expect(screen.getByText('Required Section')).toBeInTheDocument();
|
|
308
|
+
const inputs = screen.getAllByTestId('tag-input');
|
|
309
|
+
expect(inputs.length).toBeGreaterThan(0);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('renders line numbers in JSON mode matching JSON line count', () => {
|
|
313
|
+
const multiValueCustomValues = { a: 'val1', b: 'val2', c: 'val3' };
|
|
314
|
+
render(
|
|
315
|
+
<IntlProvider locale="en" messages={{}}>
|
|
316
|
+
<CustomValuesEditor
|
|
317
|
+
{...baseProps}
|
|
318
|
+
showJSON
|
|
319
|
+
customValues={multiValueCustomValues}
|
|
320
|
+
/>
|
|
321
|
+
</IntlProvider>,
|
|
322
|
+
);
|
|
323
|
+
const lineNumbers = document.querySelectorAll('.line-number');
|
|
324
|
+
const expectedLineCount = JSON.stringify(multiValueCustomValues, null, 2).split('\n').length;
|
|
325
|
+
expect(lineNumbers.length).toBe(expectedLineCount);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
it('passes fullPath as key for required tag row when fullPath is present', () => {
|
|
329
|
+
const handleChange = jest.fn();
|
|
330
|
+
render(
|
|
331
|
+
<IntlProvider locale="en" messages={{}}>
|
|
332
|
+
<CustomValuesEditor
|
|
333
|
+
{...baseProps}
|
|
334
|
+
sections={[
|
|
335
|
+
{
|
|
336
|
+
key: 'sec',
|
|
337
|
+
title: 'S',
|
|
338
|
+
requiredTags: [{ fullPath: 'my.full.path', name: 'name' }],
|
|
339
|
+
optionalTags: [],
|
|
340
|
+
},
|
|
341
|
+
]}
|
|
342
|
+
handleCustomValueChange={handleChange}
|
|
343
|
+
customValues={{ 'my.full.path': 'existing' }}
|
|
344
|
+
/>
|
|
345
|
+
</IntlProvider>,
|
|
346
|
+
);
|
|
347
|
+
const input = screen.getByTestId('tag-input');
|
|
348
|
+
expect(input.value).toBe('existing');
|
|
349
|
+
fireEvent.change(input, { target: { value: 'updated' } });
|
|
350
|
+
expect(handleChange).toHaveBeenCalledWith('my.full.path', 'updated');
|
|
351
|
+
});
|
|
352
|
+
});
|