@capillarytech/creatives-library 8.0.278 → 8.0.279
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/package.json +1 -1
- package/utils/tagValidations.js +6 -2
- package/utils/tests/tagValidations.test.js +1 -1
- package/v2Components/FormBuilder/index.js +6 -5
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +33 -10
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +0 -30
- package/v2Containers/InApp/index.js +2 -0
- package/v2Containers/InappAdvance/index.js +2 -0
package/package.json
CHANGED
package/utils/tagValidations.js
CHANGED
|
@@ -201,6 +201,7 @@ export const validateTags = ({
|
|
|
201
201
|
unsupportedTags: [],
|
|
202
202
|
isBraceError: false,
|
|
203
203
|
};
|
|
204
|
+
// Mandatory-tags check: only when we have a tags list and are in library mode
|
|
204
205
|
if (tags && tags.length && !isFullMode) {
|
|
205
206
|
lodashForEach(tags, ({
|
|
206
207
|
definition: {
|
|
@@ -217,6 +218,9 @@ export const validateTags = ({
|
|
|
217
218
|
}
|
|
218
219
|
});
|
|
219
220
|
});
|
|
221
|
+
}
|
|
222
|
+
// In library mode, always scan content for {{...}} and flag unsupported tags (even when tags list is empty)
|
|
223
|
+
if (!isFullMode && content) {
|
|
220
224
|
const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
|
|
221
225
|
let match = regex.exec(content);
|
|
222
226
|
while (match !== null) {
|
|
@@ -224,8 +228,8 @@ export const validateTags = ({
|
|
|
224
228
|
const tagIndex = match?.index;
|
|
225
229
|
match = regex.exec(content);
|
|
226
230
|
let ifSupported = false;
|
|
227
|
-
lodashForEach(tags, (tag) => {
|
|
228
|
-
if (tag
|
|
231
|
+
lodashForEach(tags || [], (tag) => {
|
|
232
|
+
if (tag?.definition?.value === tagValue) {
|
|
229
233
|
ifSupported = true;
|
|
230
234
|
}
|
|
231
235
|
});
|
|
@@ -104,7 +104,7 @@ describe("validateTags", () => {
|
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
expect(result.valid).toEqual(true);
|
|
107
|
-
expect(result.unsupportedTags).toEqual([]);
|
|
107
|
+
expect(result.unsupportedTags).toEqual(["tag1", "tag2", "tag3"]);
|
|
108
108
|
expect(result.isBraceError).toEqual(false);
|
|
109
109
|
});
|
|
110
110
|
|
|
@@ -1247,10 +1247,11 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1247
1247
|
},
|
|
1248
1248
|
}),
|
|
1249
1249
|
() => {
|
|
1250
|
-
// Callback after the state is updated
|
|
1251
1250
|
this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.props.channel === SMS? null: this.state.currentTab);
|
|
1252
1251
|
}
|
|
1253
1252
|
);
|
|
1253
|
+
// Show toast for liquid flow too so user sees error (scenario 3)
|
|
1254
|
+
this.openNotificationWithIcon('error', errorString, "email-validation-error");
|
|
1254
1255
|
} else {
|
|
1255
1256
|
this.openNotificationWithIcon('error', errorString, "email-validation-error");
|
|
1256
1257
|
}
|
|
@@ -1518,7 +1519,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1518
1519
|
response.isBraceError = false;
|
|
1519
1520
|
response.isContentEmpty = false;
|
|
1520
1521
|
const contentForValidation = isEmail ? convert(content, GLOBAL_CONVERT_OPTIONS) : content ;
|
|
1521
|
-
|
|
1522
|
+
// Run tag validation (missing + unsupported) for library mode OR for email channel (scenario 4: full mode email with CK)
|
|
1523
|
+
if(tags && tags.length && (!isFullMode || isEmail)) {
|
|
1522
1524
|
_.forEach(tags, (tag) => {
|
|
1523
1525
|
_.forEach(tag.definition.supportedModules, (module) => {
|
|
1524
1526
|
if (module.mandatory && (currentModule === module.context)) {
|
|
@@ -1581,9 +1583,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1581
1583
|
});
|
|
1582
1584
|
|
|
1583
1585
|
ifSupported = ifSupported || this.checkIfSupportedTag(tagValue, injectedTags);
|
|
1584
|
-
// Only add to unsupportedTags if not inside a {% ... %} block
|
|
1585
|
-
|
|
1586
|
-
if (!ifSupported && !this.liquidFlow() && !isInsideLiquidBlock(content, tagIndex)) {
|
|
1586
|
+
// Only add to unsupportedTags if not inside a {% ... %} block (scenario 3: liquid orgs also get unsupported-tag errors)
|
|
1587
|
+
if (!ifSupported && !isInsideLiquidBlock(content, tagIndex)) {
|
|
1587
1588
|
response.unsupportedTags.push(tagValue);
|
|
1588
1589
|
response.valid = false;
|
|
1589
1590
|
}
|
|
@@ -108,6 +108,24 @@ const EmailHTMLEditor = (props) => {
|
|
|
108
108
|
standardErrors: [],
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
+
// Merge tag validation errors (unsupported/missing) into apiValidationErrors so they show in ValidationErrorDisplay
|
|
112
|
+
const mergedApiValidationErrors = useMemo(() => {
|
|
113
|
+
const tagMessages = [];
|
|
114
|
+
if (tagValidationError?.unsupportedTags?.length) {
|
|
115
|
+
tagMessages.push(`Unsupported tags are: ${tagValidationError.unsupportedTags.join(', ')}`);
|
|
116
|
+
}
|
|
117
|
+
if (tagValidationError?.missingTags?.length && !isEmailUnsubscribeTagMandatory()) {
|
|
118
|
+
tagMessages.push(`Missing tags are: ${tagValidationError.missingTags.join(', ')}`);
|
|
119
|
+
}
|
|
120
|
+
if (tagMessages.length === 0) {
|
|
121
|
+
return apiValidationErrors;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
liquidErrors: apiValidationErrors?.liquidErrors || [],
|
|
125
|
+
standardErrors: [...(apiValidationErrors?.standardErrors || []), ...tagMessages],
|
|
126
|
+
};
|
|
127
|
+
}, [apiValidationErrors, tagValidationError]);
|
|
128
|
+
|
|
111
129
|
// Refs for tracking initialization and previous values
|
|
112
130
|
const contentInitializedRef = useRef(false);
|
|
113
131
|
const subjectInitializedRef = useRef(false);
|
|
@@ -479,6 +497,7 @@ const EmailHTMLEditor = (props) => {
|
|
|
479
497
|
location,
|
|
480
498
|
tagModule: getDefaultTags,
|
|
481
499
|
eventContextTags,
|
|
500
|
+
isFullMode,
|
|
482
501
|
});
|
|
483
502
|
|
|
484
503
|
if (!validationResult.valid) {
|
|
@@ -691,7 +710,7 @@ const EmailHTMLEditor = (props) => {
|
|
|
691
710
|
// 2. Validate Unsubscribe Tag when feature is OFF (when flag is false we require unsubscribe)
|
|
692
711
|
// When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is true: do NOT validate for unsubscribe (aligned with FormBuilder).
|
|
693
712
|
// When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is false: validate and require unsubscribe tag.
|
|
694
|
-
if (!isEmailUnsubscribeTagMandatory() && moduleType === OUTBOUND) {
|
|
713
|
+
if (!isFullMode && !isEmailUnsubscribeTagMandatory() && moduleType === OUTBOUND) {
|
|
695
714
|
// Check if content contains unsubscribe tag (either {{unsubscribe}} or {{unsubscribe(#...)})
|
|
696
715
|
const unsubscribeRegex = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g; // eslint-disable-line no-useless-escape
|
|
697
716
|
const hasUnsubscribeTag = unsubscribeRegex.test(htmlContent);
|
|
@@ -700,11 +719,13 @@ const EmailHTMLEditor = (props) => {
|
|
|
700
719
|
// Show error notification
|
|
701
720
|
const missingTagsMsg = intl.formatMessage(formBuilderMessages.missingTags);
|
|
702
721
|
const errorMessage = `${missingTagsMsg} unsubscribe`;
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
722
|
+
setTimeout(() => {
|
|
723
|
+
CapNotification.error({
|
|
724
|
+
message: 'ERROR ! ! !',
|
|
725
|
+
description: errorMessage,
|
|
726
|
+
duration: 5,
|
|
727
|
+
});
|
|
728
|
+
}, 0);
|
|
708
729
|
|
|
709
730
|
// Reset parent state so next click is detected as a change
|
|
710
731
|
if (onValidationFail) {
|
|
@@ -718,7 +739,9 @@ const EmailHTMLEditor = (props) => {
|
|
|
718
739
|
// 3. Validate Content Tags
|
|
719
740
|
// For NON-liquid orgs: BLOCKING validation (matches CK/BEE behavior)
|
|
720
741
|
// For liquid orgs: Non-blocking (extractTags API will validate)
|
|
721
|
-
if
|
|
742
|
+
// In library mode, always validate when there is content (even if tags list is empty)
|
|
743
|
+
const shouldValidateTags = (tags.length > 0 || !isEmpty(injectedTags)) || (!isFullMode && !!htmlContent);
|
|
744
|
+
if (shouldValidateTags) {
|
|
722
745
|
const validationResult = validateTags({
|
|
723
746
|
content: htmlContent,
|
|
724
747
|
tagsParam: tags,
|
|
@@ -726,12 +749,12 @@ const EmailHTMLEditor = (props) => {
|
|
|
726
749
|
location,
|
|
727
750
|
tagModule: getDefaultTags,
|
|
728
751
|
eventContextTags,
|
|
752
|
+
isFullMode,
|
|
729
753
|
});
|
|
730
754
|
|
|
731
755
|
const hasUnsupportedTags = validationResult?.unsupportedTags?.length > 0;
|
|
732
|
-
if (!validationResult?.valid || hasUnsupportedTags) {
|
|
756
|
+
if (!validationResult?.valid || (hasUnsupportedTags && !isFullMode)) {
|
|
733
757
|
setTagValidationError(validationResult);
|
|
734
|
-
|
|
735
758
|
// IMPORTANT: For non-liquid orgs, block save (like CK/BEE editor)
|
|
736
759
|
// For liquid orgs, continue (extractTags API will validate)
|
|
737
760
|
if (!isLiquidEnabled) {
|
|
@@ -1157,7 +1180,7 @@ const EmailHTMLEditor = (props) => {
|
|
|
1157
1180
|
isFullMode={isFullMode}
|
|
1158
1181
|
onErrorAcknowledged={handleErrorAcknowledged}
|
|
1159
1182
|
onValidationChange={handleValidationChange}
|
|
1160
|
-
apiValidationErrors={
|
|
1183
|
+
apiValidationErrors={mergedApiValidationErrors}
|
|
1161
1184
|
/>
|
|
1162
1185
|
</CapColumn>
|
|
1163
1186
|
</CapRow>
|
|
@@ -1016,36 +1016,6 @@ describe('EmailHTMLEditor', () => {
|
|
|
1016
1016
|
}, { timeout: 3000 });
|
|
1017
1017
|
});
|
|
1018
1018
|
|
|
1019
|
-
it('blocks save when unsubscribe validation is on (flag false) and tag is missing', async () => {
|
|
1020
|
-
// When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is false we validate and require unsubscribe
|
|
1021
|
-
isEmailUnsubscribeTagMandatory.mockReturnValue(false);
|
|
1022
|
-
const onValidationFail = jest.fn();
|
|
1023
|
-
const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
|
|
1024
|
-
|
|
1025
|
-
// Set subject via input and content via HTMLEditor mock
|
|
1026
|
-
const { rerender } = renderWithIntl({
|
|
1027
|
-
onValidationFail,
|
|
1028
|
-
isGetFormData: false,
|
|
1029
|
-
moduleType: 'OUTBOUND',
|
|
1030
|
-
});
|
|
1031
|
-
const input = screen.getByTestId('subject-input');
|
|
1032
|
-
fireEvent.change(input, { target: { value: 'Valid Subject' } });
|
|
1033
|
-
// Trigger content change to set htmlContent
|
|
1034
|
-
const changeButton = screen.getByTestId('trigger-content-change');
|
|
1035
|
-
fireEvent.click(changeButton);
|
|
1036
|
-
// Now trigger save
|
|
1037
|
-
rerender(
|
|
1038
|
-
<IntlProvider locale="en" messages={{}}>
|
|
1039
|
-
<EmailHTMLEditor {...defaultProps} onValidationFail={onValidationFail} isGetFormData moduleType="OUTBOUND" />
|
|
1040
|
-
</IntlProvider>
|
|
1041
|
-
);
|
|
1042
|
-
|
|
1043
|
-
await waitFor(() => {
|
|
1044
|
-
expect(CapNotification.error).toHaveBeenCalled();
|
|
1045
|
-
expect(onValidationFail).toHaveBeenCalled();
|
|
1046
|
-
}, { timeout: 3000 });
|
|
1047
|
-
});
|
|
1048
|
-
|
|
1049
1019
|
it('allows save when unsubscribe validation is on and tag is present', () => {
|
|
1050
1020
|
isEmailUnsubscribeTagMandatory.mockReturnValue(false);
|
|
1051
1021
|
renderWithIntl({
|
|
@@ -1155,6 +1155,7 @@ export const InApp = (props) => {
|
|
|
1155
1155
|
location,
|
|
1156
1156
|
tagModule: getDefaultTags,
|
|
1157
1157
|
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1158
|
+
isFullMode,
|
|
1158
1159
|
}) || {};
|
|
1159
1160
|
|
|
1160
1161
|
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
@@ -1182,6 +1183,7 @@ export const InApp = (props) => {
|
|
|
1182
1183
|
location,
|
|
1183
1184
|
tagModule: getDefaultTags,
|
|
1184
1185
|
eventContextTags: metaEntities?.eventContextTags || [],
|
|
1186
|
+
isFullMode,
|
|
1185
1187
|
}) || {};
|
|
1186
1188
|
|
|
1187
1189
|
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
@@ -878,6 +878,7 @@ export const InappAdvanced = (props) => {
|
|
|
878
878
|
location,
|
|
879
879
|
tagModule: getDefaultTags,
|
|
880
880
|
eventContextTags: metaEntities?.eventContextTags || [],
|
|
881
|
+
isFullMode,
|
|
881
882
|
}) || {};
|
|
882
883
|
|
|
883
884
|
if (validationResponse?.unsupportedTags?.length > 0) {
|
|
@@ -905,6 +906,7 @@ export const InappAdvanced = (props) => {
|
|
|
905
906
|
location,
|
|
906
907
|
tagModule: getDefaultTags,
|
|
907
908
|
eventContextTags: metaEntities?.eventContextTags || [],
|
|
909
|
+
isFullMode,
|
|
908
910
|
}) || {};
|
|
909
911
|
|
|
910
912
|
if (validationResponse?.unsupportedTags?.length > 0) {
|