@capillarytech/creatives-library 8.0.316-alpha.4 → 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 +1 -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/tests/tagValidations.test.js +34 -0
- package/v2Components/CapTagList/index.js +15 -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/tests/CustomValuesEditor.test.js +180 -0
- package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +96 -0
- package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +99 -0
- package/v2Components/CommonTestAndPreview/tests/index.test.js +113 -3
- package/v2Components/FormBuilder/index.js +7 -0
- 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/tests/useLocalTemplateList.test.js +95 -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/SlideBoxContent.js +28 -1
- package/v2Containers/CreativesContainer/constants.js +3 -0
- package/v2Containers/CreativesContainer/index.js +3 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
- package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -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/index.js +1 -0
- package/v2Containers/Sms/Create/index.js +2 -0
- package/v2Containers/Sms/Edit/index.js +2 -0
- package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/v2Containers/SmsTrai/Edit/index.js +2 -0
- package/v2Containers/SmsWrapper/index.js +2 -0
- package/v2Containers/TagList/index.js +41 -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/tests/sagas.test.js +83 -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 +5 -0
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +20 -0
- package/v2Containers/Zalo/index.js +2 -0
package/constants/unified.js
CHANGED
|
@@ -60,6 +60,7 @@ export const AI_CONTENT_BOT_DISABLED = 'AI_CONTENT_BOT_DISABLED';
|
|
|
60
60
|
export const AI_DOCUMENTATION_BOT_ENABLED = 'AI_DOCUMENTATION_BOT_ENABLED';
|
|
61
61
|
export const ENABLE_NEW_LEFT_NAVIGATION = 'ENABLE_NEW_LEFT_NAVIGATION';
|
|
62
62
|
export const ENABLE_PRODUCT_SUPPORT_VIDEOS = 'ENABLE_PRODUCT_SUPPORT_VIDEOS';
|
|
63
|
+
export const SUPPORT_ENGAGEMENT_MODULE = 'SUPPORT_ENGAGEMENT_MODULE';
|
|
63
64
|
export const EMBEDDED = 'embedded';
|
|
64
65
|
// --- Tag/Validation Constants ---
|
|
65
66
|
export const CARD_RELATED_TAGS = [
|
package/package.json
CHANGED
package/services/api.js
CHANGED
|
@@ -334,6 +334,12 @@ export const getTemplateDetails = async ({id, channel}) => {
|
|
|
334
334
|
return { ...compressedTemplatesData, response: decompressData};
|
|
335
335
|
};
|
|
336
336
|
|
|
337
|
+
export const getDomainProperties = (channels, orgUnitId) => {
|
|
338
|
+
const queryString = channels?.map((channel) => `channel=${channel?.toUpperCase()}`)?.join('&');
|
|
339
|
+
const url = `${CAMPAIGNS_API_ORG_ENDPOINT}/meta/domainProperties?${queryString}`;
|
|
340
|
+
return request(url, getAPICallObject('GET', null, false, true, orgUnitId));
|
|
341
|
+
};
|
|
342
|
+
|
|
337
343
|
export const getMediaDetails = async ({ id }) => {
|
|
338
344
|
const url = `${API_ENDPOINT}/media/${id}`;
|
|
339
345
|
return request(url, getAPICallObject('GET'));
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getSenderDetails,
|
|
3
|
+
getDomainProperties,
|
|
3
4
|
uploadFile,
|
|
4
5
|
getCdnTransformationConfig,
|
|
5
6
|
createWhatsappTemplate,
|
|
@@ -52,6 +53,12 @@ describe('getSenderDetails -- Test with valid responses', () => {
|
|
|
52
53
|
expect(getSenderDetails('WHATSAPP', -1)).toEqual(Promise.resolve()));
|
|
53
54
|
});
|
|
54
55
|
|
|
56
|
+
describe('getDomainProperties -- Test with valid responses', () => {
|
|
57
|
+
it('Should return correct response', () => {
|
|
58
|
+
expect(getDomainProperties(['SMS', 'EMAIL'], null)).toEqual(Promise.resolve());
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
55
62
|
describe('getCdnTransformationConfigs -- Test with valid responses', () => {
|
|
56
63
|
it('Should return correct response', () =>
|
|
57
64
|
expect(getCdnTransformationConfig()).toEqual(Promise.resolve()));
|
package/utils/common.js
CHANGED
|
@@ -25,7 +25,8 @@ import {
|
|
|
25
25
|
ENABLE_WEBPUSH,
|
|
26
26
|
SUPPORT_CK_EDITOR,
|
|
27
27
|
ENABLE_NEW_MPUSH,
|
|
28
|
-
ENABLE_NEW_EDITOR_FLOW_INAPP
|
|
28
|
+
ENABLE_NEW_EDITOR_FLOW_INAPP,
|
|
29
|
+
SUPPORT_ENGAGEMENT_MODULE,
|
|
29
30
|
} from '../constants/unified';
|
|
30
31
|
import { apiMessageFormatHandler } from './commonUtils';
|
|
31
32
|
|
|
@@ -96,6 +97,10 @@ export const hasSupportCKEditor = Auth.hasFeatureAccess.bind(
|
|
|
96
97
|
null,
|
|
97
98
|
SUPPORT_CK_EDITOR,
|
|
98
99
|
);
|
|
100
|
+
export const hasSupportEngagementModule = Auth.hasFeatureAccess.bind(
|
|
101
|
+
null,
|
|
102
|
+
SUPPORT_ENGAGEMENT_MODULE,
|
|
103
|
+
);
|
|
99
104
|
|
|
100
105
|
export const hasGiftVoucherFeature = Auth.hasFeatureAccess.bind(
|
|
101
106
|
null,
|
|
@@ -242,6 +242,7 @@ describe("validateTags", () => {
|
|
|
242
242
|
tagsParam,
|
|
243
243
|
location,
|
|
244
244
|
tagModule,
|
|
245
|
+
waitEventContextTags: {},
|
|
245
246
|
});
|
|
246
247
|
|
|
247
248
|
expect(result.valid).toEqual(true);
|
|
@@ -273,6 +274,7 @@ describe("validateTags", () => {
|
|
|
273
274
|
tagsParam: tagsParamLocal,
|
|
274
275
|
location,
|
|
275
276
|
tagModule,
|
|
277
|
+
waitEventContextTags: {},
|
|
276
278
|
});
|
|
277
279
|
|
|
278
280
|
expect(result.valid).toEqual(true);
|
|
@@ -310,6 +312,7 @@ describe("validateTags", () => {
|
|
|
310
312
|
tagsParam: tagsParamLocal,
|
|
311
313
|
location,
|
|
312
314
|
tagModule,
|
|
315
|
+
waitEventContextTags: {},
|
|
313
316
|
});
|
|
314
317
|
|
|
315
318
|
expect(result.valid).toEqual(false);
|
|
@@ -335,6 +338,7 @@ describe("validateTags", () => {
|
|
|
335
338
|
tagsParam: tagsParamUnsubscribe,
|
|
336
339
|
location,
|
|
337
340
|
tagModule,
|
|
341
|
+
waitEventContextTags: {},
|
|
338
342
|
});
|
|
339
343
|
expect(resultMissing.missingTags).toContain("unsubscribe");
|
|
340
344
|
expect(resultMissing.valid).toBe(false);
|
|
@@ -345,6 +349,7 @@ describe("validateTags", () => {
|
|
|
345
349
|
tagsParam: tagsParamUnsubscribe,
|
|
346
350
|
location,
|
|
347
351
|
tagModule,
|
|
352
|
+
waitEventContextTags: {},
|
|
348
353
|
});
|
|
349
354
|
expect(resultSkipped.missingTags).not.toContain("unsubscribe");
|
|
350
355
|
expect(resultSkipped.valid).toBe(true);
|
|
@@ -360,6 +365,35 @@ describe("validateTags", () => {
|
|
|
360
365
|
expect(resultWhitespace.valid).toBe(true);
|
|
361
366
|
expect(resultWhitespace.unsupportedTags ?? []).toEqual([]);
|
|
362
367
|
});
|
|
368
|
+
|
|
369
|
+
it('should treat tags from waitEventContextTags as supported', () => {
|
|
370
|
+
const content = 'Hello {{waitEvent.orderId}}';
|
|
371
|
+
const tagsParam = [];
|
|
372
|
+
const injectedTagsParams = [];
|
|
373
|
+
const location = { query: { module: 'DEFAULT' } };
|
|
374
|
+
const tagModule = null;
|
|
375
|
+
const waitEventContextTags = {
|
|
376
|
+
block1: {
|
|
377
|
+
eventName: 'Order Placed',
|
|
378
|
+
blockName: 'Wait Block',
|
|
379
|
+
tags: [{ tagName: 'waitEvent.orderId', label: 'Order ID' }],
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
const result = validateTags({
|
|
384
|
+
content,
|
|
385
|
+
tagsParam,
|
|
386
|
+
injectedTagsParams,
|
|
387
|
+
location,
|
|
388
|
+
tagModule,
|
|
389
|
+
eventContextTags: [],
|
|
390
|
+
waitEventContextTags,
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
expect(result.valid).toEqual(true);
|
|
394
|
+
expect(result.missingTags).toEqual([]);
|
|
395
|
+
expect(result.isBraceError).toEqual(false);
|
|
396
|
+
});
|
|
363
397
|
});
|
|
364
398
|
|
|
365
399
|
describe('validateTags wrapper (v2 consumers)', () => {
|
|
@@ -211,6 +211,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
211
211
|
} else if (info && info.selectedNodes && info.selectedNodes.length > 0 && !info.selectedNodes[0].props.isLeaf) {
|
|
212
212
|
this.handleOnExpand(selectedKeys[0]);
|
|
213
213
|
}
|
|
214
|
+
this.setState({expandedKeys: []})
|
|
214
215
|
}
|
|
215
216
|
};
|
|
216
217
|
|
|
@@ -233,6 +234,13 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
233
234
|
}
|
|
234
235
|
};
|
|
235
236
|
|
|
237
|
+
/** Single-line ellipsis within popover width; full label on hover via CapTooltip. */
|
|
238
|
+
wrapTreeTitle = (displayNode, text) => (
|
|
239
|
+
<CapTooltip title={displayNode}>
|
|
240
|
+
<CapLabel.CapLabelInline type="label15" className="cap-tag-list-tree-title-wrap">{text || displayNode}</CapLabel.CapLabelInline>
|
|
241
|
+
</CapTooltip>
|
|
242
|
+
);
|
|
243
|
+
|
|
236
244
|
renderDynamicTagFlow = () => {
|
|
237
245
|
this.setState({showModal: true, visible: false});
|
|
238
246
|
};
|
|
@@ -280,7 +288,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
280
288
|
if (temp?.length) {
|
|
281
289
|
const tagValue = (
|
|
282
290
|
<CapTreeNode
|
|
283
|
-
title={disabled ?
|
|
291
|
+
title={disabled ? this.wrapTreeTitle(loyaltyAttrDisableText, val?.name) : this.wrapTreeTitle(val?.name)}
|
|
284
292
|
tag={val}
|
|
285
293
|
key={val?.incentiveSeriesId ? `${key}(${val?.incentiveSeriesId})` : `${key}`}
|
|
286
294
|
disabled={disabled}
|
|
@@ -303,17 +311,9 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
303
311
|
<CapTreeNode
|
|
304
312
|
title={
|
|
305
313
|
childDisabled ? (
|
|
306
|
-
|
|
307
|
-
title={
|
|
308
|
-
key === CUSTOMER_BARCODE_TAG
|
|
309
|
-
? customerBarcodeDisableText
|
|
310
|
-
: loyaltyAttrDisableText
|
|
311
|
-
}
|
|
312
|
-
>
|
|
313
|
-
{val?.desc || val?.name}
|
|
314
|
-
</CapTooltip>
|
|
314
|
+
this.wrapTreeTitle(key === CUSTOMER_BARCODE_TAG ? customerBarcodeDisableText : loyaltyAttrDisableText, val?.desc || val?.name)
|
|
315
315
|
) : (
|
|
316
|
-
val?.desc || val?.name
|
|
316
|
+
this.wrapTreeTitle(val?.desc || val?.name)
|
|
317
317
|
)
|
|
318
318
|
}
|
|
319
319
|
tag={val}
|
|
@@ -339,17 +339,9 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
339
339
|
<CapTreeNode
|
|
340
340
|
title={
|
|
341
341
|
childDisabled ? (
|
|
342
|
-
|
|
343
|
-
title={
|
|
344
|
-
key === CUSTOMER_BARCODE_TAG
|
|
345
|
-
? customerBarcodeDisableText
|
|
346
|
-
: loyaltyAttrDisableText
|
|
347
|
-
}
|
|
348
|
-
>
|
|
349
|
-
{val?.desc || val?.name}
|
|
350
|
-
</CapTooltip>
|
|
342
|
+
this.wrapTreeTitle(key === CUSTOMER_BARCODE_TAG ? customerBarcodeDisableText : loyaltyAttrDisableText, val?.desc || val?.name)
|
|
351
343
|
) : (
|
|
352
|
-
val?.desc || val?.name
|
|
344
|
+
this.wrapTreeTitle(val?.desc || val?.name)
|
|
353
345
|
)
|
|
354
346
|
}
|
|
355
347
|
tag={val}
|
|
@@ -407,7 +399,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
407
399
|
},
|
|
408
400
|
];
|
|
409
401
|
const contentSection = (
|
|
410
|
-
<CapRow>
|
|
402
|
+
<CapRow className="cap-tag-list-popover-inner">
|
|
411
403
|
<CapSpin tip={formatMessage(messages.gettingTags)} spinning={shouldShowLoading}>
|
|
412
404
|
<Search
|
|
413
405
|
style={{ marginBottom: 8, width: '250px'}}
|
|
@@ -475,6 +467,7 @@ class CapTagList extends React.Component { // eslint-disable-line react/prefer-s
|
|
|
475
467
|
visible={fetchingSchemaError ? false : visible}
|
|
476
468
|
onVisibleChange={this.togglePopoverVisibility}
|
|
477
469
|
content={contentSection}
|
|
470
|
+
overlayClassName="cap-tag-list-popover-overlay"
|
|
478
471
|
trigger="click"
|
|
479
472
|
placement={this.props.popoverPlacement || (channel === EMAIL.toUpperCase() ? "leftTop" : "rightTop")}
|
|
480
473
|
overlayStyle={overlayStyle}
|
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
@import "~@capillarytech/cap-ui-library/styles/_variables";
|
|
2
2
|
|
|
3
|
+
// Tag list popover: keep overlay width aligned with search (250px); tree rows ellipsis + tooltip for full text
|
|
4
|
+
.cap-tag-list-popover-overlay.ant-popover {
|
|
5
|
+
.ant-popover-inner-content {
|
|
6
|
+
max-width: 20rem;
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.cap-tag-list-popover-inner {
|
|
13
|
+
max-width: 20rem;
|
|
14
|
+
min-width: 0;
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
|
|
17
|
+
.ant-tree.cap-tree-v2.ant-tree-icon-hide {
|
|
18
|
+
width: 100%;
|
|
19
|
+
max-width: 100%;
|
|
20
|
+
|
|
21
|
+
ul {
|
|
22
|
+
max-width: 100%;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
li {
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
max-width: 100%;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
li .ant-tree-node-content-wrapper {
|
|
31
|
+
width: calc(100% - 3.5rem); // leave room for switcher (~24px)
|
|
32
|
+
max-width: calc(100% - 3.5rem);
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
vertical-align: top;
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.cap-tag-list-tree-title-wrap {
|
|
39
|
+
display: inline-block;
|
|
40
|
+
width: 100%;
|
|
41
|
+
overflow: hidden;
|
|
42
|
+
text-overflow: ellipsis;
|
|
43
|
+
white-space: nowrap;
|
|
44
|
+
max-width: 100%;
|
|
45
|
+
vertical-align: top;
|
|
46
|
+
margin-top: 0.5rem;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
3
51
|
@media (max-height: 25rem) {
|
|
4
52
|
.ant-tree.cap-tree-v2.ant-tree-icon-hide {
|
|
5
53
|
height: 8.5714rem;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom';
|
|
4
|
+
import { IntlProvider } from 'react-intl';
|
|
5
|
+
import CapTagListWithInput from '../index';
|
|
6
|
+
|
|
7
|
+
const capturedTagListProps = { current: null };
|
|
8
|
+
|
|
9
|
+
jest.mock('../../../v2Containers/TagList', () => {
|
|
10
|
+
const React = require('react');
|
|
11
|
+
const Mock = (props) => {
|
|
12
|
+
capturedTagListProps.current = props;
|
|
13
|
+
return <div data-testid="mock-tag-list">TagList</div>;
|
|
14
|
+
};
|
|
15
|
+
return Mock;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
jest.mock('@capillarytech/cap-ui-library/CapRow', () => ({ children }) => <div>{children}</div>);
|
|
19
|
+
jest.mock('@capillarytech/cap-ui-library/CapColumn', () => ({ children }) => <div>{children}</div>);
|
|
20
|
+
jest.mock('@capillarytech/cap-ui-library/CapHeading', () => () => null);
|
|
21
|
+
jest.mock('@capillarytech/cap-ui-library/CapInput', () => () => <input data-testid="cap-input" />);
|
|
22
|
+
|
|
23
|
+
const waitMap = {
|
|
24
|
+
b1: { eventName: 'Order Placed', blockName: 'Wait', tags: [] },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('CapTagListWithInput', () => {
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
capturedTagListProps.current = null;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('forwards waitEventContextTags to TagList', () => {
|
|
33
|
+
render(
|
|
34
|
+
<IntlProvider locale="en" messages={{}}>
|
|
35
|
+
<CapTagListWithInput
|
|
36
|
+
inputId="test-url"
|
|
37
|
+
inputOnChange={jest.fn()}
|
|
38
|
+
waitEventContextTags={waitMap}
|
|
39
|
+
onTagSelect={jest.fn()}
|
|
40
|
+
onContextChange={jest.fn()}
|
|
41
|
+
/>
|
|
42
|
+
</IntlProvider>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
expect(screen.getByTestId('mock-tag-list')).toBeInTheDocument();
|
|
46
|
+
expect(capturedTagListProps.current.waitEventContextTags).toEqual(waitMap);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('uses default empty object for waitEventContextTags when omitted', () => {
|
|
50
|
+
render(
|
|
51
|
+
<IntlProvider locale="en" messages={{}}>
|
|
52
|
+
<CapTagListWithInput
|
|
53
|
+
inputId="test-url"
|
|
54
|
+
inputOnChange={jest.fn()}
|
|
55
|
+
onTagSelect={jest.fn()}
|
|
56
|
+
onContextChange={jest.fn()}
|
|
57
|
+
/>
|
|
58
|
+
</IntlProvider>
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
expect(capturedTagListProps.current.waitEventContextTags).toEqual({});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -27,6 +27,7 @@ export const CapTagListWithInput = (props) => {
|
|
|
27
27
|
userLocale = 'en',
|
|
28
28
|
eventContextTags = [],
|
|
29
29
|
restrictPersonalization = false,
|
|
30
|
+
waitEventContextTags = {},
|
|
30
31
|
// CapInput props
|
|
31
32
|
inputId,
|
|
32
33
|
inputValue = '',
|
|
@@ -77,6 +78,7 @@ export const CapTagListWithInput = (props) => {
|
|
|
77
78
|
userLocale={userLocale}
|
|
78
79
|
selectedOfferDetails={selectedOfferDetails}
|
|
79
80
|
eventContextTags={eventContextTags}
|
|
81
|
+
waitEventContextTags={waitEventContextTags}
|
|
80
82
|
style={tagListStyle}
|
|
81
83
|
popoverPlacement={popoverPlacement}
|
|
82
84
|
restrictPersonalization={restrictPersonalization}
|
|
@@ -116,6 +118,7 @@ CapTagListWithInput.propTypes = {
|
|
|
116
118
|
userLocale: PropTypes.string,
|
|
117
119
|
eventContextTags: PropTypes.array,
|
|
118
120
|
restrictPersonalization: PropTypes.bool,
|
|
121
|
+
waitEventContextTags: PropTypes.object,
|
|
119
122
|
|
|
120
123
|
// CapInput props
|
|
121
124
|
inputId: PropTypes.string.isRequired,
|
|
@@ -154,6 +157,7 @@ CapTagListWithInput.defaultProps = {
|
|
|
154
157
|
userLocale: 'en',
|
|
155
158
|
eventContextTags: [],
|
|
156
159
|
restrictPersonalization: false,
|
|
160
|
+
waitEventContextTags: {},
|
|
157
161
|
inputValue: '',
|
|
158
162
|
inputSize: 'default',
|
|
159
163
|
inputRequired: false,
|
|
@@ -52,6 +52,7 @@ export const CapWhatsappCTA = (props) => {
|
|
|
52
52
|
injectedTags = {},
|
|
53
53
|
selectedOfferDetails = [],
|
|
54
54
|
eventContextTags = [],
|
|
55
|
+
waitEventContextTags = {},
|
|
55
56
|
} = props;
|
|
56
57
|
const { formatMessage } = intl;
|
|
57
58
|
const invalidVarRegex = /{{(.*?)}}/g;
|
|
@@ -283,6 +284,7 @@ export const CapWhatsappCTA = (props) => {
|
|
|
283
284
|
injectedTags={injectedTags}
|
|
284
285
|
selectedOfferDetails={selectedOfferDetails}
|
|
285
286
|
eventContextTags={eventContextTags}
|
|
287
|
+
waitEventContextTags={waitEventContextTags}
|
|
286
288
|
/>
|
|
287
289
|
</CapColumn>
|
|
288
290
|
)}
|
|
@@ -169,4 +169,184 @@ describe('CustomValuesEditor', () => {
|
|
|
169
169
|
);
|
|
170
170
|
expect(screen.getAllByTestId('tag-input').length).toBeGreaterThan(0);
|
|
171
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
|
+
});
|
|
172
352
|
});
|
|
@@ -477,4 +477,100 @@ describe('parseSenderDetailsResponse', () => {
|
|
|
477
477
|
expect(parseSenderDetailsResponse('SMS', response)).toEqual({ domains: [] });
|
|
478
478
|
});
|
|
479
479
|
});
|
|
480
|
+
|
|
481
|
+
describe('unwrapEntity edge cases', () => {
|
|
482
|
+
it('should return empty domains when entity key is explicitly null', () => {
|
|
483
|
+
// unwrapEntity: response.entity === null → returns null → parseSenderDetailsResponse returns { domains: [] }
|
|
484
|
+
expect(parseSenderDetailsResponse('SMS', { entity: null })).toEqual({ domains: [] });
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
describe('null or empty channel', () => {
|
|
489
|
+
it('should return empty domains when channel is null', () => {
|
|
490
|
+
const response = { entity: { SMS: [{ id: 1, domainProperties: { domainName: 'D', id: 10, contactInfo: [] } }] } };
|
|
491
|
+
expect(parseSenderDetailsResponse(null, response)).toEqual({ domains: [] });
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('should return empty domains when channel is empty string', () => {
|
|
495
|
+
const response = { entity: { SMS: [{ id: 1, domainProperties: { domainName: 'D', id: 10, contactInfo: [] } }] } };
|
|
496
|
+
expect(parseSenderDetailsResponse('', response)).toEqual({ domains: [] });
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
describe('single non-array domain object', () => {
|
|
501
|
+
it('should wrap single domain with domainProperties in an array and return one domain', () => {
|
|
502
|
+
const response = {
|
|
503
|
+
entity: {
|
|
504
|
+
SMS: {
|
|
505
|
+
id: 5,
|
|
506
|
+
domainProperties: {
|
|
507
|
+
domainName: 'SingleDomain',
|
|
508
|
+
id: 99,
|
|
509
|
+
contactInfo: [{ type: 'gsm_sender_id', valid: true, value: 'GSMSINGLE' }],
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
},
|
|
513
|
+
};
|
|
514
|
+
const result = parseSenderDetailsResponse('SMS', response);
|
|
515
|
+
expect(result.domains).toHaveLength(1);
|
|
516
|
+
expect(result.domains[0].domainName).toBe('SingleDomain');
|
|
517
|
+
expect(result.domains[0].gsmSenders[0].value).toBe('GSMSINGLE');
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('should return empty domains for single non-array domain without domainProperties', () => {
|
|
521
|
+
const response = {
|
|
522
|
+
entity: { SMS: { id: 5, someProp: 'value' } },
|
|
523
|
+
};
|
|
524
|
+
expect(parseSenderDetailsResponse('SMS', response)).toEqual({ domains: [] });
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
describe('deduplication by domainId when domainName is null', () => {
|
|
529
|
+
it('should deduplicate two entries sharing the same domainId when domainName is null', () => {
|
|
530
|
+
const response = {
|
|
531
|
+
entity: {
|
|
532
|
+
SMS: [
|
|
533
|
+
{
|
|
534
|
+
id: 10,
|
|
535
|
+
priority: 1,
|
|
536
|
+
domainProperties: { domainName: null, id: 42, contactInfo: [] },
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
id: 11,
|
|
540
|
+
priority: 2,
|
|
541
|
+
domainProperties: { domainName: null, id: 42, contactInfo: [] },
|
|
542
|
+
},
|
|
543
|
+
],
|
|
544
|
+
},
|
|
545
|
+
};
|
|
546
|
+
const result = parseSenderDetailsResponse('SMS', response);
|
|
547
|
+
expect(result.domains).toHaveLength(1);
|
|
548
|
+
expect(result.domains[0].domainId).toBe(42);
|
|
549
|
+
});
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
describe('typeMatches null-type filtering', () => {
|
|
553
|
+
it('should exclude contactInfo items with null type from gsmSenders', () => {
|
|
554
|
+
const response = {
|
|
555
|
+
entity: {
|
|
556
|
+
SMS: [
|
|
557
|
+
{
|
|
558
|
+
id: 1,
|
|
559
|
+
domainProperties: {
|
|
560
|
+
domainName: 'D',
|
|
561
|
+
id: 1,
|
|
562
|
+
contactInfo: [
|
|
563
|
+
{ type: null, valid: true, value: 'EXCLUDED' },
|
|
564
|
+
{ type: 'gsm_sender_id', valid: true, value: 'INCLUDED' },
|
|
565
|
+
],
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
],
|
|
569
|
+
},
|
|
570
|
+
};
|
|
571
|
+
const result = parseSenderDetailsResponse('SMS', response);
|
|
572
|
+
expect(result.domains[0].gsmSenders).toHaveLength(1);
|
|
573
|
+
expect(result.domains[0].gsmSenders[0].value).toBe('INCLUDED');
|
|
574
|
+
});
|
|
575
|
+
});
|
|
480
576
|
});
|