@capillarytech/creatives-library 8.0.291 → 8.0.292-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/constants/unified.js +3 -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 +4 -85
  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/index.js +30 -7
  22. package/v2Containers/Email/index.js +1 -5
  23. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +23 -70
  24. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +29 -137
  25. package/v2Containers/FTP/index.js +2 -51
  26. package/v2Containers/FTP/messages.js +0 -4
  27. package/v2Containers/InApp/index.js +4 -104
  28. package/v2Containers/InApp/tests/index.test.js +17 -6
  29. package/v2Containers/InappAdvance/index.js +4 -108
  30. package/v2Containers/InappAdvance/tests/index.test.js +2 -0
  31. package/v2Containers/Line/Container/Text/index.js +0 -1
  32. package/v2Containers/MobilePush/Create/index.js +42 -19
  33. package/v2Containers/MobilePush/Edit/index.js +42 -19
  34. package/v2Containers/MobilePushNew/index.js +12 -32
  35. package/v2Containers/MobilepushWrapper/index.js +3 -1
  36. package/v2Containers/Rcs/index.js +12 -37
  37. package/v2Containers/Sms/Create/index.js +39 -3
  38. package/v2Containers/Sms/Create/messages.js +4 -0
  39. package/v2Containers/Sms/Edit/index.js +35 -3
  40. package/v2Containers/Sms/commonMethods.js +3 -6
  41. package/v2Containers/Sms/tests/commonMethods.test.js +122 -0
  42. package/v2Containers/SmsTrai/Edit/index.js +11 -47
  43. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
  44. package/v2Containers/SmsWrapper/index.js +2 -0
  45. package/v2Containers/Viber/index.js +0 -1
  46. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -3
  47. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
  48. package/v2Containers/WebPush/Create/index.js +2 -2
  49. package/v2Containers/WebPush/Create/utils/validation.js +17 -2
  50. package/v2Containers/WebPush/Create/utils/validation.test.js +59 -24
  51. package/v2Containers/Whatsapp/index.js +9 -17
  52. 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
@@ -2659,20 +2649,6 @@ const MobilePushNew = ({
2659
2649
  getLiquidTags: globalActionsProps.getLiquidTags,
2660
2650
  formatMessage,
2661
2651
  messages: formBuilderMessages,
2662
- tagLookupMap: metaEntities?.tagLookupMap || {},
2663
- eventContextTags: metaEntities?.eventContextTags || [],
2664
- isLiquidFlow: hasLiquidSupportFeature(),
2665
- forwardedTags: {},
2666
- skipTags: (tag) => {
2667
- const skipRegexes = [
2668
- /dynamic_expiry_date_after_\d+_days\.FORMAT_\d/,
2669
- /unsubscribe\(#[a-zA-Z\d]{6}\)/,
2670
- /Link_to_[a-zA-z]/,
2671
- /SURVEY.*\.TOKEN/,
2672
- /^[A-Za-z].*\([a-zA-Z\d]*\)/,
2673
- ];
2674
- return skipRegexes.some((regex) => regex.test(tag));
2675
- },
2676
2652
  singleTab: getSingleTab(accountData),
2677
2653
  });
2678
2654
  }, [
@@ -2681,12 +2657,9 @@ const MobilePushNew = ({
2681
2657
  activeTab,
2682
2658
  globalActionsProps,
2683
2659
  formatMessage,
2684
- metaEntities,
2685
2660
  accountData,
2686
2661
  ]);
2687
2662
 
2688
- const isLiquidFlow = hasLiquidSupportFeature();
2689
-
2690
2663
  useEffect(() => {
2691
2664
  // Always map to { label } for both platforms
2692
2665
  const newButtons = Array.isArray(ctaData)
@@ -2933,16 +2906,22 @@ const MobilePushNew = ({
2933
2906
  setShowTestAndPreviewSlidebox(false);
2934
2907
  }, []);
2935
2908
 
2936
- // Add useEffect to handle isGetFormData prop changes
2909
+ // Add useEffect to handle isGetFormData prop changes (e.g. Done clicked in footer)
2910
+ // In library mode: run liquid validation (extractTags) first; on success liquidMiddleWare calls handleSave
2911
+ // In full mode: call handleSave directly
2937
2912
  useEffect(() => {
2938
2913
  if (isGetFormData) {
2939
- handleSave();
2914
+ if (!isFullMode) {
2915
+ liquidMiddleWare();
2916
+ } else {
2917
+ handleSave();
2918
+ }
2940
2919
  // Reset the flag to prevent infinite loop
2941
2920
  if (onValidationFail) {
2942
2921
  onValidationFail();
2943
2922
  }
2944
2923
  }
2945
- }, [isGetFormData, handleSave, onValidationFail]);
2924
+ }, [isGetFormData, handleSave, onValidationFail, isFullMode, liquidMiddleWare]);
2946
2925
 
2947
2926
  // Add message event listener to handle parent communication (like old MobilePush components)
2948
2927
  useEffect(() => {
@@ -3083,7 +3062,8 @@ const MobilePushNew = ({
3083
3062
  <CapButton
3084
3063
  type="primary"
3085
3064
  onClick={() => {
3086
- if (isLiquidFlow) {
3065
+ // Liquid validation (extractTags) only in library mode
3066
+ if (!isFullMode) {
3087
3067
  liquidMiddleWare();
3088
3068
  } else {
3089
3069
  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
  validateLiquidTemplateContent(content, {
@@ -306,10 +288,6 @@ export const SmsTraiEdit = (props) => {
306
288
  messages: formBuilderMessages,
307
289
  onError,
308
290
  onSuccess,
309
- tagLookupMap: metaEntities?.tagLookupMap,
310
- eventContextTags,
311
- isLiquidFlow: true,
312
- forwardedTags: {},
313
291
  });
314
292
  };
315
293
 
@@ -548,17 +526,6 @@ export const SmsTraiEdit = (props) => {
548
526
  return countVarChar;
549
527
  };
550
528
 
551
- const tagValidationErrorMessage = () => {
552
- const { unsupportedTags = [] } = tagValidationResponse;
553
- let tagError = '';
554
- if (unsupportedTags.length > 0) {
555
- tagError = formatMessage(messages.unsupportedTagsValidationError, {
556
- unsupportedTags,
557
- });
558
- }
559
- return <CapError>{tagError}</CapError>;
560
- };
561
-
562
529
  const disablehandler = () => {
563
530
  if (traiData && !isEmpty(traiData)) {
564
531
  const msg = get(traiData, `versions.base.sms-editor`, '');
@@ -604,7 +571,6 @@ export const SmsTraiEdit = (props) => {
604
571
  setShowTestAndPreviewSlidebox(false);
605
572
  };
606
573
 
607
- const isLiquidSupportFeatureEnabled = hasLiquidSupportFeature();
608
574
  return (
609
575
  <>
610
576
  <CapSpin spinning={loading || fetchingLiquidTags} tip={fetchingLiquidTags && formatMessage(formBuilderMessages.liquidSpinText)}>
@@ -662,7 +628,6 @@ export const SmsTraiEdit = (props) => {
662
628
  <CapRow>
663
629
  {smsLengthForVar()}
664
630
  </CapRow>
665
- {isTagValidationError && tagValidationErrorMessage()}
666
631
  <CapCheckbox onChange={unicodeHandler} checked={isUnicodeAllowed} disabled={disablehandler()}>
667
632
  {formatMessage(messages.unicodeLabel)}
668
633
  </CapCheckbox>
@@ -693,9 +658,8 @@ export const SmsTraiEdit = (props) => {
693
658
  <FormattedMessage {...messages.testAndPreviewButtonLabel} />
694
659
  </CapButton>
695
660
  <CapButton
696
- onClick={isLiquidSupportFeatureEnabled ? onSubmitWrapper : onDoneCallback}
661
+ onClick={onSubmitWrapper}
697
662
  className="create-msg"
698
- disabled={isTagValidationError || (isLiquidSupportFeatureEnabled && !isObject(metaEntities?.tagLookupMap))}
699
663
  >
700
664
  <FormattedMessage {...messages.saveButtonLabel} />
701
665
  </CapButton>