@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
|
@@ -26,10 +26,6 @@ export default defineMessages({
|
|
|
26
26
|
id: 'creatives.components.FormBuilder.missingTagsValidationError',
|
|
27
27
|
defaultMessage: 'Missing tags: {missingTags}. Please add them to this message.',
|
|
28
28
|
},
|
|
29
|
-
unsupportedTagsValidationError: {
|
|
30
|
-
id: 'creatives.components.FormBuilder.unsupportedTagsValidationError',
|
|
31
|
-
defaultMessage: 'Unsupported tags: {unsupportedTags}. Please remove them from this message.',
|
|
32
|
-
},
|
|
33
29
|
genericTagsValidationError: {
|
|
34
30
|
id: 'creatives.components.FormBuilder.genericTagsValidationError',
|
|
35
31
|
defaultMessage: 'Please check the message content for unsupported/missing tags',
|
|
@@ -42,10 +38,6 @@ export default defineMessages({
|
|
|
42
38
|
id: 'creatives.componentsV2.FormBuilder.missingTags',
|
|
43
39
|
defaultMessage: 'Missing tags are:',
|
|
44
40
|
},
|
|
45
|
-
unsupportedTags: {
|
|
46
|
-
id: 'creatives.componentsV2.FormBuilder.unsupportedTags',
|
|
47
|
-
defaultMessage: 'Unsupported tags are:',
|
|
48
|
-
},
|
|
49
41
|
upload: {
|
|
50
42
|
id: 'creatives.componentsV2.FormBuilder.upload',
|
|
51
43
|
defaultMessage: 'Upload',
|
|
@@ -102,7 +102,6 @@ const HTMLEditor = forwardRef(({
|
|
|
102
102
|
onTagSelect = null,
|
|
103
103
|
onContextChange = null,
|
|
104
104
|
globalActions = null,
|
|
105
|
-
isLiquidEnabled = false, // Controls Liquid tab visibility in ValidationTabs
|
|
106
105
|
isFullMode = true, // Full mode vs library mode - controls layout and visibility
|
|
107
106
|
onErrorAcknowledged = null, // Callback when user clicks redirection icon to acknowledge errors
|
|
108
107
|
onValidationChange = null, // Callback when validation state changes (for parent to track errors)
|
|
@@ -294,7 +293,6 @@ const HTMLEditor = forwardRef(({
|
|
|
294
293
|
enableSanitization: true,
|
|
295
294
|
securityLevel: 'standard',
|
|
296
295
|
apiValidationErrors, // Pass API validation errors to merge with client-side validation
|
|
297
|
-
isFullMode, // Skip liquid validation in standalone/full mode
|
|
298
296
|
}, formatSanitizerMessage, formatValidatorMessage);
|
|
299
297
|
|
|
300
298
|
// Expose validation and content state via ref
|
|
@@ -574,7 +572,6 @@ const HTMLEditor = forwardRef(({
|
|
|
574
572
|
content,
|
|
575
573
|
layout,
|
|
576
574
|
validation,
|
|
577
|
-
isLiquidEnabled,
|
|
578
575
|
editorRef: getActiveEditorRef(),
|
|
579
576
|
handleLabelInsert,
|
|
580
577
|
handleSave,
|
|
@@ -594,7 +591,6 @@ const HTMLEditor = forwardRef(({
|
|
|
594
591
|
content,
|
|
595
592
|
layout,
|
|
596
593
|
validation,
|
|
597
|
-
isLiquidEnabled,
|
|
598
594
|
getActiveEditorRef,
|
|
599
595
|
handleLabelInsert,
|
|
600
596
|
handleSave,
|
|
@@ -783,7 +779,6 @@ HTMLEditor.propTypes = {
|
|
|
783
779
|
onTagSelect: PropTypes.func,
|
|
784
780
|
onContextChange: PropTypes.func, // Deprecated: use globalActions instead
|
|
785
781
|
globalActions: PropTypes.object,
|
|
786
|
-
isLiquidEnabled: PropTypes.bool, // Controls Liquid tab visibility in validation
|
|
787
782
|
isFullMode: PropTypes.bool, // Full mode vs library mode
|
|
788
783
|
onErrorAcknowledged: PropTypes.func, // Callback when user clicks redirection icon to acknowledge errors
|
|
789
784
|
onValidationChange: PropTypes.func, // Callback when validation state changes
|
|
@@ -817,7 +812,6 @@ HTMLEditor.defaultProps = {
|
|
|
817
812
|
onTagSelect: null,
|
|
818
813
|
onContextChange: null,
|
|
819
814
|
globalActions: null, // Redux actions for API calls
|
|
820
|
-
isLiquidEnabled: false,
|
|
821
815
|
isFullMode: true, // Default to full mode
|
|
822
816
|
onErrorAcknowledged: null, // Callback when user clicks redirection icon to acknowledge errors
|
|
823
817
|
onValidationChange: null, // Callback when validation state changes
|
|
@@ -1375,7 +1375,6 @@ describe('HTMLEditor', () => {
|
|
|
1375
1375
|
debounceMs: 500,
|
|
1376
1376
|
enableSanitization: true,
|
|
1377
1377
|
securityLevel: 'standard',
|
|
1378
|
-
isFullMode: true,
|
|
1379
1378
|
},
|
|
1380
1379
|
expect.any(Function),
|
|
1381
1380
|
expect.any(Function)
|
|
@@ -3202,7 +3201,6 @@ describe('HTMLEditor', () => {
|
|
|
3202
3201
|
onTagSelect={onTagSelect}
|
|
3203
3202
|
onContextChange={onContextChange}
|
|
3204
3203
|
globalActions={globalActions}
|
|
3205
|
-
isLiquidEnabled={true}
|
|
3206
3204
|
isFullMode={false}
|
|
3207
3205
|
onErrorAcknowledged={onErrorAcknowledged}
|
|
3208
3206
|
onValidationChange={onValidationChange}
|
|
@@ -3262,20 +3260,6 @@ describe('HTMLEditor', () => {
|
|
|
3262
3260
|
expect(screen.getByTestId('device-toggle')).toBeInTheDocument();
|
|
3263
3261
|
});
|
|
3264
3262
|
|
|
3265
|
-
it('should handle isLiquidEnabled prop', () => {
|
|
3266
|
-
render(
|
|
3267
|
-
<TestWrapper>
|
|
3268
|
-
<HTMLEditor isLiquidEnabled={true} />
|
|
3269
|
-
</TestWrapper>
|
|
3270
|
-
);
|
|
3271
|
-
|
|
3272
|
-
act(() => {
|
|
3273
|
-
jest.runAllTimers();
|
|
3274
|
-
});
|
|
3275
|
-
|
|
3276
|
-
expect(screen.getByTestId('editor-toolbar')).toBeInTheDocument();
|
|
3277
|
-
});
|
|
3278
|
-
|
|
3279
3263
|
it('should handle isFullMode prop', () => {
|
|
3280
3264
|
render(
|
|
3281
3265
|
<TestWrapper>
|
|
@@ -69,7 +69,7 @@ const CodeEditorPaneComponent = ({
|
|
|
69
69
|
}) => {
|
|
70
70
|
const context = useEditorContext();
|
|
71
71
|
const {
|
|
72
|
-
content, validation, variant,
|
|
72
|
+
content, validation, variant,
|
|
73
73
|
} = context || {};
|
|
74
74
|
const { content: contentValue, updateContent } = content || {};
|
|
75
75
|
const editorRef = useRef(null);
|
|
@@ -298,7 +298,6 @@ const CodeEditorPaneComponent = ({
|
|
|
298
298
|
<ValidationErrorDisplay
|
|
299
299
|
validation={validation}
|
|
300
300
|
onErrorClick={onErrorClick}
|
|
301
|
-
isLiquidEnabled={isLiquidEnabled}
|
|
302
301
|
className="code-editor-pane__validation"
|
|
303
302
|
/>
|
|
304
303
|
</div>
|
|
@@ -155,7 +155,7 @@ describe('useValidation', () => {
|
|
|
155
155
|
await Promise.resolve();
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
-
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null
|
|
158
|
+
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null);
|
|
159
159
|
});
|
|
160
160
|
|
|
161
161
|
it('updates validation when content changes', async () => {
|
|
@@ -472,7 +472,7 @@ describe('useValidation', () => {
|
|
|
472
472
|
await Promise.resolve();
|
|
473
473
|
});
|
|
474
474
|
|
|
475
|
-
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'inapp', null
|
|
475
|
+
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'inapp', null);
|
|
476
476
|
});
|
|
477
477
|
|
|
478
478
|
it('defaults to email variant', async () => {
|
|
@@ -487,7 +487,7 @@ describe('useValidation', () => {
|
|
|
487
487
|
await Promise.resolve();
|
|
488
488
|
});
|
|
489
489
|
|
|
490
|
-
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null
|
|
490
|
+
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null);
|
|
491
491
|
});
|
|
492
492
|
});
|
|
493
493
|
|
|
@@ -1242,133 +1242,4 @@ describe('useValidation', () => {
|
|
|
1242
1242
|
});
|
|
1243
1243
|
});
|
|
1244
1244
|
});
|
|
1245
|
-
|
|
1246
|
-
describe('isFullMode - skip liquid validation', () => {
|
|
1247
|
-
it('passes skipLiquidValidation: true to validateHTML when isFullMode is true', async () => {
|
|
1248
|
-
const { validateHTML } = require('../../utils/htmlValidator');
|
|
1249
|
-
|
|
1250
|
-
render(<TestComponent content="<p>Test</p>" options={{ isFullMode: true }} />);
|
|
1251
|
-
|
|
1252
|
-
await act(async () => {
|
|
1253
|
-
jest.advanceTimersByTime(500);
|
|
1254
|
-
await Promise.resolve();
|
|
1255
|
-
await Promise.resolve();
|
|
1256
|
-
await Promise.resolve();
|
|
1257
|
-
});
|
|
1258
|
-
|
|
1259
|
-
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null, { skipLiquidValidation: true });
|
|
1260
|
-
});
|
|
1261
|
-
|
|
1262
|
-
it('excludes API liquid errors from getAllIssues when isFullMode is true', async () => {
|
|
1263
|
-
let validationState;
|
|
1264
|
-
const onStateChange = (state) => { validationState = state; };
|
|
1265
|
-
|
|
1266
|
-
const apiValidationErrors = {
|
|
1267
|
-
liquidErrors: ['Unsupported tag: points_balance'],
|
|
1268
|
-
standardErrors: [],
|
|
1269
|
-
};
|
|
1270
|
-
|
|
1271
|
-
render(
|
|
1272
|
-
<TestComponent
|
|
1273
|
-
content="<p>Test</p>"
|
|
1274
|
-
options={{ apiValidationErrors, isFullMode: true }}
|
|
1275
|
-
onStateChange={onStateChange}
|
|
1276
|
-
/>
|
|
1277
|
-
);
|
|
1278
|
-
|
|
1279
|
-
await act(async () => {
|
|
1280
|
-
jest.advanceTimersByTime(500);
|
|
1281
|
-
await Promise.resolve();
|
|
1282
|
-
await Promise.resolve();
|
|
1283
|
-
await Promise.resolve();
|
|
1284
|
-
});
|
|
1285
|
-
|
|
1286
|
-
await waitFor(() => {
|
|
1287
|
-
expect(validationState).toBeDefined();
|
|
1288
|
-
});
|
|
1289
|
-
|
|
1290
|
-
const issues = validationState.getAllIssues();
|
|
1291
|
-
const liquidIssues = issues.filter((i) => i.source === 'liquid-validator');
|
|
1292
|
-
expect(liquidIssues).toHaveLength(0);
|
|
1293
|
-
});
|
|
1294
|
-
|
|
1295
|
-
it('returns isClean true when only liquid errors exist and isFullMode is true', () => {
|
|
1296
|
-
const apiValidationErrors = {
|
|
1297
|
-
liquidErrors: ['Unsupported tag: points_balance'],
|
|
1298
|
-
standardErrors: [],
|
|
1299
|
-
};
|
|
1300
|
-
|
|
1301
|
-
render(
|
|
1302
|
-
<TestComponent
|
|
1303
|
-
content=""
|
|
1304
|
-
options={{ apiValidationErrors, isFullMode: true }}
|
|
1305
|
-
/>
|
|
1306
|
-
);
|
|
1307
|
-
|
|
1308
|
-
// Before validation runs, isClean should be true because liquid errors are ignored in full mode
|
|
1309
|
-
expect(screen.getByTestId('is-clean')).toHaveTextContent('true');
|
|
1310
|
-
});
|
|
1311
|
-
|
|
1312
|
-
it('does not treat liquid errors as blocking when isFullMode is true', async () => {
|
|
1313
|
-
let validationState;
|
|
1314
|
-
const onStateChange = (state) => { validationState = state; };
|
|
1315
|
-
|
|
1316
|
-
const apiValidationErrors = {
|
|
1317
|
-
liquidErrors: ['Unsupported tag: points_balance'],
|
|
1318
|
-
standardErrors: [],
|
|
1319
|
-
};
|
|
1320
|
-
|
|
1321
|
-
render(
|
|
1322
|
-
<TestComponent
|
|
1323
|
-
content="<p>Valid</p>"
|
|
1324
|
-
options={{ apiValidationErrors, isFullMode: true }}
|
|
1325
|
-
onStateChange={onStateChange}
|
|
1326
|
-
/>
|
|
1327
|
-
);
|
|
1328
|
-
|
|
1329
|
-
await act(async () => {
|
|
1330
|
-
jest.advanceTimersByTime(500);
|
|
1331
|
-
await Promise.resolve();
|
|
1332
|
-
await Promise.resolve();
|
|
1333
|
-
await Promise.resolve();
|
|
1334
|
-
});
|
|
1335
|
-
|
|
1336
|
-
await waitFor(() => {
|
|
1337
|
-
expect(validationState).toBeDefined();
|
|
1338
|
-
});
|
|
1339
|
-
|
|
1340
|
-
expect(validationState.hasBlockingErrors).toBe(false);
|
|
1341
|
-
});
|
|
1342
|
-
|
|
1343
|
-
it('still treats standard API errors as blocking when isFullMode is true', async () => {
|
|
1344
|
-
let validationState;
|
|
1345
|
-
const onStateChange = (state) => { validationState = state; };
|
|
1346
|
-
|
|
1347
|
-
const apiValidationErrors = {
|
|
1348
|
-
liquidErrors: ['Liquid error'],
|
|
1349
|
-
standardErrors: ['Standard error'],
|
|
1350
|
-
};
|
|
1351
|
-
|
|
1352
|
-
render(
|
|
1353
|
-
<TestComponent
|
|
1354
|
-
content="<p>Valid</p>"
|
|
1355
|
-
options={{ apiValidationErrors, isFullMode: true }}
|
|
1356
|
-
onStateChange={onStateChange}
|
|
1357
|
-
/>
|
|
1358
|
-
);
|
|
1359
|
-
|
|
1360
|
-
await act(async () => {
|
|
1361
|
-
jest.advanceTimersByTime(500);
|
|
1362
|
-
await Promise.resolve();
|
|
1363
|
-
await Promise.resolve();
|
|
1364
|
-
await Promise.resolve();
|
|
1365
|
-
});
|
|
1366
|
-
|
|
1367
|
-
await waitFor(() => {
|
|
1368
|
-
expect(validationState).toBeDefined();
|
|
1369
|
-
});
|
|
1370
|
-
|
|
1371
|
-
expect(validationState.hasBlockingErrors).toBe(true);
|
|
1372
|
-
});
|
|
1373
|
-
});
|
|
1374
1245
|
});
|
|
@@ -77,7 +77,6 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
77
77
|
enableSanitization = true,
|
|
78
78
|
securityLevel = 'standard',
|
|
79
79
|
apiValidationErrors = null, // API validation errors from validateLiquidTemplateContent
|
|
80
|
-
isFullMode = false, // When true, skip liquid validation (standalone/full mode)
|
|
81
80
|
} = options;
|
|
82
81
|
|
|
83
82
|
// Validation state
|
|
@@ -138,7 +137,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
138
137
|
|
|
139
138
|
try {
|
|
140
139
|
// 1. HTML Validation
|
|
141
|
-
const htmlValidation = validateHTML(htmlContent, variant, formatValidatorMessage
|
|
140
|
+
const htmlValidation = validateHTML(htmlContent, variant, formatValidatorMessage);
|
|
142
141
|
|
|
143
142
|
// 2. CSS Validation (extract from HTML)
|
|
144
143
|
const cssValidation = extractAndValidateCSS(htmlContent, formatValidatorMessage);
|
|
@@ -207,7 +206,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
207
206
|
},
|
|
208
207
|
}));
|
|
209
208
|
}
|
|
210
|
-
}, [variant, enableSanitization, securityLevel, formatSanitizerMessage, formatValidatorMessage
|
|
209
|
+
}, [variant, enableSanitization, securityLevel, formatSanitizerMessage, formatValidatorMessage]);
|
|
211
210
|
|
|
212
211
|
/**
|
|
213
212
|
* Validates content with debouncing
|
|
@@ -340,7 +339,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
340
339
|
*/
|
|
341
340
|
const getAllIssues = useCallback(() => {
|
|
342
341
|
// API errors (liquid + standard) are blocking – they block Save/Update/Preview/Test
|
|
343
|
-
const apiLiquidErrors = (
|
|
342
|
+
const apiLiquidErrors = (apiValidationErrors?.liquidErrors || []).map((errorMessage) => {
|
|
344
343
|
const extractedLine = extractLineNumberFromMessage(errorMessage);
|
|
345
344
|
return {
|
|
346
345
|
type: VALIDATION_SEVERITY.ERROR,
|
|
@@ -421,20 +420,19 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
421
420
|
|
|
422
421
|
// Ensure we always return an array
|
|
423
422
|
return Array.isArray(allIssues) ? allIssues : [];
|
|
424
|
-
}, [validationState, apiValidationErrors, extractLineNumberFromMessage, content
|
|
423
|
+
}, [validationState, apiValidationErrors, extractLineNumberFromMessage, content]);
|
|
425
424
|
|
|
426
425
|
/**
|
|
427
426
|
* Check if validation is clean (no errors or warnings)
|
|
428
427
|
* Includes API validation errors in the check
|
|
429
428
|
*/
|
|
430
429
|
const isClean = useCallback(() => {
|
|
431
|
-
const
|
|
432
|
-
const hasApiErrors = liquidErrorCount + (apiValidationErrors?.standardErrors?.length || 0) > 0;
|
|
430
|
+
const hasApiErrors = (apiValidationErrors?.liquidErrors?.length || 0) + (apiValidationErrors?.standardErrors?.length || 0) > 0;
|
|
433
431
|
return validationState.summary.totalErrors === 0
|
|
434
432
|
&& validationState.summary.totalWarnings === 0
|
|
435
433
|
&& !validationState.summary.hasSecurityIssues
|
|
436
434
|
&& !hasApiErrors;
|
|
437
|
-
}, [validationState.summary, apiValidationErrors
|
|
435
|
+
}, [validationState.summary, apiValidationErrors]);
|
|
438
436
|
|
|
439
437
|
// Effect to validate content when it changes
|
|
440
438
|
useEffect(() => {
|
|
@@ -450,12 +448,11 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
450
448
|
}
|
|
451
449
|
}, []);
|
|
452
450
|
|
|
453
|
-
const
|
|
454
|
-
const hasApiErrors = hasApiLiquidErrors || (apiValidationErrors?.standardErrors?.length || 0) > 0;
|
|
451
|
+
const hasApiErrors = (apiValidationErrors?.liquidErrors?.length || 0) + (apiValidationErrors?.standardErrors?.length || 0) > 0;
|
|
455
452
|
|
|
456
453
|
const protocolTypes = ['JavaScript Protocol', 'Data URL', 'VBScript Protocol'];
|
|
457
|
-
// Client-side Liquid validation errors are blocking (genuine syntax errors)
|
|
458
|
-
const hasClientSideLiquidErrors =
|
|
454
|
+
// Client-side Liquid validation errors are blocking (genuine syntax errors)
|
|
455
|
+
const hasClientSideLiquidErrors = (validationState.htmlErrors || []).some((e) => e.source === ISSUE_SOURCES.LIQUID && e.severity === VALIDATION_SEVERITY.ERROR);
|
|
459
456
|
const hasBlockingErrors = (validationState.sanitizationWarnings || []).some((w) => BLOCKING_ERROR_RULE_IDS.includes(w.rule)) || (validationState.securityIssues || []).some((s) => protocolTypes.includes(s?.type)) || hasApiErrors || hasClientSideLiquidErrors;
|
|
460
457
|
|
|
461
458
|
return {
|
|
@@ -76,7 +76,7 @@ const CUSTOM_VALIDATIONS = {
|
|
|
76
76
|
* @param {Function} formatMessage - Message formatter function for internationalization
|
|
77
77
|
* @returns {Object} Validation result with errors and warnings
|
|
78
78
|
*/
|
|
79
|
-
export const validateHTML = (html, variant = 'email', formatMessage = defaultMessageFormatter
|
|
79
|
+
export const validateHTML = (html, variant = 'email', formatMessage = defaultMessageFormatter) => {
|
|
80
80
|
if (!html || typeof html !== 'string') {
|
|
81
81
|
return {
|
|
82
82
|
isValid: true,
|
|
@@ -133,9 +133,7 @@ export const validateHTML = (html, variant = 'email', formatMessage = defaultMes
|
|
|
133
133
|
// Always run custom validations and Liquid validation, even if HTMLHint failed
|
|
134
134
|
// This ensures unsafe protocol detection and other critical validations still run
|
|
135
135
|
runCustomValidations(html, variant, results, formatMessage);
|
|
136
|
-
|
|
137
|
-
runLiquidValidation(html, variant, results, formatMessage);
|
|
138
|
-
}
|
|
136
|
+
runLiquidValidation(html, variant, results, formatMessage);
|
|
139
137
|
|
|
140
138
|
return results;
|
|
141
139
|
};
|
|
@@ -2,11 +2,9 @@ export const expectedStateGetLiquidTagsRequest = {
|
|
|
2
2
|
fetchingLiquidTags: true,
|
|
3
3
|
fetchingSchema: true,
|
|
4
4
|
fetchingSchemaError: "",
|
|
5
|
-
liquidTags: [],
|
|
6
5
|
messages: [],
|
|
7
6
|
metaEntities: {
|
|
8
7
|
layouts: [],
|
|
9
|
-
tagLookupMap: {},
|
|
10
8
|
tags: [],
|
|
11
9
|
},
|
|
12
10
|
orgID: "",
|
|
@@ -17,11 +15,9 @@ export const expectedStateGetLiquidTagsFailure = {
|
|
|
17
15
|
fetchingLiquidTags: false,
|
|
18
16
|
fetchingSchema: true,
|
|
19
17
|
fetchingSchemaError: "",
|
|
20
|
-
liquidTags: [],
|
|
21
18
|
messages: [],
|
|
22
19
|
metaEntities: {
|
|
23
20
|
layouts: [],
|
|
24
|
-
tagLookupMap: {},
|
|
25
21
|
tags: [],
|
|
26
22
|
},
|
|
27
23
|
orgID: "",
|
|
@@ -32,11 +28,9 @@ export const expectedStateGetLiquidTagsSuccess = {
|
|
|
32
28
|
fetchingLiquidTags: false,
|
|
33
29
|
fetchingSchema: true,
|
|
34
30
|
fetchingSchemaError: "",
|
|
35
|
-
liquidTags: [],
|
|
36
31
|
messages: [],
|
|
37
32
|
metaEntities: {
|
|
38
33
|
layouts: [],
|
|
39
|
-
tagLookupMap: {},
|
|
40
34
|
tags: [],
|
|
41
35
|
},
|
|
42
36
|
orgID: "",
|
|
@@ -47,11 +41,9 @@ export const expectedStateGetSchemaForEntitySuccessTAG = {
|
|
|
47
41
|
fetchingLiquidTags: false,
|
|
48
42
|
fetchingSchema: false,
|
|
49
43
|
fetchingSchemaError: false,
|
|
50
|
-
liquidTags: [],
|
|
51
44
|
messages: [],
|
|
52
45
|
metaEntities: {
|
|
53
46
|
layouts: undefined,
|
|
54
|
-
tagLookupMap: { undefined: { definition: {} } },
|
|
55
47
|
tags: { standard: { random: "32" } },
|
|
56
48
|
},
|
|
57
49
|
orgID: "",
|
|
@@ -62,11 +54,9 @@ export const expectedStateGetSchemaForEntitySuccess = {
|
|
|
62
54
|
fetchingLiquidTags: false,
|
|
63
55
|
fetchingSchema: false,
|
|
64
56
|
fetchingSchemaError: false,
|
|
65
|
-
liquidTags: [],
|
|
66
57
|
messages: [],
|
|
67
58
|
metaEntities: {
|
|
68
59
|
layouts: undefined,
|
|
69
|
-
tagLookupMap: undefined,
|
|
70
60
|
tags: undefined,
|
|
71
61
|
},
|
|
72
62
|
orgID: "",
|
|
@@ -78,13 +68,9 @@ export const expectedForwardedTags = {
|
|
|
78
68
|
fetchingSchema: false,
|
|
79
69
|
fetchingSchemaError: '',
|
|
80
70
|
injectedTags: undefined,
|
|
81
|
-
liquidTags: [],
|
|
82
71
|
messages: [],
|
|
83
72
|
metaEntities: {
|
|
84
73
|
layouts: [],
|
|
85
|
-
tagLookupMap: {
|
|
86
|
-
|
|
87
|
-
},
|
|
88
74
|
tags: [],
|
|
89
75
|
},
|
|
90
76
|
orgID: "",
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Created by vivek on 22/5/17.
|
|
3
3
|
*/
|
|
4
|
-
import { fromJS
|
|
4
|
+
import { fromJS } from 'immutable';
|
|
5
5
|
import _ from 'lodash';
|
|
6
6
|
import * as types from './constants';
|
|
7
7
|
import initialState from '../../initialState';
|
|
8
8
|
import { FAILURE } from '../App/constants';
|
|
9
|
-
import { TAG } from '../Whatsapp/constants';
|
|
10
|
-
import { getTagMapValue, getForwardedMapValues, getLoyaltyTagsMapValue } from '../../utils/tagValidations';
|
|
11
9
|
|
|
12
10
|
function capReducer(state = fromJS(initialState.cap), action) {
|
|
13
11
|
switch (action.type) {
|
|
@@ -98,39 +96,6 @@ function capReducer(state = fromJS(initialState.cap), action) {
|
|
|
98
96
|
return state
|
|
99
97
|
.set('fetchingLiquidTags', false);
|
|
100
98
|
case types.GET_SCHEMA_FOR_ENTITY_SUCCESS: {
|
|
101
|
-
//Process standard tags
|
|
102
|
-
const standardTagMapInitial = _.keyBy(
|
|
103
|
-
action?.data?.metaEntities?.standard,
|
|
104
|
-
item => item?.definition?.value
|
|
105
|
-
);
|
|
106
|
-
// Mapping only the `definition` object instead of the entire item, to reduce space used
|
|
107
|
-
const standardTagMap = _.mapValues(standardTagMapInitial, item => ({
|
|
108
|
-
definition: item?.definition ?? {},
|
|
109
|
-
}));
|
|
110
|
-
|
|
111
|
-
// Process custom tags
|
|
112
|
-
const customSubtags = getTagMapValue(action?.data?.metaEntities?.custom)
|
|
113
|
-
// Process extended tags
|
|
114
|
-
const extendedSubtags = getTagMapValue(action?.data?.metaEntities?.extended);
|
|
115
|
-
|
|
116
|
-
const loyaltySubTagsData = getLoyaltyTagsMapValue(action?.data?.metaEntities?.loyaltyTags);
|
|
117
|
-
|
|
118
|
-
const getExistingTagLookupMap = (state) => {
|
|
119
|
-
if (!state || !state.get) return {};
|
|
120
|
-
const tagLookupMap = state.getIn(['metaEntities', 'tagLookupMap']);
|
|
121
|
-
return state.get('metaEntities') && ImmutableMap.isMap(tagLookupMap)
|
|
122
|
-
? tagLookupMap.toJS()
|
|
123
|
-
: {};
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
// Combine all maps
|
|
127
|
-
const combinedTagMap = {
|
|
128
|
-
...standardTagMap,
|
|
129
|
-
...customSubtags,
|
|
130
|
-
...extendedSubtags,
|
|
131
|
-
...loyaltySubTagsData,
|
|
132
|
-
...getExistingTagLookupMap(state),
|
|
133
|
-
};
|
|
134
99
|
const stateMeta = state.get("metaEntities");
|
|
135
100
|
return state
|
|
136
101
|
.set('fetchingSchema', false)
|
|
@@ -138,7 +103,6 @@ function capReducer(state = fromJS(initialState.cap), action) {
|
|
|
138
103
|
.set('metaEntities', {
|
|
139
104
|
layouts: action.data && action.entityType === 'LAYOUT' ? action.data.metaEntities : stateMeta?.layouts,
|
|
140
105
|
tags: action.data && action.entityType === 'TAG' ? action.data.metaEntities : stateMeta?.tags,
|
|
141
|
-
tagLookupMap: action?.data && action?.entityType === TAG ? combinedTagMap : stateMeta?.tagLookupMap,
|
|
142
106
|
})
|
|
143
107
|
.set('fetchingSchemaError', false);
|
|
144
108
|
}
|
|
@@ -146,7 +110,6 @@ function capReducer(state = fromJS(initialState.cap), action) {
|
|
|
146
110
|
return state.set('metaEntities', {
|
|
147
111
|
layouts: [],
|
|
148
112
|
tags: [],
|
|
149
|
-
tagLookupMap: {},
|
|
150
113
|
});
|
|
151
114
|
// eslint-disable-next-line no-case-declarations
|
|
152
115
|
case types.HIDE_TAGS:
|
|
@@ -154,23 +117,8 @@ function capReducer(state = fromJS(initialState.cap), action) {
|
|
|
154
117
|
metaEntities.tags.standard = _.filter(state.get('metaEntities').tags.standard, (tag) => action.tagList.indexOf(tag.definition.value) === -1);
|
|
155
118
|
metaEntities.tags.custom = _.filter(state.get('metaEntities').tags.custom, (tag) => action.tagList.indexOf(tag.name) === -1);
|
|
156
119
|
return state.setIn(['metaEntities'], metaEntities);
|
|
157
|
-
case types.SET_INJECTED_TAGS:
|
|
158
|
-
|
|
159
|
-
// Deep clone the tagLookupMap to avoid direct mutations
|
|
160
|
-
let updatedMetaEntitiesTagLookUp = _.cloneDeep(state.getIn(['metaEntities', 'tagLookupMap']));
|
|
161
|
-
const formattedInjectedTags = getForwardedMapValues(action?.injectedTags);
|
|
162
|
-
// Merge the injectedTags with the existing tagLookupMap
|
|
163
|
-
updatedMetaEntitiesTagLookUp = {
|
|
164
|
-
...formattedInjectedTags || {},
|
|
165
|
-
...updatedMetaEntitiesTagLookUp || {},
|
|
166
|
-
};
|
|
167
|
-
return state.set("injectedTags", action.injectedTags).setIn(
|
|
168
|
-
["metaEntities"],
|
|
169
|
-
fromJS({
|
|
170
|
-
...state.get("metaEntities"),
|
|
171
|
-
tagLookupMap: updatedMetaEntitiesTagLookUp
|
|
172
|
-
})
|
|
173
|
-
);
|
|
120
|
+
case types.SET_INJECTED_TAGS:
|
|
121
|
+
return state.set("injectedTags", action.injectedTags);
|
|
174
122
|
case types.GET_TOPBAR_MENU_DATA_REQUEST:
|
|
175
123
|
return state.set('topbarMenuData', fromJS({ status: 'request' }));
|
|
176
124
|
case types.GET_TOPBAR_MENU_DATA_SUCCESS:
|
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
expectedStateGetSchemaForEntitySuccessTAG,
|
|
22
22
|
expectedStateGetSchemaForEntitySuccess,
|
|
23
23
|
} from '../mockData';
|
|
24
|
-
import { TAG } from '../../Whatsapp/constants';
|
|
25
24
|
import { loadItem } from '../../../services/localStorageApi';
|
|
26
25
|
|
|
27
26
|
|
|
@@ -118,104 +117,3 @@ describe('should handle GET_SUPPORT_VIDEOS_CONFIG', () => {
|
|
|
118
117
|
expect(reducer(mockedInitialState, action).toJS())?.fetchingSchema?.toEqual(true);
|
|
119
118
|
});
|
|
120
119
|
});
|
|
121
|
-
|
|
122
|
-
describe('GET_SCHEMA_FOR_ENTITY_SUCCESS handler', () => {
|
|
123
|
-
it.concurrent('should handle existing tagLookupMap correctly when metaEntities and tagLookupMap exist', () => {
|
|
124
|
-
const initialStateTest = fromJS({
|
|
125
|
-
token: loadItem('token') || '',
|
|
126
|
-
orgID: loadItem('orgID') || '',
|
|
127
|
-
messages: [],
|
|
128
|
-
metaEntities: {
|
|
129
|
-
tags: [{ id: 1, name: 'tag1' }],
|
|
130
|
-
layouts: ['layout1', 'layout2'],
|
|
131
|
-
tagLookupMap: {
|
|
132
|
-
existingTag: { definition: { value: 'existing' } },
|
|
133
|
-
anotherTag: { definition: { value: 'another' } },
|
|
134
|
-
},
|
|
135
|
-
standard: [],
|
|
136
|
-
},
|
|
137
|
-
liquidTags: [],
|
|
138
|
-
fetchingLiquidTags: false,
|
|
139
|
-
fetchingSchema: false,
|
|
140
|
-
fetchingSchemaError: '',
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
let action = {
|
|
144
|
-
type: GET_SCHEMA_FOR_ENTITY_SUCCESS,
|
|
145
|
-
entityType: TAG,
|
|
146
|
-
data: {
|
|
147
|
-
metaEntities: {
|
|
148
|
-
standard: [],
|
|
149
|
-
custom: [],
|
|
150
|
-
extended: [],
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
let newState = reducer(initialStateTest, action);
|
|
156
|
-
let metaEntities = newState.get('metaEntities');
|
|
157
|
-
|
|
158
|
-
expect(metaEntities).toEqual(expect.objectContaining({
|
|
159
|
-
tagLookupMap: expect.objectContaining({
|
|
160
|
-
existingTag: expect.objectContaining({
|
|
161
|
-
definition: expect.objectContaining({
|
|
162
|
-
value: 'existing',
|
|
163
|
-
}),
|
|
164
|
-
}),
|
|
165
|
-
}),
|
|
166
|
-
}));
|
|
167
|
-
|
|
168
|
-
action = {
|
|
169
|
-
type: GET_SCHEMA_FOR_ENTITY_SUCCESS,
|
|
170
|
-
entityType: 'LAYOUT',
|
|
171
|
-
data: {
|
|
172
|
-
metaEntities: {
|
|
173
|
-
layouts: ['layout1', 'layout2'],
|
|
174
|
-
standard: [],
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
};
|
|
178
|
-
newState = reducer(initialStateTest, action);
|
|
179
|
-
metaEntities = newState.get('metaEntities');
|
|
180
|
-
expect(metaEntities).toEqual(expect.objectContaining({
|
|
181
|
-
layouts: expect.objectContaining({
|
|
182
|
-
layouts: expect.arrayContaining(['layout1', 'layout2']),
|
|
183
|
-
}),
|
|
184
|
-
}));
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it.concurrent('should handle non-existent tagLookupMap by returning empty object', () => {
|
|
188
|
-
const initialStateTest = fromJS({
|
|
189
|
-
token: loadItem('token') || '',
|
|
190
|
-
orgID: loadItem('orgID') || '',
|
|
191
|
-
messages: [],
|
|
192
|
-
metaEntities: {
|
|
193
|
-
tagLookupMap: {},
|
|
194
|
-
},
|
|
195
|
-
liquidTags: [],
|
|
196
|
-
fetchingLiquidTags: false,
|
|
197
|
-
fetchingSchema: false,
|
|
198
|
-
fetchingSchemaError: '',
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
const action = {
|
|
202
|
-
type: GET_SCHEMA_FOR_ENTITY_SUCCESS,
|
|
203
|
-
entityType: TAG,
|
|
204
|
-
data: {
|
|
205
|
-
metaEntities: {
|
|
206
|
-
standard: [],
|
|
207
|
-
custom: [],
|
|
208
|
-
extended: [],
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const newState = reducer(initialStateTest, action);
|
|
214
|
-
const metaEntities = newState.get('metaEntities');
|
|
215
|
-
|
|
216
|
-
// Updated assertions to handle plain object
|
|
217
|
-
expect(metaEntities).toBeDefined();
|
|
218
|
-
expect(metaEntities.tagLookupMap).toBeDefined();
|
|
219
|
-
expect(metaEntities.tagLookupMap).toEqual({});
|
|
220
|
-
});
|
|
221
|
-
});
|