@capillarytech/creatives-library 8.0.284 → 8.0.285-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/unified.js +0 -1
- package/initialState.js +0 -2
- package/package.json +1 -1
- package/utils/common.js +5 -8
- package/utils/commonUtils.js +2 -83
- package/utils/tagValidations.js +84 -222
- package/utils/tests/commonUtil.test.js +147 -118
- package/utils/tests/tagValidations.test.js +280 -358
- package/v2Components/ErrorInfoNote/index.js +2 -5
- package/v2Components/FormBuilder/index.js +64 -158
- package/v2Components/FormBuilder/messages.js +0 -8
- package/v2Components/HtmlEditor/HTMLEditor.js +0 -5
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -1
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +0 -15
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +1 -2
- 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/index.js +0 -1
- package/v2Containers/Email/index.js +1 -5
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +10 -62
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +12 -115
- package/v2Containers/FTP/index.js +2 -51
- package/v2Containers/FTP/messages.js +0 -4
- package/v2Containers/InApp/index.js +1 -96
- package/v2Containers/InApp/tests/index.test.js +17 -6
- package/v2Containers/InappAdvance/index.js +2 -103
- package/v2Containers/Line/Container/Text/index.js +0 -1
- package/v2Containers/MobilePushNew/index.js +2 -33
- package/v2Containers/Rcs/index.js +12 -37
- package/v2Containers/SmsTrai/Edit/index.js +6 -47
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
- 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 +18 -9
- package/v2Containers/WebPush/Create/utils/validation.test.js +0 -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,17 +1031,14 @@ describe('EmailHTMLEditor', () => {
|
|
|
1116
1031
|
// Should proceed with save (validation passes)
|
|
1117
1032
|
});
|
|
1118
1033
|
|
|
1119
|
-
it('blocks save
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1034
|
+
it('blocks save when liquid API validation fails for Email', async () => {
|
|
1035
|
+
// Liquid is always enabled for Email; blocking validation is the liquid API path.
|
|
1036
|
+
// validateLiquidTemplateContent is mocked, so simulate API validation failure by having the mock call onError.
|
|
1037
|
+
validateLiquidTemplateContent.mockImplementation((content, options) => {
|
|
1038
|
+
options.onError({ standardErrors: [], liquidErrors: ['Validation failed'] });
|
|
1039
|
+
return Promise.resolve(false);
|
|
1125
1040
|
});
|
|
1126
1041
|
const onValidationFail = jest.fn();
|
|
1127
|
-
const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
|
|
1128
|
-
|
|
1129
|
-
// Set subject and content via component interactions
|
|
1130
1042
|
const { rerender } = renderWithIntl({
|
|
1131
1043
|
onValidationFail,
|
|
1132
1044
|
isGetFormData: false,
|
|
@@ -1135,13 +1047,12 @@ describe('EmailHTMLEditor', () => {
|
|
|
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
|
|
@@ -1153,12 +1064,10 @@ describe('EmailHTMLEditor', () => {
|
|
|
1153
1064
|
standard: [{ name: 'customer.name' }],
|
|
1154
1065
|
},
|
|
1155
1066
|
}}
|
|
1156
|
-
getLiquidTags={
|
|
1067
|
+
getLiquidTags={jest.fn((content, cb) => cb({ askAiraResponse: { data: [] }, isError: false }))} />
|
|
1157
1068
|
</IntlProvider>
|
|
1158
1069
|
);
|
|
1159
|
-
|
|
1160
1070
|
await waitFor(() => {
|
|
1161
|
-
expect(CapNotification.error).toHaveBeenCalled();
|
|
1162
1071
|
expect(onValidationFail).toHaveBeenCalled();
|
|
1163
1072
|
}, { timeout: 3000 });
|
|
1164
1073
|
});
|
|
@@ -1184,7 +1093,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1184
1093
|
standard: [{ name: 'customer.name' }],
|
|
1185
1094
|
},
|
|
1186
1095
|
},
|
|
1187
|
-
isLiquidEnabled: true,
|
|
1188
1096
|
getLiquidTags,
|
|
1189
1097
|
});
|
|
1190
1098
|
const input = screen.getByTestId('subject-input');
|
|
@@ -1234,7 +1142,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1234
1142
|
const { rerender } = renderWithIntl({
|
|
1235
1143
|
isGetFormData: false,
|
|
1236
1144
|
isFullMode: true,
|
|
1237
|
-
isLiquidEnabled: true,
|
|
1238
1145
|
getLiquidTags,
|
|
1239
1146
|
});
|
|
1240
1147
|
const input = screen.getByTestId('subject-input');
|
|
@@ -1284,7 +1191,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1284
1191
|
const { rerender } = renderWithIntl({
|
|
1285
1192
|
isGetFormData: false,
|
|
1286
1193
|
isFullMode: true,
|
|
1287
|
-
isLiquidEnabled: true,
|
|
1288
1194
|
getLiquidTags,
|
|
1289
1195
|
showLiquidErrorInFooter,
|
|
1290
1196
|
onValidationFail,
|
|
@@ -1342,7 +1248,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1342
1248
|
const { rerender } = renderWithIntl({
|
|
1343
1249
|
isGetFormData: false,
|
|
1344
1250
|
isFullMode: true,
|
|
1345
|
-
isLiquidEnabled: true,
|
|
1346
1251
|
getLiquidTags,
|
|
1347
1252
|
emailActions,
|
|
1348
1253
|
templateName: 'New Template',
|
|
@@ -1378,7 +1283,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1378
1283
|
});
|
|
1379
1284
|
|
|
1380
1285
|
it('saves in full mode with create template', async () => {
|
|
1381
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1382
1286
|
const emailActions = {
|
|
1383
1287
|
...defaultProps.emailActions,
|
|
1384
1288
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1412,7 +1316,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1412
1316
|
});
|
|
1413
1317
|
|
|
1414
1318
|
it('saves in full mode with edit template', async () => {
|
|
1415
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1416
1319
|
const emailActions = {
|
|
1417
1320
|
...defaultProps.emailActions,
|
|
1418
1321
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1482,7 +1385,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1482
1385
|
});
|
|
1483
1386
|
|
|
1484
1387
|
it('handles create template error response', async () => {
|
|
1485
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1486
1388
|
const emailActions = {
|
|
1487
1389
|
...defaultProps.emailActions,
|
|
1488
1390
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1517,7 +1419,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1517
1419
|
});
|
|
1518
1420
|
|
|
1519
1421
|
it('handles create template success with getFormdata', async () => {
|
|
1520
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1521
1422
|
const emailActions = {
|
|
1522
1423
|
...defaultProps.emailActions,
|
|
1523
1424
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1554,7 +1455,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1554
1455
|
});
|
|
1555
1456
|
|
|
1556
1457
|
it('handles create template success without getFormdata', async () => {
|
|
1557
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1558
1458
|
const emailActions = {
|
|
1559
1459
|
...defaultProps.emailActions,
|
|
1560
1460
|
transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
|
|
@@ -1590,7 +1490,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1590
1490
|
});
|
|
1591
1491
|
|
|
1592
1492
|
it('saves in library mode with getFormdata', async () => {
|
|
1593
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1594
1493
|
const getFormdata = jest.fn();
|
|
1595
1494
|
|
|
1596
1495
|
// Set subject and content via component interactions
|
|
@@ -1618,7 +1517,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1618
1517
|
});
|
|
1619
1518
|
|
|
1620
1519
|
it('saves in library mode without library module', async () => {
|
|
1621
|
-
mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
|
|
1622
1520
|
const getFormdata = jest.fn();
|
|
1623
1521
|
|
|
1624
1522
|
// Set subject and content via component interactions
|
|
@@ -1758,7 +1656,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1758
1656
|
renderWithIntl({
|
|
1759
1657
|
getLiquidTags: null,
|
|
1760
1658
|
globalActions,
|
|
1761
|
-
isLiquidEnabled: true,
|
|
1762
1659
|
isGetFormData: true,
|
|
1763
1660
|
subject: 'Valid Subject',
|
|
1764
1661
|
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,
|
|
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',
|
|
@@ -31,7 +31,6 @@ import creativesMessages from '../CreativesContainer/messages';
|
|
|
31
31
|
import withCreatives from "../../hoc/withCreatives";
|
|
32
32
|
import UnifiedPreview from "../../v2Components/CommonTestAndPreview/UnifiedPreview";
|
|
33
33
|
import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
|
|
34
|
-
import { validateTags } from "../../utils/tagValidations";
|
|
35
34
|
import injectReducer from '../../utils/injectReducer';
|
|
36
35
|
import v2InAppReducer from './reducer';
|
|
37
36
|
import { v2InAppSagas } from './sagas';
|
|
@@ -58,7 +57,6 @@ import {
|
|
|
58
57
|
import { getCdnUrl } from "../../utils/cdnTransformation";
|
|
59
58
|
import { getCtaObject, hasAnyErrors, getSingleTab } from "./utils";
|
|
60
59
|
import { validateInAppContent } from "../../utils/commonUtils";
|
|
61
|
-
import { hasLiquidSupportFeature } from "../../utils/common";
|
|
62
60
|
import formBuilderMessages from "../../v2Components/FormBuilder/messages";
|
|
63
61
|
import HTMLEditor from "../../v2Components/HtmlEditor";
|
|
64
62
|
import { HTML_EDITOR_VARIANTS } from "../../v2Components/HtmlEditor/constants";
|
|
@@ -1095,9 +1093,7 @@ export const InApp = (props) => {
|
|
|
1095
1093
|
// Skip validation if no tags are available (e.g., in tests or when tags haven't loaded)
|
|
1096
1094
|
const hasTags = tags && tags.length > 0;
|
|
1097
1095
|
|
|
1098
|
-
|
|
1099
|
-
if (isLiquidFlow && hasTags) {
|
|
1100
|
-
// Validate the INAPP content
|
|
1096
|
+
if (hasTags) {
|
|
1101
1097
|
const payload = createPayload();
|
|
1102
1098
|
validateInAppContent(payload, {
|
|
1103
1099
|
currentTab: panes === ANDROID ? 1 : 2, // Convert ANDROID/IOS to tab numbers
|
|
@@ -1106,10 +1102,6 @@ export const InApp = (props) => {
|
|
|
1106
1102
|
getLiquidTags: (content, callback) => globalActions.getLiquidTags(content, callback),
|
|
1107
1103
|
formatMessage,
|
|
1108
1104
|
messages: formBuilderMessages,
|
|
1109
|
-
tagLookupMap: metaEntities?.tagLookupMap || {},
|
|
1110
|
-
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1111
|
-
isLiquidFlow,
|
|
1112
|
-
forwardedTags: {},
|
|
1113
1105
|
skipTags: (tag) => {
|
|
1114
1106
|
// Skip certain tags if needed
|
|
1115
1107
|
const skipRegexes = [
|
|
@@ -1124,98 +1116,11 @@ export const InApp = (props) => {
|
|
|
1124
1116
|
},
|
|
1125
1117
|
singleTab: getSingleTab(accountData),
|
|
1126
1118
|
});
|
|
1127
|
-
} else if (hasTags) {
|
|
1128
|
-
// For non-liquid flow, validate tags using validateTags (only if tags are available)
|
|
1129
|
-
const androidContent = htmlContentAndroid || '';
|
|
1130
|
-
const iosContent = htmlContentIos || '';
|
|
1131
|
-
|
|
1132
|
-
const androidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
|
|
1133
|
-
const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
|
|
1134
|
-
|
|
1135
|
-
let hasErrors = false;
|
|
1136
|
-
const newErrors = {
|
|
1137
|
-
STANDARD_ERROR_MSG: {
|
|
1138
|
-
ANDROID: [],
|
|
1139
|
-
IOS: [],
|
|
1140
|
-
GENERIC: [],
|
|
1141
|
-
},
|
|
1142
|
-
LIQUID_ERROR_MSG: {
|
|
1143
|
-
ANDROID: [],
|
|
1144
|
-
IOS: [],
|
|
1145
|
-
GENERIC: [],
|
|
1146
|
-
},
|
|
1147
|
-
};
|
|
1148
|
-
|
|
1149
|
-
// Validate Android content
|
|
1150
|
-
if (androidSupported && androidContent && androidContent?.trim() !== '') {
|
|
1151
|
-
const validationResponse = validateTags({
|
|
1152
|
-
content: androidContent,
|
|
1153
|
-
tagsParam: tags,
|
|
1154
|
-
injectedTagsParams: injectedTags || {},
|
|
1155
|
-
location,
|
|
1156
|
-
tagModule: getDefaultTags,
|
|
1157
|
-
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1158
|
-
isFullMode,
|
|
1159
|
-
}) || {};
|
|
1160
|
-
|
|
1161
|
-
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
1162
|
-
hasErrors = true;
|
|
1163
|
-
newErrors.LIQUID_ERROR_MSG.ANDROID.push(
|
|
1164
|
-
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
1165
|
-
unsupportedTags: validationResponse.unsupportedTags.join(", "),
|
|
1166
|
-
})
|
|
1167
|
-
);
|
|
1168
|
-
}
|
|
1169
|
-
if (validationResponse?.isBraceError) {
|
|
1170
|
-
hasErrors = true;
|
|
1171
|
-
newErrors.LIQUID_ERROR_MSG.ANDROID.push(
|
|
1172
|
-
formatMessage(globalMessages.unbalanacedCurlyBraces)
|
|
1173
|
-
);
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
// Validate iOS content
|
|
1178
|
-
if (iosSupported && iosContent && iosContent?.trim() !== '') {
|
|
1179
|
-
const validationResponse = validateTags({
|
|
1180
|
-
content: iosContent,
|
|
1181
|
-
tagsParam: tags,
|
|
1182
|
-
injectedTagsParams: injectedTags || {},
|
|
1183
|
-
location,
|
|
1184
|
-
tagModule: getDefaultTags,
|
|
1185
|
-
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1186
|
-
isFullMode,
|
|
1187
|
-
}) || {};
|
|
1188
|
-
|
|
1189
|
-
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
1190
|
-
hasErrors = true;
|
|
1191
|
-
newErrors.LIQUID_ERROR_MSG.IOS.push(
|
|
1192
|
-
formatMessage(globalMessages.unsupportedTagsValidationError, {
|
|
1193
|
-
unsupportedTags: validationResponse.unsupportedTags.join(", "),
|
|
1194
|
-
})
|
|
1195
|
-
);
|
|
1196
|
-
}
|
|
1197
|
-
if (validationResponse?.isBraceError) {
|
|
1198
|
-
hasErrors = true;
|
|
1199
|
-
newErrors.LIQUID_ERROR_MSG.IOS.push(
|
|
1200
|
-
formatMessage(globalMessages.unbalanacedCurlyBraces)
|
|
1201
|
-
);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
if (hasErrors) {
|
|
1206
|
-
setErrorMessage(newErrors);
|
|
1207
|
-
} else {
|
|
1208
|
-
// No errors, proceed with submission
|
|
1209
|
-
onSuccess();
|
|
1210
|
-
}
|
|
1211
1119
|
} else {
|
|
1212
|
-
// No tags available, skip validation and proceed directly
|
|
1213
1120
|
onSuccess();
|
|
1214
1121
|
}
|
|
1215
1122
|
};
|
|
1216
1123
|
|
|
1217
|
-
const isLiquidFlow = hasLiquidSupportFeature();
|
|
1218
|
-
|
|
1219
1124
|
// Check template data to determine editor type (for render decision)
|
|
1220
1125
|
const templateDetails = isFullMode ? editData?.templateDetails : templateData;
|
|
1221
1126
|
const versions = templateDetails?.versions || {};
|
|
@@ -21,18 +21,16 @@ import { getCtaObject } from '../utils';
|
|
|
21
21
|
jest.mock('redux-auth-wrapper/history4/redirect', () => ({
|
|
22
22
|
connectedRouterRedirect: jest.fn((config) => (Component) => Component),
|
|
23
23
|
}));
|
|
24
|
+
import * as commonUtils from '../../../utils/commonUtils';
|
|
24
25
|
|
|
25
26
|
const mockActions = {
|
|
26
27
|
getTemplateInfoById: jest.fn(),
|
|
27
28
|
resetEditTemplate: jest.fn(),
|
|
28
|
-
getTemplateDetails: jest.fn((id,
|
|
29
|
+
getTemplateDetails: jest.fn((id, setSpin) => {
|
|
29
30
|
// Simulate successful template details fetch to prevent loading state
|
|
30
31
|
// The callback is setSpin function, call it with false to stop spinner
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
setTimeout(() => {
|
|
34
|
-
callback(false);
|
|
35
|
-
}, 0);
|
|
32
|
+
if (setSpin && typeof setSpin === 'function') {
|
|
33
|
+
setTimeout(() => setSpin(false), 0);
|
|
36
34
|
}
|
|
37
35
|
}),
|
|
38
36
|
editTemplate: jest.fn(),
|
|
@@ -41,6 +39,9 @@ const mockActions = {
|
|
|
41
39
|
};
|
|
42
40
|
const mockGlobalActions = {
|
|
43
41
|
fetchSchemaForEntity: jest.fn(),
|
|
42
|
+
getLiquidTags: jest.fn((content, callback) =>
|
|
43
|
+
callback({ askAiraResponse: { data: [], errors: [] }, isError: false }),
|
|
44
|
+
),
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
jest.mock('../../../v2Containers/TagList/index.js', () => ({
|
|
@@ -66,7 +67,17 @@ const renderComponent = (props) =>
|
|
|
66
67
|
);
|
|
67
68
|
|
|
68
69
|
describe('Test activity inApp container', () => {
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
jest.restoreAllMocks();
|
|
72
|
+
});
|
|
73
|
+
|
|
69
74
|
it('test case for inApp template update flow', async () => {
|
|
75
|
+
jest
|
|
76
|
+
.spyOn(commonUtils, 'validateInAppContent')
|
|
77
|
+
.mockImplementation((payload, options) => {
|
|
78
|
+
options.onSuccess();
|
|
79
|
+
return Promise.resolve(true);
|
|
80
|
+
});
|
|
70
81
|
renderComponent({
|
|
71
82
|
actions: mockActions,
|
|
72
83
|
globalActions: mockGlobalActions,
|