@capillarytech/creatives-library 8.0.290-alpha.3 → 8.0.290

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 (76) hide show
  1. package/constants/unified.js +1 -0
  2. package/initialState.js +2 -0
  3. package/package.json +1 -1
  4. package/utils/common.js +8 -5
  5. package/utils/commonUtils.js +85 -4
  6. package/utils/tagValidations.js +222 -84
  7. package/utils/tests/commonUtil.test.js +124 -147
  8. package/utils/tests/tagValidations.test.js +358 -280
  9. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +33 -0
  10. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +397 -0
  11. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.scss +35 -0
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/TECH_DETAILING_DELIVERY_SETTINGS.md +725 -0
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +92 -0
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +243 -0
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +111 -0
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +91 -0
  17. package/v2Components/CommonTestAndPreview/SendTestMessage.js +33 -1
  18. package/v2Components/CommonTestAndPreview/actions.js +20 -0
  19. package/v2Components/CommonTestAndPreview/constants.js +10 -0
  20. package/v2Components/CommonTestAndPreview/index.js +133 -15
  21. package/v2Components/CommonTestAndPreview/reducer.js +47 -0
  22. package/v2Components/CommonTestAndPreview/sagas.js +60 -0
  23. package/v2Components/CommonTestAndPreview/selectors.js +51 -0
  24. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +782 -0
  25. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +200 -0
  26. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +235 -0
  27. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +127 -0
  28. package/v2Components/CommonTestAndPreview/tests/actions.test.js +50 -0
  29. package/v2Components/CommonTestAndPreview/tests/constants.test.js +18 -0
  30. package/v2Components/CommonTestAndPreview/tests/index.test.js +214 -1
  31. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +118 -0
  32. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +145 -0
  33. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +146 -0
  34. package/v2Components/ErrorInfoNote/index.js +5 -2
  35. package/v2Components/FormBuilder/index.js +162 -84
  36. package/v2Components/FormBuilder/messages.js +8 -0
  37. package/v2Components/HtmlEditor/HTMLEditor.js +5 -0
  38. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  39. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +15 -0
  40. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +2 -1
  41. package/v2Components/TestAndPreviewSlidebox/index.js +14 -0
  42. package/v2Containers/Cap/mockData.js +14 -0
  43. package/v2Containers/Cap/reducer.js +55 -3
  44. package/v2Containers/Cap/tests/reducer.test.js +102 -0
  45. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -3
  46. package/v2Containers/CreativesContainer/index.js +6 -19
  47. package/v2Containers/Email/index.js +5 -1
  48. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +62 -10
  49. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +115 -12
  50. package/v2Containers/FTP/index.js +51 -2
  51. package/v2Containers/FTP/messages.js +4 -0
  52. package/v2Containers/InApp/index.js +96 -1
  53. package/v2Containers/InApp/tests/index.test.js +6 -17
  54. package/v2Containers/InappAdvance/index.js +103 -2
  55. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +24 -3
  56. package/v2Containers/Line/Container/Text/index.js +1 -0
  57. package/v2Containers/MobilePush/Create/index.js +16 -6
  58. package/v2Containers/MobilePush/Edit/index.js +16 -6
  59. package/v2Containers/MobilePushNew/index.js +33 -2
  60. package/v2Containers/Rcs/index.js +37 -12
  61. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +667 -16
  62. package/v2Containers/Sms/Create/index.js +3 -35
  63. package/v2Containers/Sms/Create/messages.js +0 -4
  64. package/v2Containers/Sms/Edit/index.js +3 -33
  65. package/v2Containers/Sms/commonMethods.js +6 -6
  66. package/v2Containers/SmsTrai/Edit/index.js +47 -6
  67. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +147 -6
  68. package/v2Containers/Viber/index.js +1 -0
  69. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +3 -1
  70. package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +7 -0
  71. package/v2Containers/WebPush/Create/index.js +2 -2
  72. package/v2Containers/WebPush/Create/utils/validation.js +2 -17
  73. package/v2Containers/WebPush/Create/utils/validation.test.js +24 -0
  74. package/v2Containers/Whatsapp/index.js +18 -10
  75. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +25849 -3524
  76. package/v2Containers/Zalo/index.js +11 -3
@@ -1,5 +1,7 @@
1
1
  import '@testing-library/jest-dom';
2
2
  import {
3
+ checkSupport,
4
+ extractNames,
3
5
  getTagMapValue,
4
6
  getLoyaltyTagsMapValue,
5
7
  getForwardedMapValues,
@@ -7,8 +9,10 @@ import {
7
9
  validateIfTagClosed,
8
10
  validateTags,
9
11
  skipTags,
12
+ isInsideLiquidBlock,
10
13
  } from '../tagValidations';
11
14
  import { containsBase64Images } from '../content';
15
+ import { eventContextTags } from '../../v2Containers/TagList/tests/mockdata';
12
16
 
13
17
  describe("check if curly brackets are balanced", () => {
14
18
  it("test for balanced curly brackets", () => {
@@ -60,41 +64,53 @@ describe("validateTags", () => {
60
64
  it("should return valid response when all tags are present", () => {
61
65
  const content = "Hello {{tag1}}, {{tag2}}, {{tag3}} {{entryTrigger.lifetimePurchases}}";
62
66
 
67
+ const injectedTagsParams = [];
63
68
  const location = { query: { module: "DEFAULT" } };
64
69
  const tagModule = null;
65
70
 
66
71
  const result = validateTags({
67
72
  content,
68
73
  tagsParam,
74
+ injectedTagsParams,
69
75
  location,
70
76
  tagModule,
77
+ eventContextTags,
71
78
  });
72
79
 
73
80
  expect(result.valid).toEqual(true);
74
81
  expect(result.missingTags).toEqual([]);
82
+ expect(result.unsupportedTags).toEqual([]);
75
83
  expect(result.isBraceError).toEqual(false);
76
84
  });
77
85
 
78
- it("should return valid response when content has balanced braces and all tags present", () => {
86
+ it("should return invalid response when a mandatory tag is missing", () => {
79
87
  const content = "Hello {{tag1}}, {{tag2}}, {{tag3}}";
88
+ const updatedTagsParam = [...tagsParam, {
89
+ definition: {
90
+ supportedModules: [{ context: "DEFAULT", mandatory: true }],
91
+ value: "tag4",
92
+ },
93
+ },];
94
+ const injectedTagsParams = [];
80
95
  const location = { query: { module: "DEFAULT" } };
81
96
  const tagModule = null;
82
97
 
83
98
  const result = validateTags({
84
99
  content,
85
- tagsParam,
100
+ updatedTagsParam,
101
+ injectedTagsParams,
86
102
  location,
87
103
  tagModule,
88
104
  });
89
105
 
90
106
  expect(result.valid).toEqual(true);
91
- expect(result.missingTags).toEqual([]);
107
+ expect(result.unsupportedTags).toEqual(["tag1", "tag2", "tag3"]);
92
108
  expect(result.isBraceError).toEqual(false);
93
109
  });
94
110
 
95
- it("should return valid response when content has balanced braces with missingEventContextTags and partial tagsParam", () => {
111
+ it("should return invalid response when an unsupported tag is present", () => {
96
112
  const content = "Hello {{tag1}}, {{tag2}}, {{tag3}} {{missingEventContextTags}}";
97
- const tagsParamLocal = [
113
+ const tagsParam = [
98
114
  {
99
115
  definition: {
100
116
  supportedModules: [{ context: "DEFAULT", mandatory: true }],
@@ -108,24 +124,27 @@ describe("validateTags", () => {
108
124
  },
109
125
  },
110
126
  ];
127
+ const injectedTagsParams = [];
111
128
  const location = { query: { module: "DEFAULT" } };
112
129
  const tagModule = null;
113
130
 
114
131
  const result = validateTags({
115
132
  content,
116
- tagsParam: tagsParamLocal,
133
+ tagsParam,
134
+ injectedTagsParams,
117
135
  location,
118
136
  tagModule,
119
137
  });
120
138
 
121
139
  expect(result.valid).toEqual(true);
122
140
  expect(result.missingTags).toEqual([]);
141
+ expect(result.unsupportedTags).toEqual(["tag3", "missingEventContextTags"]);
123
142
  expect(result.isBraceError).toEqual(false);
124
143
  });
125
144
 
126
145
  it("should return invalid response when there is an unbalanced bracket error", () => {
127
146
  const content = "Hello {{tag1}, {{tag2}}, {{tag3}}";
128
- const tagsParamLocal = [
147
+ const tagsParam = [
129
148
  {
130
149
  definition: {
131
150
  supportedModules: [{ context: "DEFAULT", mandatory: true }],
@@ -145,23 +164,26 @@ describe("validateTags", () => {
145
164
  },
146
165
  },
147
166
  ];
167
+ const injectedTagsParams = [];
148
168
  const location = { query: { module: "DEFAULT" } };
149
169
  const tagModule = null;
150
170
 
151
171
  const result = validateTags({
152
172
  content,
153
- tagsParam: tagsParamLocal,
173
+ tagsParam,
174
+ injectedTagsParams,
154
175
  location,
155
176
  tagModule,
156
177
  });
157
178
 
158
179
  expect(result.valid).toEqual(false);
159
- expect(result.missingTags).toEqual([]);
180
+ expect(result.missingTags).toEqual(["tag1"]);
181
+ expect(result.unsupportedTags).toEqual([]);
160
182
  expect(result.isBraceError).toEqual(true);
161
183
  });
162
184
 
163
- it("should require unsubscribe when mandatory and missing, and accept skipped unsubscribe variant", () => {
164
- const tagsParamUnsubscribe = [
185
+ it("should remove 'unsubscribe' from missingTags if skipTags logic is triggered", () => {
186
+ const tagsParam = [
165
187
  {
166
188
  definition: {
167
189
  supportedModules: [{ context: "DEFAULT", mandatory: true }],
@@ -169,310 +191,288 @@ describe("validateTags", () => {
169
191
  },
170
192
  },
171
193
  ];
194
+ // Content does not include {{unsubscribe}}, so it would be missing
195
+ const contentMissing = "Hello {{tag1}}";
196
+ const injectedTagsParams = [];
172
197
  const location = { query: { module: "DEFAULT" } };
173
198
  const tagModule = null;
174
199
 
175
- const contentMissing = "Hello world";
200
+ // First, verify unsubscribe is missing if not present
176
201
  const resultMissing = validateTags({
177
202
  content: contentMissing,
178
- tagsParam: tagsParamUnsubscribe,
203
+ tagsParam,
204
+ injectedTagsParams,
179
205
  location,
180
206
  tagModule,
181
207
  });
182
208
  expect(resultMissing.missingTags).toContain("unsubscribe");
183
- expect(resultMissing.valid).toBe(false);
184
209
 
210
+ // Now, content includes a tag that triggers skipTags logic for unsubscribe
211
+ // e.g., {{unsubscribe(#123456)}} matches the skipTags regex
185
212
  const contentWithSkippedUnsubscribe = "Hello {{unsubscribe(#123456)}}";
186
213
  const resultSkipped = validateTags({
187
214
  content: contentWithSkippedUnsubscribe,
188
- tagsParam: tagsParamUnsubscribe,
215
+ tagsParam,
216
+ injectedTagsParams,
189
217
  location,
190
218
  tagModule,
191
219
  });
192
220
  expect(resultSkipped.missingTags).not.toContain("unsubscribe");
193
221
  expect(resultSkipped.valid).toBe(true);
194
-
195
- const contentWithWhitespaceUnsubscribe = "Hello {{ unsubscribe }}";
196
- const resultWhitespace = validateTags({
197
- content: contentWithWhitespaceUnsubscribe,
198
- tagsParam: tagsParamUnsubscribe,
199
- location,
200
- tagModule,
201
- });
202
- expect(resultWhitespace.missingTags).not.toContain("unsubscribe");
203
- expect(resultWhitespace.valid).toBe(true);
204
- expect(resultWhitespace.unsupportedTags ?? []).toEqual([]);
205
222
  });
206
223
  });
207
224
 
208
- describe('validateTags wrapper (v2 consumers)', () => {
209
- const tagsWithUnsubscribe = [
210
- {
211
- definition: {
212
- supportedModules: [{ context: 'DEFAULT', mandatory: true }],
213
- value: 'unsubscribe',
225
+ describe("extractNames", () => {
226
+ it("should return an empty array when data is empty", () => {
227
+ const data = [];
228
+ const result = extractNames(data);
229
+ expect(result).toEqual([]);
230
+ });
231
+
232
+ it("should return an array of names when data contains nodes with names", () => {
233
+ const data = [
234
+ {
235
+ name: "Node 1",
236
+ children: [
237
+ {
238
+ name: "Node 1.1",
239
+ children: [],
240
+ },
241
+ {
242
+ name: "Node 1.2",
243
+ children: [],
244
+ },
245
+ ],
214
246
  },
215
- },
216
- ];
217
- const tagsOutboundUnsubscribe = [
218
- {
219
- definition: {
220
- supportedModules: [{ context: 'outbound', mandatory: true }],
221
- value: 'unsubscribe',
247
+ {
248
+ name: "Node 2",
249
+ children: [
250
+ {
251
+ name: "Node 2.1",
252
+ children: [],
253
+ },
254
+ ],
222
255
  },
223
- },
224
- ];
225
- const tagsDefaultLowercase = [
226
- {
227
- definition: {
228
- supportedModules: [{ context: 'default', mandatory: true }],
229
- value: 'unsubscribe',
256
+ ];
257
+ const result = extractNames(data);
258
+ expect(result).toEqual([
259
+ "Node 1",
260
+ "Node 1.1",
261
+ "Node 1.2",
262
+ "Node 2",
263
+ "Node 2.1",
264
+ ]);
265
+ });
266
+
267
+ it("should ignore nodes without names", () => {
268
+ const data = [
269
+ {
270
+ name: "Node 1",
271
+ children: [
272
+ {
273
+ name: "Node 1.1",
274
+ children: [],
275
+ },
276
+ {
277
+ children: [],
278
+ },
279
+ ],
230
280
  },
231
- },
232
- ];
233
-
234
- describe('module selection (location.query.module vs tagModule vs DEFAULT)', () => {
235
- it('uses location.query.module when set and tagModule not provided', () => {
236
- const content = 'Hello world';
237
- const result = validateTags({
238
- content,
239
- tagsParam: tagsWithUnsubscribe,
240
- location: { query: { module: 'DEFAULT' } },
241
- tagModule: null,
242
- });
243
- expect(result.missingTags).toContain('unsubscribe');
244
- expect(result.valid).toBe(false);
245
- });
246
-
247
- it('uses tagModule override when provided (overrides location.query.module)', () => {
248
- const content = 'Hello world';
249
- const resultWithOutbound = validateTags({
250
- content,
251
- tagsParam: tagsOutboundUnsubscribe,
252
- location: { query: { module: 'DEFAULT' } },
253
- tagModule: 'outbound',
254
- });
255
- expect(resultWithOutbound.missingTags).toContain('unsubscribe');
256
- expect(resultWithOutbound.valid).toBe(false);
257
-
258
- const resultWithDefault = validateTags({
259
- content,
260
- tagsParam: tagsWithUnsubscribe,
261
- location: { query: { module: 'outbound' } },
262
- tagModule: 'DEFAULT',
263
- });
264
- expect(resultWithDefault.missingTags).toContain('unsubscribe');
265
- expect(resultWithDefault.valid).toBe(false);
266
- });
267
-
268
- it('uses DEFAULT (lowercase) when location is undefined', () => {
269
- const content = 'Hello world';
270
- const result = validateTags({
271
- content,
272
- tagsParam: tagsDefaultLowercase,
273
- location: undefined,
274
- tagModule: null,
275
- });
276
- expect(result.missingTags).toContain('unsubscribe');
277
- expect(result.valid).toBe(false);
278
- });
279
-
280
- it('uses DEFAULT when location.query is undefined', () => {
281
- const content = 'Hello world';
282
- const result = validateTags({
283
- content,
284
- tagsParam: tagsDefaultLowercase,
285
- location: {},
286
- tagModule: null,
287
- });
288
- expect(result.missingTags).toContain('unsubscribe');
289
- expect(result.valid).toBe(false);
290
- });
291
-
292
- it('uses DEFAULT when location.query.module is falsy', () => {
293
- const content = 'Hello world';
294
- const result = validateTags({
295
- content,
296
- tagsParam: tagsDefaultLowercase,
297
- location: { query: { module: '' } },
298
- tagModule: null,
299
- });
300
- expect(result.missingTags).toContain('unsubscribe');
301
- expect(result.valid).toBe(false);
302
- });
281
+ {
282
+ name: "Node 2",
283
+ children: [
284
+ {
285
+ name: "Node 2.1",
286
+ children: [],
287
+ },
288
+ {
289
+ name: "Node 2.2",
290
+ },
291
+ ],
292
+ },
293
+ ];
294
+ const result = extractNames(data);
295
+ expect(result).toEqual([
296
+ "Node 1",
297
+ "Node 1.1",
298
+ "Node 2",
299
+ "Node 2.1",
300
+ "Node 2.2",
301
+ ]);
303
302
  });
303
+ });
304
304
 
305
- describe('content passed to contentForBraceCheck and contentForUnsubscribeScan', () => {
306
- it('uses same content for brace check (isBraceError when unbalanced)', () => {
307
- const content = 'Hello {{tag1}, {{tag2}}';
308
- const result = validateTags({
309
- content,
310
- tagsParam: [],
311
- location: { query: { module: 'DEFAULT' } },
312
- });
313
- expect(result.isBraceError).toBe(true);
314
- expect(result.valid).toBe(false);
315
- });
316
305
 
317
- it('uses same content for unsubscribe scan (missing unsubscribe when required)', () => {
318
- const content = 'Hello {{other}}';
319
- const result = validateTags({
320
- content,
321
- tagsParam: tagsWithUnsubscribe,
322
- location: { query: { module: 'DEFAULT' } },
323
- });
324
- expect(result.missingTags).toContain('unsubscribe');
325
- expect(result.valid).toBe(false);
326
- });
327
-
328
- it('content with {{ unsubscribe }} satisfies unsubscribe requirement', () => {
329
- const content = 'Hello {{ unsubscribe }}';
330
- const result = validateTags({
331
- content,
332
- tagsParam: tagsWithUnsubscribe,
333
- location: { query: { module: 'DEFAULT' } },
334
- });
335
- expect(result.missingTags).not.toContain('unsubscribe');
336
- expect(result.valid).toBe(true);
337
- });
306
+ describe("checkSupport", () => {
307
+ it("should return an empty array when args are empty", () => {
308
+ const result = checkSupport();
309
+ expect(result).toEqual([]);
338
310
  });
339
-
340
- describe('isFullMode', () => {
341
- it('when true skips unsubscribe check (no missingTags for mandatory unsubscribe)', () => {
342
- const content = 'Hello world';
343
- const result = validateTags({
344
- content,
345
- tagsParam: tagsWithUnsubscribe,
346
- location: { query: { module: 'DEFAULT' } },
347
- isFullMode: true,
348
- });
349
- expect(result.missingTags).toEqual([]);
350
- expect(result.valid).toBe(true);
351
- });
352
-
353
- it('when true still runs brace check', () => {
354
- const content = 'Hello {{tag1}';
355
- const result = validateTags({
356
- content,
357
- tagsParam: tagsWithUnsubscribe,
358
- location: { query: { module: 'DEFAULT' } },
359
- isFullMode: true,
360
- });
361
- expect(result.isBraceError).toBe(true);
362
- expect(result.valid).toBe(false);
363
- });
311
+ it("should return an empty array when response data is empty", () => {
312
+ const response = { data: [] };
313
+ const tagObject = {};
314
+ const result = checkSupport(response, tagObject);
315
+ expect(result).toEqual([]);
364
316
  });
365
317
 
366
- describe('tagsParam null or empty', () => {
367
- it('when null: only brace check runs, no missing-tag logic', () => {
368
- const content = 'Hello {{a}}';
369
- const result = validateTags({
370
- content,
371
- tagsParam: null,
372
- location: { query: { module: 'DEFAULT' } },
373
- });
374
- expect(result.missingTags).toEqual([]);
375
- expect(result.isBraceError).toBe(false);
376
- expect(result.valid).toBe(true);
377
- });
378
-
379
- it('when empty array: only brace check runs', () => {
380
- const content = 'Hello {{a}}';
381
- const result = validateTags({
382
- content,
383
- tagsParam: [],
384
- location: { query: { module: 'DEFAULT' } },
385
- });
386
- expect(result.missingTags).toEqual([]);
387
- expect(result.valid).toBe(true);
388
- });
389
-
390
- it('when null with unbalanced braces: returns isBraceError', () => {
391
- const content = 'Hello {{a}';
392
- const result = validateTags({
393
- content,
394
- tagsParam: null,
395
- location: { query: { module: 'DEFAULT' } },
396
- });
397
- expect(result.isBraceError).toBe(true);
398
- expect(result.valid).toBe(false);
399
- expect(result.missingTags).toEqual([]);
400
- });
318
+ it("should return an empty array when tagObject is empty", () => {
319
+ const response = {
320
+ data: [{ name: "tag1" }, { name: "tag2" }, { name: "tag3" }],
321
+ };
322
+ const tagObject = {};
323
+ const result = checkSupport(response, tagObject);
324
+ expect(result).toEqual([]);
401
325
  });
402
326
 
403
- describe('v2 consumer call patterns', () => {
404
- it('Whatsapp-style: content, tagsParam, location, tagModule (getDefaultTags), isFullMode', () => {
405
- const content = 'Hello {{ unsubscribe }}';
406
- const result = validateTags({
407
- content: content,
408
- tagsParam: tagsWithUnsubscribe,
409
- location: { query: { module: 'DEFAULT' } },
410
- tagModule: 'DEFAULT',
411
- isFullMode: false,
412
- });
413
- expect(result.valid).toBe(true);
414
- expect(result.isBraceError).toBe(false);
415
- });
416
-
417
- it('Zalo / Rcs / MobilePushNew / EmailWrapper: same pattern as Whatsapp', () => {
418
- const result = validateTags({
419
- content: 'Hi {{ unsubscribe }}',
420
- tagsParam: tagsWithUnsubscribe,
421
- location: { query: { module: 'DEFAULT' } },
422
- tagModule: 'DEFAULT',
423
- isFullMode: false,
424
- });
425
- expect(result.valid).toBe(true);
426
- });
327
+ it("should return event context tags even if tagObject is empty", () => {
328
+ const response = {
329
+ data: [{ name: "tag1" }, { name: "entryTrigger.lifetimePurchases" }],
330
+ };
331
+ const tagObject = {};
332
+ const result = checkSupport(response, tagObject, eventContextTags);
333
+ expect(result).toEqual(['entryTrigger.lifetimePurchases']);
334
+ });
427
335
 
428
- it('Line / Viber: tagModule "outbound"', () => {
429
- const contentMissing = 'Hello';
430
- const resultMissing = validateTags({
431
- content: contentMissing,
432
- tagsParam: tagsOutboundUnsubscribe,
433
- location: { query: { module: 'inbound' } },
434
- tagModule: 'outbound',
435
- isFullMode: false,
436
- });
437
- expect(resultMissing.missingTags).toContain('unsubscribe');
438
- expect(resultMissing.valid).toBe(false);
439
-
440
- const resultOk = validateTags({
441
- content: 'Hello {{ unsubscribe }}',
442
- tagsParam: tagsOutboundUnsubscribe,
443
- location: {},
444
- tagModule: 'outbound',
445
- isFullMode: false,
446
- });
447
- expect(resultOk.valid).toBe(true);
448
- });
336
+ it("should return an array of supported tags", () => {
337
+ const response = {
338
+ data: [{ name: "tag1" }, { name: "tag2" }, { name: "tag3" }],
339
+ };
340
+ const tagObject = {
341
+ tag1: { definition: { subtags: ["tag1.1", "tag1.2"] } },
342
+ tag2: { definition: { subtags: ["tag2.1", "tag2.2"] } },
343
+ tag3: { definition: { subtags: ["tag3.1", "tag3.2"] } },
344
+ };
345
+ const result = checkSupport(response, tagObject);
346
+ expect(result).toEqual(["tag1", "tag2", "tag3"]);
347
+ });
449
348
 
450
- it('WebPush: validationConfig spread (content + tagsParam, location, tagModule, isFullMode)', () => {
451
- const validationConfig = {
452
- tagsParam: tagsWithUnsubscribe,
453
- location: { query: { module: 'DEFAULT' } },
454
- tagModule: 'DEFAULT',
455
- };
456
- const result = validateTags({
457
- content: 'Hello {{ unsubscribe }}',
458
- ...validationConfig,
459
- isFullMode: false,
460
- });
461
- expect(result.valid).toBe(true);
462
- expect(result.isBraceError).toBe(false);
463
- });
349
+ it("should return an array of supported tags with subtags", () => {
350
+ const response = {
351
+ data: [{ name: "tag1" }, { name: "tag2" }, { name: "tag3" }],
352
+ };
353
+ const tagObject = {
354
+ tag1: { definition: { subtags: ["tag1.1", "tag1.2"] } },
355
+ tag2: { definition: { subtags: ["tag2.1", "tag2.2"] } },
356
+ tag3: { definition: { subtags: ["tag3.1", "tag3.2"] } },
357
+ "tag1.1": {},
358
+ "tag2.1": {},
359
+ "tag3.1": {},
360
+ };
361
+ const result = checkSupport(response, tagObject);
362
+ expect(result).toEqual(["tag1", "tag2", "tag3"]);
363
+ });
464
364
 
465
- it('WebPush with isFullMode: only brace check', () => {
466
- const result = validateTags({
467
- content: 'Hello world',
468
- tagsParam: tagsWithUnsubscribe,
469
- location: { query: { module: 'DEFAULT' } },
470
- tagModule: 'DEFAULT',
471
- isFullMode: true,
472
- });
473
- expect(result.valid).toBe(true);
474
- expect(result.missingTags).toEqual([]);
475
- });
365
+ it("should return an empty array when no tags are supported", () => {
366
+ const response = {
367
+ data: [{ name: "tag1" }, { name: "tag2" }, { name: "tag3" }],
368
+ };
369
+ const tagObject = {
370
+ tag4: {},
371
+ tag5: {},
372
+ tag6: {},
373
+ };
374
+ const result = checkSupport(response, tagObject);
375
+ expect(result).toEqual([]);
376
+ });
377
+ it("should return an array of supported tags with subtags", () => {
378
+ const response = {
379
+ data: [
380
+ {
381
+ name: "tag1",
382
+ children: [{ name: "tag1.1", children: [{ name: "tag1.2" }] }],
383
+ },
384
+ { name: "tag2", children: [] },
385
+ { name: "tag3" },
386
+ ],
387
+ };
388
+ const tagObject = {
389
+ tag1: { definition: { subtags: [".1", "tag1.2"] } },
390
+ tag2: { definition: { subtags: ["tag2.1", "tag2.2"] } },
391
+ tag3: { definition: { subtags: ["tag3.1", "tag3.2"] } },
392
+ "tag1.1": {},
393
+ "tag2.1": {},
394
+ "tag3.1": {},
395
+ };
396
+ const result = checkSupport(response, tagObject);
397
+ expect(result).toEqual(["tag1", "tag1.1", "tag2", "tag3"]);
398
+ });
399
+ it("should return an array of supported tags children as an object", () => {
400
+ const response = {
401
+ data: [
402
+ { name: "tag1", children: [{ name: "tag" }] },
403
+ { name: "tag2", children: [] },
404
+ { name: "tag3" },
405
+ ],
406
+ };
407
+ const tagObject = {
408
+ tag1: { definition: { subtags: [".1", "tag1.2"] } },
409
+ tag2: { definition: { subtags: ["tag2.1", "tag2.2"] } },
410
+ tag3: { definition: { subtags: ["tag3.1", "tag3.2"] } },
411
+ "tag1.1": {},
412
+ "tag2.1": {},
413
+ "tag3.1": {},
414
+ };
415
+ const result = checkSupport(response, tagObject);
416
+ expect(result).toEqual(["tag1", "tag2", "tag3"]);
417
+ });
418
+ it("should return an empty array if tagObject is empty", () => {
419
+ const response = {
420
+ data: [
421
+ { name: "tag1", children: [{ name: "tag" }] },
422
+ { name: "tag2", children: [] },
423
+ { name: "tag3" },
424
+ ],
425
+ };
426
+ const result = checkSupport(response, {});
427
+ expect(result).toEqual([]);
428
+ });
429
+
430
+ it("should add childName to supportedList if it is a subtag of parentTag in eventContextTags", () => {
431
+ const response = { data: [{ name: "leaderboard", children: [{name: "person.userId"}]}]};
432
+ const tagObject = {};
433
+ const eventContextTags = [{ tagName: "leaderboard", subTags: ["userId"]}];
434
+ const isLiquidFlow = true;
435
+ const result = checkSupport(response, tagObject, eventContextTags, isLiquidFlow);
436
+ expect(result).toEqual( [ 'leaderboard', 'person.userId' ]);
437
+ });
438
+
439
+ it("should not add childName to supportedList which does not have dot in eventContextTags", () => {
440
+ // This case is unlikely to happen as we are not supporting tags without dot in eventContextTags
441
+ const response = { data: [{ name: "entryTrigger.lifetimePoints", children: [{name: "userId"}] }]};
442
+ const tagObject = {};
443
+ const eventContextTags = [{ tagName: "entryTrigger.lifetimePoints", children: [{name: "userId"}] }];
444
+ const isLiquidFlow = true;
445
+ const result = checkSupport(response, tagObject, eventContextTags, isLiquidFlow);
446
+ expect(result).toEqual( [ "entryTrigger.lifetimePoints" ]);
447
+ });
448
+
449
+ it("should add only parent tag to supportedList if isLiquidFlow false", () => {
450
+ const response = { data: [{ name: "leaderboard", children: [{name: "person.userId"}]}]};
451
+ const tagObject = {};
452
+ const eventContextTags = [{ tagName: "leaderboard", subTags: ["userId"]}];
453
+ const isLiquidFlow = false;
454
+ const result = checkSupport(response, tagObject, eventContextTags, isLiquidFlow);
455
+ expect(result).toEqual( [ 'leaderboard' ]);
456
+ });
457
+
458
+ it("test for checking loyalty tags in that are coming in forwardedTags", () => {
459
+ const response = { data: [{ name: "leaderboard", children: [{name: "person.userId"}]}]};
460
+ const tagObject = {};
461
+ const isLiquidFlow = true;
462
+ // forwardedTags contains tag hierarchy with parent tags and their subtags
463
+ // needed for loyalty email liquid tag resolution
464
+ const forwardedTags = {
465
+ leaderboard: {
466
+ name: "Leaderboard",
467
+ subtags: {
468
+ "person.userId": {
469
+ name: "User ID",
470
+ },
471
+ },
472
+ },
473
+ };
474
+ const result = checkSupport(response, tagObject, [], isLiquidFlow, forwardedTags);
475
+ expect(result).toEqual( [ 'leaderboard', 'person.userId' ]);
476
476
  });
477
477
  });
478
478
 
@@ -1309,6 +1309,84 @@ describe('getForwardedMapValues', () => {
1309
1309
  });
1310
1310
  });
1311
1311
 
1312
+ describe('isInsideLiquidBlock', () => {
1313
+ it('returns true for index inside a single block', () => {
1314
+ const content = 'Hello {% assign foo = 1 %} World';
1315
+ // Index of 'a' in 'assign' inside the block
1316
+ const tagIndex = content.indexOf('assign');
1317
+ expect(isInsideLiquidBlock(content, tagIndex)).toBe(true);
1318
+ });
1319
+
1320
+ it('returns false for index outside any block', () => {
1321
+ const content = 'Hello {% assign foo = 1 %} World';
1322
+ // Index of 'H' in 'Hello'
1323
+ expect(isInsideLiquidBlock(content, 0)).toBe(false);
1324
+ // Index of 'W' in 'World'
1325
+ expect(isInsideLiquidBlock(content, content.indexOf('World'))).toBe(false);
1326
+ });
1327
+
1328
+ it('returns true for index at the start of a block', () => {
1329
+ const content = 'Hello {% assign foo = 1 %} World';
1330
+ // Index of '{' in '{%'
1331
+ const tagIndex = content.indexOf('{%');
1332
+ expect(isInsideLiquidBlock(content, tagIndex)).toBe(true);
1333
+ });
1334
+
1335
+ it('returns false for index at the end of a block (exclusive)', () => {
1336
+ const content = 'Hello {% assign foo = 1 %} World';
1337
+ // Index just after the closing '%}'
1338
+ const blockEnd = content.indexOf('%}') + 2;
1339
+ expect(isInsideLiquidBlock(content, blockEnd)).toBe(false);
1340
+ });
1341
+
1342
+ it('returns true for index inside the second of multiple blocks', () => {
1343
+ const content = 'A {% first %} B {% second %} C';
1344
+ const tagIndex = content.indexOf('second');
1345
+ expect(isInsideLiquidBlock(content, tagIndex)).toBe(true);
1346
+ });
1347
+
1348
+ it('returns false for index between blocks', () => {
1349
+ const content = 'A {% first %} B {% second %} C';
1350
+ // Index of 'B' (between blocks)
1351
+ const tagIndex = content.indexOf('B');
1352
+ expect(isInsideLiquidBlock(content, tagIndex)).toBe(false);
1353
+ });
1354
+
1355
+ it('returns false for empty string', () => {
1356
+ expect(isInsideLiquidBlock('', 0)).toBe(false);
1357
+ });
1358
+
1359
+ it('returns false if there are no blocks', () => {
1360
+ const content = 'Just some text with no blocks';
1361
+ expect(isInsideLiquidBlock(content, 5)).toBe(false);
1362
+ });
1363
+
1364
+ it('returns false for negative index', () => {
1365
+ const content = 'Hello {% assign foo = 1 %} World';
1366
+ expect(isInsideLiquidBlock(content, -1)).toBe(false);
1367
+ });
1368
+
1369
+ it('returns false for index beyond string length', () => {
1370
+ const content = 'Hello {% assign foo = 1 %} World';
1371
+ expect(isInsideLiquidBlock(content, 100)).toBe(false);
1372
+ });
1373
+
1374
+ it('works for nested-like blocks (not truly nested)', () => {
1375
+ const content = 'A {% outer {% inner %} outer %} B';
1376
+ // Index of 'inner' (should be inside the first block)
1377
+ const tagIndex = content.indexOf('inner');
1378
+ expect(isInsideLiquidBlock(content, tagIndex)).toBe(true);
1379
+ });
1380
+
1381
+ it('returns true for index at last char inside block', () => {
1382
+ const content = 'A {% foo %} B';
1383
+ // Index of last char inside block (just before %})
1384
+ const blockStart = content.indexOf('{%');
1385
+ const blockEnd = content.indexOf('%}');
1386
+ expect(isInsideLiquidBlock(content, blockEnd - 1)).toBe(true);
1387
+ });
1388
+ });
1389
+
1312
1390
  describe('containsBase64Images', () => {
1313
1391
  let mockCapNotification;
1314
1392
  let mockCallback;