@featurevisor/core 2.5.0 → 2.6.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 (90) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/coverage/clover.xml +444 -78
  3. package/coverage/coverage-final.json +9 -3
  4. package/coverage/lcov-report/index.html +42 -42
  5. package/coverage/lcov-report/lib/builder/allocator.js.html +1 -1
  6. package/coverage/lcov-report/lib/builder/buildScopedConditions.js.html +373 -0
  7. package/coverage/lcov-report/lib/builder/buildScopedDatafile.js.html +403 -0
  8. package/coverage/lcov-report/lib/builder/buildScopedSegments.js.html +379 -0
  9. package/coverage/lcov-report/lib/builder/index.html +53 -8
  10. package/coverage/lcov-report/lib/builder/revision.js.html +1 -1
  11. package/coverage/lcov-report/lib/builder/traffic.js.html +1 -1
  12. package/coverage/lcov-report/lib/list/index.html +14 -14
  13. package/coverage/lcov-report/lib/list/matrix.js.html +23 -8
  14. package/coverage/lcov-report/lib/tester/helpers.js.html +1 -1
  15. package/coverage/lcov-report/lib/tester/index.html +1 -1
  16. package/coverage/lcov-report/src/builder/allocator.ts.html +1 -1
  17. package/coverage/lcov-report/src/builder/buildScopedConditions.ts.html +487 -0
  18. package/coverage/lcov-report/src/builder/buildScopedDatafile.ts.html +604 -0
  19. package/coverage/lcov-report/src/builder/buildScopedSegments.ts.html +544 -0
  20. package/coverage/lcov-report/src/builder/index.html +55 -10
  21. package/coverage/lcov-report/src/builder/revision.ts.html +1 -1
  22. package/coverage/lcov-report/src/builder/traffic.ts.html +3 -3
  23. package/coverage/lcov-report/src/list/index.html +14 -14
  24. package/coverage/lcov-report/src/list/matrix.ts.html +33 -12
  25. package/coverage/lcov-report/src/tester/helpers.ts.html +1 -1
  26. package/coverage/lcov-report/src/tester/index.html +1 -1
  27. package/coverage/lcov.info +810 -129
  28. package/jest.config.js +8 -0
  29. package/lib/builder/buildDatafile.d.ts +10 -0
  30. package/lib/builder/buildDatafile.js +27 -0
  31. package/lib/builder/buildDatafile.js.map +1 -1
  32. package/lib/builder/buildProject.d.ts +2 -0
  33. package/lib/builder/buildProject.js +38 -4
  34. package/lib/builder/buildProject.js.map +1 -1
  35. package/lib/builder/buildScopedConditions.d.ts +5 -0
  36. package/lib/builder/buildScopedConditions.js +97 -0
  37. package/lib/builder/buildScopedConditions.js.map +1 -0
  38. package/lib/builder/buildScopedConditions.spec.d.ts +1 -0
  39. package/lib/builder/buildScopedConditions.spec.js +2167 -0
  40. package/lib/builder/buildScopedConditions.spec.js.map +1 -0
  41. package/lib/builder/buildScopedDatafile.d.ts +2 -0
  42. package/lib/builder/buildScopedDatafile.js +107 -0
  43. package/lib/builder/buildScopedDatafile.js.map +1 -0
  44. package/lib/builder/buildScopedDatafile.spec.d.ts +1 -0
  45. package/lib/builder/buildScopedDatafile.spec.js +1988 -0
  46. package/lib/builder/buildScopedDatafile.spec.js.map +1 -0
  47. package/lib/builder/buildScopedSegments.d.ts +5 -0
  48. package/lib/builder/buildScopedSegments.js +99 -0
  49. package/lib/builder/buildScopedSegments.js.map +1 -0
  50. package/lib/builder/buildScopedSegments.spec.d.ts +1 -0
  51. package/lib/builder/buildScopedSegments.spec.js +1062 -0
  52. package/lib/builder/buildScopedSegments.spec.js.map +1 -0
  53. package/lib/builder/index.d.ts +1 -0
  54. package/lib/builder/index.js +1 -0
  55. package/lib/builder/index.js.map +1 -1
  56. package/lib/config/projectConfig.d.ts +9 -1
  57. package/lib/config/projectConfig.js +1 -0
  58. package/lib/config/projectConfig.js.map +1 -1
  59. package/lib/datasource/adapter.d.ts +3 -1
  60. package/lib/datasource/adapter.js.map +1 -1
  61. package/lib/datasource/filesystemAdapter.js +3 -1
  62. package/lib/datasource/filesystemAdapter.js.map +1 -1
  63. package/lib/linter/testSchema.d.ts +5 -0
  64. package/lib/linter/testSchema.js +8 -0
  65. package/lib/linter/testSchema.js.map +1 -1
  66. package/lib/list/matrix.js +5 -0
  67. package/lib/list/matrix.js.map +1 -1
  68. package/lib/tester/testFeature.d.ts +5 -3
  69. package/lib/tester/testFeature.js +34 -9
  70. package/lib/tester/testFeature.js.map +1 -1
  71. package/lib/tester/testProject.d.ts +3 -2
  72. package/lib/tester/testProject.js +40 -6
  73. package/lib/tester/testProject.js.map +1 -1
  74. package/package.json +5 -5
  75. package/src/builder/buildDatafile.ts +47 -0
  76. package/src/builder/buildProject.ts +58 -3
  77. package/src/builder/buildScopedConditions.spec.ts +2659 -0
  78. package/src/builder/buildScopedConditions.ts +134 -0
  79. package/src/builder/buildScopedDatafile.spec.ts +2236 -0
  80. package/src/builder/buildScopedDatafile.ts +173 -0
  81. package/src/builder/buildScopedSegments.spec.ts +1573 -0
  82. package/src/builder/buildScopedSegments.ts +153 -0
  83. package/src/builder/index.ts +1 -0
  84. package/src/config/projectConfig.ts +11 -1
  85. package/src/datasource/adapter.ts +4 -1
  86. package/src/datasource/filesystemAdapter.ts +4 -1
  87. package/src/linter/testSchema.ts +12 -0
  88. package/src/list/matrix.ts +7 -0
  89. package/src/tester/testFeature.ts +50 -16
  90. package/src/tester/testProject.ts +68 -8
@@ -0,0 +1,2659 @@
1
+ import type { Condition, DatafileContent } from "@featurevisor/types";
2
+
3
+ import {
4
+ buildScopedCondition,
5
+ removeRedundantConditions,
6
+ buildScopedConditions,
7
+ } from "./buildScopedConditions";
8
+ import { DatafileReader, createLogger } from "@featurevisor/sdk";
9
+
10
+ describe("core: buildScopedConditions", function () {
11
+ const emptyDatafile: DatafileContent = {
12
+ schemaVersion: "2",
13
+ revision: "unknown",
14
+ segments: {},
15
+ features: {},
16
+ };
17
+
18
+ const datafileReader = new DatafileReader({
19
+ datafile: emptyDatafile,
20
+ logger: createLogger({ level: "fatal" }),
21
+ });
22
+
23
+ describe("buildScopedConditions (plural)", function () {
24
+ test("buildScopedConditions is a function", function () {
25
+ expect(buildScopedConditions).toBeInstanceOf(Function);
26
+ });
27
+
28
+ test("simple cases", function () {
29
+ // "*" remains "*"
30
+ expect(buildScopedConditions(datafileReader, "*", {})).toEqual("*");
31
+
32
+ // Plain condition that matches
33
+ expect(
34
+ buildScopedConditions(
35
+ datafileReader,
36
+ {
37
+ attribute: "platform",
38
+ operator: "equals",
39
+ value: "web",
40
+ },
41
+ {
42
+ platform: "web",
43
+ },
44
+ ),
45
+ ).toEqual("*");
46
+
47
+ // Plain condition that doesn't match
48
+ expect(
49
+ buildScopedConditions(
50
+ datafileReader,
51
+ {
52
+ attribute: "platform",
53
+ operator: "equals",
54
+ value: "web",
55
+ },
56
+ {
57
+ platform: "mobile",
58
+ },
59
+ ),
60
+ ).toEqual({
61
+ attribute: "platform",
62
+ operator: "equals",
63
+ value: "web",
64
+ });
65
+
66
+ // Array of conditions - partial match (redundant "*" removed)
67
+ expect(
68
+ buildScopedConditions(
69
+ datafileReader,
70
+ [
71
+ {
72
+ attribute: "platform",
73
+ operator: "equals",
74
+ value: "web",
75
+ },
76
+ {
77
+ attribute: "browser",
78
+ operator: "equals",
79
+ value: "chrome",
80
+ },
81
+ ],
82
+ {
83
+ platform: "web",
84
+ },
85
+ ),
86
+ ).toEqual([
87
+ {
88
+ attribute: "browser",
89
+ operator: "equals",
90
+ value: "chrome",
91
+ },
92
+ ]);
93
+
94
+ // Array of conditions - full match (all "*" becomes "*")
95
+ expect(
96
+ buildScopedConditions(
97
+ datafileReader,
98
+ [
99
+ {
100
+ attribute: "platform",
101
+ operator: "equals",
102
+ value: "web",
103
+ },
104
+ {
105
+ attribute: "browser",
106
+ operator: "equals",
107
+ value: "chrome",
108
+ },
109
+ ],
110
+ {
111
+ platform: "web",
112
+ browser: "chrome",
113
+ },
114
+ ),
115
+ ).toEqual("*");
116
+
117
+ // Array of conditions - no match
118
+ expect(
119
+ buildScopedConditions(
120
+ datafileReader,
121
+ [
122
+ {
123
+ attribute: "platform",
124
+ operator: "equals",
125
+ value: "web",
126
+ },
127
+ {
128
+ attribute: "browser",
129
+ operator: "equals",
130
+ value: "chrome",
131
+ },
132
+ ],
133
+ {
134
+ platform: "mobile",
135
+ browser: "safari",
136
+ },
137
+ ),
138
+ ).toEqual([
139
+ {
140
+ attribute: "platform",
141
+ operator: "equals",
142
+ value: "web",
143
+ },
144
+ {
145
+ attribute: "browser",
146
+ operator: "equals",
147
+ value: "chrome",
148
+ },
149
+ ]);
150
+ });
151
+
152
+ test("AND conditions", function () {
153
+ // AND with all matching conditions (all "*" becomes "*")
154
+ expect(
155
+ buildScopedConditions(
156
+ datafileReader,
157
+ {
158
+ and: [
159
+ {
160
+ attribute: "platform",
161
+ operator: "equals",
162
+ value: "web",
163
+ },
164
+ {
165
+ attribute: "browser",
166
+ operator: "equals",
167
+ value: "chrome",
168
+ },
169
+ ],
170
+ },
171
+ {
172
+ platform: "web",
173
+ browser: "chrome",
174
+ },
175
+ ),
176
+ ).toEqual("*");
177
+
178
+ // AND with partial match (redundant "*" removed)
179
+ expect(
180
+ buildScopedConditions(
181
+ datafileReader,
182
+ {
183
+ and: [
184
+ {
185
+ attribute: "platform",
186
+ operator: "equals",
187
+ value: "web",
188
+ },
189
+ {
190
+ attribute: "browser",
191
+ operator: "equals",
192
+ value: "chrome",
193
+ },
194
+ ],
195
+ },
196
+ {
197
+ platform: "web",
198
+ },
199
+ ),
200
+ ).toEqual({
201
+ and: [
202
+ {
203
+ attribute: "browser",
204
+ operator: "equals",
205
+ value: "chrome",
206
+ },
207
+ ],
208
+ });
209
+
210
+ // AND with no matches
211
+ expect(
212
+ buildScopedConditions(
213
+ datafileReader,
214
+ {
215
+ and: [
216
+ {
217
+ attribute: "platform",
218
+ operator: "equals",
219
+ value: "web",
220
+ },
221
+ {
222
+ attribute: "browser",
223
+ operator: "equals",
224
+ value: "chrome",
225
+ },
226
+ ],
227
+ },
228
+ {
229
+ platform: "mobile",
230
+ browser: "safari",
231
+ },
232
+ ),
233
+ ).toEqual({
234
+ and: [
235
+ {
236
+ attribute: "platform",
237
+ operator: "equals",
238
+ value: "web",
239
+ },
240
+ {
241
+ attribute: "browser",
242
+ operator: "equals",
243
+ value: "chrome",
244
+ },
245
+ ],
246
+ });
247
+
248
+ // AND with "*" in it (all "*" becomes "*")
249
+ expect(
250
+ buildScopedConditions(
251
+ datafileReader,
252
+ {
253
+ and: [
254
+ "*",
255
+ {
256
+ attribute: "platform",
257
+ operator: "equals",
258
+ value: "web",
259
+ },
260
+ ],
261
+ },
262
+ {
263
+ platform: "web",
264
+ },
265
+ ),
266
+ ).toEqual("*");
267
+ });
268
+
269
+ test("OR conditions", function () {
270
+ // OR with all matching conditions (all "*" becomes "*")
271
+ // Note: This would require both conditions to match, which isn't possible with same attribute
272
+ // So testing with different attributes that both match
273
+ expect(
274
+ buildScopedConditions(
275
+ datafileReader,
276
+ {
277
+ or: [
278
+ {
279
+ attribute: "platform",
280
+ operator: "equals",
281
+ value: "web",
282
+ },
283
+ {
284
+ attribute: "browser",
285
+ operator: "equals",
286
+ value: "chrome",
287
+ },
288
+ ],
289
+ },
290
+ {
291
+ platform: "web",
292
+ browser: "chrome",
293
+ },
294
+ ),
295
+ ).toEqual("*");
296
+
297
+ // OR with partial match (redundant "*" removed, but OR structure remains)
298
+ expect(
299
+ buildScopedConditions(
300
+ datafileReader,
301
+ {
302
+ or: [
303
+ {
304
+ attribute: "platform",
305
+ operator: "equals",
306
+ value: "web",
307
+ },
308
+ {
309
+ attribute: "browser",
310
+ operator: "equals",
311
+ value: "chrome",
312
+ },
313
+ ],
314
+ },
315
+ {
316
+ platform: "web",
317
+ },
318
+ ),
319
+ ).toEqual({
320
+ or: [
321
+ {
322
+ attribute: "browser",
323
+ operator: "equals",
324
+ value: "chrome",
325
+ },
326
+ ],
327
+ });
328
+
329
+ // OR with no matches
330
+ expect(
331
+ buildScopedConditions(
332
+ datafileReader,
333
+ {
334
+ or: [
335
+ {
336
+ attribute: "platform",
337
+ operator: "equals",
338
+ value: "web",
339
+ },
340
+ {
341
+ attribute: "platform",
342
+ operator: "equals",
343
+ value: "mobile",
344
+ },
345
+ ],
346
+ },
347
+ {
348
+ platform: "desktop",
349
+ },
350
+ ),
351
+ ).toEqual({
352
+ or: [
353
+ {
354
+ attribute: "platform",
355
+ operator: "equals",
356
+ value: "web",
357
+ },
358
+ {
359
+ attribute: "platform",
360
+ operator: "equals",
361
+ value: "mobile",
362
+ },
363
+ ],
364
+ });
365
+ });
366
+
367
+ test("NOT conditions", function () {
368
+ // NOT with matching condition (all "*" becomes "*")
369
+ expect(
370
+ buildScopedConditions(
371
+ datafileReader,
372
+ {
373
+ not: [
374
+ {
375
+ attribute: "platform",
376
+ operator: "equals",
377
+ value: "web",
378
+ },
379
+ ],
380
+ },
381
+ {
382
+ platform: "web",
383
+ },
384
+ ),
385
+ ).toEqual("*");
386
+
387
+ // NOT with non-matching condition
388
+ expect(
389
+ buildScopedConditions(
390
+ datafileReader,
391
+ {
392
+ not: [
393
+ {
394
+ attribute: "platform",
395
+ operator: "equals",
396
+ value: "web",
397
+ },
398
+ ],
399
+ },
400
+ {
401
+ platform: "mobile",
402
+ },
403
+ ),
404
+ ).toEqual({
405
+ not: [
406
+ {
407
+ attribute: "platform",
408
+ operator: "equals",
409
+ value: "web",
410
+ },
411
+ ],
412
+ });
413
+
414
+ // NOT with multiple conditions (all "*" becomes "*")
415
+ expect(
416
+ buildScopedConditions(
417
+ datafileReader,
418
+ {
419
+ not: [
420
+ {
421
+ attribute: "platform",
422
+ operator: "equals",
423
+ value: "web",
424
+ },
425
+ {
426
+ attribute: "browser",
427
+ operator: "equals",
428
+ value: "chrome",
429
+ },
430
+ ],
431
+ },
432
+ {
433
+ platform: "web",
434
+ browser: "chrome",
435
+ },
436
+ ),
437
+ ).toEqual("*");
438
+ });
439
+
440
+ test("nested AND conditions", function () {
441
+ // Nested AND with all matching (all "*" becomes "*")
442
+ expect(
443
+ buildScopedConditions(
444
+ datafileReader,
445
+ {
446
+ and: [
447
+ {
448
+ attribute: "platform",
449
+ operator: "equals",
450
+ value: "web",
451
+ },
452
+ {
453
+ and: [
454
+ {
455
+ attribute: "browser",
456
+ operator: "equals",
457
+ value: "chrome",
458
+ },
459
+ {
460
+ attribute: "version",
461
+ operator: "equals",
462
+ value: "1.0",
463
+ },
464
+ ],
465
+ },
466
+ ],
467
+ },
468
+ {
469
+ platform: "web",
470
+ browser: "chrome",
471
+ version: "1.0",
472
+ },
473
+ ),
474
+ ).toEqual("*");
475
+
476
+ // Nested AND with partial match (redundant "*" removed)
477
+ expect(
478
+ buildScopedConditions(
479
+ datafileReader,
480
+ {
481
+ and: [
482
+ {
483
+ attribute: "platform",
484
+ operator: "equals",
485
+ value: "web",
486
+ },
487
+ {
488
+ and: [
489
+ {
490
+ attribute: "browser",
491
+ operator: "equals",
492
+ value: "chrome",
493
+ },
494
+ {
495
+ attribute: "version",
496
+ operator: "equals",
497
+ value: "1.0",
498
+ },
499
+ ],
500
+ },
501
+ ],
502
+ },
503
+ {
504
+ platform: "web",
505
+ browser: "chrome",
506
+ },
507
+ ),
508
+ ).toEqual({
509
+ and: [
510
+ {
511
+ and: [
512
+ {
513
+ attribute: "version",
514
+ operator: "equals",
515
+ value: "1.0",
516
+ },
517
+ ],
518
+ },
519
+ ],
520
+ });
521
+ });
522
+
523
+ test("nested OR conditions", function () {
524
+ // Nested OR with outer match (redundant "*" removed, but inner OR remains)
525
+ expect(
526
+ buildScopedConditions(
527
+ datafileReader,
528
+ {
529
+ or: [
530
+ {
531
+ attribute: "platform",
532
+ operator: "equals",
533
+ value: "web",
534
+ },
535
+ {
536
+ or: [
537
+ {
538
+ attribute: "platform",
539
+ operator: "equals",
540
+ value: "mobile",
541
+ },
542
+ {
543
+ attribute: "platform",
544
+ operator: "equals",
545
+ value: "desktop",
546
+ },
547
+ ],
548
+ },
549
+ ],
550
+ },
551
+ {
552
+ platform: "web",
553
+ },
554
+ ),
555
+ ).toEqual({
556
+ or: [
557
+ {
558
+ or: [
559
+ {
560
+ attribute: "platform",
561
+ operator: "equals",
562
+ value: "mobile",
563
+ },
564
+ {
565
+ attribute: "platform",
566
+ operator: "equals",
567
+ value: "desktop",
568
+ },
569
+ ],
570
+ },
571
+ ],
572
+ });
573
+
574
+ // Nested OR with inner match (redundant "*" removed, but outer OR remains)
575
+ expect(
576
+ buildScopedConditions(
577
+ datafileReader,
578
+ {
579
+ or: [
580
+ {
581
+ attribute: "platform",
582
+ operator: "equals",
583
+ value: "web",
584
+ },
585
+ {
586
+ or: [
587
+ {
588
+ attribute: "platform",
589
+ operator: "equals",
590
+ value: "mobile",
591
+ },
592
+ {
593
+ attribute: "platform",
594
+ operator: "equals",
595
+ value: "desktop",
596
+ },
597
+ ],
598
+ },
599
+ ],
600
+ },
601
+ {
602
+ platform: "mobile",
603
+ },
604
+ ),
605
+ ).toEqual({
606
+ or: [
607
+ {
608
+ attribute: "platform",
609
+ operator: "equals",
610
+ value: "web",
611
+ },
612
+ {
613
+ or: [
614
+ {
615
+ attribute: "platform",
616
+ operator: "equals",
617
+ value: "desktop",
618
+ },
619
+ ],
620
+ },
621
+ ],
622
+ });
623
+ });
624
+
625
+ test("nested NOT conditions", function () {
626
+ // Nested NOT (all "*" becomes "*")
627
+ expect(
628
+ buildScopedConditions(
629
+ datafileReader,
630
+ {
631
+ not: [
632
+ {
633
+ attribute: "platform",
634
+ operator: "equals",
635
+ value: "web",
636
+ },
637
+ {
638
+ not: [
639
+ {
640
+ attribute: "browser",
641
+ operator: "equals",
642
+ value: "chrome",
643
+ },
644
+ ],
645
+ },
646
+ ],
647
+ },
648
+ {
649
+ platform: "web",
650
+ browser: "chrome",
651
+ },
652
+ ),
653
+ ).toEqual("*");
654
+ });
655
+
656
+ test("mixed nested conditions", function () {
657
+ // AND with nested OR (redundant "*" removed, but OR structure remains if not all match)
658
+ expect(
659
+ buildScopedConditions(
660
+ datafileReader,
661
+ {
662
+ and: [
663
+ {
664
+ attribute: "platform",
665
+ operator: "equals",
666
+ value: "web",
667
+ },
668
+ {
669
+ or: [
670
+ {
671
+ attribute: "browser",
672
+ operator: "equals",
673
+ value: "chrome",
674
+ },
675
+ {
676
+ attribute: "browser",
677
+ operator: "equals",
678
+ value: "firefox",
679
+ },
680
+ ],
681
+ },
682
+ ],
683
+ },
684
+ {
685
+ platform: "web",
686
+ browser: "chrome",
687
+ },
688
+ ),
689
+ ).toEqual({
690
+ and: [
691
+ {
692
+ or: [
693
+ {
694
+ attribute: "browser",
695
+ operator: "equals",
696
+ value: "firefox",
697
+ },
698
+ ],
699
+ },
700
+ ],
701
+ });
702
+
703
+ // OR with nested AND (redundant "*" removed, but OR structure remains if not all match)
704
+ expect(
705
+ buildScopedConditions(
706
+ datafileReader,
707
+ {
708
+ or: [
709
+ {
710
+ attribute: "platform",
711
+ operator: "equals",
712
+ value: "web",
713
+ },
714
+ {
715
+ and: [
716
+ {
717
+ attribute: "platform",
718
+ operator: "equals",
719
+ value: "mobile",
720
+ },
721
+ {
722
+ attribute: "browser",
723
+ operator: "equals",
724
+ value: "chrome",
725
+ },
726
+ ],
727
+ },
728
+ ],
729
+ },
730
+ {
731
+ platform: "mobile",
732
+ browser: "chrome",
733
+ },
734
+ ),
735
+ ).toEqual({
736
+ or: [
737
+ {
738
+ attribute: "platform",
739
+ operator: "equals",
740
+ value: "web",
741
+ },
742
+ ],
743
+ });
744
+
745
+ // AND with nested NOT (redundant "*" removed)
746
+ expect(
747
+ buildScopedConditions(
748
+ datafileReader,
749
+ {
750
+ and: [
751
+ {
752
+ attribute: "platform",
753
+ operator: "equals",
754
+ value: "web",
755
+ },
756
+ {
757
+ not: [
758
+ {
759
+ attribute: "browser",
760
+ operator: "equals",
761
+ value: "safari",
762
+ },
763
+ ],
764
+ },
765
+ ],
766
+ },
767
+ {
768
+ platform: "web",
769
+ browser: "chrome",
770
+ },
771
+ ),
772
+ ).toEqual({
773
+ and: [
774
+ {
775
+ not: [
776
+ {
777
+ attribute: "browser",
778
+ operator: "equals",
779
+ value: "safari",
780
+ },
781
+ ],
782
+ },
783
+ ],
784
+ });
785
+
786
+ // Complex nested structure (redundant "*" removed)
787
+ expect(
788
+ buildScopedConditions(
789
+ datafileReader,
790
+ {
791
+ and: [
792
+ {
793
+ attribute: "platform",
794
+ operator: "equals",
795
+ value: "web",
796
+ },
797
+ {
798
+ or: [
799
+ {
800
+ and: [
801
+ {
802
+ attribute: "browser",
803
+ operator: "equals",
804
+ value: "chrome",
805
+ },
806
+ {
807
+ attribute: "version",
808
+ operator: "equals",
809
+ value: "1.0",
810
+ },
811
+ ],
812
+ },
813
+ {
814
+ attribute: "browser",
815
+ operator: "equals",
816
+ value: "firefox",
817
+ },
818
+ ],
819
+ },
820
+ ],
821
+ },
822
+ {
823
+ platform: "web",
824
+ browser: "chrome",
825
+ version: "1.0",
826
+ },
827
+ ),
828
+ ).toEqual({
829
+ and: [
830
+ {
831
+ or: [
832
+ {
833
+ attribute: "browser",
834
+ operator: "equals",
835
+ value: "firefox",
836
+ },
837
+ ],
838
+ },
839
+ ],
840
+ });
841
+ });
842
+
843
+ test("arrays with nested conditions", function () {
844
+ // Array with AND condition (redundant "*" removed)
845
+ expect(
846
+ buildScopedConditions(
847
+ datafileReader,
848
+ [
849
+ {
850
+ attribute: "platform",
851
+ operator: "equals",
852
+ value: "web",
853
+ },
854
+ {
855
+ and: [
856
+ {
857
+ attribute: "browser",
858
+ operator: "equals",
859
+ value: "chrome",
860
+ },
861
+ {
862
+ attribute: "version",
863
+ operator: "equals",
864
+ value: "1.0",
865
+ },
866
+ ],
867
+ },
868
+ ],
869
+ {
870
+ platform: "web",
871
+ browser: "chrome",
872
+ version: "1.0",
873
+ },
874
+ ),
875
+ ).toEqual("*");
876
+
877
+ // Array with OR condition (redundant "*" removed, but OR structure remains if not all match)
878
+ expect(
879
+ buildScopedConditions(
880
+ datafileReader,
881
+ [
882
+ {
883
+ attribute: "platform",
884
+ operator: "equals",
885
+ value: "web",
886
+ },
887
+ {
888
+ or: [
889
+ {
890
+ attribute: "browser",
891
+ operator: "equals",
892
+ value: "chrome",
893
+ },
894
+ {
895
+ attribute: "browser",
896
+ operator: "equals",
897
+ value: "firefox",
898
+ },
899
+ ],
900
+ },
901
+ ],
902
+ {
903
+ platform: "web",
904
+ browser: "chrome",
905
+ },
906
+ ),
907
+ ).toEqual([
908
+ {
909
+ or: [
910
+ {
911
+ attribute: "browser",
912
+ operator: "equals",
913
+ value: "firefox",
914
+ },
915
+ ],
916
+ },
917
+ ]);
918
+ });
919
+
920
+ test("edge cases", function () {
921
+ // Empty context
922
+ expect(
923
+ buildScopedConditions(
924
+ datafileReader,
925
+ {
926
+ attribute: "platform",
927
+ operator: "equals",
928
+ value: "web",
929
+ },
930
+ {},
931
+ ),
932
+ ).toEqual({
933
+ attribute: "platform",
934
+ operator: "equals",
935
+ value: "web",
936
+ });
937
+
938
+ // Empty array (all "*" becomes "*")
939
+ expect(buildScopedConditions(datafileReader, [], {})).toEqual("*");
940
+
941
+ // "*" in array with matching condition (all "*" becomes "*")
942
+ expect(
943
+ buildScopedConditions(
944
+ datafileReader,
945
+ [
946
+ "*",
947
+ {
948
+ attribute: "platform",
949
+ operator: "equals",
950
+ value: "web",
951
+ },
952
+ ],
953
+ {
954
+ platform: "web",
955
+ },
956
+ ),
957
+ ).toEqual("*");
958
+
959
+ // AND with empty array (all "*" becomes "*")
960
+ expect(
961
+ buildScopedConditions(
962
+ datafileReader,
963
+ {
964
+ and: [],
965
+ },
966
+ {},
967
+ ),
968
+ ).toEqual("*");
969
+
970
+ // OR with empty array (all "*" becomes "*")
971
+ expect(
972
+ buildScopedConditions(
973
+ datafileReader,
974
+ {
975
+ or: [],
976
+ },
977
+ {},
978
+ ),
979
+ ).toEqual("*");
980
+ });
981
+ });
982
+
983
+ describe("buildScopedCondition (singular)", function () {
984
+ test("buildScopedCondition is a function", function () {
985
+ expect(buildScopedCondition).toBeInstanceOf(Function);
986
+ });
987
+
988
+ test("simple cases", function () {
989
+ // "*" remains "*"
990
+ expect(buildScopedCondition(datafileReader, "*", {})).toEqual("*");
991
+
992
+ // Plain condition that matches
993
+ expect(
994
+ buildScopedCondition(
995
+ datafileReader,
996
+ {
997
+ attribute: "platform",
998
+ operator: "equals",
999
+ value: "web",
1000
+ },
1001
+ {
1002
+ platform: "web",
1003
+ },
1004
+ ),
1005
+ ).toEqual("*");
1006
+
1007
+ // Plain condition that doesn't match
1008
+ expect(
1009
+ buildScopedCondition(
1010
+ datafileReader,
1011
+ {
1012
+ attribute: "platform",
1013
+ operator: "equals",
1014
+ value: "web",
1015
+ },
1016
+ {
1017
+ platform: "mobile",
1018
+ },
1019
+ ),
1020
+ ).toEqual({
1021
+ attribute: "platform",
1022
+ operator: "equals",
1023
+ value: "web",
1024
+ });
1025
+
1026
+ // Array of conditions
1027
+ const originalConditions: Condition[] = [
1028
+ {
1029
+ attribute: "platform",
1030
+ operator: "equals",
1031
+ value: "web",
1032
+ },
1033
+ {
1034
+ attribute: "browser",
1035
+ operator: "equals",
1036
+ value: "chrome",
1037
+ },
1038
+ ];
1039
+
1040
+ // Partial match
1041
+ expect(
1042
+ buildScopedCondition(datafileReader, originalConditions, {
1043
+ platform: "web",
1044
+ }),
1045
+ ).toEqual([
1046
+ "*",
1047
+ {
1048
+ attribute: "browser",
1049
+ operator: "equals",
1050
+ value: "chrome",
1051
+ },
1052
+ ]);
1053
+
1054
+ // Full match
1055
+ expect(
1056
+ buildScopedCondition(datafileReader, originalConditions, {
1057
+ platform: "web",
1058
+ browser: "chrome",
1059
+ }),
1060
+ ).toEqual(["*", "*"]);
1061
+
1062
+ // No match
1063
+ expect(
1064
+ buildScopedCondition(datafileReader, originalConditions, {
1065
+ platform: "mobile",
1066
+ browser: "safari",
1067
+ }),
1068
+ ).toEqual([
1069
+ {
1070
+ attribute: "platform",
1071
+ operator: "equals",
1072
+ value: "web",
1073
+ },
1074
+ {
1075
+ attribute: "browser",
1076
+ operator: "equals",
1077
+ value: "chrome",
1078
+ },
1079
+ ]);
1080
+ });
1081
+
1082
+ test("AND conditions", function () {
1083
+ // AND with all matching conditions
1084
+ expect(
1085
+ buildScopedCondition(
1086
+ datafileReader,
1087
+ {
1088
+ and: [
1089
+ {
1090
+ attribute: "platform",
1091
+ operator: "equals",
1092
+ value: "web",
1093
+ },
1094
+ {
1095
+ attribute: "browser",
1096
+ operator: "equals",
1097
+ value: "chrome",
1098
+ },
1099
+ ],
1100
+ },
1101
+ {
1102
+ platform: "web",
1103
+ browser: "chrome",
1104
+ },
1105
+ ),
1106
+ ).toEqual({
1107
+ and: ["*", "*"],
1108
+ });
1109
+
1110
+ // AND with partial match
1111
+ expect(
1112
+ buildScopedCondition(
1113
+ datafileReader,
1114
+ {
1115
+ and: [
1116
+ {
1117
+ attribute: "platform",
1118
+ operator: "equals",
1119
+ value: "web",
1120
+ },
1121
+ {
1122
+ attribute: "browser",
1123
+ operator: "equals",
1124
+ value: "chrome",
1125
+ },
1126
+ ],
1127
+ },
1128
+ {
1129
+ platform: "web",
1130
+ },
1131
+ ),
1132
+ ).toEqual({
1133
+ and: [
1134
+ "*",
1135
+ {
1136
+ attribute: "browser",
1137
+ operator: "equals",
1138
+ value: "chrome",
1139
+ },
1140
+ ],
1141
+ });
1142
+
1143
+ // AND with no matches
1144
+ expect(
1145
+ buildScopedCondition(
1146
+ datafileReader,
1147
+ {
1148
+ and: [
1149
+ {
1150
+ attribute: "platform",
1151
+ operator: "equals",
1152
+ value: "web",
1153
+ },
1154
+ {
1155
+ attribute: "browser",
1156
+ operator: "equals",
1157
+ value: "chrome",
1158
+ },
1159
+ ],
1160
+ },
1161
+ {
1162
+ platform: "mobile",
1163
+ browser: "safari",
1164
+ },
1165
+ ),
1166
+ ).toEqual({
1167
+ and: [
1168
+ {
1169
+ attribute: "platform",
1170
+ operator: "equals",
1171
+ value: "web",
1172
+ },
1173
+ {
1174
+ attribute: "browser",
1175
+ operator: "equals",
1176
+ value: "chrome",
1177
+ },
1178
+ ],
1179
+ });
1180
+
1181
+ // AND with "*" in it
1182
+ expect(
1183
+ buildScopedCondition(
1184
+ datafileReader,
1185
+ {
1186
+ and: [
1187
+ "*",
1188
+ {
1189
+ attribute: "platform",
1190
+ operator: "equals",
1191
+ value: "web",
1192
+ },
1193
+ ],
1194
+ },
1195
+ {
1196
+ platform: "web",
1197
+ },
1198
+ ),
1199
+ ).toEqual({
1200
+ and: ["*", "*"],
1201
+ });
1202
+ });
1203
+
1204
+ test("OR conditions", function () {
1205
+ // OR with all matching conditions
1206
+ expect(
1207
+ buildScopedCondition(
1208
+ datafileReader,
1209
+ {
1210
+ or: [
1211
+ {
1212
+ attribute: "platform",
1213
+ operator: "equals",
1214
+ value: "web",
1215
+ },
1216
+ {
1217
+ attribute: "platform",
1218
+ operator: "equals",
1219
+ value: "mobile",
1220
+ },
1221
+ ],
1222
+ },
1223
+ {
1224
+ platform: "web",
1225
+ },
1226
+ ),
1227
+ ).toEqual({
1228
+ or: ["*", { attribute: "platform", operator: "equals", value: "mobile" }],
1229
+ });
1230
+
1231
+ // OR with partial match (first matches)
1232
+ expect(
1233
+ buildScopedCondition(
1234
+ datafileReader,
1235
+ {
1236
+ or: [
1237
+ {
1238
+ attribute: "platform",
1239
+ operator: "equals",
1240
+ value: "web",
1241
+ },
1242
+ {
1243
+ attribute: "browser",
1244
+ operator: "equals",
1245
+ value: "chrome",
1246
+ },
1247
+ ],
1248
+ },
1249
+ {
1250
+ platform: "web",
1251
+ },
1252
+ ),
1253
+ ).toEqual({
1254
+ or: [
1255
+ "*",
1256
+ {
1257
+ attribute: "browser",
1258
+ operator: "equals",
1259
+ value: "chrome",
1260
+ },
1261
+ ],
1262
+ });
1263
+
1264
+ // OR with no matches
1265
+ expect(
1266
+ buildScopedCondition(
1267
+ datafileReader,
1268
+ {
1269
+ or: [
1270
+ {
1271
+ attribute: "platform",
1272
+ operator: "equals",
1273
+ value: "web",
1274
+ },
1275
+ {
1276
+ attribute: "platform",
1277
+ operator: "equals",
1278
+ value: "mobile",
1279
+ },
1280
+ ],
1281
+ },
1282
+ {
1283
+ platform: "desktop",
1284
+ },
1285
+ ),
1286
+ ).toEqual({
1287
+ or: [
1288
+ {
1289
+ attribute: "platform",
1290
+ operator: "equals",
1291
+ value: "web",
1292
+ },
1293
+ {
1294
+ attribute: "platform",
1295
+ operator: "equals",
1296
+ value: "mobile",
1297
+ },
1298
+ ],
1299
+ });
1300
+ });
1301
+
1302
+ test("NOT conditions", function () {
1303
+ // NOT with matching condition (should not match)
1304
+ expect(
1305
+ buildScopedCondition(
1306
+ datafileReader,
1307
+ {
1308
+ not: [
1309
+ {
1310
+ attribute: "platform",
1311
+ operator: "equals",
1312
+ value: "web",
1313
+ },
1314
+ ],
1315
+ },
1316
+ {
1317
+ platform: "web",
1318
+ },
1319
+ ),
1320
+ ).toEqual({
1321
+ not: ["*"],
1322
+ });
1323
+
1324
+ // NOT with non-matching condition
1325
+ expect(
1326
+ buildScopedCondition(
1327
+ datafileReader,
1328
+ {
1329
+ not: [
1330
+ {
1331
+ attribute: "platform",
1332
+ operator: "equals",
1333
+ value: "web",
1334
+ },
1335
+ ],
1336
+ },
1337
+ {
1338
+ platform: "mobile",
1339
+ },
1340
+ ),
1341
+ ).toEqual({
1342
+ not: [
1343
+ {
1344
+ attribute: "platform",
1345
+ operator: "equals",
1346
+ value: "web",
1347
+ },
1348
+ ],
1349
+ });
1350
+
1351
+ // NOT with multiple conditions
1352
+ expect(
1353
+ buildScopedCondition(
1354
+ datafileReader,
1355
+ {
1356
+ not: [
1357
+ {
1358
+ attribute: "platform",
1359
+ operator: "equals",
1360
+ value: "web",
1361
+ },
1362
+ {
1363
+ attribute: "browser",
1364
+ operator: "equals",
1365
+ value: "chrome",
1366
+ },
1367
+ ],
1368
+ },
1369
+ {
1370
+ platform: "web",
1371
+ browser: "chrome",
1372
+ },
1373
+ ),
1374
+ ).toEqual({
1375
+ not: ["*", "*"],
1376
+ });
1377
+ });
1378
+
1379
+ test("nested AND conditions", function () {
1380
+ // Nested AND with all matching
1381
+ expect(
1382
+ buildScopedCondition(
1383
+ datafileReader,
1384
+ {
1385
+ and: [
1386
+ {
1387
+ attribute: "platform",
1388
+ operator: "equals",
1389
+ value: "web",
1390
+ },
1391
+ {
1392
+ and: [
1393
+ {
1394
+ attribute: "browser",
1395
+ operator: "equals",
1396
+ value: "chrome",
1397
+ },
1398
+ {
1399
+ attribute: "version",
1400
+ operator: "equals",
1401
+ value: "1.0",
1402
+ },
1403
+ ],
1404
+ },
1405
+ ],
1406
+ },
1407
+ {
1408
+ platform: "web",
1409
+ browser: "chrome",
1410
+ version: "1.0",
1411
+ },
1412
+ ),
1413
+ ).toEqual({
1414
+ and: [
1415
+ "*",
1416
+ {
1417
+ and: ["*", "*"],
1418
+ },
1419
+ ],
1420
+ });
1421
+
1422
+ // Nested AND with partial match
1423
+ expect(
1424
+ buildScopedCondition(
1425
+ datafileReader,
1426
+ {
1427
+ and: [
1428
+ {
1429
+ attribute: "platform",
1430
+ operator: "equals",
1431
+ value: "web",
1432
+ },
1433
+ {
1434
+ and: [
1435
+ {
1436
+ attribute: "browser",
1437
+ operator: "equals",
1438
+ value: "chrome",
1439
+ },
1440
+ {
1441
+ attribute: "version",
1442
+ operator: "equals",
1443
+ value: "1.0",
1444
+ },
1445
+ ],
1446
+ },
1447
+ ],
1448
+ },
1449
+ {
1450
+ platform: "web",
1451
+ browser: "chrome",
1452
+ },
1453
+ ),
1454
+ ).toEqual({
1455
+ and: [
1456
+ "*",
1457
+ {
1458
+ and: [
1459
+ "*",
1460
+ {
1461
+ attribute: "version",
1462
+ operator: "equals",
1463
+ value: "1.0",
1464
+ },
1465
+ ],
1466
+ },
1467
+ ],
1468
+ });
1469
+ });
1470
+
1471
+ test("nested OR conditions", function () {
1472
+ // Nested OR with matching
1473
+ expect(
1474
+ buildScopedCondition(
1475
+ datafileReader,
1476
+ {
1477
+ or: [
1478
+ {
1479
+ attribute: "platform",
1480
+ operator: "equals",
1481
+ value: "web",
1482
+ },
1483
+ {
1484
+ or: [
1485
+ {
1486
+ attribute: "platform",
1487
+ operator: "equals",
1488
+ value: "mobile",
1489
+ },
1490
+ {
1491
+ attribute: "platform",
1492
+ operator: "equals",
1493
+ value: "desktop",
1494
+ },
1495
+ ],
1496
+ },
1497
+ ],
1498
+ },
1499
+ {
1500
+ platform: "web",
1501
+ },
1502
+ ),
1503
+ ).toEqual({
1504
+ or: [
1505
+ "*",
1506
+ {
1507
+ or: [
1508
+ {
1509
+ attribute: "platform",
1510
+ operator: "equals",
1511
+ value: "mobile",
1512
+ },
1513
+ {
1514
+ attribute: "platform",
1515
+ operator: "equals",
1516
+ value: "desktop",
1517
+ },
1518
+ ],
1519
+ },
1520
+ ],
1521
+ });
1522
+
1523
+ // Nested OR with inner match
1524
+ expect(
1525
+ buildScopedCondition(
1526
+ datafileReader,
1527
+ {
1528
+ or: [
1529
+ {
1530
+ attribute: "platform",
1531
+ operator: "equals",
1532
+ value: "web",
1533
+ },
1534
+ {
1535
+ or: [
1536
+ {
1537
+ attribute: "platform",
1538
+ operator: "equals",
1539
+ value: "mobile",
1540
+ },
1541
+ {
1542
+ attribute: "platform",
1543
+ operator: "equals",
1544
+ value: "desktop",
1545
+ },
1546
+ ],
1547
+ },
1548
+ ],
1549
+ },
1550
+ {
1551
+ platform: "mobile",
1552
+ },
1553
+ ),
1554
+ ).toEqual({
1555
+ or: [
1556
+ {
1557
+ attribute: "platform",
1558
+ operator: "equals",
1559
+ value: "web",
1560
+ },
1561
+ {
1562
+ or: [
1563
+ "*",
1564
+ {
1565
+ attribute: "platform",
1566
+ operator: "equals",
1567
+ value: "desktop",
1568
+ },
1569
+ ],
1570
+ },
1571
+ ],
1572
+ });
1573
+ });
1574
+
1575
+ test("nested NOT conditions", function () {
1576
+ // Nested NOT
1577
+ expect(
1578
+ buildScopedCondition(
1579
+ datafileReader,
1580
+ {
1581
+ not: [
1582
+ {
1583
+ attribute: "platform",
1584
+ operator: "equals",
1585
+ value: "web",
1586
+ },
1587
+ {
1588
+ not: [
1589
+ {
1590
+ attribute: "browser",
1591
+ operator: "equals",
1592
+ value: "chrome",
1593
+ },
1594
+ ],
1595
+ },
1596
+ ],
1597
+ },
1598
+ {
1599
+ platform: "web",
1600
+ browser: "chrome",
1601
+ },
1602
+ ),
1603
+ ).toEqual({
1604
+ not: [
1605
+ "*",
1606
+ {
1607
+ not: ["*"],
1608
+ },
1609
+ ],
1610
+ });
1611
+ });
1612
+
1613
+ test("mixed nested conditions", function () {
1614
+ // AND with nested OR
1615
+ expect(
1616
+ buildScopedCondition(
1617
+ datafileReader,
1618
+ {
1619
+ and: [
1620
+ {
1621
+ attribute: "platform",
1622
+ operator: "equals",
1623
+ value: "web",
1624
+ },
1625
+ {
1626
+ or: [
1627
+ {
1628
+ attribute: "browser",
1629
+ operator: "equals",
1630
+ value: "chrome",
1631
+ },
1632
+ {
1633
+ attribute: "browser",
1634
+ operator: "equals",
1635
+ value: "firefox",
1636
+ },
1637
+ ],
1638
+ },
1639
+ ],
1640
+ },
1641
+ {
1642
+ platform: "web",
1643
+ browser: "chrome",
1644
+ },
1645
+ ),
1646
+ ).toEqual({
1647
+ and: [
1648
+ "*",
1649
+ {
1650
+ or: [
1651
+ "*",
1652
+ {
1653
+ attribute: "browser",
1654
+ operator: "equals",
1655
+ value: "firefox",
1656
+ },
1657
+ ],
1658
+ },
1659
+ ],
1660
+ });
1661
+
1662
+ // OR with nested AND
1663
+ expect(
1664
+ buildScopedCondition(
1665
+ datafileReader,
1666
+ {
1667
+ or: [
1668
+ {
1669
+ attribute: "platform",
1670
+ operator: "equals",
1671
+ value: "web",
1672
+ },
1673
+ {
1674
+ and: [
1675
+ {
1676
+ attribute: "platform",
1677
+ operator: "equals",
1678
+ value: "mobile",
1679
+ },
1680
+ {
1681
+ attribute: "browser",
1682
+ operator: "equals",
1683
+ value: "chrome",
1684
+ },
1685
+ ],
1686
+ },
1687
+ ],
1688
+ },
1689
+ {
1690
+ platform: "mobile",
1691
+ browser: "chrome",
1692
+ },
1693
+ ),
1694
+ ).toEqual({
1695
+ or: [
1696
+ {
1697
+ attribute: "platform",
1698
+ operator: "equals",
1699
+ value: "web",
1700
+ },
1701
+ {
1702
+ and: ["*", "*"],
1703
+ },
1704
+ ],
1705
+ });
1706
+
1707
+ // AND with nested NOT
1708
+ expect(
1709
+ buildScopedCondition(
1710
+ datafileReader,
1711
+ {
1712
+ and: [
1713
+ {
1714
+ attribute: "platform",
1715
+ operator: "equals",
1716
+ value: "web",
1717
+ },
1718
+ {
1719
+ not: [
1720
+ {
1721
+ attribute: "browser",
1722
+ operator: "equals",
1723
+ value: "safari",
1724
+ },
1725
+ ],
1726
+ },
1727
+ ],
1728
+ },
1729
+ {
1730
+ platform: "web",
1731
+ browser: "chrome",
1732
+ },
1733
+ ),
1734
+ ).toEqual({
1735
+ and: [
1736
+ "*",
1737
+ {
1738
+ not: [
1739
+ {
1740
+ attribute: "browser",
1741
+ operator: "equals",
1742
+ value: "safari",
1743
+ },
1744
+ ],
1745
+ },
1746
+ ],
1747
+ });
1748
+
1749
+ // Complex nested structure
1750
+ expect(
1751
+ buildScopedCondition(
1752
+ datafileReader,
1753
+ {
1754
+ and: [
1755
+ {
1756
+ attribute: "platform",
1757
+ operator: "equals",
1758
+ value: "web",
1759
+ },
1760
+ {
1761
+ or: [
1762
+ {
1763
+ and: [
1764
+ {
1765
+ attribute: "browser",
1766
+ operator: "equals",
1767
+ value: "chrome",
1768
+ },
1769
+ {
1770
+ attribute: "version",
1771
+ operator: "equals",
1772
+ value: "1.0",
1773
+ },
1774
+ ],
1775
+ },
1776
+ {
1777
+ attribute: "browser",
1778
+ operator: "equals",
1779
+ value: "firefox",
1780
+ },
1781
+ ],
1782
+ },
1783
+ ],
1784
+ },
1785
+ {
1786
+ platform: "web",
1787
+ browser: "chrome",
1788
+ version: "1.0",
1789
+ },
1790
+ ),
1791
+ ).toEqual({
1792
+ and: [
1793
+ "*",
1794
+ {
1795
+ or: [
1796
+ {
1797
+ and: ["*", "*"],
1798
+ },
1799
+ {
1800
+ attribute: "browser",
1801
+ operator: "equals",
1802
+ value: "firefox",
1803
+ },
1804
+ ],
1805
+ },
1806
+ ],
1807
+ });
1808
+ });
1809
+
1810
+ test("arrays with nested conditions", function () {
1811
+ // Array with AND condition
1812
+ expect(
1813
+ buildScopedCondition(
1814
+ datafileReader,
1815
+ [
1816
+ {
1817
+ attribute: "platform",
1818
+ operator: "equals",
1819
+ value: "web",
1820
+ },
1821
+ {
1822
+ and: [
1823
+ {
1824
+ attribute: "browser",
1825
+ operator: "equals",
1826
+ value: "chrome",
1827
+ },
1828
+ {
1829
+ attribute: "version",
1830
+ operator: "equals",
1831
+ value: "1.0",
1832
+ },
1833
+ ],
1834
+ },
1835
+ ],
1836
+ {
1837
+ platform: "web",
1838
+ browser: "chrome",
1839
+ version: "1.0",
1840
+ },
1841
+ ),
1842
+ ).toEqual([
1843
+ "*",
1844
+ {
1845
+ and: ["*", "*"],
1846
+ },
1847
+ ]);
1848
+
1849
+ // Array with OR condition
1850
+ expect(
1851
+ buildScopedCondition(
1852
+ datafileReader,
1853
+ [
1854
+ {
1855
+ attribute: "platform",
1856
+ operator: "equals",
1857
+ value: "web",
1858
+ },
1859
+ {
1860
+ or: [
1861
+ {
1862
+ attribute: "browser",
1863
+ operator: "equals",
1864
+ value: "chrome",
1865
+ },
1866
+ {
1867
+ attribute: "browser",
1868
+ operator: "equals",
1869
+ value: "firefox",
1870
+ },
1871
+ ],
1872
+ },
1873
+ ],
1874
+ {
1875
+ platform: "web",
1876
+ browser: "chrome",
1877
+ },
1878
+ ),
1879
+ ).toEqual([
1880
+ "*",
1881
+ {
1882
+ or: [
1883
+ "*",
1884
+ {
1885
+ attribute: "browser",
1886
+ operator: "equals",
1887
+ value: "firefox",
1888
+ },
1889
+ ],
1890
+ },
1891
+ ]);
1892
+ });
1893
+
1894
+ test("edge cases", function () {
1895
+ // Empty context
1896
+ expect(
1897
+ buildScopedCondition(
1898
+ datafileReader,
1899
+ {
1900
+ attribute: "platform",
1901
+ operator: "equals",
1902
+ value: "web",
1903
+ },
1904
+ {},
1905
+ ),
1906
+ ).toEqual({
1907
+ attribute: "platform",
1908
+ operator: "equals",
1909
+ value: "web",
1910
+ });
1911
+
1912
+ // Empty array
1913
+ expect(buildScopedCondition(datafileReader, [], {})).toEqual([]);
1914
+
1915
+ // "*" in array
1916
+ expect(
1917
+ buildScopedCondition(
1918
+ datafileReader,
1919
+ [
1920
+ "*",
1921
+ {
1922
+ attribute: "platform",
1923
+ operator: "equals",
1924
+ value: "web",
1925
+ },
1926
+ ],
1927
+ {
1928
+ platform: "web",
1929
+ },
1930
+ ),
1931
+ ).toEqual(["*", "*"]);
1932
+
1933
+ // AND with empty array
1934
+ expect(
1935
+ buildScopedCondition(
1936
+ datafileReader,
1937
+ {
1938
+ and: [],
1939
+ },
1940
+ {},
1941
+ ),
1942
+ ).toEqual({
1943
+ and: [],
1944
+ });
1945
+
1946
+ // OR with empty array
1947
+ expect(
1948
+ buildScopedCondition(
1949
+ datafileReader,
1950
+ {
1951
+ or: [],
1952
+ },
1953
+ {},
1954
+ ),
1955
+ ).toEqual({
1956
+ or: [],
1957
+ });
1958
+ });
1959
+ });
1960
+
1961
+ describe("removeRedundantConditions", function () {
1962
+ test("removeRedundantConditions is a function", function () {
1963
+ expect(removeRedundantConditions).toBeInstanceOf(Function);
1964
+ });
1965
+
1966
+ test("simple cases", function () {
1967
+ // * is *
1968
+ expect(removeRedundantConditions("*")).toEqual("*");
1969
+
1970
+ // [*, *, *] is "*"
1971
+ expect(removeRedundantConditions(["*", "*", "*"])).toEqual("*");
1972
+
1973
+ // Array with mixed conditions
1974
+ expect(
1975
+ removeRedundantConditions([
1976
+ "*",
1977
+ {
1978
+ attribute: "platform",
1979
+ operator: "equals",
1980
+ value: "web",
1981
+ },
1982
+ "*",
1983
+ {
1984
+ attribute: "browser",
1985
+ operator: "equals",
1986
+ value: "chrome",
1987
+ },
1988
+ ]),
1989
+ ).toEqual([
1990
+ {
1991
+ attribute: "platform",
1992
+ operator: "equals",
1993
+ value: "web",
1994
+ },
1995
+ {
1996
+ attribute: "browser",
1997
+ operator: "equals",
1998
+ value: "chrome",
1999
+ },
2000
+ ]);
2001
+ });
2002
+
2003
+ test("AND conditions", function () {
2004
+ // All "*" in AND
2005
+ expect(removeRedundantConditions({ and: ["*", "*", "*"] })).toEqual("*");
2006
+
2007
+ // Mixed with "*" in AND
2008
+ expect(
2009
+ removeRedundantConditions({
2010
+ and: [
2011
+ "*",
2012
+ {
2013
+ attribute: "platform",
2014
+ operator: "equals",
2015
+ value: "web",
2016
+ },
2017
+ "*",
2018
+ ],
2019
+ }),
2020
+ ).toEqual({
2021
+ and: [
2022
+ {
2023
+ attribute: "platform",
2024
+ operator: "equals",
2025
+ value: "web",
2026
+ },
2027
+ ],
2028
+ });
2029
+
2030
+ // Multiple non-* conditions in AND
2031
+ expect(
2032
+ removeRedundantConditions({
2033
+ and: [
2034
+ "*",
2035
+ {
2036
+ attribute: "platform",
2037
+ operator: "equals",
2038
+ value: "web",
2039
+ },
2040
+ "*",
2041
+ {
2042
+ attribute: "browser",
2043
+ operator: "equals",
2044
+ value: "chrome",
2045
+ },
2046
+ "*",
2047
+ ],
2048
+ }),
2049
+ ).toEqual({
2050
+ and: [
2051
+ {
2052
+ attribute: "platform",
2053
+ operator: "equals",
2054
+ value: "web",
2055
+ },
2056
+ {
2057
+ attribute: "browser",
2058
+ operator: "equals",
2059
+ value: "chrome",
2060
+ },
2061
+ ],
2062
+ });
2063
+ });
2064
+
2065
+ test("OR conditions", function () {
2066
+ // All "*" in OR
2067
+ expect(removeRedundantConditions({ or: ["*", "*", "*"] })).toEqual("*");
2068
+
2069
+ // Mixed with "*" in OR
2070
+ expect(
2071
+ removeRedundantConditions({
2072
+ or: [
2073
+ "*",
2074
+ {
2075
+ attribute: "platform",
2076
+ operator: "equals",
2077
+ value: "web",
2078
+ },
2079
+ "*",
2080
+ ],
2081
+ }),
2082
+ ).toEqual({
2083
+ or: [
2084
+ {
2085
+ attribute: "platform",
2086
+ operator: "equals",
2087
+ value: "web",
2088
+ },
2089
+ ],
2090
+ });
2091
+
2092
+ // Multiple non-* conditions in OR
2093
+ expect(
2094
+ removeRedundantConditions({
2095
+ or: [
2096
+ "*",
2097
+ {
2098
+ attribute: "platform",
2099
+ operator: "equals",
2100
+ value: "web",
2101
+ },
2102
+ "*",
2103
+ {
2104
+ attribute: "platform",
2105
+ operator: "equals",
2106
+ value: "mobile",
2107
+ },
2108
+ "*",
2109
+ ],
2110
+ }),
2111
+ ).toEqual({
2112
+ or: [
2113
+ {
2114
+ attribute: "platform",
2115
+ operator: "equals",
2116
+ value: "web",
2117
+ },
2118
+ {
2119
+ attribute: "platform",
2120
+ operator: "equals",
2121
+ value: "mobile",
2122
+ },
2123
+ ],
2124
+ });
2125
+ });
2126
+
2127
+ test("NOT conditions", function () {
2128
+ // All "*" in NOT
2129
+ expect(removeRedundantConditions({ not: ["*", "*", "*"] })).toEqual("*");
2130
+
2131
+ // Mixed with "*" in NOT
2132
+ expect(
2133
+ removeRedundantConditions({
2134
+ not: [
2135
+ "*",
2136
+ {
2137
+ attribute: "platform",
2138
+ operator: "equals",
2139
+ value: "web",
2140
+ },
2141
+ "*",
2142
+ ],
2143
+ }),
2144
+ ).toEqual({
2145
+ not: [
2146
+ {
2147
+ attribute: "platform",
2148
+ operator: "equals",
2149
+ value: "web",
2150
+ },
2151
+ ],
2152
+ });
2153
+ });
2154
+
2155
+ test("nested AND conditions", function () {
2156
+ // Nested AND with all "*"
2157
+ expect(
2158
+ removeRedundantConditions({
2159
+ and: [
2160
+ "*",
2161
+ {
2162
+ and: ["*", "*", "*"],
2163
+ },
2164
+ "*",
2165
+ ],
2166
+ }),
2167
+ ).toEqual("*");
2168
+
2169
+ // Nested AND with mixed conditions
2170
+ expect(
2171
+ removeRedundantConditions({
2172
+ and: [
2173
+ "*",
2174
+ {
2175
+ attribute: "platform",
2176
+ operator: "equals",
2177
+ value: "web",
2178
+ },
2179
+ {
2180
+ and: [
2181
+ "*",
2182
+ {
2183
+ attribute: "browser",
2184
+ operator: "equals",
2185
+ value: "chrome",
2186
+ },
2187
+ "*",
2188
+ ],
2189
+ },
2190
+ "*",
2191
+ ],
2192
+ }),
2193
+ ).toEqual({
2194
+ and: [
2195
+ {
2196
+ attribute: "platform",
2197
+ operator: "equals",
2198
+ value: "web",
2199
+ },
2200
+ {
2201
+ and: [
2202
+ {
2203
+ attribute: "browser",
2204
+ operator: "equals",
2205
+ value: "chrome",
2206
+ },
2207
+ ],
2208
+ },
2209
+ ],
2210
+ });
2211
+
2212
+ // Deeply nested AND
2213
+ expect(
2214
+ removeRedundantConditions({
2215
+ and: [
2216
+ "*",
2217
+ {
2218
+ and: [
2219
+ "*",
2220
+ {
2221
+ and: ["*", "*", "*"],
2222
+ },
2223
+ "*",
2224
+ ],
2225
+ },
2226
+ "*",
2227
+ ],
2228
+ }),
2229
+ ).toEqual("*");
2230
+ });
2231
+
2232
+ test("nested OR conditions", function () {
2233
+ // Nested OR with all "*"
2234
+ expect(
2235
+ removeRedundantConditions({
2236
+ or: [
2237
+ "*",
2238
+ {
2239
+ or: ["*", "*", "*"],
2240
+ },
2241
+ "*",
2242
+ ],
2243
+ }),
2244
+ ).toEqual("*");
2245
+
2246
+ // Nested OR with mixed conditions
2247
+ expect(
2248
+ removeRedundantConditions({
2249
+ or: [
2250
+ "*",
2251
+ {
2252
+ attribute: "platform",
2253
+ operator: "equals",
2254
+ value: "web",
2255
+ },
2256
+ {
2257
+ or: [
2258
+ "*",
2259
+ {
2260
+ attribute: "platform",
2261
+ operator: "equals",
2262
+ value: "mobile",
2263
+ },
2264
+ "*",
2265
+ ],
2266
+ },
2267
+ "*",
2268
+ ],
2269
+ }),
2270
+ ).toEqual({
2271
+ or: [
2272
+ {
2273
+ attribute: "platform",
2274
+ operator: "equals",
2275
+ value: "web",
2276
+ },
2277
+ {
2278
+ or: [
2279
+ {
2280
+ attribute: "platform",
2281
+ operator: "equals",
2282
+ value: "mobile",
2283
+ },
2284
+ ],
2285
+ },
2286
+ ],
2287
+ });
2288
+ });
2289
+
2290
+ test("nested NOT conditions", function () {
2291
+ // Nested NOT with all "*"
2292
+ expect(
2293
+ removeRedundantConditions({
2294
+ not: [
2295
+ "*",
2296
+ {
2297
+ not: ["*", "*", "*"],
2298
+ },
2299
+ "*",
2300
+ ],
2301
+ }),
2302
+ ).toEqual("*");
2303
+
2304
+ // Nested NOT with mixed conditions
2305
+ expect(
2306
+ removeRedundantConditions({
2307
+ not: [
2308
+ "*",
2309
+ {
2310
+ attribute: "platform",
2311
+ operator: "equals",
2312
+ value: "web",
2313
+ },
2314
+ {
2315
+ not: [
2316
+ "*",
2317
+ {
2318
+ attribute: "browser",
2319
+ operator: "equals",
2320
+ value: "chrome",
2321
+ },
2322
+ "*",
2323
+ ],
2324
+ },
2325
+ "*",
2326
+ ],
2327
+ }),
2328
+ ).toEqual({
2329
+ not: [
2330
+ {
2331
+ attribute: "platform",
2332
+ operator: "equals",
2333
+ value: "web",
2334
+ },
2335
+ {
2336
+ not: [
2337
+ {
2338
+ attribute: "browser",
2339
+ operator: "equals",
2340
+ value: "chrome",
2341
+ },
2342
+ ],
2343
+ },
2344
+ ],
2345
+ });
2346
+ });
2347
+
2348
+ test("mixed nested conditions", function () {
2349
+ // AND with nested OR
2350
+ expect(
2351
+ removeRedundantConditions({
2352
+ and: [
2353
+ "*",
2354
+ {
2355
+ attribute: "platform",
2356
+ operator: "equals",
2357
+ value: "web",
2358
+ },
2359
+ {
2360
+ or: [
2361
+ "*",
2362
+ {
2363
+ attribute: "browser",
2364
+ operator: "equals",
2365
+ value: "chrome",
2366
+ },
2367
+ "*",
2368
+ ],
2369
+ },
2370
+ "*",
2371
+ ],
2372
+ }),
2373
+ ).toEqual({
2374
+ and: [
2375
+ {
2376
+ attribute: "platform",
2377
+ operator: "equals",
2378
+ value: "web",
2379
+ },
2380
+ {
2381
+ or: [
2382
+ {
2383
+ attribute: "browser",
2384
+ operator: "equals",
2385
+ value: "chrome",
2386
+ },
2387
+ ],
2388
+ },
2389
+ ],
2390
+ });
2391
+
2392
+ // OR with nested AND
2393
+ expect(
2394
+ removeRedundantConditions({
2395
+ or: [
2396
+ "*",
2397
+ {
2398
+ attribute: "platform",
2399
+ operator: "equals",
2400
+ value: "web",
2401
+ },
2402
+ {
2403
+ and: [
2404
+ "*",
2405
+ {
2406
+ attribute: "browser",
2407
+ operator: "equals",
2408
+ value: "chrome",
2409
+ },
2410
+ "*",
2411
+ ],
2412
+ },
2413
+ "*",
2414
+ ],
2415
+ }),
2416
+ ).toEqual({
2417
+ or: [
2418
+ {
2419
+ attribute: "platform",
2420
+ operator: "equals",
2421
+ value: "web",
2422
+ },
2423
+ {
2424
+ and: [
2425
+ {
2426
+ attribute: "browser",
2427
+ operator: "equals",
2428
+ value: "chrome",
2429
+ },
2430
+ ],
2431
+ },
2432
+ ],
2433
+ });
2434
+
2435
+ // AND with nested NOT
2436
+ expect(
2437
+ removeRedundantConditions({
2438
+ and: [
2439
+ "*",
2440
+ {
2441
+ attribute: "platform",
2442
+ operator: "equals",
2443
+ value: "web",
2444
+ },
2445
+ {
2446
+ not: [
2447
+ "*",
2448
+ {
2449
+ attribute: "browser",
2450
+ operator: "equals",
2451
+ value: "safari",
2452
+ },
2453
+ "*",
2454
+ ],
2455
+ },
2456
+ "*",
2457
+ ],
2458
+ }),
2459
+ ).toEqual({
2460
+ and: [
2461
+ {
2462
+ attribute: "platform",
2463
+ operator: "equals",
2464
+ value: "web",
2465
+ },
2466
+ {
2467
+ not: [
2468
+ {
2469
+ attribute: "browser",
2470
+ operator: "equals",
2471
+ value: "safari",
2472
+ },
2473
+ ],
2474
+ },
2475
+ ],
2476
+ });
2477
+
2478
+ // Complex nested structure
2479
+ expect(
2480
+ removeRedundantConditions({
2481
+ and: [
2482
+ "*",
2483
+ {
2484
+ attribute: "platform",
2485
+ operator: "equals",
2486
+ value: "web",
2487
+ },
2488
+ {
2489
+ or: [
2490
+ "*",
2491
+ {
2492
+ and: [
2493
+ "*",
2494
+ {
2495
+ attribute: "browser",
2496
+ operator: "equals",
2497
+ value: "chrome",
2498
+ },
2499
+ "*",
2500
+ ],
2501
+ },
2502
+ "*",
2503
+ ],
2504
+ },
2505
+ "*",
2506
+ ],
2507
+ }),
2508
+ ).toEqual({
2509
+ and: [
2510
+ {
2511
+ attribute: "platform",
2512
+ operator: "equals",
2513
+ value: "web",
2514
+ },
2515
+ {
2516
+ or: [
2517
+ {
2518
+ and: [
2519
+ {
2520
+ attribute: "browser",
2521
+ operator: "equals",
2522
+ value: "chrome",
2523
+ },
2524
+ ],
2525
+ },
2526
+ ],
2527
+ },
2528
+ ],
2529
+ });
2530
+ });
2531
+
2532
+ test("arrays with nested conditions", function () {
2533
+ // Array with nested AND
2534
+ expect(
2535
+ removeRedundantConditions([
2536
+ "*",
2537
+ {
2538
+ and: [
2539
+ "*",
2540
+ {
2541
+ attribute: "platform",
2542
+ operator: "equals",
2543
+ value: "web",
2544
+ },
2545
+ "*",
2546
+ ],
2547
+ },
2548
+ "*",
2549
+ ]),
2550
+ ).toEqual([
2551
+ {
2552
+ and: [
2553
+ {
2554
+ attribute: "platform",
2555
+ operator: "equals",
2556
+ value: "web",
2557
+ },
2558
+ ],
2559
+ },
2560
+ ]);
2561
+
2562
+ // Array with multiple nested conditions
2563
+ expect(
2564
+ removeRedundantConditions([
2565
+ "*",
2566
+ {
2567
+ attribute: "platform",
2568
+ operator: "equals",
2569
+ value: "web",
2570
+ },
2571
+ {
2572
+ or: [
2573
+ "*",
2574
+ {
2575
+ attribute: "browser",
2576
+ operator: "equals",
2577
+ value: "chrome",
2578
+ },
2579
+ "*",
2580
+ ],
2581
+ },
2582
+ "*",
2583
+ ]),
2584
+ ).toEqual([
2585
+ {
2586
+ attribute: "platform",
2587
+ operator: "equals",
2588
+ value: "web",
2589
+ },
2590
+ {
2591
+ or: [
2592
+ {
2593
+ attribute: "browser",
2594
+ operator: "equals",
2595
+ value: "chrome",
2596
+ },
2597
+ ],
2598
+ },
2599
+ ]);
2600
+ });
2601
+
2602
+ test("edge cases", function () {
2603
+ // Single condition after filtering
2604
+ expect(
2605
+ removeRedundantConditions({
2606
+ and: [
2607
+ "*",
2608
+ {
2609
+ attribute: "platform",
2610
+ operator: "equals",
2611
+ value: "web",
2612
+ },
2613
+ "*",
2614
+ ],
2615
+ }),
2616
+ ).toEqual({
2617
+ and: [
2618
+ {
2619
+ attribute: "platform",
2620
+ operator: "equals",
2621
+ value: "web",
2622
+ },
2623
+ ],
2624
+ });
2625
+
2626
+ // Plain condition object (no change)
2627
+ expect(
2628
+ removeRedundantConditions({
2629
+ attribute: "platform",
2630
+ operator: "equals",
2631
+ value: "web",
2632
+ }),
2633
+ ).toEqual({
2634
+ attribute: "platform",
2635
+ operator: "equals",
2636
+ value: "web",
2637
+ });
2638
+
2639
+ // Array with single non-* condition
2640
+ expect(
2641
+ removeRedundantConditions([
2642
+ "*",
2643
+ {
2644
+ attribute: "platform",
2645
+ operator: "equals",
2646
+ value: "web",
2647
+ },
2648
+ "*",
2649
+ ]),
2650
+ ).toEqual([
2651
+ {
2652
+ attribute: "platform",
2653
+ operator: "equals",
2654
+ value: "web",
2655
+ },
2656
+ ]);
2657
+ });
2658
+ });
2659
+ });