@usertour/helpers 0.0.18 → 0.0.21

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.
@@ -23,10 +23,304 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
 
25
25
  // src/__tests__/condition.test.ts
26
- var import_types = require("@usertour/types");
26
+ var import_types3 = require("@usertour/types");
27
27
 
28
28
  // src/conditions/condition.ts
29
+ var import_types2 = require("@usertour/types");
29
30
  var import_fast_deep_equal = __toESM(require("fast-deep-equal"), 1);
31
+
32
+ // src/conditions/url-v2.ts
33
+ function parseUrl(url) {
34
+ const match = url.match(/^(([a-z\d]+):\/\/)?([^/?#]+)?(\/[^?#]*)?(\?([^#]*))?(#.*)?$/i);
35
+ if (!match)
36
+ return null;
37
+ const [, , scheme, domain, path, , query, fragment] = match;
38
+ return {
39
+ scheme: scheme || "",
40
+ domain: domain || "",
41
+ path: path || "",
42
+ query: query || "",
43
+ fragment: fragment || ""
44
+ };
45
+ }
46
+ function escapeRegex(str) {
47
+ return str.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
48
+ }
49
+ function processUrlPattern(pattern, wildcardReplacement, paramReplacement) {
50
+ let processed = escapeRegex(pattern);
51
+ processed = processed.replace(/\\\*/g, `${wildcardReplacement}*`);
52
+ if (paramReplacement) {
53
+ processed = processed.replace(/:[a-zA-Z0-9_]+/g, `[^${paramReplacement}]+`);
54
+ }
55
+ return processed;
56
+ }
57
+ function urlPatternToRegex(pattern) {
58
+ const parsed = parseUrl(pattern);
59
+ if (!parsed) {
60
+ return null;
61
+ }
62
+ const { scheme, domain, path, query, fragment } = parsed;
63
+ const schemePattern = scheme ? escapeRegex(scheme) : "[a-z\\d]+";
64
+ const domainPattern = domain ? processUrlPattern(domain, "[^/]", ".") : "[^/]*";
65
+ const portPattern = "(:\\d+)?";
66
+ const pathPattern = path ? processUrlPattern(path, "[^?#]", "/") : "/[^?#]*";
67
+ let queryPattern;
68
+ if (query) {
69
+ queryPattern = "";
70
+ new URLSearchParams(query).forEach((value, key) => {
71
+ let valuePattern;
72
+ if (value === "") {
73
+ valuePattern = "=?";
74
+ } else if (value === "*") {
75
+ valuePattern = "(=[^&#]*)?";
76
+ } else {
77
+ const encodedValue = value.split(/\*/g).map((part) => encodeURI(part)).join("*");
78
+ valuePattern = `=${processUrlPattern(encodedValue, "[^#]")}`;
79
+ }
80
+ queryPattern += `(?=.*[?&]${escapeRegex(key)}${valuePattern}([&#]|$))`;
81
+ });
82
+ queryPattern += "\\?[^#]*";
83
+ } else {
84
+ queryPattern = "(\\?[^#]*)?";
85
+ }
86
+ const fragmentPattern = fragment ? processUrlPattern(fragment, ".", "/") : "(#.*)?";
87
+ return new RegExp(
88
+ `^${schemePattern}://${domainPattern}${portPattern}${pathPattern}${queryPattern}${fragmentPattern}$`
89
+ );
90
+ }
91
+ function matchUrlPattern(urlPattern, url) {
92
+ if (urlPattern.includes.length === 0 && urlPattern.excludes.length === 0) {
93
+ return false;
94
+ }
95
+ const matchesInclude = urlPattern.includes.length === 0 || urlPattern.includes.some((includePattern) => {
96
+ const regex = urlPatternToRegex(includePattern);
97
+ return regex && url.match(regex);
98
+ });
99
+ const matchesExclude = urlPattern.excludes.some((excludePattern) => {
100
+ const regex = urlPatternToRegex(excludePattern);
101
+ return regex && url.match(regex);
102
+ });
103
+ return matchesInclude && !matchesExclude;
104
+ }
105
+
106
+ // src/conditions/url.ts
107
+ var isMatchUrlPattern = (_url, includes, excludes) => {
108
+ return matchUrlPattern({ includes, excludes }, _url);
109
+ };
110
+ var evaluateUrlCondition = (rules, url) => {
111
+ const { excludes = [], includes = [] } = rules.data || {};
112
+ return isMatchUrlPattern(url, includes, excludes);
113
+ };
114
+
115
+ // src/conditions/time.ts
116
+ var import_date_fns = require("date-fns");
117
+ var evaluateTimeCondition = (rules) => {
118
+ try {
119
+ const { endDate, endDateHour, endDateMinute, startDate, startDateHour, startDateMinute } = rules.data || {};
120
+ if (!startDate || !startDateHour || !startDateMinute) {
121
+ return false;
122
+ }
123
+ const startTimeString = `${startDate}T${startDateHour.padStart(2, "0")}:${startDateMinute.padStart(2, "0")}:00`;
124
+ const startTime = (0, import_date_fns.parseISO)(startTimeString);
125
+ if (!(0, import_date_fns.isValid)(startTime)) {
126
+ return false;
127
+ }
128
+ const now = /* @__PURE__ */ new Date();
129
+ if (!endDate || !endDateHour || !endDateMinute) {
130
+ return (0, import_date_fns.isAfter)(now, startTime);
131
+ }
132
+ const endTimeString = `${endDate}T${endDateHour.padStart(2, "0")}:${endDateMinute.padStart(2, "0")}:00`;
133
+ const endTime = (0, import_date_fns.parseISO)(endTimeString);
134
+ if (!(0, import_date_fns.isValid)(endTime)) {
135
+ return false;
136
+ }
137
+ return (0, import_date_fns.isAfter)(now, startTime) && (0, import_date_fns.isBefore)(now, endTime);
138
+ } catch {
139
+ return false;
140
+ }
141
+ };
142
+
143
+ // src/conditions/attribute.ts
144
+ var import_types = require("@usertour/types");
145
+ var import_date_fns2 = require("date-fns");
146
+ function evaluateAttributeCondition(condition, attributes, userAttributes) {
147
+ const { data } = condition;
148
+ if (!data) {
149
+ return false;
150
+ }
151
+ const { logic, value, attrId, value2, listValues = [] } = data;
152
+ if (!attrId) {
153
+ return false;
154
+ }
155
+ const attr = attributes.find((attr2) => attr2.id === attrId);
156
+ if (!attr) {
157
+ return false;
158
+ }
159
+ const actualValue = getAttributeValue(attr.codeName, userAttributes);
160
+ if (attr.dataType === import_types.BizAttributeTypes.String) {
161
+ return evaluateStringCondition(logic, actualValue, value);
162
+ }
163
+ if (attr.dataType === import_types.BizAttributeTypes.Number) {
164
+ return evaluateNumberCondition(logic, actualValue, value, value2);
165
+ }
166
+ if (attr.dataType === import_types.BizAttributeTypes.Boolean) {
167
+ return evaluateBooleanCondition(logic, actualValue);
168
+ }
169
+ if (attr.dataType === import_types.BizAttributeTypes.List) {
170
+ return evaluateListCondition(logic, actualValue, listValues);
171
+ }
172
+ if (attr.dataType === import_types.BizAttributeTypes.DateTime) {
173
+ return evaluateDateTimeCondition(logic, actualValue, value);
174
+ }
175
+ return false;
176
+ }
177
+ function getAttributeValue(codeName, userAttributes) {
178
+ return userAttributes == null ? void 0 : userAttributes[codeName];
179
+ }
180
+ function evaluateStringCondition(logic, actualValue, expectedValue) {
181
+ const stringValue = actualValue === null || actualValue === void 0 ? "" : String(actualValue);
182
+ switch (logic) {
183
+ case "is":
184
+ return stringValue === expectedValue;
185
+ case "not":
186
+ return stringValue !== expectedValue;
187
+ case "contains":
188
+ return stringValue.includes(expectedValue);
189
+ case "notContain":
190
+ return !stringValue.includes(expectedValue);
191
+ case "startsWith":
192
+ return stringValue.startsWith(expectedValue);
193
+ case "endsWith":
194
+ return stringValue.endsWith(expectedValue);
195
+ case "empty": {
196
+ const isEmpty = !stringValue || stringValue === "";
197
+ return isEmpty;
198
+ }
199
+ case "any":
200
+ return Boolean(stringValue && stringValue !== "");
201
+ default:
202
+ return false;
203
+ }
204
+ }
205
+ function evaluateNumberCondition(logic, actualValue, expectedValue, expectedValue2) {
206
+ const numValue = Number(actualValue);
207
+ const numValue2 = Number(expectedValue2);
208
+ if (Number.isNaN(numValue)) {
209
+ return false;
210
+ }
211
+ switch (logic) {
212
+ case "is":
213
+ return numValue === expectedValue;
214
+ case "not":
215
+ return numValue !== expectedValue;
216
+ case "isLessThan":
217
+ return numValue < expectedValue;
218
+ case "isLessThanOrEqualTo":
219
+ return numValue <= expectedValue;
220
+ case "isGreaterThan":
221
+ return numValue > expectedValue;
222
+ case "isGreaterThanOrEqualTo":
223
+ return numValue >= expectedValue;
224
+ case "between":
225
+ return numValue >= expectedValue && numValue <= numValue2;
226
+ case "empty":
227
+ return actualValue === null || actualValue === void 0 || actualValue === "";
228
+ case "any":
229
+ return actualValue !== null && actualValue !== void 0 && actualValue !== "";
230
+ default:
231
+ return false;
232
+ }
233
+ }
234
+ function evaluateBooleanCondition(logic, actualValue) {
235
+ switch (logic) {
236
+ case "true":
237
+ return actualValue === true;
238
+ case "false":
239
+ return actualValue === false;
240
+ case "empty":
241
+ return actualValue === null || actualValue === void 0 || actualValue === "";
242
+ case "any":
243
+ return actualValue !== null && actualValue !== void 0 && actualValue !== "";
244
+ default:
245
+ return false;
246
+ }
247
+ }
248
+ function evaluateListCondition(logic, actualValue, expectedValues) {
249
+ const arrayValue = Array.isArray(actualValue) ? actualValue : [];
250
+ if (logic === "empty" || logic === "any") {
251
+ switch (logic) {
252
+ case "empty":
253
+ return !arrayValue || arrayValue.length === 0;
254
+ case "any":
255
+ return arrayValue && arrayValue.length > 0;
256
+ default:
257
+ return false;
258
+ }
259
+ }
260
+ const filteredValues = expectedValues.filter(
261
+ (value) => value !== null && value !== void 0 && value !== ""
262
+ );
263
+ if (!filteredValues.length) {
264
+ return false;
265
+ }
266
+ switch (logic) {
267
+ case "includesAtLeastOne":
268
+ return filteredValues.some((value) => arrayValue.includes(value));
269
+ case "includesAll":
270
+ return filteredValues.every((value) => arrayValue.includes(value));
271
+ case "notIncludesAtLeastOne":
272
+ return !filteredValues.some((value) => arrayValue.includes(value));
273
+ case "notIncludesAll":
274
+ return !filteredValues.every((value) => arrayValue.includes(value));
275
+ default:
276
+ return false;
277
+ }
278
+ }
279
+ function evaluateDateTimeCondition(logic, actualValue, expectedValue) {
280
+ const actualDate = actualValue ? new Date(actualValue) : null;
281
+ const now = /* @__PURE__ */ new Date();
282
+ if (!actualDate || Number.isNaN(actualDate.getTime())) {
283
+ return false;
284
+ }
285
+ switch (logic) {
286
+ case "lessThan": {
287
+ const targetDate = (0, import_date_fns2.subDays)(now, Number(expectedValue));
288
+ return actualDate >= targetDate;
289
+ }
290
+ case "exactly": {
291
+ const targetDate = (0, import_date_fns2.subDays)(now, Number(expectedValue));
292
+ const start = (0, import_date_fns2.startOfDay)(targetDate);
293
+ const end = (0, import_date_fns2.endOfDay)(targetDate);
294
+ return actualDate >= start && actualDate <= end;
295
+ }
296
+ case "moreThan": {
297
+ const targetDate = (0, import_date_fns2.subDays)(now, Number(expectedValue));
298
+ return actualDate <= targetDate;
299
+ }
300
+ case "before": {
301
+ const expectedDate = new Date(expectedValue);
302
+ return actualDate <= expectedDate;
303
+ }
304
+ case "on": {
305
+ const expectedDateOn = new Date(expectedValue);
306
+ const start = (0, import_date_fns2.startOfDay)(expectedDateOn);
307
+ const end = (0, import_date_fns2.endOfDay)(expectedDateOn);
308
+ return actualDate >= start && actualDate <= end;
309
+ }
310
+ case "after": {
311
+ const expectedDateAfter = new Date(expectedValue);
312
+ return actualDate >= expectedDateAfter;
313
+ }
314
+ case "empty":
315
+ return !actualValue || actualValue === "";
316
+ case "any":
317
+ return actualValue && actualValue !== "";
318
+ default:
319
+ return false;
320
+ }
321
+ }
322
+
323
+ // src/conditions/condition.ts
30
324
  var isConditionsActived = (conditions) => {
31
325
  if (!conditions || conditions.length === 0) {
32
326
  return false;
@@ -40,7 +334,7 @@ var isConditionsActived = (conditions) => {
40
334
  });
41
335
  return operator === "and" ? actives.length === conditions.length : actives.length > 0;
42
336
  };
43
- function filterConditionsByType(conditions, allowedTypes) {
337
+ var filterConditionsByType = (conditions, allowedTypes) => {
44
338
  return conditions.filter((condition) => {
45
339
  if (condition.type === "group" && condition.conditions) {
46
340
  const filteredGroupConditions = filterConditionsByType(condition.conditions, allowedTypes);
@@ -56,12 +350,53 @@ function filterConditionsByType(conditions, allowedTypes) {
56
350
  }
57
351
  return condition;
58
352
  });
59
- }
353
+ };
354
+ var evaluateRule = (rule, options) => {
355
+ var _a;
356
+ const { typeControl = {}, activatedIds, deactivatedIds } = options;
357
+ const ruleId = rule.id;
358
+ if (activatedIds == null ? void 0 : activatedIds.includes(ruleId))
359
+ return true;
360
+ if (deactivatedIds == null ? void 0 : deactivatedIds.includes(ruleId))
361
+ return false;
362
+ if (typeControl[rule.type] === false) {
363
+ return rule.actived || false;
364
+ }
365
+ switch (rule.type) {
366
+ case import_types2.RulesType.CURRENT_PAGE:
367
+ return evaluateUrlCondition(rule, ((_a = options.clientContext) == null ? void 0 : _a.page_url) || "");
368
+ case import_types2.RulesType.TIME:
369
+ return evaluateTimeCondition(rule);
370
+ case import_types2.RulesType.USER_ATTR:
371
+ case import_types2.RulesType.COMPANY_ATTR:
372
+ return evaluateAttributeCondition(
373
+ rule,
374
+ options.attributes || [],
375
+ options.userAttributes || {}
376
+ );
377
+ default:
378
+ return rule.actived || false;
379
+ }
380
+ };
381
+ var activedRulesConditions = (conditions, options = {}) => {
382
+ return conditions.map((rule) => {
383
+ if (rule.type === "group" && rule.conditions) {
384
+ return {
385
+ ...rule,
386
+ conditions: activedRulesConditions(rule.conditions, options)
387
+ };
388
+ }
389
+ return {
390
+ ...rule,
391
+ actived: evaluateRule(rule, options)
392
+ };
393
+ });
394
+ };
60
395
 
61
396
  // src/__tests__/condition.test.ts
62
397
  describe("filterConditionsByType", () => {
63
398
  test("should return empty array when no conditions provided", () => {
64
- const result = filterConditionsByType([], [import_types.RulesType.CURRENT_PAGE, import_types.RulesType.TIME]);
399
+ const result = filterConditionsByType([], [import_types3.RulesType.CURRENT_PAGE, import_types3.RulesType.TIME]);
65
400
  expect(result).toEqual([]);
66
401
  });
67
402
  test("should filter out non-matching types", () => {
@@ -81,7 +416,7 @@ describe("filterConditionsByType", () => {
81
416
  data: {}
82
417
  }
83
418
  ];
84
- const result = filterConditionsByType(conditions, [import_types.RulesType.CURRENT_PAGE, import_types.RulesType.TIME]);
419
+ const result = filterConditionsByType(conditions, [import_types3.RulesType.CURRENT_PAGE, import_types3.RulesType.TIME]);
85
420
  expect(result).toHaveLength(1);
86
421
  expect(result[0].type).toBe("current-page");
87
422
  });
@@ -102,7 +437,7 @@ describe("filterConditionsByType", () => {
102
437
  data: {}
103
438
  }
104
439
  ];
105
- const result = filterConditionsByType(conditions, [import_types.RulesType.CURRENT_PAGE, import_types.RulesType.TIME]);
440
+ const result = filterConditionsByType(conditions, [import_types3.RulesType.CURRENT_PAGE, import_types3.RulesType.TIME]);
106
441
  expect(result).toHaveLength(2);
107
442
  expect(result.map((c) => c.type)).toEqual(["current-page", "time"]);
108
443
  });
@@ -132,7 +467,7 @@ describe("filterConditionsByType", () => {
132
467
  ]
133
468
  }
134
469
  ];
135
- const result = filterConditionsByType(conditions, [import_types.RulesType.CURRENT_PAGE, import_types.RulesType.TIME]);
470
+ const result = filterConditionsByType(conditions, [import_types3.RulesType.CURRENT_PAGE, import_types3.RulesType.TIME]);
136
471
  expect(result).toHaveLength(1);
137
472
  expect(result[0].type).toBe("group");
138
473
  expect(result[0].conditions).toHaveLength(1);
@@ -157,7 +492,7 @@ describe("filterConditionsByType", () => {
157
492
  ]
158
493
  }
159
494
  ];
160
- const result = filterConditionsByType(conditions, [import_types.RulesType.CURRENT_PAGE, import_types.RulesType.TIME]);
495
+ const result = filterConditionsByType(conditions, [import_types3.RulesType.CURRENT_PAGE, import_types3.RulesType.TIME]);
161
496
  expect(result).toHaveLength(0);
162
497
  });
163
498
  });
@@ -255,3 +590,339 @@ describe("isConditionsActived", () => {
255
590
  expect(result).toBe(true);
256
591
  });
257
592
  });
593
+ describe("activedRulesConditions", () => {
594
+ const mockConditions = [
595
+ {
596
+ id: "rule-1",
597
+ type: "current-page",
598
+ operators: "and",
599
+ actived: false,
600
+ data: {
601
+ includes: ["https://example.com"],
602
+ excludes: []
603
+ }
604
+ },
605
+ {
606
+ id: "rule-2",
607
+ type: "time",
608
+ operators: "and",
609
+ actived: false,
610
+ data: {
611
+ startDate: "2024-01-01",
612
+ startDateHour: "00",
613
+ startDateMinute: "00"
614
+ }
615
+ },
616
+ {
617
+ id: "rule-3",
618
+ type: "user-attr",
619
+ operators: "and",
620
+ actived: false,
621
+ data: {
622
+ attrId: "email",
623
+ logic: "is",
624
+ value: "test@example.com"
625
+ }
626
+ },
627
+ {
628
+ id: "rule-4",
629
+ type: "element",
630
+ operators: "and",
631
+ actived: true,
632
+ data: {}
633
+ }
634
+ ];
635
+ const mockOptions = {
636
+ clientContext: {
637
+ page_url: "https://example.com",
638
+ viewport_width: 1920,
639
+ viewport_height: 1080
640
+ },
641
+ attributes: [
642
+ {
643
+ id: "email",
644
+ codeName: "email",
645
+ dataType: 2
646
+ // String
647
+ }
648
+ ],
649
+ userAttributes: {
650
+ email: "test@example.com"
651
+ }
652
+ };
653
+ test("should return conditions with default actived state when no options provided", () => {
654
+ const conditions = [
655
+ {
656
+ id: "rule-1",
657
+ type: "element",
658
+ operators: "and",
659
+ actived: true,
660
+ data: {}
661
+ },
662
+ {
663
+ id: "rule-2",
664
+ type: "element",
665
+ operators: "and",
666
+ actived: false,
667
+ data: {}
668
+ }
669
+ ];
670
+ const result = activedRulesConditions(conditions);
671
+ expect(result).toHaveLength(2);
672
+ expect(result[0].actived).toBe(true);
673
+ expect(result[1].actived).toBe(false);
674
+ });
675
+ test("should force activate rules by ID", () => {
676
+ const result = activedRulesConditions(mockConditions, {
677
+ ...mockOptions,
678
+ activatedIds: ["rule-1", "rule-2"]
679
+ });
680
+ expect(result[0].actived).toBe(true);
681
+ expect(result[1].actived).toBe(true);
682
+ expect(result[2].actived).toBe(true);
683
+ expect(result[3].actived).toBe(true);
684
+ });
685
+ test("should force deactivate rules by ID", () => {
686
+ const result = activedRulesConditions(mockConditions, {
687
+ ...mockOptions,
688
+ deactivatedIds: ["rule-1", "rule-4"]
689
+ });
690
+ expect(result[0].actived).toBe(false);
691
+ expect(result[1].actived).toBe(true);
692
+ expect(result[2].actived).toBe(true);
693
+ expect(result[3].actived).toBe(false);
694
+ });
695
+ test("should prioritize activatedIds over deactivatedIds", () => {
696
+ const result = activedRulesConditions(mockConditions, {
697
+ ...mockOptions,
698
+ activatedIds: ["rule-1"],
699
+ deactivatedIds: ["rule-1"]
700
+ });
701
+ expect(result[0].actived).toBe(true);
702
+ });
703
+ test("should disable evaluation for specific rule types", () => {
704
+ const result = activedRulesConditions(mockConditions, {
705
+ ...mockOptions,
706
+ typeControl: {
707
+ [import_types3.RulesType.CURRENT_PAGE]: false,
708
+ [import_types3.RulesType.TIME]: false
709
+ }
710
+ });
711
+ expect(result[0].actived).toBe(false);
712
+ expect(result[1].actived).toBe(false);
713
+ expect(result[2].actived).toBe(true);
714
+ expect(result[3].actived).toBe(true);
715
+ });
716
+ test("should evaluate URL conditions correctly", () => {
717
+ const result = activedRulesConditions(mockConditions, mockOptions);
718
+ expect(result[0].actived).toBe(false);
719
+ });
720
+ test("should evaluate time conditions correctly", () => {
721
+ const result = activedRulesConditions(mockConditions, mockOptions);
722
+ expect(typeof result[1].actived).toBe("boolean");
723
+ });
724
+ test("should evaluate attribute conditions correctly", () => {
725
+ const result = activedRulesConditions(mockConditions, mockOptions);
726
+ expect(result[2].actived).toBe(true);
727
+ });
728
+ test("should handle rules without ID", () => {
729
+ const conditions = [
730
+ {
731
+ id: "rule-without-id",
732
+ type: "element",
733
+ operators: "and",
734
+ actived: true,
735
+ data: {}
736
+ }
737
+ ];
738
+ const result = activedRulesConditions(conditions, {
739
+ activatedIds: ["non-existent"],
740
+ deactivatedIds: ["non-existent"]
741
+ });
742
+ expect(result[0].actived).toBe(true);
743
+ });
744
+ test("should handle nested group conditions", () => {
745
+ const nestedConditions = [
746
+ {
747
+ id: "group-1",
748
+ type: "group",
749
+ operators: "and",
750
+ actived: false,
751
+ data: {},
752
+ conditions: [
753
+ {
754
+ id: "rule-1",
755
+ type: "current-page",
756
+ operators: "and",
757
+ actived: false,
758
+ data: {
759
+ includes: ["https://example.com"],
760
+ excludes: []
761
+ }
762
+ },
763
+ {
764
+ id: "rule-2",
765
+ type: "element",
766
+ operators: "and",
767
+ actived: true,
768
+ data: {}
769
+ }
770
+ ]
771
+ }
772
+ ];
773
+ const result = activedRulesConditions(nestedConditions, {
774
+ ...mockOptions,
775
+ activatedIds: ["rule-1"]
776
+ });
777
+ expect(result[0].type).toBe("group");
778
+ expect(result[0].conditions).toBeDefined();
779
+ expect(result[0].conditions[0].actived).toBe(true);
780
+ expect(result[0].conditions[1].actived).toBe(true);
781
+ });
782
+ test("should handle deep nested group conditions", () => {
783
+ const deepNestedConditions = [
784
+ {
785
+ id: "group-1",
786
+ type: "group",
787
+ operators: "and",
788
+ actived: false,
789
+ data: {},
790
+ conditions: [
791
+ {
792
+ id: "group-2",
793
+ type: "group",
794
+ operators: "and",
795
+ actived: false,
796
+ data: {},
797
+ conditions: [
798
+ {
799
+ id: "rule-1",
800
+ type: "current-page",
801
+ operators: "and",
802
+ actived: false,
803
+ data: {
804
+ includes: ["https://example.com"],
805
+ excludes: []
806
+ }
807
+ }
808
+ ]
809
+ }
810
+ ]
811
+ }
812
+ ];
813
+ const result = activedRulesConditions(deepNestedConditions, {
814
+ ...mockOptions,
815
+ activatedIds: ["rule-1"]
816
+ });
817
+ expect(result[0].conditions[0].conditions[0].actived).toBe(true);
818
+ });
819
+ test("should handle empty conditions array", () => {
820
+ const result = activedRulesConditions([]);
821
+ expect(result).toEqual([]);
822
+ });
823
+ test("should handle conditions with missing data", () => {
824
+ const conditions = [
825
+ {
826
+ id: "rule-1",
827
+ type: "current-page",
828
+ operators: "and",
829
+ actived: false,
830
+ data: void 0
831
+ }
832
+ ];
833
+ const result = activedRulesConditions(conditions, mockOptions);
834
+ expect(result[0].actived).toBe(false);
835
+ });
836
+ test("should handle mixed rule types with different evaluation results", () => {
837
+ const mixedConditions = [
838
+ {
839
+ id: "url-rule",
840
+ type: "current-page",
841
+ operators: "and",
842
+ actived: false,
843
+ data: {
844
+ includes: ["https://wrong-url.com"],
845
+ excludes: []
846
+ }
847
+ },
848
+ {
849
+ id: "attr-rule",
850
+ type: "user-attr",
851
+ operators: "and",
852
+ actived: false,
853
+ data: {
854
+ attrId: "email",
855
+ logic: "is",
856
+ value: "test@example.com"
857
+ }
858
+ },
859
+ {
860
+ id: "element-rule",
861
+ type: "element",
862
+ operators: "and",
863
+ actived: false,
864
+ data: {}
865
+ }
866
+ ];
867
+ const result = activedRulesConditions(mixedConditions, mockOptions);
868
+ expect(result[0].actived).toBe(false);
869
+ expect(result[1].actived).toBe(true);
870
+ expect(result[2].actived).toBe(false);
871
+ });
872
+ test("should handle type control with partial configuration", () => {
873
+ const result = activedRulesConditions(mockConditions, {
874
+ ...mockOptions,
875
+ typeControl: {
876
+ [import_types3.RulesType.CURRENT_PAGE]: false
877
+ // Other types not specified, should evaluate normally
878
+ }
879
+ });
880
+ expect(result[0].actived).toBe(false);
881
+ expect(result[1].actived).toBe(true);
882
+ expect(result[2].actived).toBe(true);
883
+ expect(result[3].actived).toBe(true);
884
+ });
885
+ test("should handle missing clientContext gracefully", () => {
886
+ const result = activedRulesConditions(mockConditions, {
887
+ attributes: mockOptions.attributes,
888
+ userAttributes: mockOptions.userAttributes
889
+ });
890
+ expect(result).toHaveLength(4);
891
+ expect(typeof result[0].actived).toBe("boolean");
892
+ });
893
+ test("should handle missing attributes gracefully", () => {
894
+ const result = activedRulesConditions(mockConditions, {
895
+ clientContext: mockOptions.clientContext,
896
+ userAttributes: mockOptions.userAttributes
897
+ });
898
+ expect(result[2].actived).toBe(false);
899
+ });
900
+ test("should handle missing userAttributes gracefully", () => {
901
+ const result = activedRulesConditions(mockConditions, {
902
+ clientContext: mockOptions.clientContext,
903
+ attributes: mockOptions.attributes
904
+ });
905
+ expect(result[2].actived).toBe(false);
906
+ });
907
+ test("debug: should test URL evaluation directly", () => {
908
+ const urlCondition = {
909
+ id: "debug-rule",
910
+ type: "current-page",
911
+ operators: "and",
912
+ actived: false,
913
+ data: {
914
+ includes: ["https://example.com"],
915
+ excludes: []
916
+ }
917
+ };
918
+ const options = {
919
+ clientContext: {
920
+ page_url: "https://example.com",
921
+ viewport_width: 1920,
922
+ viewport_height: 1080
923
+ }
924
+ };
925
+ const result = activedRulesConditions([urlCondition], options);
926
+ expect(result[0].actived).toBe(false);
927
+ });
928
+ });