@capillarytech/creatives-library 8.0.306 → 8.0.307

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 (54) hide show
  1. package/constants/unified.js +5 -1
  2. package/initialState.js +0 -2
  3. package/package.json +1 -1
  4. package/utils/common.js +5 -8
  5. package/utils/commonUtils.js +36 -93
  6. package/utils/tagValidations.js +83 -223
  7. package/utils/tests/commonUtil.test.js +147 -124
  8. package/utils/tests/tagValidations.test.js +441 -358
  9. package/v2Components/ErrorInfoNote/index.js +2 -5
  10. package/v2Components/FormBuilder/index.js +137 -203
  11. package/v2Components/FormBuilder/messages.js +0 -8
  12. package/v2Components/HtmlEditor/HTMLEditor.js +0 -5
  13. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +0 -1
  14. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +0 -15
  15. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +1 -2
  16. package/v2Containers/Cap/mockData.js +0 -14
  17. package/v2Containers/Cap/reducer.js +3 -55
  18. package/v2Containers/Cap/tests/reducer.test.js +0 -102
  19. package/v2Containers/CreativesContainer/SlideBoxContent.js +5 -1
  20. package/v2Containers/CreativesContainer/SlideBoxFooter.js +13 -5
  21. package/v2Containers/CreativesContainer/constants.js +6 -0
  22. package/v2Containers/CreativesContainer/index.js +47 -7
  23. package/v2Containers/Email/index.js +1 -5
  24. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +23 -70
  25. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -120
  26. package/v2Containers/FTP/index.js +2 -51
  27. package/v2Containers/FTP/messages.js +0 -4
  28. package/v2Containers/InApp/index.js +35 -107
  29. package/v2Containers/InApp/tests/index.test.js +17 -6
  30. package/v2Containers/InappAdvance/index.js +4 -112
  31. package/v2Containers/InappAdvance/tests/index.test.js +2 -0
  32. package/v2Containers/Line/Container/Text/index.js +0 -1
  33. package/v2Containers/MobilePush/Create/index.js +59 -19
  34. package/v2Containers/MobilePush/Edit/index.js +48 -20
  35. package/v2Containers/MobilePushNew/index.js +12 -32
  36. package/v2Containers/MobilepushWrapper/index.js +3 -1
  37. package/v2Containers/Rcs/index.js +12 -37
  38. package/v2Containers/Sms/Create/index.js +39 -3
  39. package/v2Containers/Sms/Create/messages.js +4 -0
  40. package/v2Containers/Sms/Edit/index.js +35 -3
  41. package/v2Containers/Sms/commonMethods.js +3 -6
  42. package/v2Containers/Sms/tests/commonMethods.test.js +122 -0
  43. package/v2Containers/SmsTrai/Edit/index.js +11 -47
  44. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
  45. package/v2Containers/SmsWrapper/index.js +2 -0
  46. package/v2Containers/TemplatesV2/index.js +28 -13
  47. package/v2Containers/Viber/index.js +0 -1
  48. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -3
  49. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
  50. package/v2Containers/WebPush/Create/index.js +2 -2
  51. package/v2Containers/WebPush/Create/utils/validation.js +17 -8
  52. package/v2Containers/WebPush/Create/utils/validation.test.js +44 -24
  53. package/v2Containers/Whatsapp/index.js +9 -17
  54. package/v2Containers/Zalo/index.js +3 -11
@@ -77,7 +77,6 @@ import { getContent } from "../MobilePush/commonMethods";
77
77
  import { getMessageObject } from "../../utils/messageUtils";
78
78
  import { gtmPush } from "../../utils/gtmTrackers";
79
79
  import mobilePushReducer from "./reducer";
80
- import { hasLiquidSupportFeature } from "../../utils/common";
81
80
  import formBuilderMessages from "../../v2Components/FormBuilder/messages";
82
81
  import { validateMobilePushContent } from "../../utils/commonUtils";
83
82
  import { getSingleTab } from "../InApp/utils";
@@ -803,10 +802,9 @@ const MobilePushNew = ({
803
802
  (value) => {
804
803
  let errorTemplateDescMessage = "";
805
804
 
806
- const { unsupportedTags, isBraceError } = validateTags({
805
+ const { isBraceError } = validateTags({
807
806
  content: value,
808
807
  tagsParam: tags,
809
- injectedTagsParams: injectedTags,
810
808
  location,
811
809
  tagModule: getDefaultTags,
812
810
  isFullMode,
@@ -816,14 +814,6 @@ const MobilePushNew = ({
816
814
  messages.emptyTemplateDescErrorMessage
817
815
  );
818
816
  }
819
- if (unsupportedTags?.length > 0) {
820
- errorTemplateDescMessage = formatMessage(
821
- globalMessages.unsupportedTagsValidationError,
822
- {
823
- unsupportedTags,
824
- }
825
- );
826
- }
827
817
  if (isBraceError) {
828
818
  errorTemplateDescMessage = formatMessage(
829
819
  globalMessages.unbalanacedCurlyBraces
@@ -2665,20 +2655,6 @@ const MobilePushNew = ({
2665
2655
  getLiquidTags: globalActionsProps.getLiquidTags,
2666
2656
  formatMessage,
2667
2657
  messages: formBuilderMessages,
2668
- tagLookupMap: metaEntities?.tagLookupMap || {},
2669
- eventContextTags: metaEntities?.eventContextTags || [],
2670
- isLiquidFlow: hasLiquidSupportFeature(),
2671
- forwardedTags: {},
2672
- skipTags: (tag) => {
2673
- const skipRegexes = [
2674
- /dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
2675
- /unsubscribe\(#[a-zA-Z\d]{6}\)/,
2676
- /Link_to_[a-zA-z]/,
2677
- /SURVEY.*\.TOKEN/,
2678
- /^[A-Za-z].*\([a-zA-Z\d]*\)/,
2679
- ];
2680
- return skipRegexes.some((regex) => regex.test(tag));
2681
- },
2682
2658
  singleTab: getSingleTab(accountData),
2683
2659
  });
2684
2660
  }, [
@@ -2687,13 +2663,10 @@ const MobilePushNew = ({
2687
2663
  activeTab,
2688
2664
  globalActionsProps,
2689
2665
  formatMessage,
2690
- metaEntities,
2691
2666
  accountData,
2692
2667
  isFullMode,
2693
2668
  ]);
2694
2669
 
2695
- const isLiquidFlow = hasLiquidSupportFeature();
2696
-
2697
2670
  useEffect(() => {
2698
2671
  // Always map to { label } for both platforms
2699
2672
  const newButtons = Array.isArray(ctaData)
@@ -2940,16 +2913,22 @@ const MobilePushNew = ({
2940
2913
  setShowTestAndPreviewSlidebox(false);
2941
2914
  }, []);
2942
2915
 
2943
- // Add useEffect to handle isGetFormData prop changes
2916
+ // Add useEffect to handle isGetFormData prop changes (e.g. Done clicked in footer)
2917
+ // In library mode: run liquid validation (extractTags) first; on success liquidMiddleWare calls handleSave
2918
+ // In full mode: call handleSave directly
2944
2919
  useEffect(() => {
2945
2920
  if (isGetFormData) {
2946
- handleSave();
2921
+ if (!isFullMode) {
2922
+ liquidMiddleWare();
2923
+ } else {
2924
+ handleSave();
2925
+ }
2947
2926
  // Reset the flag to prevent infinite loop
2948
2927
  if (onValidationFail) {
2949
2928
  onValidationFail();
2950
2929
  }
2951
2930
  }
2952
- }, [isGetFormData, handleSave, onValidationFail]);
2931
+ }, [isGetFormData, handleSave, onValidationFail, isFullMode, liquidMiddleWare]);
2953
2932
 
2954
2933
  // Add message event listener to handle parent communication (like old MobilePush components)
2955
2934
  useEffect(() => {
@@ -3090,7 +3069,8 @@ const MobilePushNew = ({
3090
3069
  <CapButton
3091
3070
  type="primary"
3092
3071
  onClick={() => {
3093
- if (isLiquidFlow) {
3072
+ // Liquid validation (extractTags) only in library mode
3073
+ if (!isFullMode) {
3094
3074
  liquidMiddleWare();
3095
3075
  } else {
3096
3076
  handleSave();
@@ -72,7 +72,7 @@ export class MobilepushWrapper extends React.Component { // eslint-disable-line
72
72
  }
73
73
 
74
74
  render() {
75
- const {mobilePushCreateMode, step, getFormData, setIsLoadingContent, isGetFormData, query, isFullMode, showTemplateName, type, onValidationFail, onPreviewContentClicked, onTestContentClicked, templateData, eventContextTags = [], showTestAndPreviewSlidebox, handleTestAndPreview, handleCloseTestAndPreview, restrictPersonalization, isAnonymousType, onPersonalizationTokensChange} = this.props;
75
+ const {mobilePushCreateMode, step, getFormData, getLiquidTags, setIsLoadingContent, isGetFormData, query, isFullMode, showTemplateName, type, onValidationFail, onPreviewContentClicked, onTestContentClicked, templateData, eventContextTags = [], showTestAndPreviewSlidebox, handleTestAndPreview, handleCloseTestAndPreview, restrictPersonalization, isAnonymousType, onPersonalizationTokensChange} = this.props;
76
76
  const {templateName} = this.state;
77
77
  const isShowMobilepushCreate = !isEmpty(mobilePushCreateMode);
78
78
  return (
@@ -102,6 +102,7 @@ export class MobilepushWrapper extends React.Component { // eslint-disable-line
102
102
  <div>
103
103
  {isShowMobilepushCreate && <MobilepushCreate
104
104
  getFormLibraryData={getFormData}
105
+ getLiquidTags={getLiquidTags}
105
106
  setIsLoadingContent={setIsLoadingContent}
106
107
  defaultData={{"template-name": templateName}}
107
108
  location={{
@@ -143,6 +144,7 @@ MobilepushWrapper.propTypes = {
143
144
  mobilePushCreateMode: PropTypes.string,
144
145
  step: PropTypes.string,
145
146
  getFormData: PropTypes.string,
147
+ getLiquidTags: PropTypes.func,
146
148
  setIsLoadingContent: PropTypes.func,
147
149
  onEnterTemplateName: PropTypes.func,
148
150
  onRemoveTemplateName: PropTypes.func,
@@ -394,23 +394,15 @@ export const Rcs = (props) => {
394
394
  const validationResponse =
395
395
  validateTags({
396
396
  content: contentForValidation,
397
- tagsParam: tags,
398
- injectedTagsParams: injectedTags,
399
- location,
400
- tagModule: getDefaultTags,
401
- eventContextTags,
402
- isFullMode,
403
- }) || {};
404
- const unsupportedTagsLengthCheck =
405
- validationResponse?.unsupportedTags?.length > 0;
406
- const errorMsg =
407
- (unsupportedTagsLengthCheck &&
408
- formatMessage(globalMessages.unsupportedTagsValidationError, {
409
- unsupportedTags: validationResponse.unsupportedTags,
410
- })) ||
411
- (validationResponse.isBraceError &&
412
- formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
413
- false;
397
+ tagsParam: tags,
398
+ location,
399
+ tagModule: getDefaultTags,
400
+ isFullMode,
401
+ }) || {};
402
+ const errorMsg =
403
+ (validationResponse?.isBraceError &&
404
+ formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
405
+ false;
414
406
  if (type === TITLE_TEXT) setTemplateTitleError(errorMsg);
415
407
  if (type === MESSAGE_TEXT) setTemplateDescError(errorMsg);
416
408
  };
@@ -835,10 +827,9 @@ export const Rcs = (props) => {
835
827
 
836
828
  const templateDescErrorHandler = (value) => {
837
829
  let errorMessage = false;
838
- const { unsupportedTags, isBraceError } = validateTags({
830
+ const { isBraceError } = validateTags({
839
831
  content: value,
840
832
  tagsParam: tags,
841
- injectedTagsParams: injectedTags,
842
833
  location,
843
834
  tagModule: getDefaultTags,
844
835
  isFullMode,
@@ -868,26 +859,10 @@ export const Rcs = (props) => {
868
859
  };
869
860
 
870
861
  const fallbackMessageErrorHandler = (value) => {
871
- let errorMessage = false;
872
- const { unsupportedTags } = validateTags({
873
- content: value,
874
- tagsParam: tags,
875
- injectedTagsParams: injectedTags,
876
- location,
877
- tagModule: getDefaultTags,
878
- isFullMode,
879
- }) || {};
880
862
  if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
881
- errorMessage = formatMessage(messages.fallbackMsgLenError);
882
- } else if (unsupportedTags?.length > 0) {
883
- errorMessage = formatMessage(
884
- globalMessages.unsupportedTagsValidationError,
885
- {
886
- unsupportedTags,
887
- },
888
- );
863
+ return formatMessage(messages.fallbackMsgLenError);
889
864
  }
890
- return errorMessage;
865
+ return false;
891
866
  };
892
867
 
893
868
  // Check for forbidden characters: square brackets [] and single curly braces {}
@@ -51,6 +51,7 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
51
51
  modalContent: {title: "Alert", body: "Do you really want to delete this version?", type: 'confirm', id: 'sms-version-modal'},
52
52
  showTestAndPreviewSlidebox: false,
53
53
  isTestAndPreviewMode: false,
54
+ pendingGetFormData: false,
54
55
  };
55
56
  this.saveFormData = this.saveFormData.bind(this);
56
57
  this.onFormDataChange = this.onFormDataChange.bind(this);
@@ -140,8 +141,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
140
141
  }
141
142
 
142
143
  componentWillReceiveProps(nextProps) {
143
- if (nextProps.location.query.module === 'library' && nextProps.isGetFormData) {
144
- nextProps.getFormSubscriptionData(this.getFormData());
144
+ // Only trigger on actual Done click (isGetFormData false -> true). Prevents auto-submit after user fixes brace error.
145
+ if (!nextProps.isFullMode && nextProps.isGetFormData && !this.props.isGetFormData) {
146
+ this.setState({ startValidation: true, pendingGetFormData: true });
145
147
  } else if (nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
146
148
  this.startValidation();
147
149
  }
@@ -171,6 +173,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
171
173
  }
172
174
 
173
175
  componentWillUnmount() {
176
+ if (this.pendingGetFormDataTimeout) {
177
+ clearTimeout(this.pendingGetFormDataTimeout);
178
+ }
174
179
  if (this.props.setIsLoadingContent) {
175
180
  this.props.setIsLoadingContent(true); // setting isLoading of CreativesContainer so that slidebox foot can be hidden till content is loaded
176
181
  }
@@ -199,6 +204,10 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
199
204
  if (currentTab) {
200
205
  this.setState({currentTab});
201
206
  }
207
+ // Clear footer validation errors on input change so they refresh on next validation
208
+ if (this.props.showLiquidErrorInFooter) {
209
+ this.props.showLiquidErrorInFooter({ STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] });
210
+ }
202
211
  }
203
212
 
204
213
  onTagSelect(data, currentTab) {
@@ -349,7 +358,25 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
349
358
  }
350
359
 
351
360
  setFormValidity(isFormValid, errorData) {
352
- this.setState({isFormValid, errorData});
361
+ this.setState({ isFormValid, errorData }, () => {
362
+ if (this.state.pendingGetFormData && !isFormValid) {
363
+ this.setState({ pendingGetFormData: false, startValidation: false });
364
+ // Reset parent's Done state so next Done click is a fresh attempt
365
+ if (this.props.onValidationFail) {
366
+ this.props.onValidationFail();
367
+ }
368
+ return;
369
+ }
370
+ // In library mode with SMS, submit only when FormBuilder calls onSubmit (after liquid validation).
371
+ if (this.state.pendingGetFormData && this.props.getFormSubscriptionData && this.props.isFullMode) {
372
+ if (this.pendingGetFormDataTimeout) {
373
+ clearTimeout(this.pendingGetFormDataTimeout);
374
+ this.pendingGetFormDataTimeout = null;
375
+ }
376
+ this.props.getFormSubscriptionData(this.getFormData());
377
+ this.setState({ pendingGetFormData: false, startValidation: false });
378
+ }
379
+ });
353
380
  }
354
381
 
355
382
  injectMessages(elem) {
@@ -970,6 +997,15 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
970
997
  }
971
998
 
972
999
  saveFormData() {
1000
+ // In library mode: FormBuilder calls onSubmit only after liquid validation succeeds.
1001
+ // Submit to parent here so the slidebox can close with valid data.
1002
+ if (!this.props.isFullMode) {
1003
+ if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
1004
+ this.props.getFormSubscriptionData(this.getFormData());
1005
+ this.setState({ pendingGetFormData: false, startValidation: false });
1006
+ }
1007
+ return;
1008
+ }
973
1009
  //Logic to save in db etc
974
1010
  const formData = _.cloneDeep(this.state.formData);
975
1011
  const obj = {};
@@ -90,6 +90,10 @@ export default defineMessages({
90
90
  id: 'creatives.containersV2.Create.validationError',
91
91
  defaultMessage: 'Validation error',
92
92
  },
93
+ "unbalancedCurlyBraces": {
94
+ id: 'creatives.containersV2.Create.unbalancedCurlyBraces',
95
+ defaultMessage: 'Please close all curly braces in the message.',
96
+ },
93
97
  "smsTemplateCreatedSuccess": {
94
98
  id: 'creatives.containersV2.Create.smsTemplateCreatedSuccess',
95
99
  defaultMessage: 'SMS Template Created Successfully',
@@ -52,6 +52,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
52
52
  modalContent: {title: "Alert", body: "Do you really want to delete this version?", type: 'confirm', id: 'sms-version-modal'},
53
53
  showTestAndPreviewSlidebox: false,
54
54
  isTestAndPreviewMode: false,
55
+ pendingGetFormData: false,
55
56
  };
56
57
  this.saveFormData = this.saveFormData.bind(this);
57
58
  this.onFormDataChange = this.onFormDataChange.bind(this);
@@ -134,8 +135,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
134
135
  }
135
136
 
136
137
  componentWillReceiveProps(nextProps) {
137
- if (nextProps.location.query.module === 'library' && nextProps.isGetFormData) {
138
- nextProps.getFormSubscriptionData(this.getFormData());
138
+ if (!nextProps.isFullMode && nextProps.isGetFormData && !this.props.isGetFormData) {
139
+ this.setState({ startValidation: true, pendingGetFormData: true });
139
140
  }
140
141
  if ( nextProps.location.query.module === 'library' && nextProps.subscriptionTemplateDetails && nextProps.subscriptionTemplateDetails.name && _.isEmpty(this.state.editData) && !_.isEmpty(this.state.schema)) {
141
142
  this.setEditState(nextProps.subscriptionTemplateDetails);
@@ -189,6 +190,9 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
189
190
  }
190
191
 
191
192
  componentWillUnmount() {
193
+ if (this.pendingGetFormDataTimeout) {
194
+ clearTimeout(this.pendingGetFormDataTimeout);
195
+ }
192
196
  if (this.props.setIsLoadingContent) {
193
197
  this.props.setIsLoadingContent(true); // setting isLoading of CreativesContainer so that slidebox foot can be hidden till content is loaded
194
198
  }
@@ -214,6 +218,10 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
214
218
  if (currentTab) {
215
219
  this.setState({currentTab});
216
220
  }
221
+ // Clear footer validation errors on input change so they refresh on next validation
222
+ if (this.props.showLiquidErrorInFooter) {
223
+ this.props.showLiquidErrorInFooter({ STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] });
224
+ }
217
225
  }
218
226
 
219
227
  onVersionNameChange() {
@@ -317,7 +325,23 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
317
325
  }
318
326
 
319
327
  setFormValidity(isFormValid, errorData) {
320
- this.setState({isFormValid, errorData});
328
+ this.setState({ isFormValid, errorData }, () => {
329
+ if (this.state.pendingGetFormData && !isFormValid) {
330
+ this.setState({ pendingGetFormData: false, startValidation: false });
331
+ if (this.props.onValidationFail) {
332
+ this.props.onValidationFail();
333
+ }
334
+ return;
335
+ }
336
+ if (this.state.pendingGetFormData && this.props.getFormSubscriptionData && this.props.isFullMode) {
337
+ if (this.pendingGetFormDataTimeout) {
338
+ clearTimeout(this.pendingGetFormDataTimeout);
339
+ this.pendingGetFormDataTimeout = null;
340
+ }
341
+ this.props.getFormSubscriptionData(this.getFormData());
342
+ this.setState({ pendingGetFormData: false, startValidation: false });
343
+ }
344
+ });
321
345
  }
322
346
 
323
347
  getFormData(e, value) {
@@ -923,6 +947,14 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
923
947
  this.setState({startValidation: false});
924
948
  }
925
949
  saveFormData() {
950
+ // In library mode: FormBuilder calls onSubmit only after liquid validation succeeds.
951
+ if (!this.props.isFullMode) {
952
+ if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
953
+ this.props.getFormSubscriptionData(this.getFormData());
954
+ this.setState({ pendingGetFormData: false, startValidation: false });
955
+ }
956
+ return;
957
+ }
926
958
  //Logic to save in db etc
927
959
  //saveFormData gets called only when validation result is true
928
960
 
@@ -1,17 +1,14 @@
1
1
  import isEmpty from 'lodash/isEmpty';
2
- import { CapNotification } from '@capillarytech/cap-ui-library';
2
+ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
3
3
  import messages from './Create/messages';
4
4
  export function showError() {
5
5
  const {intl} = this.props;
6
6
  const {errorData} = this.state;
7
7
  const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
8
8
  if (!isEmpty(this.state.formData) && !this.state.isFormValid) {
9
- const isSmsInvalid = Object.values(errorData[0]).includes(true);
9
+ const err0 = errorData[0] || {};
10
+ const isSmsInvalid = Object.values(err0).includes(true);
10
11
  if (isSmsInvalid) {
11
- const invalidTags = errorData[0]['invalid-tags'];
12
- if (!isEmpty(invalidTags)) {
13
- errorMessage.description = `${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
14
- }
15
12
  CapNotification.error(errorMessage);
16
13
  }
17
14
  }
@@ -0,0 +1,122 @@
1
+ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
2
+ import { showError } from '../commonMethods';
3
+
4
+ jest.mock('@capillarytech/cap-ui-library/CapNotification', () => ({
5
+ error: jest.fn(),
6
+ }));
7
+
8
+ jest.mock('../Create/messages', () => ({
9
+ __esModule: true,
10
+ default: {
11
+ validationError: { defaultMessage: 'Validation error' },
12
+ },
13
+ }));
14
+
15
+ describe('Sms commonMethods', () => {
16
+ describe('showError', () => {
17
+ beforeEach(() => {
18
+ jest.clearAllMocks();
19
+ });
20
+
21
+ it('should call CapNotification.error when formData is not empty, isFormValid is false, and errorData has at least one true value', () => {
22
+ const context = {
23
+ props: {
24
+ intl: { formatMessage: jest.fn((msg) => msg.defaultMessage || 'Validation error') },
25
+ },
26
+ state: {
27
+ formData: { message: 'test' },
28
+ isFormValid: false,
29
+ errorData: [{ message: true }],
30
+ },
31
+ };
32
+ showError.call(context);
33
+ expect(CapNotification.error).toHaveBeenCalledWith({
34
+ key: 'validation-error',
35
+ message: 'Validation error',
36
+ });
37
+ });
38
+
39
+ it('should not call CapNotification.error when formData is empty', () => {
40
+ const context = {
41
+ props: { intl: { formatMessage: jest.fn() } },
42
+ state: {
43
+ formData: {},
44
+ isFormValid: false,
45
+ errorData: [{ message: true }],
46
+ },
47
+ };
48
+ showError.call(context);
49
+ expect(CapNotification.error).not.toHaveBeenCalled();
50
+ });
51
+
52
+ it('should not call CapNotification.error when isFormValid is true', () => {
53
+ const context = {
54
+ props: { intl: { formatMessage: jest.fn() } },
55
+ state: {
56
+ formData: { message: 'test' },
57
+ isFormValid: true,
58
+ errorData: [{ message: true }],
59
+ },
60
+ };
61
+ showError.call(context);
62
+ expect(CapNotification.error).not.toHaveBeenCalled();
63
+ });
64
+
65
+ it('should not call CapNotification.error when no errorData entry has a true value', () => {
66
+ const context = {
67
+ props: { intl: { formatMessage: jest.fn() } },
68
+ state: {
69
+ formData: { message: 'test' },
70
+ isFormValid: false,
71
+ errorData: [{ message: false, title: false }],
72
+ },
73
+ };
74
+ showError.call(context);
75
+ expect(CapNotification.error).not.toHaveBeenCalled();
76
+ });
77
+
78
+ it('should not call CapNotification.error when errorData is empty', () => {
79
+ const context = {
80
+ props: { intl: { formatMessage: jest.fn() } },
81
+ state: {
82
+ formData: { message: 'test' },
83
+ isFormValid: false,
84
+ errorData: [],
85
+ },
86
+ };
87
+ showError.call(context);
88
+ expect(CapNotification.error).not.toHaveBeenCalled();
89
+ });
90
+
91
+ it('should not call CapNotification.error when errorData[0] is undefined', () => {
92
+ const context = {
93
+ props: { intl: { formatMessage: jest.fn() } },
94
+ state: {
95
+ formData: { message: 'test' },
96
+ isFormValid: false,
97
+ errorData: [],
98
+ },
99
+ };
100
+ showError.call(context);
101
+ expect(CapNotification.error).not.toHaveBeenCalled();
102
+ });
103
+
104
+ it('should call CapNotification.error when errorData[0] has multiple keys and one is true', () => {
105
+ const context = {
106
+ props: {
107
+ intl: { formatMessage: jest.fn((msg) => msg.defaultMessage || 'Validation error') },
108
+ },
109
+ state: {
110
+ formData: { message: 'hello' },
111
+ isFormValid: false,
112
+ errorData: [{ message: false, title: true }],
113
+ },
114
+ };
115
+ showError.call(context);
116
+ expect(CapNotification.error).toHaveBeenCalledWith({
117
+ key: 'validation-error',
118
+ message: 'Validation error',
119
+ });
120
+ });
121
+ });
122
+ });
@@ -39,10 +39,8 @@ import formBuilderMessages from '../../../v2Components/FormBuilder/messages';
39
39
  import UnifiedPreview from '../../../v2Components/CommonTestAndPreview/UnifiedPreview';
40
40
  import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox';
41
41
  import withCreatives from '../../../hoc/withCreatives';
42
- import { validateTags } from '../../../utils/tagValidations';
43
42
  import {
44
43
  CHARLIMIT,
45
- SMS,
46
44
  SMS_TRAI_VAR,
47
45
  TAG,
48
46
  EMBEDDED,
@@ -51,16 +49,15 @@ import {
51
49
  ALL,
52
50
  LIBRARY,
53
51
  } from './constants';
52
+ import { SMS } from '../../CreativesContainer/constants';
54
53
  import v2EditSmsReducer from '../../Sms/Edit/reducer';
55
54
  import { v2SmsEditSagas } from '../../Sms/Edit/sagas';
56
55
  import ErrorInfoNote from '../../../v2Components/ErrorInfoNote';
57
56
  import { validateLiquidTemplateContent } from '../../../utils/commonUtils';
58
- import { hasLiquidSupportFeature } from '../../../utils/common';
59
57
  import { ANDROID } from '../../../v2Components/CommonTestAndPreview/constants';
60
58
 
61
59
  let varMap = {};
62
60
  let traiData = {};
63
- let tagValidationResponse = {};
64
61
  const { TextArea } = CapInput;
65
62
  const { CapLabelInline } = CapLabel;
66
63
 
@@ -94,7 +91,6 @@ export const SmsTraiEdit = (props) => {
94
91
  const [tags, updateTags] = useState([]);
95
92
  const [textAreaId, updateTextAreaId] = useState();
96
93
  const [isValidationError, updateIsValidationError] = useState(false);
97
- const [isTagValidationError, updateIsTagValidationError] = useState(false);
98
94
  const [totalMessageLength, setTotalMessageLength] = useState(0);
99
95
  const [isUnicodeAllowed, updateIsUnicodeAllowed] = useState(true);
100
96
  const [showMsgLengthNote, updateshowMsgLengthNote] = useState(false);
@@ -229,29 +225,6 @@ export const SmsTraiEdit = (props) => {
229
225
  }
230
226
  }, []);
231
227
 
232
- //performs tag validation
233
- useEffect(() => {
234
- if (
235
- !isFullMode &&
236
- updatedSmsEditor?.length > 0 &&
237
- !updatedSmsEditor.includes(SMS_TRAI_VAR)
238
- ) {
239
- tagValidationResponse =
240
- validateTags({
241
- content: updatedSmsEditor.join(''),
242
- tagsParam: tags,
243
- injectedTagsParams: injectedTags,
244
- location,
245
- tagModule: getDefaultTags,
246
- eventContextTags,
247
- isFullMode,
248
- }) || {};
249
- updateIsTagValidationError(
250
- tagValidationResponse.unsupportedTags.length > 0,
251
- );
252
- }
253
- }, [updatedSmsEditor, tags]);
254
-
255
228
  const computeUpdatedSmsEditor = () => {
256
229
  const arr = [...tempMsgArray];
257
230
  const varMapKeys = Object.keys(varMap)?.map((key) => Number(key.slice(8)))?.sort((a, b) => a - b) || [];
@@ -288,6 +261,13 @@ export const SmsTraiEdit = (props) => {
288
261
  };
289
262
 
290
263
  const onSubmitWrapper = () => {
264
+ setIsLiquidValidationError(false);
265
+ setLiquidErrorMessages({});
266
+ // Liquid validation (extractTags) only in library mode
267
+ if (isFullMode) {
268
+ onDoneCallback();
269
+ return;
270
+ }
291
271
  const content = updatedSmsEditor.join('');
292
272
  const onError = ({ standardErrors, liquidErrors }) => {
293
273
  setLiquidErrorMessages({
@@ -298,6 +278,8 @@ export const SmsTraiEdit = (props) => {
298
278
  };
299
279
 
300
280
  const onSuccess = () => {
281
+ setIsLiquidValidationError(false);
282
+ setLiquidErrorMessages({});
301
283
  onDoneCallback();
302
284
  };
303
285
 
@@ -313,10 +295,6 @@ export const SmsTraiEdit = (props) => {
313
295
  messages: formBuilderMessages,
314
296
  onError,
315
297
  onSuccess,
316
- tagLookupMap: metaEntities?.tagLookupMap,
317
- eventContextTags,
318
- isLiquidFlow: true,
319
- forwardedTags: {},
320
298
  });
321
299
  };
322
300
 
@@ -555,17 +533,6 @@ export const SmsTraiEdit = (props) => {
555
533
  return countVarChar;
556
534
  };
557
535
 
558
- const tagValidationErrorMessage = () => {
559
- const { unsupportedTags = [] } = tagValidationResponse;
560
- let tagError = '';
561
- if (unsupportedTags.length > 0) {
562
- tagError = formatMessage(messages.unsupportedTagsValidationError, {
563
- unsupportedTags,
564
- });
565
- }
566
- return <CapError>{tagError}</CapError>;
567
- };
568
-
569
536
  const disablehandler = () => {
570
537
  if (traiData && !isEmpty(traiData)) {
571
538
  const msg = get(traiData, `versions.base.sms-editor`, '');
@@ -615,7 +582,6 @@ export const SmsTraiEdit = (props) => {
615
582
  setShowTestAndPreviewSlidebox(false);
616
583
  };
617
584
 
618
- const isLiquidSupportFeatureEnabled = hasLiquidSupportFeature();
619
585
  return (
620
586
  <>
621
587
  <CapSpin spinning={loading || fetchingLiquidTags} tip={fetchingLiquidTags && formatMessage(formBuilderMessages.liquidSpinText)}>
@@ -673,7 +639,6 @@ export const SmsTraiEdit = (props) => {
673
639
  <CapRow>
674
640
  {smsLengthForVar()}
675
641
  </CapRow>
676
- {isTagValidationError && tagValidationErrorMessage()}
677
642
  <CapCheckbox onChange={unicodeHandler} checked={isUnicodeAllowed} disabled={disablehandler()}>
678
643
  {formatMessage(messages.unicodeLabel)}
679
644
  </CapCheckbox>
@@ -704,9 +669,8 @@ export const SmsTraiEdit = (props) => {
704
669
  <FormattedMessage {...messages.testAndPreviewButtonLabel} />
705
670
  </CapButton>
706
671
  <CapButton
707
- onClick={isLiquidSupportFeatureEnabled ? onSubmitWrapper : onDoneCallback}
672
+ onClick={onSubmitWrapper}
708
673
  className="create-msg"
709
- disabled={isTagValidationError || (isLiquidSupportFeatureEnabled && !isObject(metaEntities?.tagLookupMap))}
710
674
  >
711
675
  <FormattedMessage {...messages.saveButtonLabel} />
712
676
  </CapButton>