@capillarytech/creatives-library 8.0.249 → 8.0.250-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/assets/Android.png +0 -0
- package/assets/iOS.png +0 -0
- package/constants/unified.js +2 -1
- package/initialReducer.js +2 -0
- package/package.json +1 -1
- package/services/api.js +10 -0
- package/services/tests/api.test.js +18 -0
- package/utils/common.js +5 -0
- package/utils/commonUtils.js +28 -5
- package/utils/tests/commonUtil.test.js +224 -0
- package/utils/transformTemplateConfig.js +0 -10
- package/v2Components/CapDeviceContent/index.js +61 -56
- package/v2Components/CapTagList/index.js +6 -1
- package/v2Components/CapTagListWithInput/index.js +5 -1
- package/v2Components/CapTagListWithInput/messages.js +1 -1
- package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
- package/v2Components/ErrorInfoNote/index.js +452 -72
- package/v2Components/ErrorInfoNote/messages.js +22 -0
- package/v2Components/ErrorInfoNote/style.scss +280 -4
- package/v2Components/FormBuilder/tests/index.test.js +13 -4
- package/v2Components/HtmlEditor/HTMLEditor.js +640 -94
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +874 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1167 -133
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
- package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
- package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +13 -101
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -139
- package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
- package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
- package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -0
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
- package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
- package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
- package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +3 -6
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +11 -13
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +49 -31
- package/v2Components/HtmlEditor/components/ValidationPanel/index.js +68 -39
- package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +254 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/index.js +391 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
- package/v2Components/HtmlEditor/constants.js +42 -20
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.apiErrors.test.js +795 -0
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
- package/v2Components/HtmlEditor/hooks/useValidation.js +189 -53
- package/v2Components/HtmlEditor/index.js +1 -1
- package/v2Components/HtmlEditor/messages.js +95 -85
- package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +94 -45
- package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
- package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +134 -102
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
- package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
- package/v2Components/MobilePushPreviewV2/index.js +32 -7
- package/v2Components/TemplatePreview/_templatePreview.scss +44 -24
- package/v2Components/TemplatePreview/index.js +47 -32
- package/v2Components/TemplatePreview/messages.js +4 -0
- package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
- package/v2Containers/BeeEditor/index.js +172 -90
- package/v2Containers/BeePopupEditor/constants.js +10 -0
- package/v2Containers/BeePopupEditor/index.js +193 -0
- package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -51
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +163 -13
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
- package/v2Containers/CreativesContainer/constants.js +1 -0
- package/v2Containers/CreativesContainer/index.js +239 -46
- package/v2Containers/CreativesContainer/messages.js +8 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +106 -0
- package/v2Containers/Email/actions.js +7 -0
- package/v2Containers/Email/constants.js +5 -1
- package/v2Containers/Email/index.js +222 -27
- package/v2Containers/Email/messages.js +32 -0
- package/v2Containers/Email/reducer.js +12 -1
- package/v2Containers/Email/sagas.js +61 -7
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
- package/v2Containers/Email/tests/sagas.test.js +320 -29
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1321 -0
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +210 -15
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +1749 -0
- package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
- package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
- package/v2Containers/EmailWrapper/constants.js +2 -0
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +629 -77
- package/v2Containers/EmailWrapper/index.js +103 -23
- package/v2Containers/EmailWrapper/messages.js +61 -1
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +643 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +594 -77
- package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
- package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
- package/v2Containers/InApp/actions.js +7 -0
- package/v2Containers/InApp/constants.js +20 -4
- package/v2Containers/InApp/index.js +802 -359
- package/v2Containers/InApp/index.scss +4 -3
- package/v2Containers/InApp/messages.js +7 -3
- package/v2Containers/InApp/reducer.js +21 -3
- package/v2Containers/InApp/sagas.js +29 -9
- package/v2Containers/InApp/selectors.js +25 -5
- package/v2Containers/InApp/tests/index.test.js +154 -50
- package/v2Containers/InApp/tests/reducer.test.js +34 -0
- package/v2Containers/InApp/tests/sagas.test.js +61 -9
- package/v2Containers/InApp/tests/selectors.test.js +612 -0
- package/v2Containers/InAppWrapper/components/InAppWrapperView.js +162 -0
- package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
- package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +9 -0
- package/v2Containers/InAppWrapper/constants.js +16 -0
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
- package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
- package/v2Containers/InAppWrapper/index.js +148 -0
- package/v2Containers/InAppWrapper/messages.js +49 -0
- package/v2Containers/InappAdvance/index.js +1099 -0
- package/v2Containers/InappAdvance/index.scss +10 -0
- package/v2Containers/InappAdvance/tests/index.test.js +448 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
- package/v2Containers/TagList/index.js +62 -19
- package/v2Containers/Templates/_templates.scss +60 -1
- package/v2Containers/Templates/index.js +89 -4
- package/v2Containers/Templates/messages.js +4 -0
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
- package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import {
|
|
8
8
|
validateHTML,
|
|
9
9
|
validateCSS,
|
|
10
|
-
extractAndValidateCSS
|
|
10
|
+
extractAndValidateCSS,
|
|
11
11
|
} from '../htmlValidator';
|
|
12
12
|
|
|
13
13
|
// Mock liquidTemplateSupport module
|
|
@@ -15,8 +15,8 @@ jest.mock('../liquidTemplateSupport', () => ({
|
|
|
15
15
|
validateLiquidHTML: jest.fn(() => ({
|
|
16
16
|
errors: [],
|
|
17
17
|
warnings: [],
|
|
18
|
-
info: []
|
|
19
|
-
}))
|
|
18
|
+
info: [],
|
|
19
|
+
})),
|
|
20
20
|
}));
|
|
21
21
|
|
|
22
22
|
// Mock console.warn to avoid noise in tests
|
|
@@ -80,7 +80,7 @@ describe('Enhanced htmlValidator Tests', () => {
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
it('handles very long HTML content', () => {
|
|
83
|
-
const longHtml =
|
|
83
|
+
const longHtml = `<p>${'a'.repeat(50000)}</p>`;
|
|
84
84
|
const result = validateHTML(longHtml);
|
|
85
85
|
|
|
86
86
|
expect(result).toBeDefined();
|
|
@@ -109,24 +109,36 @@ describe('Enhanced htmlValidator Tests', () => {
|
|
|
109
109
|
const html = '<a href="javascript:alert(1)">Click</a>';
|
|
110
110
|
const result = validateHTML(html);
|
|
111
111
|
|
|
112
|
+
// Unsafe protocols are BLOCKING ERRORS (sanitizer.dangerousProtocolDetected)
|
|
112
113
|
expect(result.errors.length).toBeGreaterThan(0);
|
|
113
114
|
expect(result.isValid).toBe(false);
|
|
115
|
+
// Verify it's the correct rule
|
|
116
|
+
const error = result.errors.find((e) => e.rule === 'sanitizer.dangerousProtocolDetected');
|
|
117
|
+
expect(error).toBeDefined();
|
|
114
118
|
});
|
|
115
119
|
|
|
116
120
|
it('detects potentially unsafe data protocol', () => {
|
|
117
121
|
const html = '<img src="data:image/svg+xml,<svg>...">';
|
|
118
122
|
const result = validateHTML(html);
|
|
119
123
|
|
|
124
|
+
// Unsafe protocols are BLOCKING ERRORS (sanitizer.dangerousProtocolDetected)
|
|
120
125
|
expect(result.errors.length).toBeGreaterThan(0);
|
|
121
126
|
expect(result.isValid).toBe(false);
|
|
127
|
+
// Verify it's the correct rule
|
|
128
|
+
const error = result.errors.find((e) => e.rule === 'sanitizer.dangerousProtocolDetected');
|
|
129
|
+
expect(error).toBeDefined();
|
|
122
130
|
});
|
|
123
131
|
|
|
124
132
|
it('detects potentially unsafe vbscript protocol', () => {
|
|
125
133
|
const html = '<a href="vbscript:msgbox(1)">Click</a>';
|
|
126
134
|
const result = validateHTML(html);
|
|
127
135
|
|
|
136
|
+
// Unsafe protocols are BLOCKING ERRORS (sanitizer.dangerousProtocolDetected)
|
|
128
137
|
expect(result.errors.length).toBeGreaterThan(0);
|
|
129
138
|
expect(result.isValid).toBe(false);
|
|
139
|
+
// Verify it's the correct rule
|
|
140
|
+
const error = result.errors.find((e) => e.rule === 'sanitizer.dangerousProtocolDetected');
|
|
141
|
+
expect(error).toBeDefined();
|
|
130
142
|
});
|
|
131
143
|
|
|
132
144
|
it('handles script tags appropriately', () => {
|
|
@@ -417,7 +429,7 @@ describe('Enhanced htmlValidator Tests', () => {
|
|
|
417
429
|
});
|
|
418
430
|
|
|
419
431
|
it('handles very long CSS content', () => {
|
|
420
|
-
const longCSS =
|
|
432
|
+
const longCSS = `.long { ${'color: red; '.repeat(1000)}}`;
|
|
421
433
|
const result = validateCSS(longCSS);
|
|
422
434
|
|
|
423
435
|
expect(result).toBeDefined();
|
|
@@ -436,8 +448,10 @@ describe('Enhanced htmlValidator Tests', () => {
|
|
|
436
448
|
const result = validateCSS(css);
|
|
437
449
|
|
|
438
450
|
expect(result).toBeDefined();
|
|
439
|
-
|
|
440
|
-
expect(result.
|
|
451
|
+
// CSS validation issues are WARNINGS (not blocking errors)
|
|
452
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
453
|
+
// isValid can be true if only warnings exist (warnings don't block)
|
|
454
|
+
expect(typeof result.isValid).toBe('boolean');
|
|
441
455
|
});
|
|
442
456
|
|
|
443
457
|
it('detects empty rules', () => {
|
|
@@ -448,8 +462,10 @@ describe('Enhanced htmlValidator Tests', () => {
|
|
|
448
462
|
const result = validateCSS(css);
|
|
449
463
|
|
|
450
464
|
expect(result).toBeDefined();
|
|
451
|
-
|
|
452
|
-
expect(result.
|
|
465
|
+
// CSS validation issues are WARNINGS (not blocking errors)
|
|
466
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
467
|
+
// isValid can be true if only warnings exist (warnings don't block)
|
|
468
|
+
expect(typeof result.isValid).toBe('boolean');
|
|
453
469
|
});
|
|
454
470
|
});
|
|
455
471
|
});
|
|
@@ -536,8 +552,10 @@ describe('Enhanced htmlValidator Tests', () => {
|
|
|
536
552
|
const result = extractAndValidateCSS(html);
|
|
537
553
|
|
|
538
554
|
expect(result).toBeDefined();
|
|
539
|
-
|
|
540
|
-
expect(result.
|
|
555
|
+
// CSS validation issues are WARNINGS (not blocking errors)
|
|
556
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
557
|
+
// isValid can be true if only warnings exist (warnings don't block)
|
|
558
|
+
expect(typeof result.isValid).toBe('boolean');
|
|
541
559
|
});
|
|
542
560
|
});
|
|
543
561
|
|
|
@@ -576,7 +594,7 @@ describe('Enhanced htmlValidator Tests', () => {
|
|
|
576
594
|
const mockHTMLHint = {
|
|
577
595
|
verify: jest.fn(() => {
|
|
578
596
|
throw new Error('HTMLHint validation failed');
|
|
579
|
-
})
|
|
597
|
+
}),
|
|
580
598
|
};
|
|
581
599
|
|
|
582
600
|
// Temporarily replace HTMLHint
|
|
@@ -585,9 +603,13 @@ describe('Enhanced htmlValidator Tests', () => {
|
|
|
585
603
|
const html = '<div>Test content</div>';
|
|
586
604
|
const result = validateHTML(html);
|
|
587
605
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
expect(result
|
|
606
|
+
// HTMLHint errors are caught and handled gracefully - result should still be defined
|
|
607
|
+
// When HTMLHint throws, it's caught and a warning is added (not an error)
|
|
608
|
+
expect(result).toBeDefined();
|
|
609
|
+
expect(typeof result.isValid).toBe('boolean');
|
|
610
|
+
// HTMLHint errors are converted to warnings in the catch block
|
|
611
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
612
|
+
expect(result.warnings[0].message).toContain('HTMLHint validation failed');
|
|
591
613
|
|
|
592
614
|
// Restore original HTMLHint
|
|
593
615
|
require('htmlhint').HTMLHint = originalHTMLHint;
|
|
@@ -640,10 +662,9 @@ describe('Enhanced htmlValidator Tests', () => {
|
|
|
640
662
|
});
|
|
641
663
|
|
|
642
664
|
it('falls back to default formatter when custom formatter fails', () => {
|
|
643
|
-
const failingFormatter = jest.fn((key, values) =>
|
|
665
|
+
const failingFormatter = jest.fn((key, values) =>
|
|
644
666
|
// Don't throw error, just return the key as fallback behavior
|
|
645
|
-
|
|
646
|
-
});
|
|
667
|
+
key);
|
|
647
668
|
|
|
648
669
|
const html = '<a href="javascript:alert(1)">Unsafe link</a>';
|
|
649
670
|
const result = validateHTML(html, 'email', failingFormatter);
|
|
@@ -721,8 +742,10 @@ line4`;
|
|
|
721
742
|
const css = '.class { }';
|
|
722
743
|
const result = validateCSS(css);
|
|
723
744
|
|
|
724
|
-
|
|
725
|
-
expect(result.
|
|
745
|
+
// CSS validation issues are WARNINGS (not blocking errors)
|
|
746
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
747
|
+
// isValid can be true if only warnings exist (warnings don't block)
|
|
748
|
+
expect(typeof result.isValid).toBe('boolean');
|
|
726
749
|
});
|
|
727
750
|
|
|
728
751
|
it('handles multiple consecutive unsafe protocols', () => {
|
|
@@ -755,9 +778,15 @@ line4`;
|
|
|
755
778
|
// Get the mocked module and set it to return specific results
|
|
756
779
|
const { validateLiquidHTML } = require('../liquidTemplateSupport');
|
|
757
780
|
validateLiquidHTML.mockImplementation(() => ({
|
|
758
|
-
errors: [{
|
|
759
|
-
|
|
760
|
-
|
|
781
|
+
errors: [{
|
|
782
|
+
type: 'error', message: 'Liquid error', line: 1, column: 1, rule: 'liquid-test', severity: 'error', source: 'liquid',
|
|
783
|
+
}],
|
|
784
|
+
warnings: [{
|
|
785
|
+
type: 'warning', message: 'Liquid warning', line: 1, column: 1, rule: 'liquid-test', severity: 'warning', source: 'liquid',
|
|
786
|
+
}],
|
|
787
|
+
info: [{
|
|
788
|
+
type: 'info', message: 'Liquid info', line: 1, column: 1, rule: 'liquid-test', severity: 'info', source: 'liquid',
|
|
789
|
+
}],
|
|
761
790
|
}));
|
|
762
791
|
|
|
763
792
|
const html = '<div>{{ liquid.template }}</div>';
|
|
@@ -790,7 +819,9 @@ line4`;
|
|
|
790
819
|
// Get the mocked module and set it to return only some result types
|
|
791
820
|
const { validateLiquidHTML } = require('../liquidTemplateSupport');
|
|
792
821
|
validateLiquidHTML.mockImplementation(() => ({
|
|
793
|
-
errors: [{
|
|
822
|
+
errors: [{
|
|
823
|
+
type: 'error', message: 'Liquid error', line: 1, column: 1, rule: 'liquid-test', severity: 'error', source: 'liquid',
|
|
824
|
+
}],
|
|
794
825
|
// Missing warnings and info arrays
|
|
795
826
|
}));
|
|
796
827
|
|
|
@@ -808,7 +839,9 @@ line4`;
|
|
|
808
839
|
// Get the mocked module and set it to return only warnings
|
|
809
840
|
const { validateLiquidHTML } = require('../liquidTemplateSupport');
|
|
810
841
|
validateLiquidHTML.mockImplementation(() => ({
|
|
811
|
-
warnings: [{
|
|
842
|
+
warnings: [{
|
|
843
|
+
type: 'warning', message: 'Liquid warning', line: 1, column: 1, rule: 'liquid-test', severity: 'warning', source: 'liquid',
|
|
844
|
+
}],
|
|
812
845
|
}));
|
|
813
846
|
|
|
814
847
|
const html = '<div>{{ liquid.template }}</div>';
|
|
@@ -825,7 +858,9 @@ line4`;
|
|
|
825
858
|
// Get the mocked module and set it to return only info
|
|
826
859
|
const { validateLiquidHTML } = require('../liquidTemplateSupport');
|
|
827
860
|
validateLiquidHTML.mockImplementation(() => ({
|
|
828
|
-
info: [{
|
|
861
|
+
info: [{
|
|
862
|
+
type: 'info', message: 'Liquid info', line: 1, column: 1, rule: 'liquid-test', severity: 'info', source: 'liquid',
|
|
863
|
+
}],
|
|
829
864
|
}));
|
|
830
865
|
|
|
831
866
|
const html = '<div>{{ liquid.template }}</div>';
|
|
@@ -851,8 +886,10 @@ line4`;
|
|
|
851
886
|
`;
|
|
852
887
|
const result = validateCSS(css);
|
|
853
888
|
|
|
854
|
-
|
|
855
|
-
expect(result.
|
|
889
|
+
// CSS validation issues are WARNINGS (not blocking errors)
|
|
890
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
891
|
+
// isValid can be true if only warnings exist (warnings don't block)
|
|
892
|
+
expect(typeof result.isValid).toBe('boolean');
|
|
856
893
|
});
|
|
857
894
|
|
|
858
895
|
it('handles CSS with nested braces', () => {
|
|
@@ -863,8 +900,10 @@ line4`;
|
|
|
863
900
|
`;
|
|
864
901
|
const result = validateCSS(css);
|
|
865
902
|
|
|
866
|
-
|
|
867
|
-
expect(result.
|
|
903
|
+
// CSS validation issues are WARNINGS (not blocking errors)
|
|
904
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
905
|
+
// isValid can be true if only warnings exist (warnings don't block)
|
|
906
|
+
expect(typeof result.isValid).toBe('boolean');
|
|
868
907
|
});
|
|
869
908
|
|
|
870
909
|
it('handles CSS with mixed valid and invalid rules', () => {
|
|
@@ -876,9 +915,11 @@ line4`;
|
|
|
876
915
|
`;
|
|
877
916
|
const result = validateCSS(css);
|
|
878
917
|
|
|
879
|
-
|
|
880
|
-
// Should detect at least one empty rule
|
|
881
|
-
expect(result.
|
|
918
|
+
// CSS validation issues are WARNINGS (not blocking errors)
|
|
919
|
+
// Should detect at least one empty rule as warning
|
|
920
|
+
expect(result.warnings.length).toBeGreaterThanOrEqual(1);
|
|
921
|
+
// isValid can be true if only warnings exist (warnings don't block)
|
|
922
|
+
expect(typeof result.isValid).toBe('boolean');
|
|
882
923
|
});
|
|
883
924
|
|
|
884
925
|
it('handles CSS validation exception scenarios', () => {
|
|
@@ -927,8 +968,10 @@ line4`;
|
|
|
927
968
|
`;
|
|
928
969
|
const result = extractAndValidateCSS(html);
|
|
929
970
|
|
|
930
|
-
|
|
931
|
-
expect(result.
|
|
971
|
+
// CSS validation issues are WARNINGS (not blocking errors)
|
|
972
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
973
|
+
// isValid can be true if only warnings exist (warnings don't block)
|
|
974
|
+
expect(typeof result.isValid).toBe('boolean');
|
|
932
975
|
});
|
|
933
976
|
|
|
934
977
|
it('handles style tags with complex CSS', () => {
|
|
@@ -943,17 +986,21 @@ line4`;
|
|
|
943
986
|
`;
|
|
944
987
|
const result = extractAndValidateCSS(html);
|
|
945
988
|
|
|
946
|
-
|
|
947
|
-
expect(result.
|
|
989
|
+
// CSS validation issues are WARNINGS (not blocking errors)
|
|
990
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
991
|
+
// isValid can be true if only warnings exist (warnings don't block)
|
|
992
|
+
expect(typeof result.isValid).toBe('boolean');
|
|
948
993
|
});
|
|
949
994
|
|
|
950
995
|
it('correctly identifies unclosed style tags', () => {
|
|
951
996
|
const html = '<style>.test { color: red; }';
|
|
952
997
|
const result = extractAndValidateCSS(html);
|
|
953
998
|
|
|
954
|
-
|
|
955
|
-
expect(result.
|
|
956
|
-
expect(result.
|
|
999
|
+
// CSS validation issues are WARNINGS (not blocking errors)
|
|
1000
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
1001
|
+
expect(result.warnings.some((warning) => warning.rule === 'unclosed-style-tag')).toBe(true);
|
|
1002
|
+
// isValid can be true if only warnings exist (warnings don't block)
|
|
1003
|
+
expect(typeof result.isValid).toBe('boolean');
|
|
957
1004
|
});
|
|
958
1005
|
|
|
959
1006
|
it('handles multiple unclosed style tags', () => {
|
|
@@ -964,8 +1011,10 @@ line4`;
|
|
|
964
1011
|
`;
|
|
965
1012
|
const result = extractAndValidateCSS(html);
|
|
966
1013
|
|
|
967
|
-
|
|
968
|
-
expect(result.
|
|
1014
|
+
// CSS validation issues are WARNINGS (not blocking errors)
|
|
1015
|
+
expect(result.warnings.length).toBeGreaterThan(0);
|
|
1016
|
+
// isValid can be true if only warnings exist (warnings don't block)
|
|
1017
|
+
expect(typeof result.isValid).toBe('boolean');
|
|
969
1018
|
});
|
|
970
1019
|
});
|
|
971
1020
|
|
|
@@ -1017,10 +1066,10 @@ line4`;
|
|
|
1017
1066
|
'<table><tr><td>Table content</td></tr></table>',
|
|
1018
1067
|
'<form><input type="text"><button>Submit</button></form>',
|
|
1019
1068
|
'<ul><li>List item 1</li><li>List item 2</li></ul>',
|
|
1020
|
-
'<article><header><h1>Article</h1></header><p>Content</p></article>'
|
|
1069
|
+
'<article><header><h1>Article</h1></header><p>Content</p></article>',
|
|
1021
1070
|
];
|
|
1022
1071
|
|
|
1023
|
-
testCases.forEach(html => {
|
|
1072
|
+
testCases.forEach((html) => {
|
|
1024
1073
|
const result = validateHTML(html);
|
|
1025
1074
|
expect(result).toBeDefined();
|
|
1026
1075
|
expect(typeof result.isValid).toBe('boolean');
|
|
@@ -1039,4 +1088,4 @@ line4`;
|
|
|
1039
1088
|
expect(typeof inappResult.isValid).toBe('boolean');
|
|
1040
1089
|
});
|
|
1041
1090
|
});
|
|
1042
|
-
});
|
|
1091
|
+
});
|
|
@@ -24,7 +24,7 @@ const defaultMessageFormatter = (messageKey, values = {}) => {
|
|
|
24
24
|
'sanitizer.productionValidHtml': 'Provide valid HTML content before deploying to production',
|
|
25
25
|
'sanitizer.productionSanitized': 'Content has been sanitized for security - review changes before deploying',
|
|
26
26
|
'sanitizer.productionInlineCss': 'Consider inlining CSS for better email client compatibility',
|
|
27
|
-
'sanitizer.productionLargeContent': `Content is large (${values.size || 'unknown'} characters) - consider optimizing for mobile performance
|
|
27
|
+
'sanitizer.productionLargeContent': `Content is large (${values.size || 'unknown'} characters) - consider optimizing for mobile performance`,
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
return fallbackMessages[messageKey] || messageKey;
|
|
@@ -33,17 +33,17 @@ const defaultMessageFormatter = (messageKey, values = {}) => {
|
|
|
33
33
|
// Constants for better maintainability
|
|
34
34
|
const SANITIZER_VARIANTS = {
|
|
35
35
|
EMAIL: 'email',
|
|
36
|
-
INAPP: 'inapp'
|
|
36
|
+
INAPP: 'inapp',
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
const SECURITY_LEVELS = {
|
|
40
40
|
STANDARD: 'standard',
|
|
41
|
-
STRICT: 'strict'
|
|
41
|
+
STRICT: 'strict',
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
const CONTENT_LIMITS = {
|
|
45
45
|
LARGE_CONTENT_SIZE: 50000,
|
|
46
|
-
MIN_CONTENT_LENGTH: 0
|
|
46
|
+
MIN_CONTENT_LENGTH: 0,
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
const DANGEROUS_PROTOCOLS = ['javascript:', 'data:', 'vbscript:'];
|
|
@@ -51,7 +51,7 @@ const DANGEROUS_PROTOCOLS = ['javascript:', 'data:', 'vbscript:'];
|
|
|
51
51
|
const EVENT_HANDLERS = [
|
|
52
52
|
'onclick', 'onload', 'onerror', 'onmouseover', 'onmouseout',
|
|
53
53
|
'onmousedown', 'onmouseup', 'onkeydown', 'onkeyup', 'onkeypress',
|
|
54
|
-
'onfocus', 'onblur', 'onchange', 'onsubmit', 'onreset'
|
|
54
|
+
'onfocus', 'onblur', 'onchange', 'onsubmit', 'onreset',
|
|
55
55
|
];
|
|
56
56
|
|
|
57
57
|
// Email-specific sanitization config
|
|
@@ -62,18 +62,18 @@ const EMAIL_CONFIG = {
|
|
|
62
62
|
'a', 'img', 'table', 'tr', 'td', 'th', 'thead', 'tbody', 'tfoot',
|
|
63
63
|
'ul', 'ol', 'li', 'strong', 'b', 'em', 'i', 'u', 'center',
|
|
64
64
|
'font', 'small', 'big', 'sup', 'sub', 'pre', 'code',
|
|
65
|
-
'blockquote', 'cite', 'abbr', 'acronym', 'address'
|
|
65
|
+
'blockquote', 'cite', 'abbr', 'acronym', 'address',
|
|
66
66
|
],
|
|
67
67
|
ALLOWED_ATTR: [
|
|
68
68
|
'src', 'alt', 'title', 'href', 'target', 'rel',
|
|
69
69
|
'width', 'height', 'style', 'class', 'id',
|
|
70
70
|
'align', 'valign', 'bgcolor', 'color', 'border',
|
|
71
71
|
'cellpadding', 'cellspacing', 'colspan', 'rowspan',
|
|
72
|
-
'type', 'charset', 'content', 'name', 'http-equiv'
|
|
72
|
+
'type', 'charset', 'content', 'name', 'http-equiv',
|
|
73
73
|
],
|
|
74
74
|
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'applet', 'form', 'input'],
|
|
75
75
|
FORBID_ATTR: ['onclick', 'onload', 'onerror', 'onmouseover'],
|
|
76
|
-
ALLOW_DATA_ATTR: false
|
|
76
|
+
ALLOW_DATA_ATTR: false,
|
|
77
77
|
};
|
|
78
78
|
|
|
79
79
|
// InApp-specific sanitization config
|
|
@@ -83,29 +83,29 @@ const INAPP_CONFIG = {
|
|
|
83
83
|
'div', 'span', 'p', 'br', 'hr', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
84
84
|
'a', 'img', 'button', 'ul', 'ol', 'li', 'strong', 'b', 'em', 'i', 'u',
|
|
85
85
|
'small', 'big', 'sup', 'sub', 'pre', 'code', 'blockquote', 'cite',
|
|
86
|
-
'video', 'audio', 'source', 'canvas'
|
|
86
|
+
'video', 'audio', 'source', 'canvas', // Mobile-friendly multimedia
|
|
87
87
|
],
|
|
88
88
|
ALLOWED_ATTR: [
|
|
89
89
|
'src', 'alt', 'title', 'href', 'target', 'rel',
|
|
90
90
|
'width', 'height', 'style', 'class', 'id',
|
|
91
91
|
'type', 'controls', 'autoplay', 'loop', 'muted',
|
|
92
|
-
'poster', 'preload'
|
|
92
|
+
'poster', 'preload',
|
|
93
93
|
],
|
|
94
94
|
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'applet', 'form', 'input'],
|
|
95
95
|
FORBID_ATTR: ['onclick', 'onload', 'onerror', 'onmouseover'],
|
|
96
|
-
ALLOW_DATA_ATTR: true
|
|
96
|
+
ALLOW_DATA_ATTR: true,
|
|
97
97
|
};
|
|
98
98
|
|
|
99
99
|
// Strict sanitization config for production
|
|
100
100
|
const STRICT_CONFIG = {
|
|
101
101
|
ALLOWED_TAGS: [
|
|
102
102
|
'div', 'span', 'p', 'br', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
|
|
103
|
-
'a', 'img', 'strong', 'b', 'em', 'i', 'u', 'ul', 'ol', 'li'
|
|
103
|
+
'a', 'img', 'strong', 'b', 'em', 'i', 'u', 'ul', 'ol', 'li',
|
|
104
104
|
],
|
|
105
105
|
ALLOWED_ATTR: ['src', 'alt', 'title', 'href', 'style', 'class'],
|
|
106
106
|
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'applet', 'form', 'input', 'style'],
|
|
107
107
|
FORBID_ATTR: ['onclick', 'onload', 'onerror', 'onmouseover'],
|
|
108
|
-
ALLOW_DATA_ATTR: false
|
|
108
|
+
ALLOW_DATA_ATTR: false,
|
|
109
109
|
};
|
|
110
110
|
|
|
111
111
|
/**
|
|
@@ -126,8 +126,9 @@ export const sanitizeHTML = (html, variant = SANITIZER_VARIANTS.EMAIL, level = S
|
|
|
126
126
|
warnings: html === null || html === undefined ? [] : [{
|
|
127
127
|
type: 'warning',
|
|
128
128
|
message: formatMessage('sanitizer.invalidInput'),
|
|
129
|
-
source: 'sanitizer'
|
|
130
|
-
|
|
129
|
+
source: 'sanitizer',
|
|
130
|
+
rule: 'sanitizer.invalidInput', // Rule Group #1 – blocking error for UI gating
|
|
131
|
+
}],
|
|
131
132
|
};
|
|
132
133
|
}
|
|
133
134
|
|
|
@@ -137,7 +138,7 @@ export const sanitizeHTML = (html, variant = SANITIZER_VARIANTS.EMAIL, level = S
|
|
|
137
138
|
sanitized: '',
|
|
138
139
|
isClean: true,
|
|
139
140
|
removedElements: [],
|
|
140
|
-
warnings: []
|
|
141
|
+
warnings: [],
|
|
141
142
|
};
|
|
142
143
|
}
|
|
143
144
|
|
|
@@ -159,7 +160,7 @@ export const sanitizeHTML = (html, variant = SANITIZER_VARIANTS.EMAIL, level = S
|
|
|
159
160
|
sanitized: '',
|
|
160
161
|
isClean: true,
|
|
161
162
|
removedElements: [],
|
|
162
|
-
warnings: []
|
|
163
|
+
warnings: [],
|
|
163
164
|
};
|
|
164
165
|
|
|
165
166
|
try {
|
|
@@ -188,8 +189,8 @@ export const sanitizeHTML = (html, variant = SANITIZER_VARIANTS.EMAIL, level = S
|
|
|
188
189
|
CUSTOM_ELEMENT_HANDLING: {
|
|
189
190
|
tagNameCheck: null,
|
|
190
191
|
attributeNameCheck: null,
|
|
191
|
-
allowCustomizedBuiltInElements: false
|
|
192
|
-
}
|
|
192
|
+
allowCustomizedBuiltInElements: false,
|
|
193
|
+
},
|
|
193
194
|
};
|
|
194
195
|
|
|
195
196
|
// Sanitize the content directly
|
|
@@ -202,12 +203,12 @@ export const sanitizeHTML = (html, variant = SANITIZER_VARIANTS.EMAIL, level = S
|
|
|
202
203
|
|
|
203
204
|
// Add variant-specific warnings (check original HTML before sanitization)
|
|
204
205
|
addVariantWarnings(html, variant, result, formatMessage);
|
|
205
|
-
|
|
206
206
|
} catch (error) {
|
|
207
207
|
result.warnings.push({
|
|
208
208
|
type: 'error',
|
|
209
209
|
message: formatMessage('sanitizer.sanitizationFailed', { error: error.message }),
|
|
210
|
-
source: 'sanitizer'
|
|
210
|
+
source: 'sanitizer',
|
|
211
|
+
rule: 'sanitizer.sanitizationFailed', // Rule Group #1 – blocking error for UI gating
|
|
211
212
|
});
|
|
212
213
|
result.sanitized = ''; // Return empty content if sanitization fails
|
|
213
214
|
result.isClean = false;
|
|
@@ -229,30 +230,30 @@ const addVariantWarnings = (html, variant, result, formatMessage = defaultMessag
|
|
|
229
230
|
if (variant === SANITIZER_VARIANTS.EMAIL) {
|
|
230
231
|
// Check for potentially problematic email elements
|
|
231
232
|
const emailProblematicElements = ['<video', '<audio', '<canvas'];
|
|
232
|
-
if (emailProblematicElements.some(element => html.includes(element))) {
|
|
233
|
+
if (emailProblematicElements.some((element) => html.includes(element))) {
|
|
233
234
|
result.warnings.push({
|
|
234
235
|
type: 'warning',
|
|
235
236
|
message: formatMessage('sanitizer.emailMultimediaNotSupported'),
|
|
236
|
-
source: 'email-compatibility'
|
|
237
|
+
source: 'email-compatibility',
|
|
237
238
|
});
|
|
238
239
|
}
|
|
239
240
|
|
|
240
241
|
const problematicStyles = ['position: fixed', 'position: sticky', 'position:fixed', 'position:sticky'];
|
|
241
|
-
if (problematicStyles.some(style => html.includes(style))) {
|
|
242
|
+
if (problematicStyles.some((style) => html.includes(style))) {
|
|
242
243
|
result.warnings.push({
|
|
243
244
|
type: 'warning',
|
|
244
245
|
message: formatMessage('sanitizer.emailPositioningNotSupported'),
|
|
245
|
-
source: 'email-compatibility'
|
|
246
|
+
source: 'email-compatibility',
|
|
246
247
|
});
|
|
247
248
|
}
|
|
248
249
|
|
|
249
250
|
// Check for CSS Grid/Flexbox which may have limited email support
|
|
250
251
|
const modernCssFeatures = ['display: grid', 'display: flex', 'display:grid', 'display:flex'];
|
|
251
|
-
if (modernCssFeatures.some(feature => html.includes(feature))) {
|
|
252
|
+
if (modernCssFeatures.some((feature) => html.includes(feature))) {
|
|
252
253
|
result.warnings.push({
|
|
253
254
|
type: 'info',
|
|
254
255
|
message: formatMessage('sanitizer.emailModernCssLimited'),
|
|
255
|
-
source: 'email-compatibility'
|
|
256
|
+
source: 'email-compatibility',
|
|
256
257
|
});
|
|
257
258
|
}
|
|
258
259
|
} else if (variant === SANITIZER_VARIANTS.INAPP) {
|
|
@@ -261,7 +262,7 @@ const addVariantWarnings = (html, variant, result, formatMessage = defaultMessag
|
|
|
261
262
|
result.warnings.push({
|
|
262
263
|
type: 'info',
|
|
263
264
|
message: formatMessage('sanitizer.mobileTablesNotFriendly'),
|
|
264
|
-
source: 'mobile-optimization'
|
|
265
|
+
source: 'mobile-optimization',
|
|
265
266
|
});
|
|
266
267
|
}
|
|
267
268
|
|
|
@@ -270,7 +271,7 @@ const addVariantWarnings = (html, variant, result, formatMessage = defaultMessag
|
|
|
270
271
|
result.warnings.push({
|
|
271
272
|
type: 'info',
|
|
272
273
|
message: formatMessage('sanitizer.mobileRelativeFontSizes'),
|
|
273
|
-
source: 'mobile-optimization'
|
|
274
|
+
source: 'mobile-optimization',
|
|
274
275
|
});
|
|
275
276
|
}
|
|
276
277
|
|
|
@@ -279,7 +280,7 @@ const addVariantWarnings = (html, variant, result, formatMessage = defaultMessag
|
|
|
279
280
|
result.warnings.push({
|
|
280
281
|
type: 'info',
|
|
281
282
|
message: formatMessage('sanitizer.mobileFixedWidthsProblematic'),
|
|
282
|
-
source: 'mobile-optimization'
|
|
283
|
+
source: 'mobile-optimization',
|
|
283
284
|
});
|
|
284
285
|
}
|
|
285
286
|
}
|
|
@@ -302,9 +303,9 @@ export const prepareForProduction = (html, variant = SANITIZER_VARIANTS.EMAIL, f
|
|
|
302
303
|
warnings: [{
|
|
303
304
|
type: 'error',
|
|
304
305
|
message: formatMessage('sanitizer.invalidInputNonEmpty'),
|
|
305
|
-
source: 'production-validator'
|
|
306
|
+
source: 'production-validator',
|
|
306
307
|
}],
|
|
307
|
-
recommendations: [formatMessage('sanitizer.productionValidHtml')]
|
|
308
|
+
recommendations: [formatMessage('sanitizer.productionValidHtml')],
|
|
308
309
|
};
|
|
309
310
|
}
|
|
310
311
|
|
|
@@ -316,7 +317,7 @@ export const prepareForProduction = (html, variant = SANITIZER_VARIANTS.EMAIL, f
|
|
|
316
317
|
isProductionReady: sanitizeResult.isClean,
|
|
317
318
|
securityIssues: sanitizeResult.removedElements,
|
|
318
319
|
warnings: sanitizeResult.warnings,
|
|
319
|
-
recommendations: []
|
|
320
|
+
recommendations: [],
|
|
320
321
|
};
|
|
321
322
|
|
|
322
323
|
// Add production readiness recommendations
|
|
@@ -355,9 +356,7 @@ export const isContentSafe = (html) => {
|
|
|
355
356
|
if (!html || typeof html !== 'string') return true;
|
|
356
357
|
|
|
357
358
|
// Create dynamic patterns from constants
|
|
358
|
-
const protocolPatterns = DANGEROUS_PROTOCOLS.map(protocol =>
|
|
359
|
-
new RegExp(protocol.replace(':', '\\:'), 'gi')
|
|
360
|
-
);
|
|
359
|
+
const protocolPatterns = DANGEROUS_PROTOCOLS.map((protocol) => new RegExp(protocol.replace(':', '\\:'), 'gi'));
|
|
361
360
|
|
|
362
361
|
const eventHandlerPattern = new RegExp(EVENT_HANDLERS.join('|'), 'gi');
|
|
363
362
|
|
|
@@ -369,10 +368,10 @@ export const isContentSafe = (html) => {
|
|
|
369
368
|
/<object/gi,
|
|
370
369
|
/<embed/gi,
|
|
371
370
|
/<applet/gi,
|
|
372
|
-
/<form/gi
|
|
371
|
+
/<form/gi,
|
|
373
372
|
];
|
|
374
373
|
|
|
375
|
-
return !dangerousPatterns.some(pattern => pattern.test(html));
|
|
374
|
+
return !dangerousPatterns.some((pattern) => pattern.test(html));
|
|
376
375
|
};
|
|
377
376
|
|
|
378
377
|
/**
|
|
@@ -395,7 +394,7 @@ export const findUnsafeContent = (html) => {
|
|
|
395
394
|
'Iframe': /<iframe[^>]*>/gi,
|
|
396
395
|
'Object/Embed': /<(object|embed)[^>]*>/gi,
|
|
397
396
|
'Applet': /<applet[^>]*>/gi,
|
|
398
|
-
'Form': /<form[^>]*>/gi
|
|
397
|
+
'Form': /<form[^>]*>/gi,
|
|
399
398
|
};
|
|
400
399
|
|
|
401
400
|
Object.entries(patterns).forEach(([name, pattern]) => {
|
|
@@ -406,7 +405,7 @@ export const findUnsafeContent = (html) => {
|
|
|
406
405
|
type: name,
|
|
407
406
|
content: match[0],
|
|
408
407
|
position: match.index,
|
|
409
|
-
length: match[0].length
|
|
408
|
+
length: match[0].length,
|
|
410
409
|
});
|
|
411
410
|
|
|
412
411
|
// Prevent infinite loop for global patterns
|
|
@@ -429,5 +428,5 @@ export default {
|
|
|
429
428
|
// Include constants in default export for convenience
|
|
430
429
|
VARIANTS: SANITIZER_VARIANTS,
|
|
431
430
|
SECURITY_LEVELS,
|
|
432
|
-
CONTENT_LIMITS
|
|
431
|
+
CONTENT_LIMITS,
|
|
433
432
|
};
|