@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
@@ -15,7 +15,7 @@ import HTMLEditor from '../../../v2Components/HtmlEditor';
15
15
  import CapTagListWithInput from '../../../v2Components/CapTagListWithInput';
16
16
  import formBuilderMessages from '../../../v2Components/FormBuilder/messages';
17
17
  import { validateLiquidTemplateContent } from '../../../utils/commonUtils';
18
- import { hasLiquidSupportFeature, isEmailUnsubscribeTagMandatory } from '../../../utils/common';
18
+ import { isEmailUnsubscribeTagOptional } from '../../../utils/common';
19
19
  import history from '../../../utils/history';
20
20
  import messages from '../messages';
21
21
  import emailMessages from '../../Email/messages';
@@ -108,13 +108,10 @@ const EmailHTMLEditor = (props) => {
108
108
  standardErrors: [],
109
109
  });
110
110
 
111
- // Merge tag validation errors (unsupported/missing) into apiValidationErrors so they show in ValidationErrorDisplay
111
+ // Merge tag validation errors (missing) into apiValidationErrors so they show in ValidationErrorDisplay
112
112
  const mergedApiValidationErrors = useMemo(() => {
113
113
  const tagMessages = [];
114
- if (tagValidationError?.unsupportedTags?.length) {
115
- tagMessages.push(`Unsupported tags are: ${tagValidationError.unsupportedTags.join(', ')}`);
116
- }
117
- if (tagValidationError?.missingTags?.length && !isEmailUnsubscribeTagMandatory()) {
114
+ if (tagValidationError?.missingTags?.length && !isEmailUnsubscribeTagOptional()) {
118
115
  tagMessages.push(`Missing tags are: ${tagValidationError.missingTags.join(', ')}`);
119
116
  }
120
117
  if (tagMessages.length === 0) {
@@ -190,9 +187,6 @@ const EmailHTMLEditor = (props) => {
190
187
  },
191
188
  }), [htmlContent, subject, currentOrgDetails]);
192
189
 
193
- // Check if liquid support is enabled
194
- const isLiquidEnabled = hasLiquidSupportFeature();
195
-
196
190
  // Detect edit mode: when isEditEmail is false (create flow), never treat as edit or fetch template details
197
191
  const hasParamsId = params?.id || location?.query?.id || location?.params?.id || location?.pathname?.includes('/edit/');
198
192
  const currentTemplateId = isEditEmail
@@ -488,15 +482,25 @@ const EmailHTMLEditor = (props) => {
488
482
  const handleContentChange = useCallback((content) => {
489
483
  setHtmlContent(content);
490
484
 
485
+ // Clear previous liquid/API validation errors so Done button can be enabled after user fixes content
486
+ setApiValidationErrors({
487
+ liquidErrors: [],
488
+ standardErrors: [],
489
+ });
490
+ if (showLiquidErrorInFooter) {
491
+ showLiquidErrorInFooter({
492
+ STANDARD_ERROR_MSG: [],
493
+ LIQUID_ERROR_MSG: [],
494
+ });
495
+ }
496
+
491
497
  // Validate tags
492
498
  if (tags.length > 0 || !isEmpty(injectedTags)) {
493
499
  const validationResult = validateTags({
494
500
  content,
495
501
  tagsParam: tags,
496
- injectedTagsParams: injectedTags,
497
502
  location,
498
503
  tagModule: getDefaultTags,
499
- eventContextTags,
500
504
  isFullMode,
501
505
  });
502
506
 
@@ -506,7 +510,7 @@ const EmailHTMLEditor = (props) => {
506
510
  setTagValidationError(null);
507
511
  }
508
512
  }
509
- }, [tags, injectedTags, location, getDefaultTags, eventContextTags]);
513
+ }, [tags, injectedTags, location, getDefaultTags, eventContextTags, showLiquidErrorInFooter]);
510
514
 
511
515
  // Store the last validation state received from HTMLEditor
512
516
  const lastValidationStateRef = useRef(null);
@@ -650,7 +654,7 @@ const EmailHTMLEditor = (props) => {
650
654
  // IMPORTANT: Clear API validation errors FIRST before checking for validation errors
651
655
  // This ensures that old API errors don't block the save when user fixes content and clicks Update again
652
656
  // We'll re-validate with fresh API call anyway
653
- if (isLiquidEnabled && getLiquidTags) {
657
+ if (getLiquidTags) {
654
658
  setApiValidationErrors({
655
659
  liquidErrors: [],
656
660
  standardErrors: [],
@@ -712,7 +716,7 @@ const EmailHTMLEditor = (props) => {
712
716
  // When EMAIL_UNSUBSCRIBE_TAG_MANDATORY is false: validate and require unsubscribe tag.
713
717
  // Run for both library and full mode so liquid-enabled orgs also get the error (notification + ValidationErrorDisplay).
714
718
  const isModuleTypeOutbound = (moduleType || '').toUpperCase() === OUTBOUND;
715
- if (!isEmailUnsubscribeTagMandatory() && isModuleTypeOutbound) {
719
+ if (!isEmailUnsubscribeTagOptional() && isModuleTypeOutbound) {
716
720
  const unsubscribeRegex = /{{unsubscribe(\(#[a-zA-Z\d]{6}\))?}}/g; // eslint-disable-line no-useless-escape
717
721
  const hasUnsubscribeTag = unsubscribeRegex.test(htmlContent);
718
722
 
@@ -744,52 +748,17 @@ const EmailHTMLEditor = (props) => {
744
748
  const validationResult = validateTags({
745
749
  content: htmlContent,
746
750
  tagsParam: tags,
747
- injectedTagsParams: injectedTags,
748
751
  location,
749
752
  tagModule: getDefaultTags,
750
- eventContextTags,
751
753
  isFullMode,
752
754
  });
753
755
 
754
- const hasUnsupportedTags = validationResult?.unsupportedTags?.length > 0;
755
- if (!validationResult?.valid || (hasUnsupportedTags && !isFullMode)) {
756
+ if (!validationResult?.valid) {
756
757
  setTagValidationError(validationResult);
757
- // IMPORTANT: For non-liquid orgs, block save (like CK/BEE editor)
758
- // For liquid orgs, continue (extractTags API will validate)
759
- if (!isLiquidEnabled) {
760
- // Show notification popup like CK/BEE editor
761
- const baseLanguage = get(currentOrgDetails, 'basic_details.base_language', 'en');
762
-
763
- const contentNotValidMsg = intl.formatMessage(formBuilderMessages.contentNotValidLanguage);
764
- let errorMessage = `${contentNotValidMsg} ${baseLanguage}`;
765
-
766
- if (hasUnsupportedTags) {
767
- const unsupportedTagsMsg = intl.formatMessage(formBuilderMessages.unsupportedTags);
768
- errorMessage += `\n${unsupportedTagsMsg} ${validationResult?.unsupportedTags?.join(', ')}`;
769
- }
770
- if (validationResult?.missingTags?.length > 0) {
771
- const missingTagsMsg = intl.formatMessage(formBuilderMessages.missingTags);
772
- errorMessage += `\n${missingTagsMsg} ${validationResult?.missingTags?.join(', ')}`;
773
- }
774
-
775
- const type = 'error';
776
- CapNotification[type]({
777
- message: `${type.toUpperCase()} ! ! ! `,
778
- description: errorMessage,
779
- duration: 5,
780
- });
781
-
782
- // Reset parent state so next click is detected as a change
783
- if (onValidationFail) {
784
- onValidationFail();
785
- }
786
- // Block save for non-liquid orgs
787
- return;
788
- }
789
- // For liquid orgs, just show warning and continue
758
+ // For liquid orgs, show warning and continue (extractTags API will validate)
790
759
  }
791
760
  // Clear tag errors if valid
792
- if (tagValidationError && validationResult?.valid && !hasUnsupportedTags) {
761
+ if (tagValidationError && validationResult?.valid) {
793
762
  setTagValidationError(null);
794
763
  }
795
764
  }
@@ -956,13 +925,9 @@ const EmailHTMLEditor = (props) => {
956
925
  }
957
926
  };
958
927
 
959
- // If liquid enabled, validate first using extractTags API
960
- if (isLiquidEnabled && getLiquidTags) {
961
- // Note: API validation errors are already cleared at the start of handleSave
962
- // This ensures fresh validation on every save attempt
963
-
928
+ // Liquid validation (extractTags) only in library mode
929
+ if (getLiquidTags && !isFullMode) {
964
930
  const onError = ({ standardErrors, liquidErrors }) => {
965
- // Store API validation errors in state so they can be displayed in UI
966
931
  setApiValidationErrors({
967
932
  liquidErrors: liquidErrors || [],
968
933
  standardErrors: standardErrors || [],
@@ -974,15 +939,12 @@ const EmailHTMLEditor = (props) => {
974
939
  LIQUID_ERROR_MSG: liquidErrors || [],
975
940
  });
976
941
  }
977
- // Don't reset ref here - liquid validation is async and resetting causes infinite loop
978
- // The parent's isGetFormData will be reset by onValidationFail, and the next click will be detected
979
942
  if (onValidationFail) {
980
943
  onValidationFail();
981
944
  }
982
945
  };
983
946
 
984
947
  const onSuccess = () => {
985
- // Clear API validation errors on success
986
948
  setApiValidationErrors({
987
949
  liquidErrors: [],
988
950
  standardErrors: [],
@@ -998,10 +960,6 @@ const EmailHTMLEditor = (props) => {
998
960
  messages: formBuilderMessages,
999
961
  onError,
1000
962
  onSuccess,
1001
- tagLookupMap: metaEntities?.tagLookupMap,
1002
- eventContextTags,
1003
- isLiquidFlow: true,
1004
- forwardedTags: forwardedTags || {},
1005
963
  });
1006
964
  } else {
1007
965
  performSave();
@@ -1013,7 +971,6 @@ const EmailHTMLEditor = (props) => {
1013
971
  injectedTags,
1014
972
  location,
1015
973
  getDefaultTags,
1016
- eventContextTags,
1017
974
  formatMessage,
1018
975
  subjectError,
1019
976
  isFullMode,
@@ -1025,11 +982,8 @@ const EmailHTMLEditor = (props) => {
1025
982
  emailActions,
1026
983
  getFormdata,
1027
984
  isGetFormData,
1028
- isLiquidEnabled,
1029
985
  getLiquidTags,
1030
986
  showLiquidErrorInFooter,
1031
- metaEntities,
1032
- forwardedTags,
1033
987
  globalActions,
1034
988
  intl,
1035
989
  extractedTemplateName,
@@ -1175,7 +1129,6 @@ const EmailHTMLEditor = (props) => {
1175
1129
  userLocale={intl.locale || 'en'}
1176
1130
  moduleFilterEnabled={location?.query?.type !== EMBEDDED}
1177
1131
  onTagContextChange={handleOnTagsContextChange}
1178
- isLiquidEnabled={isLiquidEnabled}
1179
1132
  isFullMode={isFullMode}
1180
1133
  onErrorAcknowledged={handleErrorAcknowledged}
1181
1134
  onValidationChange={handleValidationChange}
@@ -13,7 +13,7 @@ import { IntlProvider } from 'react-intl';
13
13
  import EmailHTMLEditor from '../EmailHTMLEditor';
14
14
  import { validateLiquidTemplateContent } from '../../../../utils/commonUtils';
15
15
  import { validateTags } from '../../../../utils/tagValidations';
16
- import { isEmailUnsubscribeTagMandatory } from '../../../../utils/common';
16
+ import { isEmailUnsubscribeTagOptional } from '../../../../utils/common';
17
17
 
18
18
  // Mock dependencies
19
19
  jest.mock('../../../../utils/commonUtils', () => ({
@@ -24,11 +24,8 @@ jest.mock('../../../../utils/tagValidations', () => ({
24
24
  validateTags: jest.fn(),
25
25
  }));
26
26
 
27
- // Create mutable mock for hasLiquidSupportFeature
28
- const mockHasLiquidSupportFeature = jest.fn(() => true);
29
27
  jest.mock('../../../../utils/common', () => ({
30
- hasLiquidSupportFeature: (...args) => mockHasLiquidSupportFeature(...args),
31
- isEmailUnsubscribeTagMandatory: jest.fn(() => false),
28
+ isEmailUnsubscribeTagOptional: jest.fn(() => false),
32
29
  }));
33
30
 
34
31
  jest.mock('../../../../utils/history', () => ({
@@ -420,7 +417,7 @@ describe('EmailHTMLEditor', () => {
420
417
  jest.clearAllMocks();
421
418
  validateLiquidTemplateContent.mockResolvedValue(true);
422
419
  validateTags.mockReturnValue({ valid: true });
423
- isEmailUnsubscribeTagMandatory.mockReturnValue(false);
420
+ isEmailUnsubscribeTagOptional.mockReturnValue(false);
424
421
  // Reset mock functions
425
422
  mockGetAllIssues.mockReturnValue([]);
426
423
  mockGetValidationState.mockReturnValue({
@@ -428,88 +425,6 @@ describe('EmailHTMLEditor', () => {
428
425
  hasErrors: false,
429
426
  issueCounts: { errors: 0, warnings: 0, total: 0 },
430
427
  });
431
- // Reset hasLiquidSupportFeature mock to return true by default
432
- mockHasLiquidSupportFeature.mockReturnValue(true);
433
- capturedApiValidationErrorsRef.current = null;
434
- });
435
-
436
- describe('mergedApiValidationErrors (lines 124-125)', () => {
437
- beforeEach(() => {
438
- global.__captureApiValidationErrorsRef = capturedApiValidationErrorsRef;
439
- });
440
-
441
- afterEach(() => {
442
- delete global.__captureApiValidationErrorsRef;
443
- });
444
-
445
- it('merges tag unsupported errors into standardErrors when tagValidationError has unsupportedTags', async () => {
446
- validateTags.mockReturnValue({
447
- valid: false,
448
- unsupportedTags: ['tagA', 'tagB'],
449
- });
450
- renderWithIntl({
451
- metaEntities: { tags: { standard: [{ name: 'customer.name' }] } },
452
- tags: [{ name: 'customer.name' }],
453
- supportedTags: [],
454
- });
455
- const changeButton = screen.getByTestId('trigger-content-change');
456
- await act(async () => {
457
- fireEvent.click(changeButton);
458
- });
459
- await waitFor(() => {
460
- expect(capturedApiValidationErrorsRef.current).not.toBeNull();
461
- expect(capturedApiValidationErrorsRef.current.liquidErrors).toEqual([]);
462
- expect(capturedApiValidationErrorsRef.current.standardErrors).toContain(
463
- 'Unsupported tags are: tagA, tagB',
464
- );
465
- });
466
- });
467
-
468
- it('merges tag missing errors into standardErrors when tagValidationError has missingTags and unsubscribe not mandatory', async () => {
469
- isEmailUnsubscribeTagMandatory.mockReturnValue(false);
470
- validateTags.mockReturnValue({
471
- valid: false,
472
- missingTags: ['unsubscribe'],
473
- });
474
- renderWithIntl({
475
- metaEntities: { tags: { standard: [{ name: 'customer.name' }] } },
476
- tags: [{ name: 'customer.name' }],
477
- supportedTags: [],
478
- });
479
- const changeButton = screen.getByTestId('trigger-content-change');
480
- await act(async () => {
481
- fireEvent.click(changeButton);
482
- });
483
- await waitFor(() => {
484
- expect(capturedApiValidationErrorsRef.current).not.toBeNull();
485
- expect(capturedApiValidationErrorsRef.current.standardErrors).toContain(
486
- 'Missing tags are: unsubscribe',
487
- );
488
- });
489
- });
490
-
491
- it('uses apiValidationErrors.liquidErrors and concatenates apiValidationErrors.standardErrors with tag messages (merge shape)', async () => {
492
- // When tag messages exist, mergedApiValidationErrors returns liquidErrors from apiValidationErrors
493
- // and standardErrors = [...(apiValidationErrors?.standardErrors || []), ...tagMessages] (lines 124-125)
494
- validateTags.mockReturnValue({
495
- valid: false,
496
- unsupportedTags: ['customTag'],
497
- });
498
- renderWithIntl({
499
- metaEntities: { tags: { standard: [{ name: 'customer.name' }] } },
500
- tags: [{ name: 'customer.name' }],
501
- });
502
- const changeButton = screen.getByTestId('trigger-content-change');
503
- await act(async () => {
504
- fireEvent.click(changeButton);
505
- });
506
- await waitFor(() => {
507
- expect(capturedApiValidationErrorsRef.current).not.toBeNull();
508
- const { liquidErrors, standardErrors } = capturedApiValidationErrorsRef.current;
509
- expect(liquidErrors).toEqual([]);
510
- expect(standardErrors).toContain('Unsupported tags are: customTag');
511
- });
512
- });
513
428
  });
514
429
 
515
430
  describe('Default Parameter Values (lines 60-63)', () => {
@@ -1106,7 +1021,7 @@ describe('EmailHTMLEditor', () => {
1106
1021
  });
1107
1022
 
1108
1023
  it('allows save when unsubscribe validation is on and tag is present', () => {
1109
- isEmailUnsubscribeTagMandatory.mockReturnValue(false);
1024
+ isEmailUnsubscribeTagOptional.mockReturnValue(false);
1110
1025
  renderWithIntl({
1111
1026
  isGetFormData: true,
1112
1027
  subject: 'Valid Subject',
@@ -1116,49 +1031,44 @@ describe('EmailHTMLEditor', () => {
1116
1031
  // Should proceed with save (validation passes)
1117
1032
  });
1118
1033
 
1119
- it('blocks save for non-liquid orgs when tag validation fails', async () => {
1120
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1121
- validateTags.mockReturnValue({
1122
- valid: false,
1123
- unsupportedTags: ['tag1'],
1124
- missingTags: ['tag2'],
1034
+ it('blocks save when liquid API validation fails for Email', async () => {
1035
+ // Liquid validation runs in library mode (!isFullMode). Simulate API validation failure via mock onError.
1036
+ validateLiquidTemplateContent.mockImplementation((content, options) => {
1037
+ options.onError({ standardErrors: [], liquidErrors: ['Validation failed'] });
1038
+ return Promise.resolve(false);
1125
1039
  });
1126
1040
  const onValidationFail = jest.fn();
1127
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
1128
-
1129
- // Set subject and content via component interactions
1130
1041
  const { rerender } = renderWithIntl({
1131
1042
  onValidationFail,
1132
1043
  isGetFormData: false,
1044
+ isFullMode: false,
1133
1045
  metaEntities: {
1134
1046
  tags: {
1135
1047
  standard: [{ name: 'customer.name' }],
1136
1048
  },
1137
1049
  },
1138
- getLiquidTags: null, // No liquid tags for non-liquid org
1050
+ getLiquidTags: jest.fn((content, cb) => cb({ askAiraResponse: { data: [] }, isError: false })),
1139
1051
  });
1140
1052
  const input = screen.getByTestId('subject-input');
1141
1053
  fireEvent.change(input, { target: { value: 'Valid Subject' } });
1142
1054
  const changeButton = screen.getByTestId('trigger-content-change');
1143
1055
  fireEvent.click(changeButton);
1144
- // Now trigger save
1145
1056
  rerender(
1146
1057
  <IntlProvider locale="en" messages={{}}>
1147
1058
  <EmailHTMLEditor
1148
1059
  {...defaultProps}
1149
1060
  onValidationFail={onValidationFail}
1150
1061
  isGetFormData
1062
+ isFullMode={false}
1151
1063
  metaEntities={{
1152
1064
  tags: {
1153
1065
  standard: [{ name: 'customer.name' }],
1154
1066
  },
1155
1067
  }}
1156
- getLiquidTags={null} />
1068
+ getLiquidTags={jest.fn((content, cb) => cb({ askAiraResponse: { data: [] }, isError: false }))} />
1157
1069
  </IntlProvider>
1158
1070
  );
1159
-
1160
1071
  await waitFor(() => {
1161
- expect(CapNotification.error).toHaveBeenCalled();
1162
1072
  expect(onValidationFail).toHaveBeenCalled();
1163
1073
  }, { timeout: 3000 });
1164
1074
  });
@@ -1175,16 +1085,15 @@ describe('EmailHTMLEditor', () => {
1175
1085
  // Ensure no HTML/Label/Liquid errors from HtmlEditor
1176
1086
  mockGetAllIssues.mockReturnValue([]);
1177
1087
 
1178
- // Set subject and content via component interactions
1088
+ // Liquid validation runs only in library mode
1179
1089
  const { rerender } = renderWithIntl({
1180
1090
  isGetFormData: false,
1181
- isFullMode: true,
1091
+ isFullMode: false,
1182
1092
  metaEntities: {
1183
1093
  tags: {
1184
1094
  standard: [{ name: 'customer.name' }],
1185
1095
  },
1186
1096
  },
1187
- isLiquidEnabled: true,
1188
1097
  getLiquidTags,
1189
1098
  });
1190
1099
  const input = screen.getByTestId('subject-input');
@@ -1206,7 +1115,7 @@ describe('EmailHTMLEditor', () => {
1206
1115
  <EmailHTMLEditor
1207
1116
  {...defaultProps}
1208
1117
  isGetFormData
1209
- isFullMode
1118
+ isFullMode={false}
1210
1119
  metaEntities={{
1211
1120
  tags: {
1212
1121
  standard: [{ name: 'customer.name' }],
@@ -1230,11 +1139,10 @@ describe('EmailHTMLEditor', () => {
1230
1139
  // Ensure no HTML/Label/Liquid errors from HtmlEditor
1231
1140
  mockGetAllIssues.mockReturnValue([]);
1232
1141
 
1233
- // Set subject and content via component interactions
1142
+ // Liquid validation runs only in library mode
1234
1143
  const { rerender } = renderWithIntl({
1235
1144
  isGetFormData: false,
1236
- isFullMode: true,
1237
- isLiquidEnabled: true,
1145
+ isFullMode: false,
1238
1146
  getLiquidTags,
1239
1147
  });
1240
1148
  const input = screen.getByTestId('subject-input');
@@ -1253,7 +1161,7 @@ describe('EmailHTMLEditor', () => {
1253
1161
  await act(async () => {
1254
1162
  rerender(
1255
1163
  <IntlProvider locale="en" messages={{}}>
1256
- <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} />
1164
+ <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode={false} getLiquidTags={getLiquidTags} />
1257
1165
  </IntlProvider>
1258
1166
  );
1259
1167
  });
@@ -1280,11 +1188,10 @@ describe('EmailHTMLEditor', () => {
1280
1188
  // Ensure no HTML/Label/Liquid errors from HtmlEditor
1281
1189
  mockGetAllIssues.mockReturnValue([]);
1282
1190
 
1283
- // Set subject and content via component interactions
1191
+ // Liquid validation runs only in library mode
1284
1192
  const { rerender } = renderWithIntl({
1285
1193
  isGetFormData: false,
1286
- isFullMode: true,
1287
- isLiquidEnabled: true,
1194
+ isFullMode: false,
1288
1195
  getLiquidTags,
1289
1196
  showLiquidErrorInFooter,
1290
1197
  onValidationFail,
@@ -1305,7 +1212,7 @@ describe('EmailHTMLEditor', () => {
1305
1212
  await act(async () => {
1306
1213
  rerender(
1307
1214
  <IntlProvider locale="en" messages={{}}>
1308
- <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} showLiquidErrorInFooter={showLiquidErrorInFooter} onValidationFail={onValidationFail} />
1215
+ <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode={false} getLiquidTags={getLiquidTags} showLiquidErrorInFooter={showLiquidErrorInFooter} onValidationFail={onValidationFail} />
1309
1216
  </IntlProvider>
1310
1217
  );
1311
1218
  });
@@ -1325,27 +1232,20 @@ describe('EmailHTMLEditor', () => {
1325
1232
  return Promise.resolve(true);
1326
1233
  });
1327
1234
 
1328
- const emailActions = {
1329
- ...defaultProps.emailActions,
1330
- transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
1331
- createTemplate: jest.fn((obj, callback) => {
1332
- callback({ templateId: { _id: '123', versions: {} } });
1333
- }),
1334
- };
1235
+ const getFormdata = jest.fn();
1335
1236
  const getLiquidTags = jest.fn((content, callback) => {
1336
1237
  callback({ askAiraResponse: { data: [] }, isError: false });
1337
1238
  });
1338
1239
  // Ensure no HTML/Label/Liquid errors from HtmlEditor
1339
1240
  mockGetAllIssues.mockReturnValue([]);
1340
1241
 
1341
- // Set subject and content via component interactions
1242
+ // Liquid validation runs only in library mode; then performSave calls getFormdata
1342
1243
  const { rerender } = renderWithIntl({
1343
1244
  isGetFormData: false,
1344
- isFullMode: true,
1345
- isLiquidEnabled: true,
1245
+ isFullMode: false,
1346
1246
  getLiquidTags,
1347
- emailActions,
1348
- templateName: 'New Template',
1247
+ getFormdata,
1248
+ location: { query: { module: 'library' } },
1349
1249
  });
1350
1250
  const input = screen.getByTestId('subject-input');
1351
1251
  await act(async () => {
@@ -1363,7 +1263,7 @@ describe('EmailHTMLEditor', () => {
1363
1263
  await act(async () => {
1364
1264
  rerender(
1365
1265
  <IntlProvider locale="en" messages={{}}>
1366
- <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode getLiquidTags={getLiquidTags} emailActions={emailActions} templateName="New Template" />
1266
+ <EmailHTMLEditor {...defaultProps} isGetFormData isFullMode={false} getLiquidTags={getLiquidTags} getFormdata={getFormdata} location={{ query: { module: 'library' } }} />
1367
1267
  </IntlProvider>
1368
1268
  );
1369
1269
  });
@@ -1373,12 +1273,11 @@ describe('EmailHTMLEditor', () => {
1373
1273
  }, { timeout: 5000 });
1374
1274
 
1375
1275
  await waitFor(() => {
1376
- expect(emailActions.createTemplate).toHaveBeenCalled();
1276
+ expect(getFormdata).toHaveBeenCalled();
1377
1277
  }, { timeout: 5000 });
1378
1278
  });
1379
1279
 
1380
1280
  it('saves in full mode with create template', async () => {
1381
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1382
1281
  const emailActions = {
1383
1282
  ...defaultProps.emailActions,
1384
1283
  transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
@@ -1412,7 +1311,6 @@ describe('EmailHTMLEditor', () => {
1412
1311
  });
1413
1312
 
1414
1313
  it('saves in full mode with edit template', async () => {
1415
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1416
1314
  const emailActions = {
1417
1315
  ...defaultProps.emailActions,
1418
1316
  transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
@@ -1482,7 +1380,6 @@ describe('EmailHTMLEditor', () => {
1482
1380
  });
1483
1381
 
1484
1382
  it('handles create template error response', async () => {
1485
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1486
1383
  const emailActions = {
1487
1384
  ...defaultProps.emailActions,
1488
1385
  transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
@@ -1517,7 +1414,6 @@ describe('EmailHTMLEditor', () => {
1517
1414
  });
1518
1415
 
1519
1416
  it('handles create template success with getFormdata', async () => {
1520
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1521
1417
  const emailActions = {
1522
1418
  ...defaultProps.emailActions,
1523
1419
  transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
@@ -1554,7 +1450,6 @@ describe('EmailHTMLEditor', () => {
1554
1450
  });
1555
1451
 
1556
1452
  it('handles create template success without getFormdata', async () => {
1557
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1558
1453
  const emailActions = {
1559
1454
  ...defaultProps.emailActions,
1560
1455
  transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
@@ -1590,7 +1485,6 @@ describe('EmailHTMLEditor', () => {
1590
1485
  });
1591
1486
 
1592
1487
  it('saves in library mode with getFormdata', async () => {
1593
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1594
1488
  const getFormdata = jest.fn();
1595
1489
 
1596
1490
  // Set subject and content via component interactions
@@ -1618,7 +1512,6 @@ describe('EmailHTMLEditor', () => {
1618
1512
  });
1619
1513
 
1620
1514
  it('saves in library mode without library module', async () => {
1621
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1622
1515
  const getFormdata = jest.fn();
1623
1516
 
1624
1517
  // Set subject and content via component interactions
@@ -1758,7 +1651,6 @@ describe('EmailHTMLEditor', () => {
1758
1651
  renderWithIntl({
1759
1652
  getLiquidTags: null,
1760
1653
  globalActions,
1761
- isLiquidEnabled: true,
1762
1654
  isGetFormData: true,
1763
1655
  subject: 'Valid Subject',
1764
1656
  htmlContent: '<p>Content</p>',
@@ -22,7 +22,7 @@ import {
22
22
  CapTooltip,
23
23
  } from '@capillarytech/cap-ui-library';
24
24
  import { FONT_SIZE_L } from '@capillarytech/cap-ui-library/styled/variables';
25
- import _, { find, cloneDeep, findIndex, isEmpty, isEqual, filter, flattenDeep, replace } from 'lodash';
25
+ import _, { find, cloneDeep, findIndex, isEmpty, isEqual, filter, replace } from 'lodash';
26
26
  import * as actions from './actions';
27
27
  import { makeSelectFTP, makeSelectMetaEntities } from './selectors';
28
28
  import { makeSelectLoyaltyPromotionDisplay, setInjectedTags } from '../Cap/selectors';
@@ -33,7 +33,6 @@ import * as globalActions from '../Cap/actions';
33
33
  import { TagList } from '../TagList';
34
34
  import { NO_COMMUNICATION, CREATE, EDIT, PREVIEW } from '../App/constants';
35
35
  import { getTreeStructuredTags } from '../../utils/common';
36
- import { transformInjectedTags, checkIfSupportedTag, skipTags } from '../../utils/tagValidations';
37
36
  import injectSaga from '../../utils/injectSaga';
38
37
  import injectReducer from '../../utils/injectReducer';
39
38
 
@@ -235,63 +234,16 @@ export class FTP extends React.Component {
235
234
  }));
236
235
  };
237
236
 
238
- getFlatTags = (tags) => {
239
- const flatTags = [];
240
- tags.forEach((tag) => {
241
- if ((tag.children || []).length) {
242
- const innerTags = this.getFlatTags(tag.children);
243
- flatTags.push(innerTags);
244
- } else {
245
- flatTags.push(tag.value);
246
- }
247
- });
248
-
249
- return flattenDeep(flatTags);
250
- };
251
-
252
- validateTags(content, tagsParam, injectedTagsParams) {
253
- const tags = tagsParam;
254
- const injectedTags = transformInjectedTags(injectedTagsParams);
255
- const response = {};
256
- response.valid = true;
257
- response.unsupportedTags = [];
258
- const flatTags = this.getFlatTags(tags);
259
- if (flatTags && flatTags.length) {
260
- const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
261
- const matchedTags = [...content.matchAll(regex)];
262
- matchedTags.forEach((tag) => {
263
- let ifSupported = !!flatTags.find((t) => t === tag[0]);
264
- const tagValue = tag[0].substring(this.indexOfEnd(tag[0], '{{'), tag[0].indexOf('}}'));
265
- ifSupported = ifSupported || checkIfSupportedTag(tagValue, injectedTags) || skipTags(tagValue);
266
- if (!ifSupported) {
267
- response.unsupportedTags.push(tagValue);
268
- response.valid = false;
269
- }
270
- if (response.unsupportedTags.length === 0) {
271
- response.valid = true;
272
- }
273
- });
274
- }
275
- return response;
276
- }
277
-
278
237
  indexOfEnd(targetString, string) {
279
238
  const io = targetString.indexOf(string);
280
239
  return io == -1 ? -1 : io + string.length;
281
240
  }
282
241
 
283
242
  getMessageContent = () => {
284
- const { messageContent, tagsTree = []} = this.state;
243
+ const { messageContent } = this.state;
285
244
  const { formatMessage } = this.props.intl;
286
245
  const { metaEntities, selectedOfferDetails, injectedTags } = this.props;
287
246
  const tagsRaw = metaEntities && metaEntities.tags ? metaEntities.tags.standard : [];
288
- const validateTagResponse = !this.props?.isFullMode ? this.validateTags(messageContent, tagsTree, injectedTags) : { valid: true, unsupportedTags: [] };
289
- let unsupportedTags = null;
290
- let errorMessageText = '';
291
- if (!validateTagResponse.valid) {
292
- unsupportedTags = validateTagResponse.unsupportedTags.join(', ').toString();
293
- errorMessageText = formatMessage(messages.unsupportedTagsValidationError, {unsupportedTags});
294
- }
295
247
  return (
296
248
  <CapRow>
297
249
  <CapColumn span={11}>
@@ -309,7 +261,6 @@ export class FTP extends React.Component {
309
261
  label={formatMessage(messages.messageHeader)}
310
262
  onChange={this.updateMessageBody}
311
263
  value={messageContent}
312
- errorMessage={errorMessageText}
313
264
  />
314
265
  </div>
315
266
  </CapColumn>