@capillarytech/creatives-library 8.0.303 → 8.0.305-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 +3 -1
- package/initialState.js +0 -2
- package/package.json +1 -1
- package/utils/common.js +5 -8
- package/utils/commonUtils.js +36 -93
- package/utils/tagValidations.js +83 -223
- package/utils/tests/commonUtil.test.js +147 -124
- package/utils/tests/tagValidations.test.js +441 -358
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -3
- package/v2Components/ErrorInfoNote/index.js +2 -5
- package/v2Components/FormBuilder/index.js +137 -203
- package/v2Components/FormBuilder/messages.js +0 -8
- package/v2Components/HtmlEditor/HTMLEditor.js +0 -6
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -1
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +0 -16
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +1 -2
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +3 -132
- package/v2Components/HtmlEditor/hooks/useValidation.js +9 -12
- package/v2Components/HtmlEditor/utils/htmlValidator.js +2 -4
- package/v2Containers/Cap/mockData.js +0 -14
- package/v2Containers/Cap/reducer.js +3 -55
- package/v2Containers/Cap/tests/reducer.test.js +0 -102
- package/v2Containers/CreativesContainer/SlideBoxContent.js +5 -1
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +13 -5
- package/v2Containers/CreativesContainer/constants.js +6 -0
- package/v2Containers/CreativesContainer/index.js +47 -7
- package/v2Containers/Email/index.js +1 -5
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +23 -70
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +19 -219
- package/v2Containers/FTP/index.js +2 -51
- package/v2Containers/FTP/messages.js +0 -4
- package/v2Containers/InApp/index.js +33 -106
- package/v2Containers/InApp/tests/index.test.js +17 -6
- package/v2Containers/InappAdvance/index.js +6 -110
- package/v2Containers/InappAdvance/tests/index.test.js +2 -0
- package/v2Containers/Line/Container/Text/index.js +0 -1
- package/v2Containers/MobilePush/Create/index.js +59 -19
- package/v2Containers/MobilePush/Edit/index.js +48 -20
- package/v2Containers/MobilePushNew/index.js +12 -32
- package/v2Containers/MobilepushWrapper/index.js +3 -1
- package/v2Containers/Rcs/index.js +12 -37
- package/v2Containers/Sms/Create/index.js +39 -3
- package/v2Containers/Sms/Create/messages.js +4 -0
- package/v2Containers/Sms/Edit/index.js +35 -3
- package/v2Containers/Sms/commonMethods.js +3 -6
- package/v2Containers/Sms/tests/commonMethods.test.js +122 -0
- package/v2Containers/SmsTrai/Edit/index.js +11 -47
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
- package/v2Containers/SmsWrapper/index.js +2 -0
- package/v2Containers/TemplatesV2/index.js +28 -13
- package/v2Containers/Viber/index.js +0 -1
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -3
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
- package/v2Containers/WebPush/Create/index.js +2 -2
- package/v2Containers/WebPush/Create/utils/validation.js +17 -8
- package/v2Containers/WebPush/Create/utils/validation.test.js +44 -24
- package/v2Containers/Whatsapp/index.js +9 -17
- package/v2Containers/Zalo/index.js +3 -11
|
@@ -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 { isEmailUnsubscribeTagOptional } from '../../../../utils/common';
|
|
17
17
|
|
|
18
18
|
// Mock dependencies
|
|
19
19
|
jest.mock('../../../../utils/commonUtils', () => ({
|
|
@@ -24,11 +24,8 @@ 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);
|
|
29
27
|
jest.mock('../../../../utils/common', () => ({
|
|
30
|
-
|
|
31
|
-
isEmailUnsubscribeTagMandatory: jest.fn(() => false),
|
|
28
|
+
isEmailUnsubscribeTagOptional: jest.fn(() => false),
|
|
32
29
|
}));
|
|
33
30
|
|
|
34
31
|
jest.mock('../../../../utils/history', () => ({
|
|
@@ -420,7 +417,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
420
417
|
jest.clearAllMocks();
|
|
421
418
|
validateLiquidTemplateContent.mockResolvedValue(true);
|
|
422
419
|
validateTags.mockReturnValue({ valid: true });
|
|
423
|
-
|
|
420
|
+
isEmailUnsubscribeTagOptional.mockReturnValue(false);
|
|
424
421
|
// Reset mock functions
|
|
425
422
|
mockGetAllIssues.mockReturnValue([]);
|
|
426
423
|
mockGetValidationState.mockReturnValue({
|
|
@@ -428,88 +425,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
428
425
|
hasErrors: false,
|
|
429
426
|
issueCounts: { errors: 0, warnings: 0, total: 0 },
|
|
430
427
|
});
|
|
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
|
-
});
|
|
513
428
|
});
|
|
514
429
|
|
|
515
430
|
describe('Default Parameter Values (lines 60-63)', () => {
|
|
@@ -1106,7 +1021,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1106
1021
|
});
|
|
1107
1022
|
|
|
1108
1023
|
it('allows save when unsubscribe validation is on and tag is present', () => {
|
|
1109
|
-
|
|
1024
|
+
isEmailUnsubscribeTagOptional.mockReturnValue(false);
|
|
1110
1025
|
renderWithIntl({
|
|
1111
1026
|
isGetFormData: true,
|
|
1112
1027
|
subject: 'Valid Subject',
|
|
@@ -1116,49 +1031,44 @@ describe('EmailHTMLEditor', () => {
|
|
|
1116
1031
|
// Should proceed with save (validation passes)
|
|
1117
1032
|
});
|
|
1118
1033
|
|
|
1119
|
-
it('blocks save
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
missingTags: ['tag2'],
|
|
1034
|
+
it('blocks save when liquid API validation fails for Email', async () => {
|
|
1035
|
+
// Liquid validation runs in library mode (!isFullMode). Simulate API validation failure via mock onError.
|
|
1036
|
+
validateLiquidTemplateContent.mockImplementation((content, options) => {
|
|
1037
|
+
options.onError({ standardErrors: [], liquidErrors: ['Validation failed'] });
|
|
1038
|
+
return Promise.resolve(false);
|
|
1125
1039
|
});
|
|
1126
1040
|
const onValidationFail = jest.fn();
|
|
1127
|
-
const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
|
|
1128
|
-
|
|
1129
|
-
// Set subject and content via component interactions
|
|
1130
1041
|
const { rerender } = renderWithIntl({
|
|
1131
1042
|
onValidationFail,
|
|
1132
1043
|
isGetFormData: false,
|
|
1044
|
+
isFullMode: false,
|
|
1133
1045
|
metaEntities: {
|
|
1134
1046
|
tags: {
|
|
1135
1047
|
standard: [{ name: 'customer.name' }],
|
|
1136
1048
|
},
|
|
1137
1049
|
},
|
|
1138
|
-
getLiquidTags:
|
|
1050
|
+
getLiquidTags: jest.fn((content, cb) => cb({ askAiraResponse: { data: [] }, isError: false })),
|
|
1139
1051
|
});
|
|
1140
1052
|
const input = screen.getByTestId('subject-input');
|
|
1141
1053
|
fireEvent.change(input, { target: { value: 'Valid Subject' } });
|
|
1142
1054
|
const changeButton = screen.getByTestId('trigger-content-change');
|
|
1143
1055
|
fireEvent.click(changeButton);
|
|
1144
|
-
// Now trigger save
|
|
1145
1056
|
rerender(
|
|
1146
1057
|
<IntlProvider locale="en" messages={{}}>
|
|
1147
1058
|
<EmailHTMLEditor
|
|
1148
1059
|
{...defaultProps}
|
|
1149
1060
|
onValidationFail={onValidationFail}
|
|
1150
1061
|
isGetFormData
|
|
1062
|
+
isFullMode={false}
|
|
1151
1063
|
metaEntities={{
|
|
1152
1064
|
tags: {
|
|
1153
1065
|
standard: [{ name: 'customer.name' }],
|
|
1154
1066
|
},
|
|
1155
1067
|
}}
|
|
1156
|
-
getLiquidTags={
|
|
1068
|
+
getLiquidTags={jest.fn((content, cb) => cb({ askAiraResponse: { data: [] }, isError: false }))} />
|
|
1157
1069
|
</IntlProvider>
|
|
1158
1070
|
);
|
|
1159
|
-
|
|
1160
1071
|
await waitFor(() => {
|
|
1161
|
-
expect(CapNotification.error).toHaveBeenCalled();
|
|
1162
1072
|
expect(onValidationFail).toHaveBeenCalled();
|
|
1163
1073
|
}, { timeout: 3000 });
|
|
1164
1074
|
});
|
|
@@ -1175,8 +1085,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1175
1085
|
// Ensure no HTML/Label/Liquid errors from HtmlEditor
|
|
1176
1086
|
mockGetAllIssues.mockReturnValue([]);
|
|
1177
1087
|
|
|
1178
|
-
//
|
|
1179
|
-
// Use isFullMode: false (library mode) to test liquid validation path
|
|
1088
|
+
// Liquid validation runs only in library mode
|
|
1180
1089
|
const { rerender } = renderWithIntl({
|
|
1181
1090
|
isGetFormData: false,
|
|
1182
1091
|
isFullMode: false,
|
|
@@ -1185,7 +1094,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1185
1094
|
standard: [{ name: 'customer.name' }],
|
|
1186
1095
|
},
|
|
1187
1096
|
},
|
|
1188
|
-
isLiquidEnabled: true,
|
|
1189
1097
|
getLiquidTags,
|
|
1190
1098
|
});
|
|
1191
1099
|
const input = screen.getByTestId('subject-input');
|
|
@@ -1231,12 +1139,10 @@ describe('EmailHTMLEditor', () => {
|
|
|
1231
1139
|
// Ensure no HTML/Label/Liquid errors from HtmlEditor
|
|
1232
1140
|
mockGetAllIssues.mockReturnValue([]);
|
|
1233
1141
|
|
|
1234
|
-
//
|
|
1235
|
-
// Use isFullMode: false (library mode) to test liquid validation path
|
|
1142
|
+
// Liquid validation runs only in library mode
|
|
1236
1143
|
const { rerender } = renderWithIntl({
|
|
1237
1144
|
isGetFormData: false,
|
|
1238
1145
|
isFullMode: false,
|
|
1239
|
-
isLiquidEnabled: true,
|
|
1240
1146
|
getLiquidTags,
|
|
1241
1147
|
});
|
|
1242
1148
|
const input = screen.getByTestId('subject-input');
|
|
@@ -1282,12 +1188,10 @@ describe('EmailHTMLEditor', () => {
|
|
|
1282
1188
|
// Ensure no HTML/Label/Liquid errors from HtmlEditor
|
|
1283
1189
|
mockGetAllIssues.mockReturnValue([]);
|
|
1284
1190
|
|
|
1285
|
-
//
|
|
1286
|
-
// Use isFullMode: false (library mode) to test liquid validation path
|
|
1191
|
+
// Liquid validation runs only in library mode
|
|
1287
1192
|
const { rerender } = renderWithIntl({
|
|
1288
1193
|
isGetFormData: false,
|
|
1289
1194
|
isFullMode: false,
|
|
1290
|
-
isLiquidEnabled: true,
|
|
1291
1195
|
getLiquidTags,
|
|
1292
1196
|
showLiquidErrorInFooter,
|
|
1293
1197
|
onValidationFail,
|
|
@@ -1335,14 +1239,13 @@ describe('EmailHTMLEditor', () => {
|
|
|
1335
1239
|
// Ensure no HTML/Label/Liquid errors from HtmlEditor
|
|
1336
1240
|
mockGetAllIssues.mockReturnValue([]);
|
|
1337
1241
|
|
|
1338
|
-
//
|
|
1339
|
-
// Use isFullMode: false (library mode) to test liquid validation path
|
|
1242
|
+
// Liquid validation runs only in library mode; then performSave calls getFormdata
|
|
1340
1243
|
const { rerender } = renderWithIntl({
|
|
1341
1244
|
isGetFormData: false,
|
|
1342
1245
|
isFullMode: false,
|
|
1343
1246
|
getLiquidTags,
|
|
1344
1247
|
getFormdata,
|
|
1345
|
-
|
|
1248
|
+
location: { query: { module: 'library' } },
|
|
1346
1249
|
});
|
|
1347
1250
|
const input = screen.getByTestId('subject-input');
|
|
1348
1251
|
await act(async () => {
|
|
@@ -1360,7 +1263,7 @@ describe('EmailHTMLEditor', () => {
|
|
|
1360
1263
|
await act(async () => {
|
|
1361
1264
|
rerender(
|
|
1362
1265
|
<IntlProvider locale="en" messages={{}}>
|
|
1363
|
-
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode={false} getLiquidTags={getLiquidTags} getFormdata={getFormdata}
|
|
1266
|
+
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode={false} getLiquidTags={getLiquidTags} getFormdata={getFormdata} location={{ query: { module: 'library' } }} />
|
|
1364
1267
|
</IntlProvider>
|
|
1365
1268
|
);
|
|
1366
1269
|
});
|
|
@@ -1369,14 +1272,12 @@ describe('EmailHTMLEditor', () => {
|
|
|
1369
1272
|
expect(validateLiquidTemplateContent).toHaveBeenCalled();
|
|
1370
1273
|
}, { timeout: 5000 });
|
|
1371
1274
|
|
|
1372
|
-
// In library mode (isFullMode=false), save proceeds via getFormdata
|
|
1373
1275
|
await waitFor(() => {
|
|
1374
1276
|
expect(getFormdata).toHaveBeenCalled();
|
|
1375
1277
|
}, { timeout: 5000 });
|
|
1376
1278
|
});
|
|
1377
1279
|
|
|
1378
1280
|
it('saves in full mode with create template', async () => {
|
|
1379
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1380
1281
|
const emailActions = {
|
|
1381
1282
|
...defaultProps.emailActions,
|
|
1382
1283
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1410,7 +1311,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1410
1311
|
});
|
|
1411
1312
|
|
|
1412
1313
|
it('saves in full mode with edit template', async () => {
|
|
1413
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1414
1314
|
const emailActions = {
|
|
1415
1315
|
...defaultProps.emailActions,
|
|
1416
1316
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1480,7 +1380,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1480
1380
|
});
|
|
1481
1381
|
|
|
1482
1382
|
it('handles create template error response', async () => {
|
|
1483
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1484
1383
|
const emailActions = {
|
|
1485
1384
|
...defaultProps.emailActions,
|
|
1486
1385
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1515,7 +1414,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1515
1414
|
});
|
|
1516
1415
|
|
|
1517
1416
|
it('handles create template success with getFormdata', async () => {
|
|
1518
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1519
1417
|
const emailActions = {
|
|
1520
1418
|
...defaultProps.emailActions,
|
|
1521
1419
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1552,7 +1450,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1552
1450
|
});
|
|
1553
1451
|
|
|
1554
1452
|
it('handles create template success without getFormdata', async () => {
|
|
1555
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1556
1453
|
const emailActions = {
|
|
1557
1454
|
...defaultProps.emailActions,
|
|
1558
1455
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1588,7 +1485,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1588
1485
|
});
|
|
1589
1486
|
|
|
1590
1487
|
it('saves in library mode with getFormdata', async () => {
|
|
1591
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1592
1488
|
const getFormdata = jest.fn();
|
|
1593
1489
|
|
|
1594
1490
|
// Set subject and content via component interactions
|
|
@@ -1616,7 +1512,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1616
1512
|
});
|
|
1617
1513
|
|
|
1618
1514
|
it('saves in library mode without library module', async () => {
|
|
1619
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1620
1515
|
const getFormdata = jest.fn();
|
|
1621
1516
|
|
|
1622
1517
|
// Set subject and content via component interactions
|
|
@@ -1756,7 +1651,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1756
1651
|
renderWithIntl({
|
|
1757
1652
|
getLiquidTags: null,
|
|
1758
1653
|
globalActions,
|
|
1759
|
-
isLiquidEnabled: true,
|
|
1760
1654
|
isGetFormData: true,
|
|
1761
1655
|
subject: 'Valid Subject',
|
|
1762
1656
|
htmlContent: '<p>Content</p>',
|
|
@@ -2709,98 +2603,4 @@ describe('EmailHTMLEditor', () => {
|
|
|
2709
2603
|
expect(screen.getByTestId('html-editor')).toBeInTheDocument();
|
|
2710
2604
|
});
|
|
2711
2605
|
});
|
|
2712
|
-
|
|
2713
|
-
describe('isFullMode - skip liquid validation on save', () => {
|
|
2714
|
-
it('skips liquid validation when isFullMode is true', async () => {
|
|
2715
|
-
validateLiquidTemplateContent.mockClear();
|
|
2716
|
-
const getLiquidTags = jest.fn((content, callback) => {
|
|
2717
|
-
callback({ askAiraResponse: { data: [] }, isError: false });
|
|
2718
|
-
});
|
|
2719
|
-
const getFormdata = jest.fn();
|
|
2720
|
-
// Ensure no HTML/Label/Liquid errors from HtmlEditor
|
|
2721
|
-
mockGetAllIssues.mockReturnValue([]);
|
|
2722
|
-
|
|
2723
|
-
const { rerender } = renderWithIntl({
|
|
2724
|
-
isGetFormData: false,
|
|
2725
|
-
isFullMode: true,
|
|
2726
|
-
isLiquidEnabled: true,
|
|
2727
|
-
getLiquidTags,
|
|
2728
|
-
getFormdata,
|
|
2729
|
-
});
|
|
2730
|
-
const input = screen.getByTestId('subject-input');
|
|
2731
|
-
await act(async () => {
|
|
2732
|
-
fireEvent.change(input, { target: { value: 'Valid Subject' } });
|
|
2733
|
-
});
|
|
2734
|
-
const changeButton = screen.getByTestId('trigger-content-change');
|
|
2735
|
-
await act(async () => {
|
|
2736
|
-
fireEvent.click(changeButton);
|
|
2737
|
-
});
|
|
2738
|
-
await act(async () => {
|
|
2739
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2740
|
-
});
|
|
2741
|
-
// Trigger save
|
|
2742
|
-
await act(async () => {
|
|
2743
|
-
rerender(
|
|
2744
|
-
<IntlProvider locale="en" messages={{}}>
|
|
2745
|
-
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} getFormdata={getFormdata} />
|
|
2746
|
-
</IntlProvider>
|
|
2747
|
-
);
|
|
2748
|
-
});
|
|
2749
|
-
|
|
2750
|
-
// In full mode, liquid validation should be skipped entirely
|
|
2751
|
-
await act(async () => {
|
|
2752
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
2753
|
-
});
|
|
2754
|
-
expect(validateLiquidTemplateContent).not.toHaveBeenCalled();
|
|
2755
|
-
});
|
|
2756
|
-
|
|
2757
|
-
it('proceeds directly to save without liquid validation in full mode', async () => {
|
|
2758
|
-
validateLiquidTemplateContent.mockClear();
|
|
2759
|
-
const getLiquidTags = jest.fn((content, callback) => {
|
|
2760
|
-
callback({ askAiraResponse: { data: [] }, isError: false });
|
|
2761
|
-
});
|
|
2762
|
-
const emailActions = {
|
|
2763
|
-
...defaultProps.emailActions,
|
|
2764
|
-
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
2765
|
-
createTemplate: jest.fn((obj, callback) => {
|
|
2766
|
-
callback({ templateId: { _id: '123', versions: {} } });
|
|
2767
|
-
}),
|
|
2768
|
-
};
|
|
2769
|
-
// Ensure no HTML/Label/Liquid errors from HtmlEditor
|
|
2770
|
-
mockGetAllIssues.mockReturnValue([]);
|
|
2771
|
-
|
|
2772
|
-
const { rerender } = renderWithIntl({
|
|
2773
|
-
isGetFormData: false,
|
|
2774
|
-
isFullMode: true,
|
|
2775
|
-
getLiquidTags,
|
|
2776
|
-
emailActions,
|
|
2777
|
-
templateName: 'New Template',
|
|
2778
|
-
});
|
|
2779
|
-
const input = screen.getByTestId('subject-input');
|
|
2780
|
-
await act(async () => {
|
|
2781
|
-
fireEvent.change(input, { target: { value: 'Valid Subject' } });
|
|
2782
|
-
});
|
|
2783
|
-
const changeButton = screen.getByTestId('trigger-content-change');
|
|
2784
|
-
await act(async () => {
|
|
2785
|
-
fireEvent.click(changeButton);
|
|
2786
|
-
});
|
|
2787
|
-
await act(async () => {
|
|
2788
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2789
|
-
});
|
|
2790
|
-
// Trigger save
|
|
2791
|
-
await act(async () => {
|
|
2792
|
-
rerender(
|
|
2793
|
-
<IntlProvider locale="en" messages={{}}>
|
|
2794
|
-
<EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} emailActions={emailActions} templateName="New Template" />
|
|
2795
|
-
</IntlProvider>
|
|
2796
|
-
);
|
|
2797
|
-
});
|
|
2798
|
-
|
|
2799
|
-
// Should skip liquid validation and proceed to save directly
|
|
2800
|
-
await waitFor(() => {
|
|
2801
|
-
expect(emailActions.createTemplate).toHaveBeenCalled();
|
|
2802
|
-
}, { timeout: 5000 });
|
|
2803
|
-
expect(validateLiquidTemplateContent).not.toHaveBeenCalled();
|
|
2804
|
-
});
|
|
2805
|
-
});
|
|
2806
2606
|
});
|
|
@@ -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,
|
|
25
|
+
import _, { find, cloneDeep, findIndex, isEmpty, isEqual, filter, 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,7 +33,6 @@ 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';
|
|
37
36
|
import injectSaga from '../../utils/injectSaga';
|
|
38
37
|
import injectReducer from '../../utils/injectReducer';
|
|
39
38
|
|
|
@@ -235,63 +234,16 @@ export class FTP extends React.Component {
|
|
|
235
234
|
}));
|
|
236
235
|
};
|
|
237
236
|
|
|
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
|
-
|
|
278
237
|
indexOfEnd(targetString, string) {
|
|
279
238
|
const io = targetString.indexOf(string);
|
|
280
239
|
return io == -1 ? -1 : io + string.length;
|
|
281
240
|
}
|
|
282
241
|
|
|
283
242
|
getMessageContent = () => {
|
|
284
|
-
const { messageContent
|
|
243
|
+
const { messageContent } = this.state;
|
|
285
244
|
const { formatMessage } = this.props.intl;
|
|
286
245
|
const { metaEntities, selectedOfferDetails, injectedTags } = this.props;
|
|
287
246
|
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
|
-
}
|
|
295
247
|
return (
|
|
296
248
|
<CapRow>
|
|
297
249
|
<CapColumn span={11}>
|
|
@@ -309,7 +261,6 @@ export class FTP extends React.Component {
|
|
|
309
261
|
label={formatMessage(messages.messageHeader)}
|
|
310
262
|
onChange={this.updateMessageBody}
|
|
311
263
|
value={messageContent}
|
|
312
|
-
errorMessage={errorMessageText}
|
|
313
264
|
/>
|
|
314
265
|
</div>
|
|
315
266
|
</CapColumn>
|
|
@@ -21,10 +21,6 @@ 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
|
-
},
|
|
28
24
|
selectTag: {
|
|
29
25
|
id: 'creatives.containersV2.FTP.selectTag',
|
|
30
26
|
defaultMessage: 'Select tag',
|