cap-creatives-ui 8.0.280 → 8.0.321
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/.github/workflows/pr-title-check.yml +88 -0
- package/app/constants/unified.js +21 -1
- package/app/containers/App/constants.js +0 -1
- package/app/containers/Login/test/index.test.js +123 -0
- package/app/containers/Login/test/selectors.test.js +165 -0
- package/app/initialState.js +0 -2
- package/app/services/api.js +6 -0
- package/app/services/tests/api.test.js +7 -0
- package/app/services/tests/getSchema.test.js +95 -0
- package/app/utils/common.js +23 -9
- package/app/utils/commonUtils.js +64 -93
- package/app/utils/tagValidations.js +83 -219
- package/app/utils/templateVarUtils.js +172 -0
- package/app/utils/tests/common.test.js +265 -323
- package/app/utils/tests/commonUtil.test.js +461 -118
- package/app/utils/tests/commonUtils.test.js +581 -0
- package/app/utils/tests/messageUtils.test.js +95 -0
- package/app/utils/tests/smsCharCount.test.js +304 -0
- package/app/utils/tests/smsCharCountV2.test.js +213 -10
- package/app/utils/tests/tagValidations.test.js +474 -357
- package/app/utils/tests/templateVarUtils.test.js +160 -0
- package/app/v2Components/CapDeviceContent/index.js +10 -7
- package/app/v2Components/CapTagList/index.js +32 -24
- package/app/v2Components/CapTagList/style.scss +48 -0
- package/app/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
- package/app/v2Components/CapTagListWithInput/index.js +8 -0
- package/app/v2Components/CapWhatsappCTA/index.js +2 -0
- package/app/v2Components/CapWhatsappCarouselButton/index.js +32 -14
- package/app/v2Components/CapWhatsappCarouselButton/tests/index.test.js +120 -2
- package/app/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +39 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +606 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.scss +36 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +79 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/index.js +314 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +141 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +156 -0
- package/app/v2Components/CommonTestAndPreview/SendTestMessage.js +57 -1
- package/app/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
- package/app/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
- package/app/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +210 -4
- package/app/v2Components/CommonTestAndPreview/actions.js +20 -0
- package/app/v2Components/CommonTestAndPreview/constants.js +57 -1
- package/app/v2Components/CommonTestAndPreview/index.js +878 -156
- package/app/v2Components/CommonTestAndPreview/messages.js +41 -3
- package/app/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/app/v2Components/CommonTestAndPreview/reducer.js +47 -0
- package/app/v2Components/CommonTestAndPreview/sagas.js +75 -5
- package/app/v2Components/CommonTestAndPreview/selectors.js +51 -0
- package/app/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
- package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +1156 -0
- package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +334 -0
- package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +576 -0
- package/app/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +156 -0
- package/app/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
- package/app/v2Components/CommonTestAndPreview/tests/actions.test.js +50 -0
- package/app/v2Components/CommonTestAndPreview/tests/constants.test.js +18 -7
- package/app/v2Components/CommonTestAndPreview/tests/index.test.js +914 -5
- package/app/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/app/v2Components/CommonTestAndPreview/tests/reducer.test.js +118 -0
- package/app/v2Components/CommonTestAndPreview/tests/sagas.test.js +146 -378
- package/app/v2Components/CommonTestAndPreview/tests/selectors.test.js +146 -0
- package/app/v2Components/ErrorInfoNote/index.js +24 -26
- package/app/v2Components/FormBuilder/index.js +182 -204
- package/app/v2Components/FormBuilder/messages.js +4 -8
- package/app/v2Components/HtmlEditor/HTMLEditor.js +7 -6
- package/app/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -1
- package/app/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +928 -17
- package/app/v2Components/HtmlEditor/components/CodeEditorPane/index.js +4 -2
- package/app/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +452 -3
- package/app/v2Components/HtmlEditor/hooks/useValidation.js +12 -9
- package/app/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +132 -0
- package/app/v2Components/HtmlEditor/utils/htmlValidator.js +4 -2
- package/app/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
- package/app/v2Components/SmsFallback/constants.js +73 -0
- package/app/v2Components/SmsFallback/index.js +956 -0
- package/app/v2Components/SmsFallback/index.scss +265 -0
- package/app/v2Components/SmsFallback/messages.js +78 -0
- package/app/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
- package/app/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
- package/app/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
- package/app/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
- package/app/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
- package/app/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
- package/app/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/app/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/app/v2Components/TestAndPreviewSlidebox/index.js +22 -1
- package/app/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
- package/app/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
- package/app/v2Components/VarSegmentMessageEditor/constants.js +2 -0
- package/app/v2Components/VarSegmentMessageEditor/index.js +125 -0
- package/app/v2Components/VarSegmentMessageEditor/index.scss +46 -0
- package/app/v2Containers/BeeEditor/index.js +3 -0
- package/app/v2Containers/BeePopupEditor/index.js +9 -2
- package/app/v2Containers/Cap/mockData.js +0 -14
- package/app/v2Containers/Cap/reducer.js +3 -55
- package/app/v2Containers/Cap/tests/reducer.test.js +0 -102
- package/app/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
- package/app/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
- package/app/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
- package/app/v2Containers/CommunicationFlow/constants.js +200 -0
- package/app/v2Containers/CommunicationFlow/index.js +102 -0
- package/app/v2Containers/CommunicationFlow/messages.js +346 -0
- package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
- package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
- package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
- package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
- package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
- package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
- package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
- package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
- package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
- package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
- package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
- package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
- package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
- package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
- package/app/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
- package/app/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
- package/app/v2Containers/CreativesContainer/SlideBoxContent.js +127 -11
- package/app/v2Containers/CreativesContainer/SlideBoxFooter.js +62 -9
- package/app/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
- package/app/v2Containers/CreativesContainer/constants.js +24 -0
- package/app/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
- package/app/v2Containers/CreativesContainer/index.js +346 -71
- package/app/v2Containers/CreativesContainer/index.scss +51 -1
- package/app/v2Containers/CreativesContainer/messages.js +12 -0
- package/app/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/app/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +69 -1
- package/app/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +443 -0
- package/app/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
- package/app/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +147 -4
- package/app/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
- package/app/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +57 -10
- package/app/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/app/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/app/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/app/v2Containers/Email/index.js +2 -5
- package/app/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +58 -77
- package/app/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
- package/app/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +158 -89
- package/app/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
- package/app/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +17 -12
- package/app/v2Containers/EmailWrapper/index.js +4 -0
- package/app/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
- package/app/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +133 -0
- package/app/v2Containers/FTP/index.js +2 -51
- package/app/v2Containers/FTP/messages.js +0 -4
- package/app/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +110 -155
- package/app/v2Containers/InApp/index.js +297 -118
- package/app/v2Containers/InApp/tests/index.test.js +17 -6
- package/app/v2Containers/InApp/tests/mockData.js +1 -1
- package/app/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +19 -0
- package/app/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +3 -0
- package/app/v2Containers/InAppWrapper/index.js +3 -0
- package/app/v2Containers/InappAdvance/index.js +5 -104
- package/app/v2Containers/InappAdvance/tests/index.test.js +2 -0
- package/app/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +24 -3
- package/app/v2Containers/Line/Container/Text/index.js +0 -1
- package/app/v2Containers/MobilePush/Create/index.js +105 -28
- package/app/v2Containers/MobilePush/Create/messages.js +4 -0
- package/app/v2Containers/MobilePush/Edit/index.js +250 -68
- package/app/v2Containers/MobilePush/Edit/messages.js +4 -0
- package/app/v2Containers/MobilePushNew/components/PlatformContentFields.js +36 -12
- package/app/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +68 -27
- package/app/v2Containers/MobilePushNew/index.js +78 -35
- package/app/v2Containers/MobilePushNew/messages.js +8 -0
- package/app/v2Containers/MobilepushWrapper/index.js +11 -1
- package/app/v2Containers/Rcs/constants.js +32 -1
- package/app/v2Containers/Rcs/index.js +963 -916
- package/app/v2Containers/Rcs/index.scss +85 -6
- package/app/v2Containers/Rcs/messages.js +10 -1
- package/app/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
- package/app/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +41136 -1566
- package/app/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
- package/app/v2Containers/Rcs/tests/index.test.js +41 -38
- package/app/v2Containers/Rcs/tests/mockData.js +38 -0
- package/app/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
- package/app/v2Containers/Rcs/tests/utils.test.js +379 -1
- package/app/v2Containers/Rcs/utils.js +358 -10
- package/app/v2Containers/Sms/Create/index.js +122 -39
- package/app/v2Containers/Sms/Create/messages.js +4 -0
- package/app/v2Containers/Sms/Edit/index.js +37 -3
- package/app/v2Containers/Sms/commonMethods.js +3 -6
- package/app/v2Containers/Sms/smsFormDataHelpers.js +67 -0
- package/app/v2Containers/Sms/tests/commonMethods.test.js +122 -0
- package/app/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/app/v2Containers/SmsTrai/Create/index.js +9 -4
- package/app/v2Containers/SmsTrai/Create/index.scss +1 -1
- package/app/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/app/v2Containers/SmsTrai/Edit/index.js +667 -160
- package/app/v2Containers/SmsTrai/Edit/index.scss +121 -0
- package/app/v2Containers/SmsTrai/Edit/messages.js +9 -4
- package/app/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4590 -2436
- package/app/v2Containers/SmsWrapper/index.js +41 -8
- package/app/v2Containers/TagList/index.js +63 -2
- package/app/v2Containers/TagList/messages.js +8 -0
- package/app/v2Containers/TagList/tests/TagList.test.js +122 -20
- package/app/v2Containers/TagList/tests/mockdata.js +17 -0
- package/app/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/app/v2Containers/Templates/_templates.scss +61 -2
- package/app/v2Containers/Templates/actions.js +11 -0
- package/app/v2Containers/Templates/constants.js +2 -0
- package/app/v2Containers/Templates/index.js +90 -40
- package/app/v2Containers/Templates/reducer.js +3 -1
- package/app/v2Containers/Templates/sagas.js +57 -12
- package/app/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/app/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
- package/app/v2Containers/Templates/tests/reducer.test.js +12 -0
- package/app/v2Containers/Templates/tests/sagas.test.js +193 -12
- package/app/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/app/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
- package/app/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
- package/app/v2Containers/TemplatesV2/index.js +147 -49
- package/app/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/app/v2Containers/Viber/index.js +9 -10
- package/app/v2Containers/Viber/index.scss +1 -1
- package/app/v2Containers/WebPush/Create/components/BrandIconSection.test.js +264 -0
- package/app/v2Containers/WebPush/Create/components/MessageSection.js +78 -19
- package/app/v2Containers/WebPush/Create/components/MessageSection.test.js +82 -0
- package/app/v2Containers/WebPush/Create/components/__snapshots__/BrandIconSection.test.js.snap +187 -0
- package/app/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +25 -17
- package/app/v2Containers/WebPush/Create/hooks/useAiraTriggerPosition.js +80 -0
- package/app/v2Containers/WebPush/Create/hooks/useAiraTriggerPosition.test.js +210 -0
- package/app/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -5
- package/app/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
- package/app/v2Containers/WebPush/Create/index.js +36 -6
- package/app/v2Containers/WebPush/Create/index.scss +5 -0
- package/app/v2Containers/WebPush/Create/messages.js +8 -1
- package/app/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +269 -0
- package/app/v2Containers/WebPush/Create/utils/validation.js +31 -15
- package/app/v2Containers/WebPush/Create/utils/validation.test.js +72 -24
- package/app/v2Containers/Whatsapp/index.js +28 -53
- package/app/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +26939 -3982
- package/app/v2Containers/Whatsapp/tests/index.test.js +172 -0
- package/app/v2Containers/Zalo/index.js +5 -11
- package/package.json +2 -2
- package/version +9 -0
|
@@ -59,6 +59,7 @@ const CodeEditorPaneComponent = ({
|
|
|
59
59
|
injectedTags = {},
|
|
60
60
|
location,
|
|
61
61
|
eventContextTags = [],
|
|
62
|
+
waitEventContextTags = {},
|
|
62
63
|
selectedOfferDetails = [],
|
|
63
64
|
channel,
|
|
64
65
|
userLocale = 'en',
|
|
@@ -69,7 +70,7 @@ const CodeEditorPaneComponent = ({
|
|
|
69
70
|
}) => {
|
|
70
71
|
const context = useEditorContext();
|
|
71
72
|
const {
|
|
72
|
-
content, validation, variant,
|
|
73
|
+
content, validation, variant,
|
|
73
74
|
} = context || {};
|
|
74
75
|
const { content: contentValue, updateContent } = content || {};
|
|
75
76
|
const editorRef = useRef(null);
|
|
@@ -289,6 +290,7 @@ const CodeEditorPaneComponent = ({
|
|
|
289
290
|
location={location}
|
|
290
291
|
selectedOfferDetails={selectedOfferDetails}
|
|
291
292
|
eventContextTags={eventContextTags}
|
|
293
|
+
waitEventContextTags={waitEventContextTags}
|
|
292
294
|
popoverPlacement="rightTop"
|
|
293
295
|
/>
|
|
294
296
|
</CapRow>
|
|
@@ -298,7 +300,6 @@ const CodeEditorPaneComponent = ({
|
|
|
298
300
|
<ValidationErrorDisplay
|
|
299
301
|
validation={validation}
|
|
300
302
|
onErrorClick={onErrorClick}
|
|
301
|
-
isLiquidEnabled={isLiquidEnabled}
|
|
302
303
|
className="code-editor-pane__validation"
|
|
303
304
|
/>
|
|
304
305
|
</div>
|
|
@@ -328,6 +329,7 @@ CodeEditorPane.propTypes = {
|
|
|
328
329
|
injectedTags: PropTypes.object,
|
|
329
330
|
location: PropTypes.object,
|
|
330
331
|
eventContextTags: PropTypes.array,
|
|
332
|
+
waitEventContextTags: PropTypes.object,
|
|
331
333
|
selectedOfferDetails: PropTypes.array,
|
|
332
334
|
channel: PropTypes.string,
|
|
333
335
|
userLocale: PropTypes.string,
|
|
@@ -155,7 +155,7 @@ describe('useValidation', () => {
|
|
|
155
155
|
await Promise.resolve();
|
|
156
156
|
});
|
|
157
157
|
|
|
158
|
-
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null);
|
|
158
|
+
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null, { skipLiquidValidation: false });
|
|
159
159
|
});
|
|
160
160
|
|
|
161
161
|
it('updates validation when content changes', async () => {
|
|
@@ -472,7 +472,7 @@ describe('useValidation', () => {
|
|
|
472
472
|
await Promise.resolve();
|
|
473
473
|
});
|
|
474
474
|
|
|
475
|
-
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'inapp', null);
|
|
475
|
+
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'inapp', null, { skipLiquidValidation: false });
|
|
476
476
|
});
|
|
477
477
|
|
|
478
478
|
it('defaults to email variant', async () => {
|
|
@@ -487,7 +487,7 @@ describe('useValidation', () => {
|
|
|
487
487
|
await Promise.resolve();
|
|
488
488
|
});
|
|
489
489
|
|
|
490
|
-
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null);
|
|
490
|
+
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null, { skipLiquidValidation: false });
|
|
491
491
|
});
|
|
492
492
|
});
|
|
493
493
|
|
|
@@ -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');
|
|
@@ -922,4 +1242,133 @@ describe('useValidation', () => {
|
|
|
922
1242
|
});
|
|
923
1243
|
});
|
|
924
1244
|
});
|
|
1245
|
+
|
|
1246
|
+
describe('isFullMode - skip liquid validation', () => {
|
|
1247
|
+
it('passes skipLiquidValidation: true to validateHTML when isFullMode is true', async () => {
|
|
1248
|
+
const { validateHTML } = require('../../utils/htmlValidator');
|
|
1249
|
+
|
|
1250
|
+
render(<TestComponent content="<p>Test</p>" options={{ isFullMode: true }} />);
|
|
1251
|
+
|
|
1252
|
+
await act(async () => {
|
|
1253
|
+
jest.advanceTimersByTime(500);
|
|
1254
|
+
await Promise.resolve();
|
|
1255
|
+
await Promise.resolve();
|
|
1256
|
+
await Promise.resolve();
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
expect(validateHTML).toHaveBeenCalledWith('<p>Test</p>', 'email', null, { skipLiquidValidation: true });
|
|
1260
|
+
});
|
|
1261
|
+
|
|
1262
|
+
it('excludes API liquid errors from getAllIssues when isFullMode is true', async () => {
|
|
1263
|
+
let validationState;
|
|
1264
|
+
const onStateChange = (state) => { validationState = state; };
|
|
1265
|
+
|
|
1266
|
+
const apiValidationErrors = {
|
|
1267
|
+
liquidErrors: ['Unsupported tag: points_balance'],
|
|
1268
|
+
standardErrors: [],
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1271
|
+
render(
|
|
1272
|
+
<TestComponent
|
|
1273
|
+
content="<p>Test</p>"
|
|
1274
|
+
options={{ apiValidationErrors, isFullMode: true }}
|
|
1275
|
+
onStateChange={onStateChange}
|
|
1276
|
+
/>
|
|
1277
|
+
);
|
|
1278
|
+
|
|
1279
|
+
await act(async () => {
|
|
1280
|
+
jest.advanceTimersByTime(500);
|
|
1281
|
+
await Promise.resolve();
|
|
1282
|
+
await Promise.resolve();
|
|
1283
|
+
await Promise.resolve();
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1286
|
+
await waitFor(() => {
|
|
1287
|
+
expect(validationState).toBeDefined();
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
const issues = validationState.getAllIssues();
|
|
1291
|
+
const liquidIssues = issues.filter((i) => i.source === 'liquid-validator');
|
|
1292
|
+
expect(liquidIssues).toHaveLength(0);
|
|
1293
|
+
});
|
|
1294
|
+
|
|
1295
|
+
it('returns isClean true when only liquid errors exist and isFullMode is true', () => {
|
|
1296
|
+
const apiValidationErrors = {
|
|
1297
|
+
liquidErrors: ['Unsupported tag: points_balance'],
|
|
1298
|
+
standardErrors: [],
|
|
1299
|
+
};
|
|
1300
|
+
|
|
1301
|
+
render(
|
|
1302
|
+
<TestComponent
|
|
1303
|
+
content=""
|
|
1304
|
+
options={{ apiValidationErrors, isFullMode: true }}
|
|
1305
|
+
/>
|
|
1306
|
+
);
|
|
1307
|
+
|
|
1308
|
+
// Before validation runs, isClean should be true because liquid errors are ignored in full mode
|
|
1309
|
+
expect(screen.getByTestId('is-clean')).toHaveTextContent('true');
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1312
|
+
it('does not treat liquid errors as blocking when isFullMode is true', async () => {
|
|
1313
|
+
let validationState;
|
|
1314
|
+
const onStateChange = (state) => { validationState = state; };
|
|
1315
|
+
|
|
1316
|
+
const apiValidationErrors = {
|
|
1317
|
+
liquidErrors: ['Unsupported tag: points_balance'],
|
|
1318
|
+
standardErrors: [],
|
|
1319
|
+
};
|
|
1320
|
+
|
|
1321
|
+
render(
|
|
1322
|
+
<TestComponent
|
|
1323
|
+
content="<p>Valid</p>"
|
|
1324
|
+
options={{ apiValidationErrors, isFullMode: true }}
|
|
1325
|
+
onStateChange={onStateChange}
|
|
1326
|
+
/>
|
|
1327
|
+
);
|
|
1328
|
+
|
|
1329
|
+
await act(async () => {
|
|
1330
|
+
jest.advanceTimersByTime(500);
|
|
1331
|
+
await Promise.resolve();
|
|
1332
|
+
await Promise.resolve();
|
|
1333
|
+
await Promise.resolve();
|
|
1334
|
+
});
|
|
1335
|
+
|
|
1336
|
+
await waitFor(() => {
|
|
1337
|
+
expect(validationState).toBeDefined();
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1340
|
+
expect(validationState.hasBlockingErrors).toBe(false);
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
it('still treats standard API errors as blocking when isFullMode is true', async () => {
|
|
1344
|
+
let validationState;
|
|
1345
|
+
const onStateChange = (state) => { validationState = state; };
|
|
1346
|
+
|
|
1347
|
+
const apiValidationErrors = {
|
|
1348
|
+
liquidErrors: ['Liquid error'],
|
|
1349
|
+
standardErrors: ['Standard error'],
|
|
1350
|
+
};
|
|
1351
|
+
|
|
1352
|
+
render(
|
|
1353
|
+
<TestComponent
|
|
1354
|
+
content="<p>Valid</p>"
|
|
1355
|
+
options={{ apiValidationErrors, isFullMode: true }}
|
|
1356
|
+
onStateChange={onStateChange}
|
|
1357
|
+
/>
|
|
1358
|
+
);
|
|
1359
|
+
|
|
1360
|
+
await act(async () => {
|
|
1361
|
+
jest.advanceTimersByTime(500);
|
|
1362
|
+
await Promise.resolve();
|
|
1363
|
+
await Promise.resolve();
|
|
1364
|
+
await Promise.resolve();
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
await waitFor(() => {
|
|
1368
|
+
expect(validationState).toBeDefined();
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
expect(validationState.hasBlockingErrors).toBe(true);
|
|
1372
|
+
});
|
|
1373
|
+
});
|
|
925
1374
|
});
|
|
@@ -77,6 +77,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
77
77
|
enableSanitization = true,
|
|
78
78
|
securityLevel = 'standard',
|
|
79
79
|
apiValidationErrors = null, // API validation errors from validateLiquidTemplateContent
|
|
80
|
+
isFullMode = false, // When true, skip liquid validation (standalone/full mode)
|
|
80
81
|
} = options;
|
|
81
82
|
|
|
82
83
|
// Validation state
|
|
@@ -137,7 +138,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
137
138
|
|
|
138
139
|
try {
|
|
139
140
|
// 1. HTML Validation
|
|
140
|
-
const htmlValidation = validateHTML(htmlContent, variant, formatValidatorMessage);
|
|
141
|
+
const htmlValidation = validateHTML(htmlContent, variant, formatValidatorMessage, { skipLiquidValidation: isFullMode });
|
|
141
142
|
|
|
142
143
|
// 2. CSS Validation (extract from HTML)
|
|
143
144
|
const cssValidation = extractAndValidateCSS(htmlContent, formatValidatorMessage);
|
|
@@ -206,7 +207,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
206
207
|
},
|
|
207
208
|
}));
|
|
208
209
|
}
|
|
209
|
-
}, [variant, enableSanitization, securityLevel, formatSanitizerMessage, formatValidatorMessage]);
|
|
210
|
+
}, [variant, enableSanitization, securityLevel, formatSanitizerMessage, formatValidatorMessage, isFullMode]);
|
|
210
211
|
|
|
211
212
|
/**
|
|
212
213
|
* Validates content with debouncing
|
|
@@ -339,7 +340,7 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
339
340
|
*/
|
|
340
341
|
const getAllIssues = useCallback(() => {
|
|
341
342
|
// API errors (liquid + standard) are blocking – they block Save/Update/Preview/Test
|
|
342
|
-
const apiLiquidErrors = (apiValidationErrors?.liquidErrors || []).map((errorMessage) => {
|
|
343
|
+
const apiLiquidErrors = (isFullMode ? [] : (apiValidationErrors?.liquidErrors || [])).map((errorMessage) => {
|
|
343
344
|
const extractedLine = extractLineNumberFromMessage(errorMessage);
|
|
344
345
|
return {
|
|
345
346
|
type: VALIDATION_SEVERITY.ERROR,
|
|
@@ -420,19 +421,20 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
420
421
|
|
|
421
422
|
// Ensure we always return an array
|
|
422
423
|
return Array.isArray(allIssues) ? allIssues : [];
|
|
423
|
-
}, [validationState, apiValidationErrors, extractLineNumberFromMessage, content]);
|
|
424
|
+
}, [validationState, apiValidationErrors, extractLineNumberFromMessage, content, isFullMode]);
|
|
424
425
|
|
|
425
426
|
/**
|
|
426
427
|
* Check if validation is clean (no errors or warnings)
|
|
427
428
|
* Includes API validation errors in the check
|
|
428
429
|
*/
|
|
429
430
|
const isClean = useCallback(() => {
|
|
430
|
-
const
|
|
431
|
+
const liquidErrorCount = isFullMode ? 0 : (apiValidationErrors?.liquidErrors?.length || 0);
|
|
432
|
+
const hasApiErrors = liquidErrorCount + (apiValidationErrors?.standardErrors?.length || 0) > 0;
|
|
431
433
|
return validationState.summary.totalErrors === 0
|
|
432
434
|
&& validationState.summary.totalWarnings === 0
|
|
433
435
|
&& !validationState.summary.hasSecurityIssues
|
|
434
436
|
&& !hasApiErrors;
|
|
435
|
-
}, [validationState.summary, apiValidationErrors]);
|
|
437
|
+
}, [validationState.summary, apiValidationErrors, isFullMode]);
|
|
436
438
|
|
|
437
439
|
// Effect to validate content when it changes
|
|
438
440
|
useEffect(() => {
|
|
@@ -448,11 +450,12 @@ export const useValidation = (content, variant = 'email', options = {}, formatSa
|
|
|
448
450
|
}
|
|
449
451
|
}, []);
|
|
450
452
|
|
|
451
|
-
const
|
|
453
|
+
const hasApiLiquidErrors = isFullMode ? false : (apiValidationErrors?.liquidErrors?.length || 0) > 0;
|
|
454
|
+
const hasApiErrors = hasApiLiquidErrors || (apiValidationErrors?.standardErrors?.length || 0) > 0;
|
|
452
455
|
|
|
453
456
|
const protocolTypes = ['JavaScript Protocol', 'Data URL', 'VBScript Protocol'];
|
|
454
|
-
// Client-side Liquid validation errors are blocking (genuine syntax errors)
|
|
455
|
-
const hasClientSideLiquidErrors = (validationState.htmlErrors || []).some((e) => e.source === ISSUE_SOURCES.LIQUID && e.severity === VALIDATION_SEVERITY.ERROR);
|
|
457
|
+
// Client-side Liquid validation errors are blocking (genuine syntax errors) - skip in full mode
|
|
458
|
+
const hasClientSideLiquidErrors = isFullMode ? false : (validationState.htmlErrors || []).some((e) => e.source === ISSUE_SOURCES.LIQUID && e.severity === VALIDATION_SEVERITY.ERROR);
|
|
456
459
|
const hasBlockingErrors = (validationState.sanitizationWarnings || []).some((w) => BLOCKING_ERROR_RULE_IDS.includes(w.rule)) || (validationState.securityIssues || []).some((s) => protocolTypes.includes(s?.type)) || hasApiErrors || hasClientSideLiquidErrors;
|
|
457
460
|
|
|
458
461
|
return {
|