@capillarytech/creatives-library 8.0.292-alpha.0 → 8.0.292-alpha.10
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 -3
- package/initialState.js +2 -0
- package/package.json +1 -1
- package/utils/common.js +8 -5
- package/utils/commonUtils.js +85 -4
- package/utils/tagValidations.js +223 -83
- package/utils/tests/commonUtil.test.js +124 -147
- package/utils/tests/tagValidations.test.js +358 -441
- package/v2Components/ErrorInfoNote/index.js +5 -2
- package/v2Components/FormBuilder/index.js +203 -137
- package/v2Components/FormBuilder/messages.js +8 -0
- package/v2Components/HtmlEditor/HTMLEditor.js +11 -2
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +15 -0
- package/v2Components/HtmlEditor/_htmlEditor.scss +6 -1
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +10 -10
- package/v2Components/HtmlEditor/components/DeviceToggle/FLOW_AND_CLICK_BEHAVIOUR.md +70 -0
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +15 -8
- package/v2Containers/Cap/mockData.js +14 -0
- package/v2Containers/Cap/reducer.js +55 -3
- package/v2Containers/Cap/tests/reducer.test.js +102 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +2 -5
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -13
- package/v2Containers/CreativesContainer/index.js +10 -30
- package/v2Containers/Email/index.js +5 -1
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +70 -23
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +137 -29
- package/v2Containers/FTP/index.js +51 -2
- package/v2Containers/FTP/messages.js +4 -0
- package/v2Containers/InApp/index.js +139 -22
- package/v2Containers/InApp/tests/index.test.js +6 -17
- package/v2Containers/InappAdvance/index.js +118 -8
- package/v2Containers/InappAdvance/tests/index.test.js +2 -3
- package/v2Containers/Line/Container/Text/index.js +1 -0
- package/v2Containers/MobilePush/Create/index.js +19 -42
- package/v2Containers/MobilePush/Edit/index.js +19 -42
- package/v2Containers/MobilePushNew/index.js +32 -12
- package/v2Containers/MobilepushWrapper/index.js +1 -3
- package/v2Containers/Rcs/index.js +37 -12
- package/v2Containers/Sms/Create/index.js +3 -39
- package/v2Containers/Sms/Create/messages.js +0 -4
- package/v2Containers/Sms/Edit/index.js +3 -35
- package/v2Containers/Sms/commonMethods.js +6 -3
- package/v2Containers/SmsTrai/Edit/index.js +47 -11
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
- package/v2Containers/SmsWrapper/index.js +0 -2
- package/v2Containers/Viber/index.js +1 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +3 -1
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +7 -0
- package/v2Containers/WebPush/Create/index.js +2 -2
- package/v2Containers/WebPush/Create/utils/validation.js +2 -17
- package/v2Containers/WebPush/Create/utils/validation.test.js +24 -59
- package/v2Containers/Whatsapp/index.js +17 -9
- package/v2Containers/Zalo/index.js +11 -3
- package/v2Containers/Sms/tests/commonMethods.test.js +0 -122
|
@@ -13,7 +13,7 @@ import { IntlProvider } from 'react-intl';
|
|
|
13
13
|
import EmailHTMLEditor from '../EmailHTMLEditor';
|
|
14
14
|
import { validateLiquidTemplateContent } from '../../../../utils/commonUtils';
|
|
15
15
|
import { validateTags } from '../../../../utils/tagValidations';
|
|
16
|
-
import {
|
|
16
|
+
import { isEmailUnsubscribeTagMandatory } from '../../../../utils/common';
|
|
17
17
|
|
|
18
18
|
// Mock dependencies
|
|
19
19
|
jest.mock('../../../../utils/commonUtils', () => ({
|
|
@@ -24,8 +24,11 @@ jest.mock('../../../../utils/tagValidations', () => ({
|
|
|
24
24
|
validateTags: jest.fn(),
|
|
25
25
|
}));
|
|
26
26
|
|
|
27
|
+
// Create mutable mock for hasLiquidSupportFeature
|
|
28
|
+
const mockHasLiquidSupportFeature = jest.fn(() => true);
|
|
27
29
|
jest.mock('../../../../utils/common', () => ({
|
|
28
|
-
|
|
30
|
+
hasLiquidSupportFeature: (...args) => mockHasLiquidSupportFeature(...args),
|
|
31
|
+
isEmailUnsubscribeTagMandatory: jest.fn(() => false),
|
|
29
32
|
}));
|
|
30
33
|
|
|
31
34
|
jest.mock('../../../../utils/history', () => ({
|
|
@@ -417,7 +420,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
417
420
|
jest.clearAllMocks();
|
|
418
421
|
validateLiquidTemplateContent.mockResolvedValue(true);
|
|
419
422
|
validateTags.mockReturnValue({ valid: true });
|
|
420
|
-
|
|
423
|
+
isEmailUnsubscribeTagMandatory.mockReturnValue(false);
|
|
421
424
|
// Reset mock functions
|
|
422
425
|
mockGetAllIssues.mockReturnValue([]);
|
|
423
426
|
mockGetValidationState.mockReturnValue({
|
|
@@ -425,6 +428,88 @@ describe('EmailHTMLEditor', () => {
|
|
|
425
428
|
hasErrors: false,
|
|
426
429
|
issueCounts: { errors: 0, warnings: 0, total: 0 },
|
|
427
430
|
});
|
|
431
|
+
// Reset hasLiquidSupportFeature mock to return true by default
|
|
432
|
+
mockHasLiquidSupportFeature.mockReturnValue(true);
|
|
433
|
+
capturedApiValidationErrorsRef.current = null;
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
describe('mergedApiValidationErrors (lines 124-125)', () => {
|
|
437
|
+
beforeEach(() => {
|
|
438
|
+
global.__captureApiValidationErrorsRef = capturedApiValidationErrorsRef;
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
afterEach(() => {
|
|
442
|
+
delete global.__captureApiValidationErrorsRef;
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('merges tag unsupported errors into standardErrors when tagValidationError has unsupportedTags', async () => {
|
|
446
|
+
validateTags.mockReturnValue({
|
|
447
|
+
valid: false,
|
|
448
|
+
unsupportedTags: ['tagA', 'tagB'],
|
|
449
|
+
});
|
|
450
|
+
renderWithIntl({
|
|
451
|
+
metaEntities: { tags: { standard: [{ name: 'customer.name' }] } },
|
|
452
|
+
tags: [{ name: 'customer.name' }],
|
|
453
|
+
supportedTags: [],
|
|
454
|
+
});
|
|
455
|
+
const changeButton = screen.getByTestId('trigger-content-change');
|
|
456
|
+
await act(async () => {
|
|
457
|
+
fireEvent.click(changeButton);
|
|
458
|
+
});
|
|
459
|
+
await waitFor(() => {
|
|
460
|
+
expect(capturedApiValidationErrorsRef.current).not.toBeNull();
|
|
461
|
+
expect(capturedApiValidationErrorsRef.current.liquidErrors).toEqual([]);
|
|
462
|
+
expect(capturedApiValidationErrorsRef.current.standardErrors).toContain(
|
|
463
|
+
'Unsupported tags are: tagA, tagB',
|
|
464
|
+
);
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('merges tag missing errors into standardErrors when tagValidationError has missingTags and unsubscribe not mandatory', async () => {
|
|
469
|
+
isEmailUnsubscribeTagMandatory.mockReturnValue(false);
|
|
470
|
+
validateTags.mockReturnValue({
|
|
471
|
+
valid: false,
|
|
472
|
+
missingTags: ['unsubscribe'],
|
|
473
|
+
});
|
|
474
|
+
renderWithIntl({
|
|
475
|
+
metaEntities: { tags: { standard: [{ name: 'customer.name' }] } },
|
|
476
|
+
tags: [{ name: 'customer.name' }],
|
|
477
|
+
supportedTags: [],
|
|
478
|
+
});
|
|
479
|
+
const changeButton = screen.getByTestId('trigger-content-change');
|
|
480
|
+
await act(async () => {
|
|
481
|
+
fireEvent.click(changeButton);
|
|
482
|
+
});
|
|
483
|
+
await waitFor(() => {
|
|
484
|
+
expect(capturedApiValidationErrorsRef.current).not.toBeNull();
|
|
485
|
+
expect(capturedApiValidationErrorsRef.current.standardErrors).toContain(
|
|
486
|
+
'Missing tags are: unsubscribe',
|
|
487
|
+
);
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it('uses apiValidationErrors.liquidErrors and concatenates apiValidationErrors.standardErrors with tag messages (merge shape)', async () => {
|
|
492
|
+
// When tag messages exist, mergedApiValidationErrors returns liquidErrors from apiValidationErrors
|
|
493
|
+
// and standardErrors = [...(apiValidationErrors?.standardErrors || []), ...tagMessages] (lines 124-125)
|
|
494
|
+
validateTags.mockReturnValue({
|
|
495
|
+
valid: false,
|
|
496
|
+
unsupportedTags: ['customTag'],
|
|
497
|
+
});
|
|
498
|
+
renderWithIntl({
|
|
499
|
+
metaEntities: { tags: { standard: [{ name: 'customer.name' }] } },
|
|
500
|
+
tags: [{ name: 'customer.name' }],
|
|
501
|
+
});
|
|
502
|
+
const changeButton = screen.getByTestId('trigger-content-change');
|
|
503
|
+
await act(async () => {
|
|
504
|
+
fireEvent.click(changeButton);
|
|
505
|
+
});
|
|
506
|
+
await waitFor(() => {
|
|
507
|
+
expect(capturedApiValidationErrorsRef.current).not.toBeNull();
|
|
508
|
+
const { liquidErrors, standardErrors } = capturedApiValidationErrorsRef.current;
|
|
509
|
+
expect(liquidErrors).toEqual([]);
|
|
510
|
+
expect(standardErrors).toContain('Unsupported tags are: customTag');
|
|
511
|
+
});
|
|
512
|
+
});
|
|
428
513
|
});
|
|
429
514
|
|
|
430
515
|
describe('Default Parameter Values (lines 60-63)', () => {
|
|
@@ -1021,7 +1106,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1021
1106
|
});
|
|
1022
1107
|
|
|
1023
1108
|
it('allows save when unsubscribe validation is on and tag is present', () => {
|
|
1024
|
-
|
|
1109
|
+
isEmailUnsubscribeTagMandatory.mockReturnValue(false);
|
|
1025
1110
|
renderWithIntl({
|
|
1026
1111
|
isGetFormData: true,
|
|
1027
1112
|
subject: 'Valid Subject',
|
|
@@ -1031,44 +1116,49 @@ describe('EmailHTMLEditor', () => {
|
|
|
1031
1116
|
// Should proceed with save (validation passes)
|
|
1032
1117
|
});
|
|
1033
1118
|
|
|
1034
|
-
it('blocks save
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1119
|
+
it('blocks save for non-liquid orgs when tag validation fails', async () => {
|
|
1120
|
+
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1121
|
+
validateTags.mockReturnValue({
|
|
1122
|
+
valid: false,
|
|
1123
|
+
unsupportedTags: ['tag1'],
|
|
1124
|
+
missingTags: ['tag2'],
|
|
1039
1125
|
});
|
|
1040
1126
|
const onValidationFail = jest.fn();
|
|
1127
|
+
const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
|
|
1128
|
+
|
|
1129
|
+
// Set subject and content via component interactions
|
|
1041
1130
|
const { rerender } = renderWithIntl({
|
|
1042
1131
|
onValidationFail,
|
|
1043
1132
|
isGetFormData: false,
|
|
1044
|
-
isFullMode: false,
|
|
1045
1133
|
metaEntities: {
|
|
1046
1134
|
tags: {
|
|
1047
1135
|
standard: [{ name: 'customer.name' }],
|
|
1048
1136
|
},
|
|
1049
1137
|
},
|
|
1050
|
-
getLiquidTags:
|
|
1138
|
+
getLiquidTags: null, // No liquid tags for non-liquid org
|
|
1051
1139
|
});
|
|
1052
1140
|
const input = screen.getByTestId('subject-input');
|
|
1053
1141
|
fireEvent.change(input, { target: { value: 'Valid Subject' } });
|
|
1054
1142
|
const changeButton = screen.getByTestId('trigger-content-change');
|
|
1055
1143
|
fireEvent.click(changeButton);
|
|
1144
|
+
// Now trigger save
|
|
1056
1145
|
rerender(
|
|
1057
1146
|
<IntlProvider locale="en" messages={{}}>
|
|
1058
1147
|
<EmailHTMLEditor
|
|
1059
1148
|
{...defaultProps}
|
|
1060
1149
|
onValidationFail={onValidationFail}
|
|
1061
1150
|
isGetFormData
|
|
1062
|
-
isFullMode={false}
|
|
1063
1151
|
metaEntities={{
|
|
1064
1152
|
tags: {
|
|
1065
1153
|
standard: [{ name: 'customer.name' }],
|
|
1066
1154
|
},
|
|
1067
1155
|
}}
|
|
1068
|
-
getLiquidTags={
|
|
1156
|
+
getLiquidTags={null} />
|
|
1069
1157
|
</IntlProvider>
|
|
1070
1158
|
);
|
|
1159
|
+
|
|
1071
1160
|
await waitFor(() => {
|
|
1161
|
+
expect(CapNotification.error).toHaveBeenCalled();
|
|
1072
1162
|
expect(onValidationFail).toHaveBeenCalled();
|
|
1073
1163
|
}, { timeout: 3000 });
|
|
1074
1164
|
});
|
|
@@ -1085,15 +1175,16 @@ describe('EmailHTMLEditor', () => {
|
|
|
1085
1175
|
// Ensure no HTML/Label/Liquid errors from HtmlEditor
|
|
1086
1176
|
mockGetAllIssues.mockReturnValue([]);
|
|
1087
1177
|
|
|
1088
|
-
//
|
|
1178
|
+
// Set subject and content via component interactions
|
|
1089
1179
|
const { rerender } = renderWithIntl({
|
|
1090
1180
|
isGetFormData: false,
|
|
1091
|
-
isFullMode:
|
|
1181
|
+
isFullMode: true,
|
|
1092
1182
|
metaEntities: {
|
|
1093
1183
|
tags: {
|
|
1094
1184
|
standard: [{ name: 'customer.name' }],
|
|
1095
1185
|
},
|
|
1096
1186
|
},
|
|
1187
|
+
isLiquidEnabled: true,
|
|
1097
1188
|
getLiquidTags,
|
|
1098
1189
|
});
|
|
1099
1190
|
const input = screen.getByTestId('subject-input');
|
|
@@ -1115,7 +1206,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1115
1206
|
<EmailHTMLEditor
|
|
1116
1207
|
{...defaultProps}
|
|
1117
1208
|
isGetFormData
|
|
1118
|
-
isFullMode
|
|
1209
|
+
isFullMode
|
|
1119
1210
|
metaEntities={{
|
|
1120
1211
|
tags: {
|
|
1121
1212
|
standard: [{ name: 'customer.name' }],
|
|
@@ -1139,10 +1230,11 @@ describe('EmailHTMLEditor', () => {
|
|
|
1139
1230
|
// Ensure no HTML/Label/Liquid errors from HtmlEditor
|
|
1140
1231
|
mockGetAllIssues.mockReturnValue([]);
|
|
1141
1232
|
|
|
1142
|
-
//
|
|
1233
|
+
// Set subject and content via component interactions
|
|
1143
1234
|
const { rerender } = renderWithIntl({
|
|
1144
1235
|
isGetFormData: false,
|
|
1145
|
-
isFullMode:
|
|
1236
|
+
isFullMode: true,
|
|
1237
|
+
isLiquidEnabled: true,
|
|
1146
1238
|
getLiquidTags,
|
|
1147
1239
|
});
|
|
1148
1240
|
const input = screen.getByTestId('subject-input');
|
|
@@ -1161,7 +1253,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1161
1253
|
await act(async () => {
|
|
1162
1254
|
rerender(
|
|
1163
1255
|
<IntlProvider locale="en" messages={{}}>
|
|
1164
|
-
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode
|
|
1256
|
+
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} />
|
|
1165
1257
|
</IntlProvider>
|
|
1166
1258
|
);
|
|
1167
1259
|
});
|
|
@@ -1188,10 +1280,11 @@ describe('EmailHTMLEditor', () => {
|
|
|
1188
1280
|
// Ensure no HTML/Label/Liquid errors from HtmlEditor
|
|
1189
1281
|
mockGetAllIssues.mockReturnValue([]);
|
|
1190
1282
|
|
|
1191
|
-
//
|
|
1283
|
+
// Set subject and content via component interactions
|
|
1192
1284
|
const { rerender } = renderWithIntl({
|
|
1193
1285
|
isGetFormData: false,
|
|
1194
|
-
isFullMode:
|
|
1286
|
+
isFullMode: true,
|
|
1287
|
+
isLiquidEnabled: true,
|
|
1195
1288
|
getLiquidTags,
|
|
1196
1289
|
showLiquidErrorInFooter,
|
|
1197
1290
|
onValidationFail,
|
|
@@ -1212,7 +1305,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1212
1305
|
await act(async () => {
|
|
1213
1306
|
rerender(
|
|
1214
1307
|
<IntlProvider locale="en" messages={{}}>
|
|
1215
|
-
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode
|
|
1308
|
+
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} showLiquidErrorInFooter={showLiquidErrorInFooter} onValidationFail={onValidationFail} />
|
|
1216
1309
|
</IntlProvider>
|
|
1217
1310
|
);
|
|
1218
1311
|
});
|
|
@@ -1232,20 +1325,27 @@ describe('EmailHTMLEditor', () => {
|
|
|
1232
1325
|
return Promise.resolve(true);
|
|
1233
1326
|
});
|
|
1234
1327
|
|
|
1235
|
-
const
|
|
1328
|
+
const emailActions = {
|
|
1329
|
+
...defaultProps.emailActions,
|
|
1330
|
+
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
1331
|
+
createTemplate: jest.fn((obj, callback) => {
|
|
1332
|
+
callback({ templateId: { _id: '123', versions: {} } });
|
|
1333
|
+
}),
|
|
1334
|
+
};
|
|
1236
1335
|
const getLiquidTags = jest.fn((content, callback) => {
|
|
1237
1336
|
callback({ askAiraResponse: { data: [] }, isError: false });
|
|
1238
1337
|
});
|
|
1239
1338
|
// Ensure no HTML/Label/Liquid errors from HtmlEditor
|
|
1240
1339
|
mockGetAllIssues.mockReturnValue([]);
|
|
1241
1340
|
|
|
1242
|
-
//
|
|
1341
|
+
// Set subject and content via component interactions
|
|
1243
1342
|
const { rerender } = renderWithIntl({
|
|
1244
1343
|
isGetFormData: false,
|
|
1245
|
-
isFullMode:
|
|
1344
|
+
isFullMode: true,
|
|
1345
|
+
isLiquidEnabled: true,
|
|
1246
1346
|
getLiquidTags,
|
|
1247
|
-
|
|
1248
|
-
|
|
1347
|
+
emailActions,
|
|
1348
|
+
templateName: 'New Template',
|
|
1249
1349
|
});
|
|
1250
1350
|
const input = screen.getByTestId('subject-input');
|
|
1251
1351
|
await act(async () => {
|
|
@@ -1263,7 +1363,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1263
1363
|
await act(async () => {
|
|
1264
1364
|
rerender(
|
|
1265
1365
|
<IntlProvider locale="en" messages={{}}>
|
|
1266
|
-
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode
|
|
1366
|
+
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} emailActions={emailActions} templateName="New Template" />
|
|
1267
1367
|
</IntlProvider>
|
|
1268
1368
|
);
|
|
1269
1369
|
});
|
|
@@ -1273,11 +1373,12 @@ describe('EmailHTMLEditor', () => {
|
|
|
1273
1373
|
}, { timeout: 5000 });
|
|
1274
1374
|
|
|
1275
1375
|
await waitFor(() => {
|
|
1276
|
-
expect(
|
|
1376
|
+
expect(emailActions.createTemplate).toHaveBeenCalled();
|
|
1277
1377
|
}, { timeout: 5000 });
|
|
1278
1378
|
});
|
|
1279
1379
|
|
|
1280
1380
|
it('saves in full mode with create template', async () => {
|
|
1381
|
+
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1281
1382
|
const emailActions = {
|
|
1282
1383
|
...defaultProps.emailActions,
|
|
1283
1384
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1311,6 +1412,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1311
1412
|
});
|
|
1312
1413
|
|
|
1313
1414
|
it('saves in full mode with edit template', async () => {
|
|
1415
|
+
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1314
1416
|
const emailActions = {
|
|
1315
1417
|
...defaultProps.emailActions,
|
|
1316
1418
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1380,6 +1482,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1380
1482
|
});
|
|
1381
1483
|
|
|
1382
1484
|
it('handles create template error response', async () => {
|
|
1485
|
+
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1383
1486
|
const emailActions = {
|
|
1384
1487
|
...defaultProps.emailActions,
|
|
1385
1488
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1414,6 +1517,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1414
1517
|
});
|
|
1415
1518
|
|
|
1416
1519
|
it('handles create template success with getFormdata', async () => {
|
|
1520
|
+
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1417
1521
|
const emailActions = {
|
|
1418
1522
|
...defaultProps.emailActions,
|
|
1419
1523
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1450,6 +1554,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1450
1554
|
});
|
|
1451
1555
|
|
|
1452
1556
|
it('handles create template success without getFormdata', async () => {
|
|
1557
|
+
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1453
1558
|
const emailActions = {
|
|
1454
1559
|
...defaultProps.emailActions,
|
|
1455
1560
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1485,6 +1590,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1485
1590
|
});
|
|
1486
1591
|
|
|
1487
1592
|
it('saves in library mode with getFormdata', async () => {
|
|
1593
|
+
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1488
1594
|
const getFormdata = jest.fn();
|
|
1489
1595
|
|
|
1490
1596
|
// Set subject and content via component interactions
|
|
@@ -1512,6 +1618,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1512
1618
|
});
|
|
1513
1619
|
|
|
1514
1620
|
it('saves in library mode without library module', async () => {
|
|
1621
|
+
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1515
1622
|
const getFormdata = jest.fn();
|
|
1516
1623
|
|
|
1517
1624
|
// Set subject and content via component interactions
|
|
@@ -1651,6 +1758,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1651
1758
|
renderWithIntl({
|
|
1652
1759
|
getLiquidTags: null,
|
|
1653
1760
|
globalActions,
|
|
1761
|
+
isLiquidEnabled: true,
|
|
1654
1762
|
isGetFormData: true,
|
|
1655
1763
|
subject: 'Valid Subject',
|
|
1656
1764
|
htmlContent: '<p>Content</p>',
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
CapTooltip,
|
|
23
23
|
} from '@capillarytech/cap-ui-library';
|
|
24
24
|
import { FONT_SIZE_L } from '@capillarytech/cap-ui-library/styled/variables';
|
|
25
|
-
import _, { find, cloneDeep, findIndex, isEmpty, isEqual, filter, replace } from 'lodash';
|
|
25
|
+
import _, { find, cloneDeep, findIndex, isEmpty, isEqual, filter, flattenDeep, replace } from 'lodash';
|
|
26
26
|
import * as actions from './actions';
|
|
27
27
|
import { makeSelectFTP, makeSelectMetaEntities } from './selectors';
|
|
28
28
|
import { makeSelectLoyaltyPromotionDisplay, setInjectedTags } from '../Cap/selectors';
|
|
@@ -33,6 +33,7 @@ import * as globalActions from '../Cap/actions';
|
|
|
33
33
|
import { TagList } from '../TagList';
|
|
34
34
|
import { NO_COMMUNICATION, CREATE, EDIT, PREVIEW } from '../App/constants';
|
|
35
35
|
import { getTreeStructuredTags } from '../../utils/common';
|
|
36
|
+
import { transformInjectedTags, checkIfSupportedTag, skipTags } from '../../utils/tagValidations';
|
|
36
37
|
import injectSaga from '../../utils/injectSaga';
|
|
37
38
|
import injectReducer from '../../utils/injectReducer';
|
|
38
39
|
|
|
@@ -234,16 +235,63 @@ export class FTP extends React.Component {
|
|
|
234
235
|
}));
|
|
235
236
|
};
|
|
236
237
|
|
|
238
|
+
getFlatTags = (tags) => {
|
|
239
|
+
const flatTags = [];
|
|
240
|
+
tags.forEach((tag) => {
|
|
241
|
+
if ((tag.children || []).length) {
|
|
242
|
+
const innerTags = this.getFlatTags(tag.children);
|
|
243
|
+
flatTags.push(innerTags);
|
|
244
|
+
} else {
|
|
245
|
+
flatTags.push(tag.value);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
return flattenDeep(flatTags);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
validateTags(content, tagsParam, injectedTagsParams) {
|
|
253
|
+
const tags = tagsParam;
|
|
254
|
+
const injectedTags = transformInjectedTags(injectedTagsParams);
|
|
255
|
+
const response = {};
|
|
256
|
+
response.valid = true;
|
|
257
|
+
response.unsupportedTags = [];
|
|
258
|
+
const flatTags = this.getFlatTags(tags);
|
|
259
|
+
if (flatTags && flatTags.length) {
|
|
260
|
+
const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
|
|
261
|
+
const matchedTags = [...content.matchAll(regex)];
|
|
262
|
+
matchedTags.forEach((tag) => {
|
|
263
|
+
let ifSupported = !!flatTags.find((t) => t === tag[0]);
|
|
264
|
+
const tagValue = tag[0].substring(this.indexOfEnd(tag[0], '{{'), tag[0].indexOf('}}'));
|
|
265
|
+
ifSupported = ifSupported || checkIfSupportedTag(tagValue, injectedTags) || skipTags(tagValue);
|
|
266
|
+
if (!ifSupported) {
|
|
267
|
+
response.unsupportedTags.push(tagValue);
|
|
268
|
+
response.valid = false;
|
|
269
|
+
}
|
|
270
|
+
if (response.unsupportedTags.length === 0) {
|
|
271
|
+
response.valid = true;
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
return response;
|
|
276
|
+
}
|
|
277
|
+
|
|
237
278
|
indexOfEnd(targetString, string) {
|
|
238
279
|
const io = targetString.indexOf(string);
|
|
239
280
|
return io == -1 ? -1 : io + string.length;
|
|
240
281
|
}
|
|
241
282
|
|
|
242
283
|
getMessageContent = () => {
|
|
243
|
-
const { messageContent } = this.state;
|
|
284
|
+
const { messageContent, tagsTree = []} = this.state;
|
|
244
285
|
const { formatMessage } = this.props.intl;
|
|
245
286
|
const { metaEntities, selectedOfferDetails, injectedTags } = this.props;
|
|
246
287
|
const tagsRaw = metaEntities && metaEntities.tags ? metaEntities.tags.standard : [];
|
|
288
|
+
const validateTagResponse = !this.props?.isFullMode ? this.validateTags(messageContent, tagsTree, injectedTags) : { valid: true, unsupportedTags: [] };
|
|
289
|
+
let unsupportedTags = null;
|
|
290
|
+
let errorMessageText = '';
|
|
291
|
+
if (!validateTagResponse.valid) {
|
|
292
|
+
unsupportedTags = validateTagResponse.unsupportedTags.join(', ').toString();
|
|
293
|
+
errorMessageText = formatMessage(messages.unsupportedTagsValidationError, {unsupportedTags});
|
|
294
|
+
}
|
|
247
295
|
return (
|
|
248
296
|
<CapRow>
|
|
249
297
|
<CapColumn span={11}>
|
|
@@ -261,6 +309,7 @@ export class FTP extends React.Component {
|
|
|
261
309
|
label={formatMessage(messages.messageHeader)}
|
|
262
310
|
onChange={this.updateMessageBody}
|
|
263
311
|
value={messageContent}
|
|
312
|
+
errorMessage={errorMessageText}
|
|
264
313
|
/>
|
|
265
314
|
</div>
|
|
266
315
|
</CapColumn>
|
|
@@ -21,6 +21,10 @@ export default defineMessages({
|
|
|
21
21
|
id: 'creatives.containersV2.FTP.addColumn',
|
|
22
22
|
defaultMessage: 'Add column',
|
|
23
23
|
},
|
|
24
|
+
unsupportedTagsValidationError: {
|
|
25
|
+
id: 'creatives.containersV2.FTP.unsupportedTagsValidationError',
|
|
26
|
+
defaultMessage: 'Unsupported tags: {unsupportedTags}. Please remove them from this message.',
|
|
27
|
+
},
|
|
24
28
|
selectTag: {
|
|
25
29
|
id: 'creatives.containersV2.FTP.selectTag',
|
|
26
30
|
defaultMessage: 'Select tag',
|