@capillarytech/creatives-library 8.0.298 → 8.0.299-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 +1 -0
- package/package.json +1 -1
- package/services/api.js +17 -0
- package/services/tests/api.test.js +85 -0
- package/utils/common.js +12 -5
- package/utils/commonUtils.js +10 -0
- package/utils/tests/commonUtil.test.js +169 -0
- package/v2Components/CapDeviceContent/index.js +10 -7
- package/v2Components/CommonTestAndPreview/AddTestCustomer.js +42 -0
- package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +284 -0
- package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +72 -0
- package/v2Components/CommonTestAndPreview/SendTestMessage.js +78 -49
- package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +189 -4
- package/v2Components/CommonTestAndPreview/actions.js +10 -0
- package/v2Components/CommonTestAndPreview/constants.js +18 -1
- package/v2Components/CommonTestAndPreview/index.js +259 -14
- package/v2Components/CommonTestAndPreview/messages.js +94 -0
- package/v2Components/CommonTestAndPreview/reducer.js +10 -0
- package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +66 -0
- package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +653 -0
- package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +316 -0
- package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +114 -0
- package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +53 -0
- package/v2Components/CommonTestAndPreview/tests/constants.test.js +25 -2
- package/v2Components/CommonTestAndPreview/tests/index.test.js +7 -0
- package/v2Components/CommonTestAndPreview/tests/reducer.test.js +71 -0
- package/v2Components/CommonTestAndPreview/tests/selectors.test.js +17 -0
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +320 -0
- package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +132 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +35 -3
- package/v2Containers/InApp/index.js +182 -13
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +1408 -1276
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +321 -288
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +5246 -4872
|
@@ -644,6 +644,326 @@ describe('useValidation', () => {
|
|
|
644
644
|
// The functionality is verified through integration tests and the hasErrors test above
|
|
645
645
|
});
|
|
646
646
|
|
|
647
|
+
describe('getLineAndColumnFromPosition utility (lines 36-46)', () => {
|
|
648
|
+
it('computes correct line and column from position in security issue', async () => {
|
|
649
|
+
const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
|
|
650
|
+
const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
|
|
651
|
+
let validationState;
|
|
652
|
+
|
|
653
|
+
validateHTML.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
|
|
654
|
+
extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
|
|
655
|
+
isContentSafe.mockImplementationOnce(() => false);
|
|
656
|
+
// position 12 in "line1\nline2\nX" → line 3, column 1
|
|
657
|
+
const content = 'line1\nline2\njavascript:alert(1)';
|
|
658
|
+
findUnsafeContent.mockImplementationOnce(() => [{ type: 'JavaScript Protocol', position: 12 }]);
|
|
659
|
+
|
|
660
|
+
render(
|
|
661
|
+
<TestComponent
|
|
662
|
+
content={content}
|
|
663
|
+
options={{ enableRealTime: false }}
|
|
664
|
+
onStateChange={(state) => { validationState = state; }}
|
|
665
|
+
/>
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
await waitFor(() => { expect(validationState).toBeDefined(); });
|
|
669
|
+
await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
|
|
670
|
+
|
|
671
|
+
await waitFor(() => {
|
|
672
|
+
const issues = validationState.getAllIssues();
|
|
673
|
+
const secIssue = issues.find((i) => i.source === 'security');
|
|
674
|
+
expect(secIssue).toBeDefined();
|
|
675
|
+
expect(secIssue.line).toBe(3);
|
|
676
|
+
expect(secIssue.column).toBe(1);
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
it('defaults line/column to 1 when security issue has no position', async () => {
|
|
681
|
+
const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
|
|
682
|
+
const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
|
|
683
|
+
let validationState;
|
|
684
|
+
|
|
685
|
+
validateHTML.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
|
|
686
|
+
extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
|
|
687
|
+
isContentSafe.mockImplementationOnce(() => false);
|
|
688
|
+
findUnsafeContent.mockImplementationOnce(() => [{ type: 'JavaScript Protocol' }]); // no position
|
|
689
|
+
|
|
690
|
+
render(
|
|
691
|
+
<TestComponent
|
|
692
|
+
content="<a href='javascript:x'>x</a>"
|
|
693
|
+
options={{ enableRealTime: false }}
|
|
694
|
+
onStateChange={(state) => { validationState = state; }}
|
|
695
|
+
/>
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
await waitFor(() => { expect(validationState).toBeDefined(); });
|
|
699
|
+
await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
|
|
700
|
+
|
|
701
|
+
await waitFor(() => {
|
|
702
|
+
const issues = validationState.getAllIssues();
|
|
703
|
+
const secIssue = issues.find((i) => i.source === 'security');
|
|
704
|
+
expect(secIssue).toBeDefined();
|
|
705
|
+
expect(secIssue.line).toBe(1);
|
|
706
|
+
expect(secIssue.column).toBe(1);
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
it('handles negative or undefined position in getLineAndColumnFromPosition', async () => {
|
|
711
|
+
const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
|
|
712
|
+
const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
|
|
713
|
+
let validationState;
|
|
714
|
+
|
|
715
|
+
validateHTML.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
|
|
716
|
+
extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
|
|
717
|
+
isContentSafe.mockImplementationOnce(() => false);
|
|
718
|
+
findUnsafeContent.mockImplementationOnce(() => [{ type: 'JavaScript Protocol', position: -5 }]);
|
|
719
|
+
|
|
720
|
+
render(
|
|
721
|
+
<TestComponent
|
|
722
|
+
content="<a href='javascript:x'>x</a>"
|
|
723
|
+
options={{ enableRealTime: false }}
|
|
724
|
+
onStateChange={(state) => { validationState = state; }}
|
|
725
|
+
/>
|
|
726
|
+
);
|
|
727
|
+
|
|
728
|
+
await waitFor(() => { expect(validationState).toBeDefined(); });
|
|
729
|
+
await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
|
|
730
|
+
|
|
731
|
+
await waitFor(() => {
|
|
732
|
+
const issues = validationState.getAllIssues();
|
|
733
|
+
const secIssue = issues.find((i) => i.source === 'security');
|
|
734
|
+
expect(secIssue).toBeDefined();
|
|
735
|
+
// negative position falls back to line 1, column 1
|
|
736
|
+
expect(secIssue.line).toBe(1);
|
|
737
|
+
expect(secIssue.column).toBe(1);
|
|
738
|
+
});
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
describe('getAllIssues spread — all six arrays included (lines 401-411)', () => {
|
|
743
|
+
it('includes items from htmlErrors, htmlWarnings, htmlInfo, cssErrors, cssWarnings, cssInfo', async () => {
|
|
744
|
+
const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
|
|
745
|
+
const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
|
|
746
|
+
let validationState;
|
|
747
|
+
|
|
748
|
+
validateHTML.mockImplementationOnce(() => ({
|
|
749
|
+
isValid: false,
|
|
750
|
+
errors: [{ type: 'error', message: 'html error', line: 1, column: 1, rule: 'r1', severity: 'error', source: 'htmlhint' }],
|
|
751
|
+
warnings: [{ type: 'warning', message: 'html warning', line: 2, column: 1, rule: 'r2', severity: 'warning', source: 'htmlhint' }],
|
|
752
|
+
info: [{ type: 'info', message: 'html info', line: 3, column: 1, rule: 'r3', severity: 'info', source: 'htmlhint' }],
|
|
753
|
+
}));
|
|
754
|
+
extractAndValidateCSS.mockImplementationOnce(() => ({
|
|
755
|
+
isValid: false,
|
|
756
|
+
errors: [{ type: 'error', message: 'css error', line: 4, column: 1, rule: 'r4', severity: 'error', source: 'css-validator' }],
|
|
757
|
+
warnings: [{ type: 'warning', message: 'css warning', line: 5, column: 1, rule: 'r5', severity: 'warning', source: 'css-validator' }],
|
|
758
|
+
info: [{ type: 'info', message: 'css info', line: 6, column: 1, rule: 'r6', severity: 'info', source: 'css-validator' }],
|
|
759
|
+
}));
|
|
760
|
+
isContentSafe.mockImplementationOnce(() => true);
|
|
761
|
+
findUnsafeContent.mockImplementationOnce(() => []);
|
|
762
|
+
|
|
763
|
+
render(
|
|
764
|
+
<TestComponent
|
|
765
|
+
content="<div>test</div>"
|
|
766
|
+
options={{ enableRealTime: false, enableSanitization: false }}
|
|
767
|
+
onStateChange={(state) => { validationState = state; }}
|
|
768
|
+
/>
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
await waitFor(() => { expect(validationState).toBeDefined(); });
|
|
772
|
+
await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
|
|
773
|
+
|
|
774
|
+
await waitFor(() => {
|
|
775
|
+
const issues = validationState.getAllIssues();
|
|
776
|
+
const rules = issues.map((i) => i.rule);
|
|
777
|
+
expect(rules).toContain('r1'); // htmlErrors
|
|
778
|
+
expect(rules).toContain('r2'); // htmlWarnings
|
|
779
|
+
expect(rules).toContain('r3'); // htmlInfo
|
|
780
|
+
expect(rules).toContain('r4'); // cssErrors
|
|
781
|
+
expect(rules).toContain('r5'); // cssWarnings
|
|
782
|
+
expect(rules).toContain('r6'); // cssInfo
|
|
783
|
+
expect(issues.length).toBeGreaterThanOrEqual(6);
|
|
784
|
+
});
|
|
785
|
+
});
|
|
786
|
+
|
|
787
|
+
it('sorts issues: errors before warnings before info', async () => {
|
|
788
|
+
const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
|
|
789
|
+
const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
|
|
790
|
+
let validationState;
|
|
791
|
+
|
|
792
|
+
validateHTML.mockImplementationOnce(() => ({
|
|
793
|
+
isValid: false,
|
|
794
|
+
errors: [{ type: 'error', message: 'html error', line: 10, column: 1, rule: 're', severity: 'error', source: 'htmlhint' }],
|
|
795
|
+
warnings: [{ type: 'warning', message: 'html warning', line: 1, column: 1, rule: 'rw', severity: 'warning', source: 'htmlhint' }],
|
|
796
|
+
info: [{ type: 'info', message: 'html info', line: 1, column: 1, rule: 'ri', severity: 'info', source: 'htmlhint' }],
|
|
797
|
+
}));
|
|
798
|
+
extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
|
|
799
|
+
isContentSafe.mockImplementationOnce(() => true);
|
|
800
|
+
findUnsafeContent.mockImplementationOnce(() => []);
|
|
801
|
+
|
|
802
|
+
render(
|
|
803
|
+
<TestComponent
|
|
804
|
+
content="<div>test</div>"
|
|
805
|
+
options={{ enableRealTime: false, enableSanitization: false }}
|
|
806
|
+
onStateChange={(state) => { validationState = state; }}
|
|
807
|
+
/>
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
await waitFor(() => { expect(validationState).toBeDefined(); });
|
|
811
|
+
await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
|
|
812
|
+
|
|
813
|
+
await waitFor(() => {
|
|
814
|
+
const issues = validationState.getAllIssues();
|
|
815
|
+
const severities = issues.map((i) => i.severity);
|
|
816
|
+
// Errors should come before warnings, warnings before info
|
|
817
|
+
const firstError = severities.indexOf('error');
|
|
818
|
+
const firstWarning = severities.indexOf('warning');
|
|
819
|
+
const firstInfo = severities.indexOf('info');
|
|
820
|
+
if (firstError !== -1 && firstWarning !== -1) expect(firstError).toBeLessThan(firstWarning);
|
|
821
|
+
if (firstWarning !== -1 && firstInfo !== -1) expect(firstWarning).toBeLessThan(firstInfo);
|
|
822
|
+
});
|
|
823
|
+
});
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
describe('hasClientSideLiquidErrors and hasBlockingErrors (lines 455-456)', () => {
|
|
827
|
+
it('sets hasBlockingErrors=true when htmlErrors has a liquid-validator error', async () => {
|
|
828
|
+
const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
|
|
829
|
+
const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
|
|
830
|
+
let validationState;
|
|
831
|
+
|
|
832
|
+
validateHTML.mockImplementationOnce(() => ({
|
|
833
|
+
isValid: false,
|
|
834
|
+
errors: [{
|
|
835
|
+
type: 'error',
|
|
836
|
+
message: 'Liquid syntax error',
|
|
837
|
+
line: 1,
|
|
838
|
+
column: 1,
|
|
839
|
+
rule: 'liquid-syntax',
|
|
840
|
+
severity: 'error',
|
|
841
|
+
source: 'liquid-validator', // ISSUE_SOURCES.LIQUID
|
|
842
|
+
}],
|
|
843
|
+
warnings: [],
|
|
844
|
+
info: [],
|
|
845
|
+
}));
|
|
846
|
+
extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
|
|
847
|
+
isContentSafe.mockImplementationOnce(() => true);
|
|
848
|
+
findUnsafeContent.mockImplementationOnce(() => []);
|
|
849
|
+
|
|
850
|
+
render(
|
|
851
|
+
<TestComponent
|
|
852
|
+
content="{{ invalid liquid }}"
|
|
853
|
+
options={{ enableRealTime: false, enableSanitization: false }}
|
|
854
|
+
onStateChange={(state) => { validationState = state; }}
|
|
855
|
+
/>
|
|
856
|
+
);
|
|
857
|
+
|
|
858
|
+
await waitFor(() => { expect(validationState).toBeDefined(); });
|
|
859
|
+
await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
|
|
860
|
+
|
|
861
|
+
await waitFor(() => {
|
|
862
|
+
// hasClientSideLiquidErrors → true because htmlErrors has liquid-validator+error item
|
|
863
|
+
// therefore hasBlockingErrors → true
|
|
864
|
+
expect(validationState.hasBlockingErrors).toBe(true);
|
|
865
|
+
});
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
it('does NOT set hasBlockingErrors from liquid-validator warning (non-error severity)', async () => {
|
|
869
|
+
const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
|
|
870
|
+
const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
|
|
871
|
+
let validationState;
|
|
872
|
+
|
|
873
|
+
validateHTML.mockImplementationOnce(() => ({
|
|
874
|
+
isValid: true,
|
|
875
|
+
errors: [],
|
|
876
|
+
warnings: [{
|
|
877
|
+
type: 'warning',
|
|
878
|
+
message: 'Liquid warning',
|
|
879
|
+
line: 1,
|
|
880
|
+
column: 1,
|
|
881
|
+
rule: 'liquid-warning',
|
|
882
|
+
severity: 'warning',
|
|
883
|
+
source: 'liquid-validator',
|
|
884
|
+
}],
|
|
885
|
+
info: [],
|
|
886
|
+
}));
|
|
887
|
+
extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
|
|
888
|
+
isContentSafe.mockImplementationOnce(() => true);
|
|
889
|
+
findUnsafeContent.mockImplementationOnce(() => []);
|
|
890
|
+
|
|
891
|
+
render(
|
|
892
|
+
<TestComponent
|
|
893
|
+
content="<div>test</div>"
|
|
894
|
+
options={{ enableRealTime: false, enableSanitization: false }}
|
|
895
|
+
onStateChange={(state) => { validationState = state; }}
|
|
896
|
+
/>
|
|
897
|
+
);
|
|
898
|
+
|
|
899
|
+
await waitFor(() => { expect(validationState).toBeDefined(); });
|
|
900
|
+
await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
|
|
901
|
+
|
|
902
|
+
await waitFor(() => {
|
|
903
|
+
// hasClientSideLiquidErrors → false (severity is warning, not error)
|
|
904
|
+
// No other blocking conditions → hasBlockingErrors → false
|
|
905
|
+
expect(validationState.hasBlockingErrors).toBe(false);
|
|
906
|
+
});
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
it('sets hasBlockingErrors=true via API liquid errors', async () => {
|
|
910
|
+
const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
|
|
911
|
+
const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
|
|
912
|
+
let validationState;
|
|
913
|
+
|
|
914
|
+
validateHTML.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
|
|
915
|
+
extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
|
|
916
|
+
isContentSafe.mockImplementationOnce(() => true);
|
|
917
|
+
findUnsafeContent.mockImplementationOnce(() => []);
|
|
918
|
+
|
|
919
|
+
render(
|
|
920
|
+
<TestComponent
|
|
921
|
+
content="<div>test</div>"
|
|
922
|
+
options={{
|
|
923
|
+
enableRealTime: false,
|
|
924
|
+
enableSanitization: false,
|
|
925
|
+
apiValidationErrors: { liquidErrors: ['API liquid error'], standardErrors: [] },
|
|
926
|
+
}}
|
|
927
|
+
onStateChange={(state) => { validationState = state; }}
|
|
928
|
+
/>
|
|
929
|
+
);
|
|
930
|
+
|
|
931
|
+
await waitFor(() => { expect(validationState).toBeDefined(); });
|
|
932
|
+
await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
|
|
933
|
+
|
|
934
|
+
await waitFor(() => {
|
|
935
|
+
// hasApiErrors → true → hasBlockingErrors → true
|
|
936
|
+
expect(validationState.hasBlockingErrors).toBe(true);
|
|
937
|
+
});
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
it('hasBlockingErrors=false when no blocking conditions are present', async () => {
|
|
941
|
+
const { validateHTML, extractAndValidateCSS } = require('../../utils/htmlValidator');
|
|
942
|
+
const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
|
|
943
|
+
let validationState;
|
|
944
|
+
|
|
945
|
+
validateHTML.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
|
|
946
|
+
extractAndValidateCSS.mockImplementationOnce(() => ({ isValid: true, errors: [], warnings: [], info: [] }));
|
|
947
|
+
isContentSafe.mockImplementationOnce(() => true);
|
|
948
|
+
findUnsafeContent.mockImplementationOnce(() => []);
|
|
949
|
+
|
|
950
|
+
render(
|
|
951
|
+
<TestComponent
|
|
952
|
+
content="<div>clean</div>"
|
|
953
|
+
options={{ enableRealTime: false, enableSanitization: false }}
|
|
954
|
+
onStateChange={(state) => { validationState = state; }}
|
|
955
|
+
/>
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
await waitFor(() => { expect(validationState).toBeDefined(); });
|
|
959
|
+
await act(async () => { validationState.forceValidation(); await Promise.resolve(); });
|
|
960
|
+
|
|
961
|
+
await waitFor(() => {
|
|
962
|
+
expect(validationState.hasBlockingErrors).toBe(false);
|
|
963
|
+
});
|
|
964
|
+
});
|
|
965
|
+
});
|
|
966
|
+
|
|
647
967
|
describe('Blocking errors', () => {
|
|
648
968
|
it('treats protocol security issues as blocking errors', async () => {
|
|
649
969
|
const { isContentSafe, findUnsafeContent } = require('../../utils/contentSanitizer');
|
|
@@ -1076,6 +1076,138 @@ line4`;
|
|
|
1076
1076
|
expect(result).toBeDefined();
|
|
1077
1077
|
expect(Array.isArray(result.warnings)).toBe(true);
|
|
1078
1078
|
});
|
|
1079
|
+
|
|
1080
|
+
it('routes warning-severity issues to warnings array — exact item check (line 112-113)', () => {
|
|
1081
|
+
// Mock HTMLHint to return an issue with a warningRule ruleId
|
|
1082
|
+
// getSeverityLevel('error', 'tag-pair') → 'warning' (warningRules match)
|
|
1083
|
+
const htmlhint = require('htmlhint');
|
|
1084
|
+
const original = htmlhint.HTMLHint.verify;
|
|
1085
|
+
htmlhint.HTMLHint.verify = jest.fn(() => ([
|
|
1086
|
+
{
|
|
1087
|
+
type: 'error',
|
|
1088
|
+
message: 'Tag must be paired',
|
|
1089
|
+
line: 1,
|
|
1090
|
+
col: 5,
|
|
1091
|
+
rule: { id: 'tag-pair' },
|
|
1092
|
+
},
|
|
1093
|
+
]));
|
|
1094
|
+
|
|
1095
|
+
const result = validateHTML('<p>unclosed');
|
|
1096
|
+
|
|
1097
|
+
expect(result.warnings.length).toBeGreaterThanOrEqual(1);
|
|
1098
|
+
const item = result.warnings.find((w) => w.rule === 'tag-pair');
|
|
1099
|
+
expect(item).toBeDefined();
|
|
1100
|
+
expect(item.severity).toBe('warning');
|
|
1101
|
+
|
|
1102
|
+
htmlhint.HTMLHint.verify = original;
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
it('routes info-severity issues to info array — exact item check (line 114-115)', () => {
|
|
1106
|
+
// getSeverityLevel('warning', 'space-tab-mixed-disabled') → 'info'
|
|
1107
|
+
// (not in warningRules, type !== 'error')
|
|
1108
|
+
const htmlhint = require('htmlhint');
|
|
1109
|
+
const original = htmlhint.HTMLHint.verify;
|
|
1110
|
+
htmlhint.HTMLHint.verify = jest.fn(() => ([
|
|
1111
|
+
{
|
|
1112
|
+
type: 'warning',
|
|
1113
|
+
message: 'Mixed spaces and tabs',
|
|
1114
|
+
line: 1,
|
|
1115
|
+
col: 1,
|
|
1116
|
+
rule: { id: 'space-tab-mixed-disabled' },
|
|
1117
|
+
},
|
|
1118
|
+
]));
|
|
1119
|
+
|
|
1120
|
+
const result = validateHTML('<div>test</div>');
|
|
1121
|
+
|
|
1122
|
+
expect(result.info.length).toBeGreaterThanOrEqual(1);
|
|
1123
|
+
const item = result.info.find((i) => i.rule === 'space-tab-mixed-disabled');
|
|
1124
|
+
expect(item).toBeDefined();
|
|
1125
|
+
expect(item.severity).toBe('info');
|
|
1126
|
+
|
|
1127
|
+
htmlhint.HTMLHint.verify = original;
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
it('downgrades HTMLHint error type to warning for non-warningRule (line 163-164) — exact item check', () => {
|
|
1131
|
+
// getSeverityLevel('error', 'src-not-empty') → 'warning' (not in warningRules, but type === 'error')
|
|
1132
|
+
const htmlhint = require('htmlhint');
|
|
1133
|
+
const original = htmlhint.HTMLHint.verify;
|
|
1134
|
+
htmlhint.HTMLHint.verify = jest.fn(() => ([
|
|
1135
|
+
{
|
|
1136
|
+
type: 'error',
|
|
1137
|
+
message: 'The src attribute cannot be empty',
|
|
1138
|
+
line: 1,
|
|
1139
|
+
col: 1,
|
|
1140
|
+
rule: { id: 'src-not-empty' },
|
|
1141
|
+
},
|
|
1142
|
+
]));
|
|
1143
|
+
|
|
1144
|
+
const result = validateHTML('<img src="">');
|
|
1145
|
+
|
|
1146
|
+
// Should be downgraded to warning (not error)
|
|
1147
|
+
expect(result.warnings.length).toBeGreaterThanOrEqual(1);
|
|
1148
|
+
const item = result.warnings.find((w) => w.rule === 'src-not-empty');
|
|
1149
|
+
expect(item).toBeDefined();
|
|
1150
|
+
expect(item.severity).toBe('warning');
|
|
1151
|
+
// Should NOT be in errors
|
|
1152
|
+
expect(result.errors.find((e) => e.rule === 'src-not-empty')).toBeUndefined();
|
|
1153
|
+
|
|
1154
|
+
htmlhint.HTMLHint.verify = original;
|
|
1155
|
+
});
|
|
1156
|
+
|
|
1157
|
+
it('returns info for HTMLHint warning type with unknown ruleId (line 165-166)', () => {
|
|
1158
|
+
// getSeverityLevel('warning', 'unknown-rule') → 'info' (no warningRules match, type !== 'error')
|
|
1159
|
+
const htmlhint = require('htmlhint');
|
|
1160
|
+
const original = htmlhint.HTMLHint.verify;
|
|
1161
|
+
htmlhint.HTMLHint.verify = jest.fn(() => ([
|
|
1162
|
+
{
|
|
1163
|
+
type: 'warning',
|
|
1164
|
+
message: 'Some custom warning',
|
|
1165
|
+
line: 2,
|
|
1166
|
+
col: 3,
|
|
1167
|
+
rule: { id: 'unknown-custom-rule' },
|
|
1168
|
+
},
|
|
1169
|
+
]));
|
|
1170
|
+
|
|
1171
|
+
const result = validateHTML('<div>test</div>');
|
|
1172
|
+
|
|
1173
|
+
expect(result.info.length).toBeGreaterThanOrEqual(1);
|
|
1174
|
+
const item = result.info.find((i) => i.rule === 'unknown-custom-rule');
|
|
1175
|
+
expect(item).toBeDefined();
|
|
1176
|
+
expect(item.severity).toBe('info');
|
|
1177
|
+
expect(item.line).toBe(2);
|
|
1178
|
+
expect(item.column).toBe(3);
|
|
1179
|
+
// Should NOT be in errors or warnings
|
|
1180
|
+
expect(result.errors.find((e) => e.rule === 'unknown-custom-rule')).toBeUndefined();
|
|
1181
|
+
expect(result.warnings.find((w) => w.rule === 'unknown-custom-rule')).toBeUndefined();
|
|
1182
|
+
|
|
1183
|
+
htmlhint.HTMLHint.verify = original;
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
it('processes multiple HTMLHint issues with mixed severity correctly (lines 101-118)', () => {
|
|
1187
|
+
const htmlhint = require('htmlhint');
|
|
1188
|
+
const original = htmlhint.HTMLHint.verify;
|
|
1189
|
+
htmlhint.HTMLHint.verify = jest.fn(() => ([
|
|
1190
|
+
{ type: 'error', message: 'Tag pair', line: 1, col: 1, rule: { id: 'tag-pair' } }, // → warning
|
|
1191
|
+
{ type: 'error', message: 'Src empty', line: 2, col: 1, rule: { id: 'src-not-empty' } }, // → warning (downgrade)
|
|
1192
|
+
{ type: 'warning', message: 'Mixed tabs', line: 3, col: 1, rule: { id: 'space-tab-mixed-disabled' } }, // → info
|
|
1193
|
+
]));
|
|
1194
|
+
|
|
1195
|
+
const result = validateHTML('<div>test</div>');
|
|
1196
|
+
|
|
1197
|
+
const tagPair = result.warnings.find((w) => w.rule === 'tag-pair');
|
|
1198
|
+
expect(tagPair).toBeDefined();
|
|
1199
|
+
expect(tagPair.severity).toBe('warning');
|
|
1200
|
+
|
|
1201
|
+
const srcEmpty = result.warnings.find((w) => w.rule === 'src-not-empty');
|
|
1202
|
+
expect(srcEmpty).toBeDefined();
|
|
1203
|
+
expect(srcEmpty.severity).toBe('warning');
|
|
1204
|
+
|
|
1205
|
+
const mixedTabs = result.info.find((i) => i.rule === 'space-tab-mixed-disabled');
|
|
1206
|
+
expect(mixedTabs).toBeDefined();
|
|
1207
|
+
expect(mixedTabs.severity).toBe('info');
|
|
1208
|
+
|
|
1209
|
+
htmlhint.HTMLHint.verify = original;
|
|
1210
|
+
});
|
|
1079
1211
|
});
|
|
1080
1212
|
|
|
1081
1213
|
describe('extractAndValidateCSS Advanced Tests', () => {
|
|
@@ -1078,8 +1078,38 @@ export function SlideBoxContent(props) {
|
|
|
1078
1078
|
)}
|
|
1079
1079
|
|
|
1080
1080
|
{isCreateInApp && (
|
|
1081
|
-
|
|
1082
|
-
|
|
1081
|
+
(isFullMode && !commonUtil.hasNewEditorFlowInAppEnabled()) ||
|
|
1082
|
+
(!isFullMode && isLoyaltyModule) ||
|
|
1083
|
+
(!isFullMode && !isLoyaltyModule && !commonUtil.hasNewEditorFlowInAppEnabled()) ? (
|
|
1084
|
+
<InApp
|
|
1085
|
+
key="creatives-inapp-create"
|
|
1086
|
+
location={{ pathname: '/inapp/create', query, search: '' }}
|
|
1087
|
+
setIsLoadingContent={setIsLoadingContent}
|
|
1088
|
+
isGetFormData={isGetFormData}
|
|
1089
|
+
getFormData={getFormData}
|
|
1090
|
+
getDefaultTags={type}
|
|
1091
|
+
isFullMode={isFullMode}
|
|
1092
|
+
templateData={templateData}
|
|
1093
|
+
cap={cap}
|
|
1094
|
+
showTemplateName={showTemplateName}
|
|
1095
|
+
showLiquidErrorInFooter={showLiquidErrorInFooter}
|
|
1096
|
+
onValidationFail={onValidationFail}
|
|
1097
|
+
forwardedTags={forwardedTags}
|
|
1098
|
+
selectedOfferDetails={selectedOfferDetails}
|
|
1099
|
+
onPreviewContentClicked={onPreviewContentClicked}
|
|
1100
|
+
onTestContentClicked={onTestContentClicked}
|
|
1101
|
+
eventContextTags={eventContextTags}
|
|
1102
|
+
onCreateComplete={onCreateComplete}
|
|
1103
|
+
handleClose={handleClose}
|
|
1104
|
+
moduleType={moduleType}
|
|
1105
|
+
showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
|
|
1106
|
+
handleTestAndPreview={handleTestAndPreview}
|
|
1107
|
+
handleCloseTestAndPreview={handleCloseTestAndPreview}
|
|
1108
|
+
isTestAndPreviewMode={isTestAndPreviewMode}
|
|
1109
|
+
/>
|
|
1110
|
+
) : (
|
|
1111
|
+
<InAppWrapper
|
|
1112
|
+
key="creatives-inapp-wrapper"
|
|
1083
1113
|
date={new Date().getMilliseconds()}
|
|
1084
1114
|
setIsLoadingContent={setIsLoadingContent}
|
|
1085
1115
|
onInAppEditorTypeChange={onInAppEditorTypeChange}
|
|
@@ -1114,10 +1144,12 @@ export function SlideBoxContent(props) {
|
|
|
1114
1144
|
handleCloseTestAndPreview={handleCloseTestAndPreview}
|
|
1115
1145
|
isTestAndPreviewMode={isTestAndPreviewMode}
|
|
1116
1146
|
/>
|
|
1147
|
+
)
|
|
1117
1148
|
)}
|
|
1118
|
-
|
|
1149
|
+
|
|
1119
1150
|
{isEditInApp && (<InApp
|
|
1120
1151
|
isFullMode={isFullMode}
|
|
1152
|
+
isLoyaltyModule={isLoyaltyModule}
|
|
1121
1153
|
templateData={templateData}
|
|
1122
1154
|
getFormData={getFormData}
|
|
1123
1155
|
getDefaultTags={type}
|