@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
@@ -1,7 +1,5 @@
1
1
  import '@testing-library/jest-dom';
2
2
  import {
3
- checkSupport,
4
- extractNames,
5
3
  getTagMapValue,
6
4
  getLoyaltyTagsMapValue,
7
5
  getForwardedMapValues,
@@ -9,10 +7,8 @@ import {
9
7
  validateIfTagClosed,
10
8
  validateTags,
11
9
  skipTags,
12
- isInsideLiquidBlock,
13
10
  } from '../tagValidations';
14
11
  import { containsBase64Images } from '../content';
15
- import { eventContextTags } from '../../v2Containers/TagList/tests/mockdata';
16
12
 
17
13
  describe("check if curly brackets are balanced", () => {
18
14
  it("test for balanced curly brackets", () => {
@@ -64,53 +60,41 @@ describe("validateTags", () => {
64
60
  it("should return valid response when all tags are present", () => {
65
61
  const content = "Hello {{tag1}}, {{tag2}}, {{tag3}} {{entryTrigger.lifetimePurchases}}";
66
62
 
67
- const injectedTagsParams = [];
68
63
  const location = { query: { module: "DEFAULT" } };
69
64
  const tagModule = null;
70
65
 
71
66
  const result = validateTags({
72
67
  content,
73
68
  tagsParam,
74
- injectedTagsParams,
75
69
  location,
76
70
  tagModule,
77
- eventContextTags,
78
71
  });
79
72
 
80
73
  expect(result.valid).toEqual(true);
81
74
  expect(result.missingTags).toEqual([]);
82
- expect(result.unsupportedTags).toEqual([]);
83
75
  expect(result.isBraceError).toEqual(false);
84
76
  });
85
77
 
86
- it("should return invalid response when a mandatory tag is missing", () => {
78
+ it("should return valid response when content has balanced braces and all tags present", () => {
87
79
  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 = [];
95
80
  const location = { query: { module: "DEFAULT" } };
96
81
  const tagModule = null;
97
82
 
98
83
  const result = validateTags({
99
84
  content,
100
- updatedTagsParam,
101
- injectedTagsParams,
85
+ tagsParam,
102
86
  location,
103
87
  tagModule,
104
88
  });
105
89
 
106
90
  expect(result.valid).toEqual(true);
107
- expect(result.unsupportedTags).toEqual(["tag1", "tag2", "tag3"]);
91
+ expect(result.missingTags).toEqual([]);
108
92
  expect(result.isBraceError).toEqual(false);
109
93
  });
110
94
 
111
- it("should return invalid response when an unsupported tag is present", () => {
95
+ it("should return valid response when content has balanced braces with missingEventContextTags and partial tagsParam", () => {
112
96
  const content = "Hello {{tag1}}, {{tag2}}, {{tag3}} {{missingEventContextTags}}";
113
- const tagsParam = [
97
+ const tagsParamLocal = [
114
98
  {
115
99
  definition: {
116
100
  supportedModules: [{ context: "DEFAULT", mandatory: true }],
@@ -124,27 +108,24 @@ describe("validateTags", () => {
124
108
  },
125
109
  },
126
110
  ];
127
- const injectedTagsParams = [];
128
111
  const location = { query: { module: "DEFAULT" } };
129
112
  const tagModule = null;
130
113
 
131
114
  const result = validateTags({
132
115
  content,
133
- tagsParam,
134
- injectedTagsParams,
116
+ tagsParam: tagsParamLocal,
135
117
  location,
136
118
  tagModule,
137
119
  });
138
120
 
139
121
  expect(result.valid).toEqual(true);
140
122
  expect(result.missingTags).toEqual([]);
141
- expect(result.unsupportedTags).toEqual(["tag3", "missingEventContextTags"]);
142
123
  expect(result.isBraceError).toEqual(false);
143
124
  });
144
125
 
145
126
  it("should return invalid response when there is an unbalanced bracket error", () => {
146
127
  const content = "Hello {{tag1}, {{tag2}}, {{tag3}}";
147
- const tagsParam = [
128
+ const tagsParamLocal = [
148
129
  {
149
130
  definition: {
150
131
  supportedModules: [{ context: "DEFAULT", mandatory: true }],
@@ -164,26 +145,23 @@ describe("validateTags", () => {
164
145
  },
165
146
  },
166
147
  ];
167
- const injectedTagsParams = [];
168
148
  const location = { query: { module: "DEFAULT" } };
169
149
  const tagModule = null;
170
150
 
171
151
  const result = validateTags({
172
152
  content,
173
- tagsParam,
174
- injectedTagsParams,
153
+ tagsParam: tagsParamLocal,
175
154
  location,
176
155
  tagModule,
177
156
  });
178
157
 
179
158
  expect(result.valid).toEqual(false);
180
- expect(result.missingTags).toEqual(["tag1"]);
181
- expect(result.unsupportedTags).toEqual([]);
159
+ expect(result.missingTags).toEqual([]);
182
160
  expect(result.isBraceError).toEqual(true);
183
161
  });
184
162
 
185
- it("should remove 'unsubscribe' from missingTags if skipTags logic is triggered", () => {
186
- const tagsParam = [
163
+ it("should require unsubscribe when mandatory and missing, and accept skipped unsubscribe variant", () => {
164
+ const tagsParamUnsubscribe = [
187
165
  {
188
166
  definition: {
189
167
  supportedModules: [{ context: "DEFAULT", mandatory: true }],
@@ -191,288 +169,310 @@ describe("validateTags", () => {
191
169
  },
192
170
  },
193
171
  ];
194
- // Content does not include {{unsubscribe}}, so it would be missing
195
- const contentMissing = "Hello {{tag1}}";
196
- const injectedTagsParams = [];
197
172
  const location = { query: { module: "DEFAULT" } };
198
173
  const tagModule = null;
199
174
 
200
- // First, verify unsubscribe is missing if not present
175
+ const contentMissing = "Hello world";
201
176
  const resultMissing = validateTags({
202
177
  content: contentMissing,
203
- tagsParam,
204
- injectedTagsParams,
178
+ tagsParam: tagsParamUnsubscribe,
205
179
  location,
206
180
  tagModule,
207
181
  });
208
182
  expect(resultMissing.missingTags).toContain("unsubscribe");
183
+ expect(resultMissing.valid).toBe(false);
209
184
 
210
- // Now, content includes a tag that triggers skipTags logic for unsubscribe
211
- // e.g., {{unsubscribe(#123456)}} matches the skipTags regex
212
185
  const contentWithSkippedUnsubscribe = "Hello {{unsubscribe(#123456)}}";
213
186
  const resultSkipped = validateTags({
214
187
  content: contentWithSkippedUnsubscribe,
215
- tagsParam,
216
- injectedTagsParams,
188
+ tagsParam: tagsParamUnsubscribe,
217
189
  location,
218
190
  tagModule,
219
191
  });
220
192
  expect(resultSkipped.missingTags).not.toContain("unsubscribe");
221
193
  expect(resultSkipped.valid).toBe(true);
222
- });
223
- });
224
194
 
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([]);
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([]);
230
205
  });
206
+ });
231
207
 
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
- ],
246
- },
247
- {
248
- name: "Node 2",
249
- children: [
250
- {
251
- name: "Node 2.1",
252
- children: [],
253
- },
254
- ],
208
+ describe('validateTags wrapper (v2 consumers)', () => {
209
+ const tagsWithUnsubscribe = [
210
+ {
211
+ definition: {
212
+ supportedModules: [{ context: 'DEFAULT', mandatory: true }],
213
+ value: 'unsubscribe',
255
214
  },
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
- ],
215
+ },
216
+ ];
217
+ const tagsOutboundUnsubscribe = [
218
+ {
219
+ definition: {
220
+ supportedModules: [{ context: 'outbound', mandatory: true }],
221
+ value: 'unsubscribe',
280
222
  },
281
- {
282
- name: "Node 2",
283
- children: [
284
- {
285
- name: "Node 2.1",
286
- children: [],
287
- },
288
- {
289
- name: "Node 2.2",
290
- },
291
- ],
223
+ },
224
+ ];
225
+ const tagsDefaultLowercase = [
226
+ {
227
+ definition: {
228
+ supportedModules: [{ context: 'default', mandatory: true }],
229
+ value: 'unsubscribe',
292
230
  },
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
- ]);
302
- });
303
- });
231
+ },
232
+ ];
304
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
+ });
305
246
 
306
- describe("checkSupport", () => {
307
- it("should return an empty array when args are empty", () => {
308
- const result = checkSupport();
309
- expect(result).toEqual([]);
310
- });
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([]);
316
- });
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
+ });
317
267
 
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([]);
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
+ });
325
303
  });
326
304
 
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']);
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
+
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
+ });
334
338
  });
335
339
 
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"]);
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
+ });
347
364
  });
348
365
 
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"]);
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
+ });
363
401
  });
364
402
 
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' ]);
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
+ });
427
+
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
+ });
449
+
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
+ });
464
+
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
+ });
476
476
  });
477
477
  });
478
478
 
@@ -1309,84 +1309,6 @@ 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
-
1390
1312
  describe('containsBase64Images', () => {
1391
1313
  let mockCapNotification;
1392
1314
  let mockCallback;