@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.
Files changed (136) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +2 -1
  4. package/initialReducer.js +2 -0
  5. package/package.json +1 -1
  6. package/services/api.js +10 -0
  7. package/services/tests/api.test.js +18 -0
  8. package/utils/common.js +5 -0
  9. package/utils/commonUtils.js +28 -5
  10. package/utils/tests/commonUtil.test.js +224 -0
  11. package/utils/transformTemplateConfig.js +0 -10
  12. package/v2Components/CapDeviceContent/index.js +61 -56
  13. package/v2Components/CapTagList/index.js +6 -1
  14. package/v2Components/CapTagListWithInput/index.js +5 -1
  15. package/v2Components/CapTagListWithInput/messages.js +1 -1
  16. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  17. package/v2Components/ErrorInfoNote/index.js +452 -72
  18. package/v2Components/ErrorInfoNote/messages.js +22 -0
  19. package/v2Components/ErrorInfoNote/style.scss +280 -4
  20. package/v2Components/FormBuilder/tests/index.test.js +13 -4
  21. package/v2Components/HtmlEditor/HTMLEditor.js +640 -94
  22. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +874 -0
  23. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1167 -133
  24. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
  25. package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
  26. package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
  27. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +13 -101
  28. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -139
  29. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
  30. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  31. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -0
  32. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
  33. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
  34. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  35. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  36. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  37. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  38. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  39. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +3 -6
  40. package/v2Components/HtmlEditor/components/PreviewPane/index.js +11 -13
  41. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  42. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +49 -31
  43. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +68 -39
  44. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +254 -0
  45. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +391 -0
  46. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  47. package/v2Components/HtmlEditor/constants.js +42 -20
  48. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  49. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.apiErrors.test.js +795 -0
  50. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  51. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  52. package/v2Components/HtmlEditor/hooks/useValidation.js +189 -53
  53. package/v2Components/HtmlEditor/index.js +1 -1
  54. package/v2Components/HtmlEditor/messages.js +95 -85
  55. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +94 -45
  56. package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
  57. package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
  58. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +134 -102
  59. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  60. package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
  61. package/v2Components/MobilePushPreviewV2/index.js +32 -7
  62. package/v2Components/TemplatePreview/_templatePreview.scss +44 -24
  63. package/v2Components/TemplatePreview/index.js +47 -32
  64. package/v2Components/TemplatePreview/messages.js +4 -0
  65. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
  66. package/v2Containers/BeeEditor/index.js +172 -90
  67. package/v2Containers/BeePopupEditor/constants.js +10 -0
  68. package/v2Containers/BeePopupEditor/index.js +193 -0
  69. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  70. package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -51
  71. package/v2Containers/CreativesContainer/SlideBoxFooter.js +163 -13
  72. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
  73. package/v2Containers/CreativesContainer/constants.js +1 -0
  74. package/v2Containers/CreativesContainer/index.js +239 -46
  75. package/v2Containers/CreativesContainer/messages.js +8 -0
  76. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
  77. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  78. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +106 -0
  79. package/v2Containers/Email/actions.js +7 -0
  80. package/v2Containers/Email/constants.js +5 -1
  81. package/v2Containers/Email/index.js +222 -27
  82. package/v2Containers/Email/messages.js +32 -0
  83. package/v2Containers/Email/reducer.js +12 -1
  84. package/v2Containers/Email/sagas.js +61 -7
  85. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  86. package/v2Containers/Email/tests/sagas.test.js +320 -29
  87. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1321 -0
  88. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +210 -15
  89. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  90. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +1749 -0
  91. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
  92. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  93. package/v2Containers/EmailWrapper/constants.js +2 -0
  94. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +629 -77
  95. package/v2Containers/EmailWrapper/index.js +103 -23
  96. package/v2Containers/EmailWrapper/messages.js +61 -1
  97. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +643 -0
  98. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +594 -77
  99. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  100. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  101. package/v2Containers/InApp/actions.js +7 -0
  102. package/v2Containers/InApp/constants.js +20 -4
  103. package/v2Containers/InApp/index.js +802 -359
  104. package/v2Containers/InApp/index.scss +4 -3
  105. package/v2Containers/InApp/messages.js +7 -3
  106. package/v2Containers/InApp/reducer.js +21 -3
  107. package/v2Containers/InApp/sagas.js +29 -9
  108. package/v2Containers/InApp/selectors.js +25 -5
  109. package/v2Containers/InApp/tests/index.test.js +154 -50
  110. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  111. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  112. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  113. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +162 -0
  114. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  115. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +9 -0
  116. package/v2Containers/InAppWrapper/constants.js +16 -0
  117. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  118. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  119. package/v2Containers/InAppWrapper/index.js +148 -0
  120. package/v2Containers/InAppWrapper/messages.js +49 -0
  121. package/v2Containers/InappAdvance/index.js +1099 -0
  122. package/v2Containers/InappAdvance/index.scss +10 -0
  123. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  124. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  125. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  126. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
  127. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
  128. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
  129. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
  130. package/v2Containers/TagList/index.js +62 -19
  131. package/v2Containers/Templates/_templates.scss +60 -1
  132. package/v2Containers/Templates/index.js +89 -4
  133. package/v2Containers/Templates/messages.js +4 -0
  134. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
  135. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
  136. 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 = '<p>' + 'a'.repeat(50000) + '</p>';
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 = '.long { ' + 'color: red; '.repeat(1000) + '}';
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
- expect(result.isValid).toBe(false);
440
- expect(result.errors.length).toBeGreaterThan(0);
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
- expect(result.isValid).toBe(false);
452
- expect(result.errors.length).toBeGreaterThan(0);
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
- expect(result.isValid).toBe(false);
540
- expect(result.errors.length).toBeGreaterThan(0);
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
- expect(result.isValid).toBe(false);
589
- expect(result.errors.length).toBeGreaterThan(0);
590
- expect(result.errors[0].message).toContain('HTMLHint validation failed');
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
- return key;
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
- expect(result.isValid).toBe(false);
725
- expect(result.errors.length).toBeGreaterThan(0);
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: [{ type: 'error', message: 'Liquid error', line: 1, column: 1, rule: 'liquid-test', severity: 'error', source: 'liquid' }],
759
- warnings: [{ type: 'warning', message: 'Liquid warning', line: 1, column: 1, rule: 'liquid-test', severity: 'warning', source: 'liquid' }],
760
- info: [{ type: 'info', message: 'Liquid info', line: 1, column: 1, rule: 'liquid-test', severity: 'info', source: 'liquid' }]
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: [{ type: 'error', message: 'Liquid error', line: 1, column: 1, rule: 'liquid-test', severity: 'error', source: 'liquid' }],
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: [{ type: 'warning', message: 'Liquid warning', line: 1, column: 1, rule: 'liquid-test', severity: 'warning', source: 'liquid' }],
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: [{ type: 'info', message: 'Liquid info', line: 1, column: 1, rule: 'liquid-test', severity: 'info', source: 'liquid' }],
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
- expect(result.isValid).toBe(false);
855
- expect(result.errors.length).toBeGreaterThan(0);
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
- expect(result.isValid).toBe(false);
867
- expect(result.errors.length).toBeGreaterThan(0);
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
- expect(result.isValid).toBe(false);
880
- // Should detect at least one empty rule
881
- expect(result.errors.length).toBeGreaterThanOrEqual(1);
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
- expect(result.isValid).toBe(false);
931
- expect(result.errors.length).toBeGreaterThan(0);
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
- expect(result.isValid).toBe(false);
947
- expect(result.errors.length).toBeGreaterThan(0);
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
- expect(result.isValid).toBe(false);
955
- expect(result.errors.length).toBeGreaterThan(0);
956
- expect(result.errors.some(error => error.rule === 'unclosed-style-tag')).toBe(true);
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
- expect(result.isValid).toBe(false);
968
- expect(result.errors.length).toBeGreaterThan(0);
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' // Mobile-friendly multimedia
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
  };