@capillarytech/creatives-library 8.0.113 → 8.0.114-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 (106) hide show
  1. package/containers/App/test/saga.test.js +1 -1
  2. package/containers/Assets/Gallery/tests/__snapshots__/reducer.test.js.snap +1 -1
  3. package/containers/Assets/Gallery/tests/actions.test.js +2 -3
  4. package/containers/Assets/Gallery/tests/reducer.test.js +7 -7
  5. package/containers/Assets/Gallery/tests/saga.test.js +9 -9
  6. package/containers/Dashboard/test/saga.test.js +1 -1
  7. package/containers/Ebill/test/saga.test.js +1 -1
  8. package/containers/Email/test/saga.test.js +33 -33
  9. package/containers/LanguageProvider/tests/actions.test.js +1 -1
  10. package/containers/LanguageProvider/tests/reducer.test.js +2 -2
  11. package/containers/LanguageProvider/tests/selectors.test.js +1 -1
  12. package/containers/Line/Create/tests/saga.test.js +2 -9
  13. package/containers/Line/Edit/test/saga.test.js +10 -14
  14. package/containers/MobilePush/Create/test/saga.test.js +2 -2
  15. package/containers/MobilePush/Edit/tests/saga.test.js +14 -14
  16. package/containers/Sms/Create/test/saga.test.js +4 -5
  17. package/containers/Sms/Edit/test/saga.test.js +1 -1
  18. package/containers/Templates/test/saga.test.js +14 -17
  19. package/containers/WeChat/MapTemplates/test/saga.test.js +9 -13
  20. package/containers/WeChat/RichmediaTemplates/Create/test/saga.test.js +1 -1
  21. package/containers/WeChat/RichmediaTemplates/Edit/test/saga.test.js +1 -1
  22. package/package.json +1 -1
  23. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +8 -9
  24. package/utils/commonUtils.js +389 -3
  25. package/utils/tagValidations.js +20 -5
  26. package/utils/tests/authWrapper.test.js +2 -2
  27. package/utils/tests/cdnTransformation.test.js +16 -15
  28. package/utils/tests/commonUtil.test.js +324 -178
  29. package/v2Components/CapVideoUpload/tests/index.test.js +1 -1
  30. package/v2Components/CapWhatsappCTA/tests/index.test.js +1 -2
  31. package/v2Components/ErrorInfoNote/ErrorTypeRenderer.js +130 -0
  32. package/v2Components/ErrorInfoNote/ErrorTypeRenderer.test.js +146 -0
  33. package/v2Components/ErrorInfoNote/index.js +114 -46
  34. package/v2Components/ErrorInfoNote/messages.js +25 -0
  35. package/v2Components/ErrorInfoNote/style.scss +14 -1
  36. package/v2Components/ErrorInfoNote/utils.js +28 -0
  37. package/v2Components/ErrorInfoNote/utils.test.js +93 -0
  38. package/v2Components/FormBuilder/index.js +200 -127
  39. package/v2Components/FormBuilder/messages.js +1 -1
  40. package/v2Components/MarketingObjective/test/index.test.js +1 -1
  41. package/v2Components/NavigationBar/tests/saga.test.js +2 -3
  42. package/v2Containers/Assets/Gallery/tests/__snapshots__/reducer.test.js.snap +1 -1
  43. package/v2Containers/Assets/Gallery/tests/actions.test.js +2 -3
  44. package/v2Containers/Assets/Gallery/tests/reducer.test.js +7 -7
  45. package/v2Containers/Assets/Gallery/tests/saga.test.js +2 -2
  46. package/v2Containers/BeeEditor/test/saga.test.js +1 -1
  47. package/v2Containers/CallTask/test/saga.test.js +1 -1
  48. package/v2Containers/Cap/reducer.js +4 -4
  49. package/v2Containers/Cap/tests/actions.test.js +1 -1
  50. package/v2Containers/Cap/tests/reducer.test.js +11 -11
  51. package/v2Containers/Cap/tests/saga.test.js +1 -1
  52. package/v2Containers/Cap/tests/selectors.test.js +3 -3
  53. package/v2Containers/CapFacebookPreview/tests/saga.test.js +1 -1
  54. package/v2Containers/CreativesContainer/SlideBoxContent.js +23 -3
  55. package/v2Containers/CreativesContainer/SlideBoxFooter.js +3 -1
  56. package/v2Containers/CreativesContainer/constants.js +2 -1
  57. package/v2Containers/CreativesContainer/index.js +37 -10
  58. package/v2Containers/CreativesContainer/messages.js +4 -0
  59. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +21 -3
  60. package/v2Containers/Ebill/index.js +3 -3
  61. package/v2Containers/Ebill/test/saga.test.js +1 -1
  62. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +4 -4
  63. package/v2Containers/Email/tests/actions.test.js +1 -1
  64. package/v2Containers/Email/tests/reducer.test.js +2 -2
  65. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +1 -1
  66. package/v2Containers/FTP/test/saga.test.js +1 -1
  67. package/v2Containers/Facebook/test/saga.test.js +7 -7
  68. package/v2Containers/InApp/index.js +127 -49
  69. package/v2Containers/InApp/tests/action.test.js +7 -7
  70. package/v2Containers/InApp/tests/index.test.js +2 -4
  71. package/v2Containers/InApp/tests/reducer.test.js +175 -89
  72. package/v2Containers/InApp/tests/sagas.test.js +1 -1
  73. package/v2Containers/InApp/utils.js +37 -0
  74. package/v2Containers/LanguageProvider/tests/actions.test.js +1 -1
  75. package/v2Containers/LanguageProvider/tests/reducer.test.js +3 -3
  76. package/v2Containers/LanguageProvider/tests/saga.test.js +2 -2
  77. package/v2Containers/LanguageProvider/tests/selectors.test.js +1 -1
  78. package/v2Containers/Line/Container/ImageCarousel/tests/utils.test.js +3 -3
  79. package/v2Containers/Line/Container/Sticker/tests/utils.test.js +6 -6
  80. package/v2Containers/MobilePush/Create/index.js +24 -20
  81. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  82. package/v2Containers/MobilePush/Edit/index.js +4 -1
  83. package/v2Containers/MobilePush/Edit/test/saga.test.js +14 -14
  84. package/v2Containers/MobilepushWrapper/index.js +2 -0
  85. package/v2Containers/Rcs/tests/saga.test.js +1 -1
  86. package/v2Containers/Sms/Create/index.js +14 -2
  87. package/v2Containers/Sms/Create/test/saga.test.js +1 -1
  88. package/v2Containers/Sms/Edit/index.js +2 -0
  89. package/v2Containers/Sms/Edit/test/saga.test.js +5 -5
  90. package/v2Containers/SmsTrai/Create/tests/saga.test.js +1 -1
  91. package/v2Containers/SmsTrai/Create/tests/selectors.test.js +1 -1
  92. package/v2Containers/SmsWrapper/index.js +2 -0
  93. package/v2Containers/TagList/tests/TagList.test.js +1 -3
  94. package/v2Containers/TagList/tests/utils.test.js +3 -3
  95. package/v2Containers/Templates/tests/actions.test.js +1 -1
  96. package/v2Containers/Templates/tests/reducer.test.js +8 -8
  97. package/v2Containers/Templates/tests/sagas.test.js +2 -4
  98. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -13
  99. package/v2Containers/WeChat/RichmediaTemplates/Create/test/saga.test.js +1 -1
  100. package/v2Containers/WeChat/RichmediaTemplates/Edit/test/saga.test.js +1 -1
  101. package/v2Containers/Whatsapp/tests/__snapshots__/utils.test.js.snap +9 -9
  102. package/v2Containers/Whatsapp/tests/actions.test.js +3 -3
  103. package/v2Containers/Whatsapp/tests/reducer.test.js +32 -36
  104. package/v2Containers/Whatsapp/tests/utils.test.js +19 -19
  105. package/v2Containers/Zalo/tests/actions.test.js +3 -3
  106. package/v2Containers/Zalo/tests/reducer.test.js +72 -42
@@ -1,9 +1,22 @@
1
1
  import React from 'react';
2
2
  import { FormattedMessage } from 'react-intl';
3
3
  import get from 'lodash/get';
4
- import { EMBEDDED } from '../v2Containers/Whatsapp/constants';
5
- import { EDIT, PREVIEW } from '../v2Containers/App/constants';
6
-
4
+ import { convert } from "html-to-text";
5
+ import { EMBEDDED } from "../v2Containers/Whatsapp/constants";
6
+ import {
7
+ EDIT,
8
+ EMAIL,
9
+ INAPP,
10
+ PREVIEW,
11
+ } from "../v2Containers/App/constants";
12
+ import {
13
+ MOBILE_PUSH,
14
+ SMS,
15
+ ANDROID,
16
+ IOS,
17
+ } from "../v2Containers/CreativesContainer/constants";
18
+ import { GLOBAL_CONVERT_OPTIONS } from "../v2Components/FormBuilder/constants";
19
+ import { checkSupport, extractNames } from "./tagValidations";
7
20
  export const apiMessageFormatHandler = (id, fallback) => (
8
21
  <FormattedMessage id={id} defaultMessage={fallback} />
9
22
  );
@@ -83,3 +96,376 @@ export const transformCustomFieldsData = (customFields) => {
83
96
  };
84
97
 
85
98
  export const isTagIncluded = (value) => value && value.includes('{{') && value.includes('}}');
99
+
100
+
101
+ /**
102
+ *
103
+ * @param {*} channel
104
+ * @param {*} formData
105
+ * @returns
106
+ */
107
+ export const getChannelData = (channel, formData) => {
108
+ switch (channel?.toUpperCase()) {
109
+ case EMAIL.toUpperCase():
110
+ return convert(
111
+ formData?.base?.en?.["template-content"] || "",
112
+ GLOBAL_CONVERT_OPTIONS
113
+ );
114
+
115
+ case SMS:
116
+ return (
117
+ `${formData?.base?.["sms-editor"]} ${formData?.["template-name"]}` || ""
118
+ );
119
+
120
+ default:
121
+ return JSON.stringify(formData);
122
+ }
123
+ };
124
+
125
+ /**
126
+ * Validates liquid template content and handles errors
127
+ * @param {string} content - The content to validate
128
+ * @param {string} channel - Channel type (EMAIL, SMS, MOBILE_PUSH, etc.)
129
+ * @param {object} options - Additional options for validation
130
+ * @param {function} options.getLiquidTags - Function to call to get liquid tags
131
+ * @param {function} options.formatMessage - Intl formatMessage function for i18n
132
+ * @param {object} options.messages - Messages for i18n
133
+ * @param {function} options.onError - Callback when error occurs
134
+ * @param {function} options.onSuccess - Callback when validation succeeds
135
+ * @param {object} options.tagLookupMap - Map of supported tags
136
+ * @param {array} options.eventContextTags - Context-specific tags
137
+ * @param {boolean} options.isLiquidFlow - Whether liquid flow is enabled
138
+ * @param {object} options.forwardedTags - Any forwarded tags (for Loyalty module)
139
+ * @param {string} options.tabType - For MPUSH: 'android' or 'ios'
140
+ * @param {number} options.currentTab - For MPUSH: current active tab (1 for Android, 2 for iOS)
141
+ * @param {function} options.skipTags - Function to determine if tags should be skipped
142
+ * @returns {Promise} - Promise that resolves to validation result
143
+ */
144
+ export const validateLiquidTemplateContent = async (
145
+ content,
146
+ channel,
147
+ options
148
+ ) => {
149
+ const {
150
+ getLiquidTags,
151
+ formatMessage,
152
+ messages,
153
+ onError = () => {},
154
+ onSuccess = () => {},
155
+ tagLookupMap,
156
+ eventContextTags,
157
+ isLiquidFlow,
158
+ forwardedTags = {},
159
+ tabType,
160
+ skipTags = () => false,
161
+ } = options;
162
+
163
+ // Empty content check
164
+ if (!content || content.trim() === "") {
165
+ const errorMsg = formatMessage(messages.emailBodyEmptyError);
166
+ onError({
167
+ standardErrors: [errorMsg],
168
+ liquidErrors: [],
169
+ tabType,
170
+ });
171
+ return false;
172
+ }
173
+
174
+ // Await getLiquidTags as a promise if it doesn't already return one
175
+ const getLiquidTagsAsync = (inputContent) => new Promise((resolve) => {
176
+ getLiquidTags(inputContent, (result) => {
177
+ resolve(result);
178
+ });
179
+ });
180
+
181
+ const result = await getLiquidTagsAsync(content);
182
+ const validString = /\S/.test(content);
183
+
184
+ // Handle API errors or empty content
185
+ if (result?.errors?.length > 0 || !validString) {
186
+ let standardErrors = [];
187
+ if (!validString) {
188
+ const emptyBodyError = formatMessage(messages.emailBodyEmptyError);
189
+ standardErrors = [emptyBodyError];
190
+ }
191
+ let liquidErrors;
192
+ if (result && Array.isArray(result?.errors)) {
193
+ liquidErrors = result?.errors?.map((error) => {
194
+ const message = typeof error?.message === "string"
195
+ ? error.message
196
+ : formatMessage(messages.somethingWentWrong);
197
+ return message;
198
+ });
199
+ } else {
200
+ const somethingWrongMsg = formatMessage(messages.somethingWentWrong);
201
+ liquidErrors = [somethingWrongMsg];
202
+ }
203
+ onError({
204
+ standardErrors,
205
+ liquidErrors,
206
+ tabType,
207
+ });
208
+ return false;
209
+ }
210
+
211
+ // Extract and validate tags
212
+ const extractedLiquidTags = extractNames(result?.data || []);
213
+ // Get supported tags
214
+ const supportedLiquidTags = checkSupport(
215
+ result,
216
+ tagLookupMap,
217
+ eventContextTags,
218
+ isLiquidFlow,
219
+ forwardedTags
220
+ );
221
+ // Find unsupported tags
222
+ const unsupportedLiquidTags = extractedLiquidTags?.filter(
223
+ (tag) => !supportedLiquidTags?.includes(tag) && !skipTags(tag)
224
+ );
225
+ // Handle unsupported tags
226
+ if (unsupportedLiquidTags?.length > 0) {
227
+ const errorMsg = formatMessage(messages.unsupportedTagsValidationError, {
228
+ unsupportedTags: unsupportedLiquidTags.join(", "),
229
+ });
230
+ onError({
231
+ standardErrors: [],
232
+ liquidErrors: [errorMsg],
233
+ tabType,
234
+ });
235
+ return false;
236
+ }
237
+ // All validations passed
238
+ onSuccess(content, tabType);
239
+ return true;
240
+ };
241
+
242
+ // Internal helper function to validate platform-specific content
243
+ export const _validatePlatformSpecificContent = async (
244
+ androidContent,
245
+ iosContent,
246
+ channel,
247
+ options // Contains parent's onError and other options for validateLiquidTemplateContent
248
+ ) => {
249
+ const {
250
+ onError: parentOnError, // The onError callback from the calling function (e.g., validateMobilePushContent)
251
+ ...commonVltcOptions // Other options like getLiquidTags, formatMessage, messages, currentTab, etc.
252
+ } = options;
253
+
254
+ let isAndroidValid = !androidContent; // True if no Android content, hence valid by default
255
+ let isIosValid = !iosContent; // True if no iOS content, hence valid by default
256
+
257
+ // Initialize error structure with platform-specific categories
258
+ const aggregatedErrors = {
259
+ standardErrors: {
260
+ [ANDROID]: [],
261
+ [IOS]: [],
262
+ generic: [],
263
+ },
264
+ liquidErrors: {
265
+ [ANDROID]: [],
266
+ [IOS]: [],
267
+ generic: [],
268
+ },
269
+ };
270
+
271
+ // This aggregator is passed to validateLiquidTemplateContent.
272
+ // It accumulates errors in the new structure and calls the parentOnError
273
+ const internalOnErrorAggregator = ({
274
+ standardErrors = [],
275
+ liquidErrors = [],
276
+ tabType: tabTypeFromVLTC,
277
+ }) => {
278
+ aggregatedErrors.standardErrors[tabTypeFromVLTC?.toUpperCase()].push(
279
+ ...standardErrors
280
+ );
281
+ aggregatedErrors.liquidErrors[tabTypeFromVLTC?.toUpperCase()].push(
282
+ ...liquidErrors
283
+ );
284
+ parentOnError(aggregatedErrors);
285
+ };
286
+
287
+ // Base options for calling validateLiquidTemplateContent
288
+ const vltcCallOptions = {
289
+ ...commonVltcOptions,
290
+ onError: internalOnErrorAggregator,
291
+ onSuccess: () => {},
292
+ };
293
+
294
+ if (androidContent) {
295
+ isAndroidValid = await validateLiquidTemplateContent(
296
+ androidContent,
297
+ channel,
298
+ { ...vltcCallOptions, tabType: ANDROID }
299
+ );
300
+ }
301
+
302
+ if (iosContent) {
303
+ isIosValid = await validateLiquidTemplateContent(
304
+ iosContent,
305
+ channel,
306
+ { ...vltcCallOptions, tabType: IOS }
307
+ );
308
+ }
309
+ return isAndroidValid && isIosValid; // Overall success
310
+ };
311
+
312
+ /**
313
+ * Validate Mobile Push content for both Android and iOS tabs
314
+ * @param {object} formData - Form data containing Android and iOS content
315
+ * @param {object} options - Options for validation
316
+ * @returns {Promise} - Promise that resolves when validation completes
317
+ */
318
+ export const validateMobilePushContent = async (formData, options) => {
319
+ const {
320
+ currentTab,
321
+ onError, // This is the onError callback passed by the caller of validateMobilePushContent
322
+ onSuccess, // This is the FINAL success callback for MobilePush
323
+ ...restOptions // Options for validateLiquidTemplateContent (getLiquidTags, formatMessage, etc.)
324
+ } = options;
325
+ // Clear previous errors by calling the passed onError
326
+ onError({ standardErrors: [], liquidErrors: [] });
327
+
328
+ const androidContent = JSON.stringify(formData?.[0]);
329
+ const iosContent = JSON.stringify(formData?.[1]);
330
+ // Pass the original 'onError' from this function's arguments,
331
+ // 'currentTab', and other relevant options to the helper.
332
+ const overallSuccess = await _validatePlatformSpecificContent(
333
+ androidContent,
334
+ iosContent,
335
+ MOBILE_PUSH,
336
+ { ...restOptions, currentTab, onError }
337
+ );
338
+ if (overallSuccess) {
339
+ // Determine content to submit based on the *active* tab preference
340
+ let contentToSubmit = "";
341
+ let tabTypeToSubmit = "";
342
+ if (currentTab === 1 && androidContent) {
343
+ contentToSubmit = androidContent;
344
+ tabTypeToSubmit = "android";
345
+ } else if (currentTab === 2 && iosContent) {
346
+ contentToSubmit = iosContent;
347
+ tabTypeToSubmit = "ios";
348
+ } else if (androidContent) {
349
+ // Fallback
350
+ contentToSubmit = androidContent;
351
+ tabTypeToSubmit = "android";
352
+ } else if (iosContent) {
353
+ // Fallback
354
+ contentToSubmit = iosContent;
355
+ tabTypeToSubmit = "ios";
356
+ }
357
+ // Call the FINAL onSuccess only ONCE here
358
+ onSuccess(contentToSubmit, tabTypeToSubmit);
359
+ return true;
360
+ }
361
+ return false;
362
+ };
363
+
364
+ /**
365
+ * Validate INAPP content for both Android and iOS
366
+ * @param {object} formData - Form data containing Android and iOS content
367
+ * @param {object} options - Options for validation
368
+ * @returns {Promise} - Promise that resolves when validation completes
369
+ */
370
+ export const validateInAppContent = async (formData, options) => {
371
+ const {
372
+ onError, // This is the onError callback passed by the caller of validateInAppContent
373
+ onSuccess, // FINAL success callback
374
+ ...restOptions // Options for validateLiquidTemplateContent
375
+ } = options;
376
+
377
+ // Clear previous errors with new structure
378
+ onError({
379
+ standardErrors: {
380
+ [ANDROID]: [],
381
+ [IOS]: [],
382
+ generic: [],
383
+ },
384
+ liquidErrors: {
385
+ [ANDROID]: [],
386
+ [IOS]: [],
387
+ generic: [],
388
+ },
389
+ });
390
+
391
+ let androidContent = "";
392
+ let iosContent = "";
393
+ let parseError = null;
394
+
395
+ // Extract content
396
+ try {
397
+ const androidData = formData?.versions?.base?.content?.ANDROID;
398
+ if (androidData) {
399
+ androidContent = [
400
+ androidData.title,
401
+ androidData.message,
402
+ ...(androidData.ctas?.map((cta) => cta.text || cta.actionLink) || []),
403
+ ]
404
+ .filter(Boolean)
405
+ .join(" ");
406
+ }
407
+
408
+ const iosData = formData?.versions?.base?.content?.IOS;
409
+ if (iosData) {
410
+ iosContent = [
411
+ iosData.title,
412
+ iosData.message,
413
+ ...(iosData.ctas?.map((cta) => cta.text || cta.actionLink) || []),
414
+ ]
415
+ .filter(Boolean)
416
+ .join(" ");
417
+ }
418
+ } catch (error) {
419
+ parseError = error; // Capture parsing error
420
+ }
421
+
422
+ // Handle parsing errors immediately
423
+ if (parseError) {
424
+ onError({
425
+ standardErrors: {
426
+ generic: [
427
+ restOptions.formatMessage(restOptions.messages.somethingWentWrong),
428
+ ],
429
+ [ANDROID]: [],
430
+ [IOS]: [],
431
+ },
432
+ liquidErrors: {
433
+ generic: [],
434
+ [ANDROID]: [],
435
+ [IOS]: [],
436
+ },
437
+ });
438
+ return false;
439
+ }
440
+
441
+ // Check if *any* content exists
442
+ if (parseError) {
443
+ onError((prevErrors) => ({
444
+ ...prevErrors,
445
+ standardErrors: {
446
+ ...prevErrors.standardErrors,
447
+ generic: [
448
+ restOptions.formatMessage(restOptions.messages.somethingWentWrong),
449
+ ],
450
+ },
451
+ }));
452
+ return false;
453
+ }
454
+
455
+ // Pass the original 'onError' from this function's arguments
456
+ // and other relevant options to the helper.
457
+ const overallSuccess = await _validatePlatformSpecificContent(
458
+ androidContent,
459
+ iosContent,
460
+ INAPP,
461
+ { ...restOptions, onError }
462
+ );
463
+
464
+ if (overallSuccess) {
465
+ // Call FINAL onSuccess ONCE
466
+ onSuccess();
467
+ return true;
468
+ }
469
+ // Errors already reported via the 'onError' callback passed to _validatePlatformSpecificContent
470
+ return false;
471
+ };
@@ -115,6 +115,20 @@ export function extractNames(data) {
115
115
  return names;
116
116
  }
117
117
 
118
+ // Helper to check if a tag is inside a {% ... %} block
119
+ export const isInsideLiquidBlock = (content, tagIndex) => {
120
+ const blockRegex = /{%(.*?)%}/gs;
121
+ let match;
122
+ for (match = blockRegex.exec(content); match !== null; match = blockRegex.exec(content)) {
123
+ const blockStart = match.index;
124
+ const blockEnd = blockStart + match[0].length;
125
+ if (tagIndex >= blockStart && tagIndex < blockEnd) {
126
+ return true;
127
+ }
128
+ }
129
+ return false;
130
+ };
131
+
118
132
  /**
119
133
  * Validates the tags based on the provided parameters.
120
134
  * @param {Object} params - The parameters for tag validation.
@@ -130,7 +144,6 @@ export const validateTags = ({
130
144
  const tags = tagsParam;
131
145
  const injectedTags = transformInjectedTags(injectedTagsParams);
132
146
  let currentModule = location?.query?.module ? location?.query?.module : DEFAULT;
133
-
134
147
  if (tagModule) {
135
148
  currentModule = tagModule;
136
149
  }
@@ -160,6 +173,7 @@ export const validateTags = ({
160
173
  let match = regex.exec(content);
161
174
  while (match !== null) {
162
175
  const tagValue = match[0].substring(indexOfEnd(match[0], '{{'), match[0].indexOf('}}'));
176
+ const tagIndex = match?.index;
163
177
  match = regex.exec(content);
164
178
  let ifSupported = false;
165
179
  lodashForEach(tags, (tag) => {
@@ -170,10 +184,10 @@ export const validateTags = ({
170
184
  const ifSkipped = skipTags(tagValue);
171
185
  if (ifSkipped) {
172
186
  ifSupported = true;
173
- let isUnsubscribeSkipped = tagValue.indexOf("unsubscribe") != -1 ;
187
+ const isUnsubscribeSkipped = tagValue.indexOf("unsubscribe") !== -1;
174
188
  if (isUnsubscribeSkipped) {
175
189
  const missingTagIndex = response.missingTags.indexOf("unsubscribe");
176
- if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
190
+ if (missingTagIndex !== -1) {
177
191
  response.missingTags.splice(missingTagIndex, 1);
178
192
  }
179
193
  }
@@ -185,11 +199,12 @@ export const validateTags = ({
185
199
  }
186
200
  });
187
201
  ifSupported = ifSupported || checkIfSupportedTag(tagValue, injectedTags);
188
- if (!ifSupported) {
202
+ // Only add to unsupportedTags if not inside a {% ... %} block and does not contain a dot
203
+ if (!ifSupported && !isInsideLiquidBlock(content, tagIndex) && tagValue?.indexOf('.') === -1) {
189
204
  response.unsupportedTags.push(tagValue);
190
205
  response.valid = false;
191
206
  }
192
- if (response.unsupportedTags.length == 0 && response.missingTags.length == 0 ) {
207
+ if (response.unsupportedTags.length === 0 && response.missingTags.length === 0) {
193
208
  response.valid = true;
194
209
  }
195
210
  }
@@ -76,7 +76,7 @@ describe("Authentication Redirects", () => {
76
76
 
77
77
  await waitFor(() => {
78
78
  expect(history.location.pathname).toEqual("/");
79
- });
79
+ }, { timeout: 5000, interval: 100 });
80
80
  });
81
81
 
82
82
 
@@ -94,7 +94,7 @@ describe("Authentication Redirects", () => {
94
94
 
95
95
  await waitFor(() => {
96
96
  expect(history.location.pathname).toEqual("/some-route");
97
- });
97
+ }, { timeout: 5000, interval: 100 });
98
98
  });
99
99
  });
100
100
 
@@ -1,15 +1,15 @@
1
+ import * as nodeHtmlParser from 'node-html-parser';
2
+ import Bugsnag from '@bugsnag/js';
1
3
  import * as cdnUtils from "../cdnTransformation";
2
4
  import * as mockdata from "./cdnTransformation.mockdata";
3
- import * as nodeHtmlParser from 'node-html-parser'
4
- import Bugsnag from '@bugsnag/js';
5
5
  import * as cdnTransformationHandler from './cdnTransformation.handler.mockdata';
6
6
 
7
- var bugsnagSpy;
8
- var localStorageMock;
9
- jest.setTimeout(180000);
7
+ let bugsnagSpy;
8
+ let localStorageMock;
9
+ jest.setTimeout(30000);
10
10
  beforeEach(() => {
11
- localStorageMock = (function () {
12
- var store = {
11
+ localStorageMock = (() => {
12
+ let store = {
13
13
  CREATIVES_CDN_TRANSFORMATION_URL_SUFFIX: "cdn-cgi/image",
14
14
  CREATIVES_S3_BUCKET_PATH: "intouch_creative_assets",
15
15
  CREATIVES_CDN_BASE_URL: "https://storage.crm.n.content-cdn.io",
@@ -18,16 +18,16 @@ beforeEach(() => {
18
18
  S3_CDN_MAP: '{"crm-nightly-new-fileservice.s3.amazonaws.com":{"cdn_host":"storage.crm.n.content-cdn.io","bucket_path":"intouch_creative_assets"}, "crm-prod-new-fileservice.s3.amazonaws.com": { "cdn_host": "storage.crm.p.content-cdn.io", "bucket_path": "fileservice.p/intouch_creative_assets" }}',
19
19
  };
20
20
  return {
21
- getItem: function (key) {
21
+ getItem(key) {
22
22
  return store[key];
23
23
  },
24
- setItem: function (key, value) {
24
+ setItem(key, value) {
25
25
  store[key] = value.toString();
26
26
  },
27
- clear: function () {
27
+ clear() {
28
28
  store = {};
29
29
  },
30
- removeItem: function (key) {
30
+ removeItem(key) {
31
31
  delete store[key];
32
32
  },
33
33
  };
@@ -225,20 +225,21 @@ describe("cdnTransformationTests", () => {
225
225
  });
226
226
 
227
227
  it("should call bugsnag when parse() throws some unexpected error.", async () => {
228
- const spy = jest.spyOn(nodeHtmlParser, 'parse').mockImplementation(()=>{
228
+ const parseSpy = jest.spyOn(nodeHtmlParser, 'parse').mockImplementation(() => {
229
229
  throw new Error();
230
- })
230
+ });
231
231
  expect(await cdnUtils.updateImagesInHtml(mockdata.emailRawInput)).toEqual(mockdata.emailRawInput);
232
232
  expect(bugsnagSpy).toHaveBeenCalled();
233
+ parseSpy.mockRestore();
233
234
  });
234
235
 
235
- it("Should update images in html, (api call made here to find s3 images file size which are not in window object)", async ()=>{
236
+ it("Should update images in html, (api call made here to find s3 images file sizes which are not in window object)", async () => {
236
237
  /**
237
238
  * Out of 3 images. One s3 url is in window object, one is cdn url and another is also s3 url but not in window object.
238
239
  */
239
240
  cdnTransformationHandler.server.listen();
240
241
  cdnTransformationHandler.server.use(cdnTransformationHandler.getS3FileSizesFromUrlSuccessResponse2);
241
- localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
242
+ localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY", "true");
242
243
  localStorage.setItem("CREATIVES_CDN_OVERRIDE_DEFAULT_EMAIL_QUALITY_MAPPING", "[[30, 90], [80, 85], [150, 80], [400, 75], [600, 70], [1200, 65], [102400, 60]]");
243
244
  window[cdnUtils.CREATIVES_S3_ASSET_FILESIZES] = {
244
245
  "https://crm-nightly-new-fileservice.s3.amazonaws.com/intouch_creative_assets/394c053f-309b-49fc-ba74-6e700b1.jpeg": 76800, //this image has quality 85 since file size less than 80kb