@capillarytech/creatives-library 8.0.284 → 8.0.285-alpha.1

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 (40) hide show
  1. package/constants/unified.js +0 -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 +2 -83
  6. package/utils/tagValidations.js +84 -222
  7. package/utils/tests/commonUtil.test.js +147 -118
  8. package/utils/tests/tagValidations.test.js +280 -358
  9. package/v2Components/ErrorInfoNote/index.js +2 -5
  10. package/v2Components/FormBuilder/index.js +64 -158
  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/index.js +0 -1
  20. package/v2Containers/Email/index.js +1 -5
  21. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +10 -62
  22. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +12 -115
  23. package/v2Containers/FTP/index.js +2 -51
  24. package/v2Containers/FTP/messages.js +0 -4
  25. package/v2Containers/InApp/index.js +1 -96
  26. package/v2Containers/InApp/tests/index.test.js +17 -6
  27. package/v2Containers/InappAdvance/index.js +2 -103
  28. package/v2Containers/Line/Container/Text/index.js +0 -1
  29. package/v2Containers/MobilePushNew/index.js +2 -33
  30. package/v2Containers/Rcs/index.js +12 -37
  31. package/v2Containers/SmsTrai/Edit/index.js +6 -47
  32. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +6 -6
  33. package/v2Containers/Viber/index.js +0 -1
  34. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -3
  35. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
  36. package/v2Containers/WebPush/Create/index.js +2 -2
  37. package/v2Containers/WebPush/Create/utils/validation.js +18 -9
  38. package/v2Containers/WebPush/Create/utils/validation.test.js +0 -24
  39. package/v2Containers/Whatsapp/index.js +9 -17
  40. package/v2Containers/Zalo/index.js +3 -11
@@ -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,17 +1031,14 @@ 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 is always enabled for Email; blocking validation is the liquid API path.
1036
+ // validateLiquidTemplateContent is mocked, so simulate API validation failure by having the mock call onError.
1037
+ validateLiquidTemplateContent.mockImplementation((content, options) => {
1038
+ options.onError({ standardErrors: [], liquidErrors: ['Validation failed'] });
1039
+ return Promise.resolve(false);
1125
1040
  });
1126
1041
  const onValidationFail = jest.fn();
1127
- const CapNotification = require('@capillarytech/cap-ui-library/CapNotification');
1128
-
1129
- // Set subject and content via component interactions
1130
1042
  const { rerender } = renderWithIntl({
1131
1043
  onValidationFail,
1132
1044
  isGetFormData: false,
@@ -1135,13 +1047,12 @@ describe('EmailHTMLEditor', () => {
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
@@ -1153,12 +1064,10 @@ describe('EmailHTMLEditor', () => {
1153
1064
  standard: [{ name: 'customer.name' }],
1154
1065
  },
1155
1066
  }}
1156
- getLiquidTags={null} />
1067
+ getLiquidTags={jest.fn((content, cb) => cb({ askAiraResponse: { data: [] }, isError: false }))} />
1157
1068
  </IntlProvider>
1158
1069
  );
1159
-
1160
1070
  await waitFor(() => {
1161
- expect(CapNotification.error).toHaveBeenCalled();
1162
1071
  expect(onValidationFail).toHaveBeenCalled();
1163
1072
  }, { timeout: 3000 });
1164
1073
  });
@@ -1184,7 +1093,6 @@ describe('EmailHTMLEditor', () => {
1184
1093
  standard: [{ name: 'customer.name' }],
1185
1094
  },
1186
1095
  },
1187
- isLiquidEnabled: true,
1188
1096
  getLiquidTags,
1189
1097
  });
1190
1098
  const input = screen.getByTestId('subject-input');
@@ -1234,7 +1142,6 @@ describe('EmailHTMLEditor', () => {
1234
1142
  const { rerender } = renderWithIntl({
1235
1143
  isGetFormData: false,
1236
1144
  isFullMode: true,
1237
- isLiquidEnabled: true,
1238
1145
  getLiquidTags,
1239
1146
  });
1240
1147
  const input = screen.getByTestId('subject-input');
@@ -1284,7 +1191,6 @@ describe('EmailHTMLEditor', () => {
1284
1191
  const { rerender } = renderWithIntl({
1285
1192
  isGetFormData: false,
1286
1193
  isFullMode: true,
1287
- isLiquidEnabled: true,
1288
1194
  getLiquidTags,
1289
1195
  showLiquidErrorInFooter,
1290
1196
  onValidationFail,
@@ -1342,7 +1248,6 @@ describe('EmailHTMLEditor', () => {
1342
1248
  const { rerender } = renderWithIntl({
1343
1249
  isGetFormData: false,
1344
1250
  isFullMode: true,
1345
- isLiquidEnabled: true,
1346
1251
  getLiquidTags,
1347
1252
  emailActions,
1348
1253
  templateName: 'New Template',
@@ -1378,7 +1283,6 @@ describe('EmailHTMLEditor', () => {
1378
1283
  });
1379
1284
 
1380
1285
  it('saves in full mode with create template', async () => {
1381
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1382
1286
  const emailActions = {
1383
1287
  ...defaultProps.emailActions,
1384
1288
  transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
@@ -1412,7 +1316,6 @@ describe('EmailHTMLEditor', () => {
1412
1316
  });
1413
1317
 
1414
1318
  it('saves in full mode with edit template', async () => {
1415
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1416
1319
  const emailActions = {
1417
1320
  ...defaultProps.emailActions,
1418
1321
  transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
@@ -1482,7 +1385,6 @@ describe('EmailHTMLEditor', () => {
1482
1385
  });
1483
1386
 
1484
1387
  it('handles create template error response', async () => {
1485
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1486
1388
  const emailActions = {
1487
1389
  ...defaultProps.emailActions,
1488
1390
  transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
@@ -1517,7 +1419,6 @@ describe('EmailHTMLEditor', () => {
1517
1419
  });
1518
1420
 
1519
1421
  it('handles create template success with getFormdata', async () => {
1520
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1521
1422
  const emailActions = {
1522
1423
  ...defaultProps.emailActions,
1523
1424
  transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
@@ -1554,7 +1455,6 @@ describe('EmailHTMLEditor', () => {
1554
1455
  });
1555
1456
 
1556
1457
  it('handles create template success without getFormdata', async () => {
1557
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1558
1458
  const emailActions = {
1559
1459
  ...defaultProps.emailActions,
1560
1460
  transformEmailTemplate: jest.fn((obj, callback) => callback(obj)),
@@ -1590,7 +1490,6 @@ describe('EmailHTMLEditor', () => {
1590
1490
  });
1591
1491
 
1592
1492
  it('saves in library mode with getFormdata', async () => {
1593
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1594
1493
  const getFormdata = jest.fn();
1595
1494
 
1596
1495
  // Set subject and content via component interactions
@@ -1618,7 +1517,6 @@ describe('EmailHTMLEditor', () => {
1618
1517
  });
1619
1518
 
1620
1519
  it('saves in library mode without library module', async () => {
1621
- mockHasLiquidSupportFeature.mockReturnValue(false); // Non-liquid org
1622
1520
  const getFormdata = jest.fn();
1623
1521
 
1624
1522
  // Set subject and content via component interactions
@@ -1758,7 +1656,6 @@ describe('EmailHTMLEditor', () => {
1758
1656
  renderWithIntl({
1759
1657
  getLiquidTags: null,
1760
1658
  globalActions,
1761
- isLiquidEnabled: true,
1762
1659
  isGetFormData: true,
1763
1660
  subject: 'Valid Subject',
1764
1661
  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>
@@ -21,10 +21,6 @@ export default defineMessages({
21
21
  id: 'creatives.containersV2.FTP.addColumn',
22
22
  defaultMessage: 'Add column',
23
23
  },
24
- unsupportedTagsValidationError: {
25
- id: 'creatives.containersV2.FTP.unsupportedTagsValidationError',
26
- defaultMessage: 'Unsupported tags: {unsupportedTags}. Please remove them from this message.',
27
- },
28
24
  selectTag: {
29
25
  id: 'creatives.containersV2.FTP.selectTag',
30
26
  defaultMessage: 'Select tag',
@@ -31,7 +31,6 @@ import creativesMessages from '../CreativesContainer/messages';
31
31
  import withCreatives from "../../hoc/withCreatives";
32
32
  import UnifiedPreview from "../../v2Components/CommonTestAndPreview/UnifiedPreview";
33
33
  import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
34
- import { validateTags } from "../../utils/tagValidations";
35
34
  import injectReducer from '../../utils/injectReducer';
36
35
  import v2InAppReducer from './reducer';
37
36
  import { v2InAppSagas } from './sagas';
@@ -58,7 +57,6 @@ import {
58
57
  import { getCdnUrl } from "../../utils/cdnTransformation";
59
58
  import { getCtaObject, hasAnyErrors, getSingleTab } from "./utils";
60
59
  import { validateInAppContent } from "../../utils/commonUtils";
61
- import { hasLiquidSupportFeature } from "../../utils/common";
62
60
  import formBuilderMessages from "../../v2Components/FormBuilder/messages";
63
61
  import HTMLEditor from "../../v2Components/HtmlEditor";
64
62
  import { HTML_EDITOR_VARIANTS } from "../../v2Components/HtmlEditor/constants";
@@ -1095,9 +1093,7 @@ export const InApp = (props) => {
1095
1093
  // Skip validation if no tags are available (e.g., in tests or when tags haven't loaded)
1096
1094
  const hasTags = tags && tags.length > 0;
1097
1095
 
1098
- // For liquid flow, use validateInAppContent
1099
- if (isLiquidFlow && hasTags) {
1100
- // Validate the INAPP content
1096
+ if (hasTags) {
1101
1097
  const payload = createPayload();
1102
1098
  validateInAppContent(payload, {
1103
1099
  currentTab: panes === ANDROID ? 1 : 2, // Convert ANDROID/IOS to tab numbers
@@ -1106,10 +1102,6 @@ export const InApp = (props) => {
1106
1102
  getLiquidTags: (content, callback) => globalActions.getLiquidTags(content, callback),
1107
1103
  formatMessage,
1108
1104
  messages: formBuilderMessages,
1109
- tagLookupMap: metaEntities?.tagLookupMap || {},
1110
- eventContextTags: metaEntities?.eventContextTags || [],
1111
- isLiquidFlow,
1112
- forwardedTags: {},
1113
1105
  skipTags: (tag) => {
1114
1106
  // Skip certain tags if needed
1115
1107
  const skipRegexes = [
@@ -1124,98 +1116,11 @@ export const InApp = (props) => {
1124
1116
  },
1125
1117
  singleTab: getSingleTab(accountData),
1126
1118
  });
1127
- } else if (hasTags) {
1128
- // For non-liquid flow, validate tags using validateTags (only if tags are available)
1129
- const androidContent = htmlContentAndroid || '';
1130
- const iosContent = htmlContentIos || '';
1131
-
1132
- const androidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
1133
- const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
1134
-
1135
- let hasErrors = false;
1136
- const newErrors = {
1137
- STANDARD_ERROR_MSG: {
1138
- ANDROID: [],
1139
- IOS: [],
1140
- GENERIC: [],
1141
- },
1142
- LIQUID_ERROR_MSG: {
1143
- ANDROID: [],
1144
- IOS: [],
1145
- GENERIC: [],
1146
- },
1147
- };
1148
-
1149
- // Validate Android content
1150
- if (androidSupported && androidContent && androidContent?.trim() !== '') {
1151
- const validationResponse = validateTags({
1152
- content: androidContent,
1153
- tagsParam: tags,
1154
- injectedTagsParams: injectedTags || {},
1155
- location,
1156
- tagModule: getDefaultTags,
1157
- eventContextTags: metaEntities?.eventContextTags || [],
1158
- isFullMode,
1159
- }) || {};
1160
-
1161
- if (validationResponse?.unsupportedTags?.length > 0) {
1162
- hasErrors = true;
1163
- newErrors.LIQUID_ERROR_MSG.ANDROID.push(
1164
- formatMessage(globalMessages.unsupportedTagsValidationError, {
1165
- unsupportedTags: validationResponse.unsupportedTags.join(", "),
1166
- })
1167
- );
1168
- }
1169
- if (validationResponse?.isBraceError) {
1170
- hasErrors = true;
1171
- newErrors.LIQUID_ERROR_MSG.ANDROID.push(
1172
- formatMessage(globalMessages.unbalanacedCurlyBraces)
1173
- );
1174
- }
1175
- }
1176
-
1177
- // Validate iOS content
1178
- if (iosSupported && iosContent && iosContent?.trim() !== '') {
1179
- const validationResponse = validateTags({
1180
- content: iosContent,
1181
- tagsParam: tags,
1182
- injectedTagsParams: injectedTags || {},
1183
- location,
1184
- tagModule: getDefaultTags,
1185
- eventContextTags: metaEntities?.eventContextTags || [],
1186
- isFullMode,
1187
- }) || {};
1188
-
1189
- if (validationResponse?.unsupportedTags?.length > 0) {
1190
- hasErrors = true;
1191
- newErrors.LIQUID_ERROR_MSG.IOS.push(
1192
- formatMessage(globalMessages.unsupportedTagsValidationError, {
1193
- unsupportedTags: validationResponse.unsupportedTags.join(", "),
1194
- })
1195
- );
1196
- }
1197
- if (validationResponse?.isBraceError) {
1198
- hasErrors = true;
1199
- newErrors.LIQUID_ERROR_MSG.IOS.push(
1200
- formatMessage(globalMessages.unbalanacedCurlyBraces)
1201
- );
1202
- }
1203
- }
1204
-
1205
- if (hasErrors) {
1206
- setErrorMessage(newErrors);
1207
- } else {
1208
- // No errors, proceed with submission
1209
- onSuccess();
1210
- }
1211
1119
  } else {
1212
- // No tags available, skip validation and proceed directly
1213
1120
  onSuccess();
1214
1121
  }
1215
1122
  };
1216
1123
 
1217
- const isLiquidFlow = hasLiquidSupportFeature();
1218
-
1219
1124
  // Check template data to determine editor type (for render decision)
1220
1125
  const templateDetails = isFullMode ? editData?.templateDetails : templateData;
1221
1126
  const versions = templateDetails?.versions || {};
@@ -21,18 +21,16 @@ import { getCtaObject } from '../utils';
21
21
  jest.mock('redux-auth-wrapper/history4/redirect', () => ({
22
22
  connectedRouterRedirect: jest.fn((config) => (Component) => Component),
23
23
  }));
24
+ import * as commonUtils from '../../../utils/commonUtils';
24
25
 
25
26
  const mockActions = {
26
27
  getTemplateInfoById: jest.fn(),
27
28
  resetEditTemplate: jest.fn(),
28
- getTemplateDetails: jest.fn((id, callback) => {
29
+ getTemplateDetails: jest.fn((id, setSpin) => {
29
30
  // Simulate successful template details fetch to prevent loading state
30
31
  // The callback is setSpin function, call it with false to stop spinner
31
- if (callback && typeof callback === 'function') {
32
- // Use setTimeout to ensure it's called after render
33
- setTimeout(() => {
34
- callback(false);
35
- }, 0);
32
+ if (setSpin && typeof setSpin === 'function') {
33
+ setTimeout(() => setSpin(false), 0);
36
34
  }
37
35
  }),
38
36
  editTemplate: jest.fn(),
@@ -41,6 +39,9 @@ const mockActions = {
41
39
  };
42
40
  const mockGlobalActions = {
43
41
  fetchSchemaForEntity: jest.fn(),
42
+ getLiquidTags: jest.fn((content, callback) =>
43
+ callback({ askAiraResponse: { data: [], errors: [] }, isError: false }),
44
+ ),
44
45
  };
45
46
 
46
47
  jest.mock('../../../v2Containers/TagList/index.js', () => ({
@@ -66,7 +67,17 @@ const renderComponent = (props) =>
66
67
  );
67
68
 
68
69
  describe('Test activity inApp container', () => {
70
+ afterEach(() => {
71
+ jest.restoreAllMocks();
72
+ });
73
+
69
74
  it('test case for inApp template update flow', async () => {
75
+ jest
76
+ .spyOn(commonUtils, 'validateInAppContent')
77
+ .mockImplementation((payload, options) => {
78
+ options.onSuccess();
79
+ return Promise.resolve(true);
80
+ });
70
81
  renderComponent({
71
82
  actions: mockActions,
72
83
  globalActions: mockGlobalActions,