@featurevisor/core 2.5.0 → 2.6.7

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 (91) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/LICENSE +1 -1
  3. package/coverage/clover.xml +444 -78
  4. package/coverage/coverage-final.json +9 -3
  5. package/coverage/lcov-report/index.html +42 -42
  6. package/coverage/lcov-report/lib/builder/allocator.js.html +1 -1
  7. package/coverage/lcov-report/lib/builder/buildScopedConditions.js.html +373 -0
  8. package/coverage/lcov-report/lib/builder/buildScopedDatafile.js.html +403 -0
  9. package/coverage/lcov-report/lib/builder/buildScopedSegments.js.html +379 -0
  10. package/coverage/lcov-report/lib/builder/index.html +53 -8
  11. package/coverage/lcov-report/lib/builder/revision.js.html +1 -1
  12. package/coverage/lcov-report/lib/builder/traffic.js.html +1 -1
  13. package/coverage/lcov-report/lib/list/index.html +14 -14
  14. package/coverage/lcov-report/lib/list/matrix.js.html +23 -8
  15. package/coverage/lcov-report/lib/tester/helpers.js.html +1 -1
  16. package/coverage/lcov-report/lib/tester/index.html +1 -1
  17. package/coverage/lcov-report/src/builder/allocator.ts.html +1 -1
  18. package/coverage/lcov-report/src/builder/buildScopedConditions.ts.html +487 -0
  19. package/coverage/lcov-report/src/builder/buildScopedDatafile.ts.html +604 -0
  20. package/coverage/lcov-report/src/builder/buildScopedSegments.ts.html +544 -0
  21. package/coverage/lcov-report/src/builder/index.html +55 -10
  22. package/coverage/lcov-report/src/builder/revision.ts.html +1 -1
  23. package/coverage/lcov-report/src/builder/traffic.ts.html +3 -3
  24. package/coverage/lcov-report/src/list/index.html +14 -14
  25. package/coverage/lcov-report/src/list/matrix.ts.html +33 -12
  26. package/coverage/lcov-report/src/tester/helpers.ts.html +1 -1
  27. package/coverage/lcov-report/src/tester/index.html +1 -1
  28. package/coverage/lcov.info +810 -129
  29. package/jest.config.js +8 -0
  30. package/lib/builder/buildDatafile.d.ts +10 -0
  31. package/lib/builder/buildDatafile.js +27 -0
  32. package/lib/builder/buildDatafile.js.map +1 -1
  33. package/lib/builder/buildProject.d.ts +2 -0
  34. package/lib/builder/buildProject.js +38 -4
  35. package/lib/builder/buildProject.js.map +1 -1
  36. package/lib/builder/buildScopedConditions.d.ts +5 -0
  37. package/lib/builder/buildScopedConditions.js +97 -0
  38. package/lib/builder/buildScopedConditions.js.map +1 -0
  39. package/lib/builder/buildScopedConditions.spec.d.ts +1 -0
  40. package/lib/builder/buildScopedConditions.spec.js +2167 -0
  41. package/lib/builder/buildScopedConditions.spec.js.map +1 -0
  42. package/lib/builder/buildScopedDatafile.d.ts +2 -0
  43. package/lib/builder/buildScopedDatafile.js +107 -0
  44. package/lib/builder/buildScopedDatafile.js.map +1 -0
  45. package/lib/builder/buildScopedDatafile.spec.d.ts +1 -0
  46. package/lib/builder/buildScopedDatafile.spec.js +1988 -0
  47. package/lib/builder/buildScopedDatafile.spec.js.map +1 -0
  48. package/lib/builder/buildScopedSegments.d.ts +5 -0
  49. package/lib/builder/buildScopedSegments.js +99 -0
  50. package/lib/builder/buildScopedSegments.js.map +1 -0
  51. package/lib/builder/buildScopedSegments.spec.d.ts +1 -0
  52. package/lib/builder/buildScopedSegments.spec.js +1062 -0
  53. package/lib/builder/buildScopedSegments.spec.js.map +1 -0
  54. package/lib/builder/index.d.ts +1 -0
  55. package/lib/builder/index.js +1 -0
  56. package/lib/builder/index.js.map +1 -1
  57. package/lib/config/projectConfig.d.ts +9 -1
  58. package/lib/config/projectConfig.js +1 -0
  59. package/lib/config/projectConfig.js.map +1 -1
  60. package/lib/datasource/adapter.d.ts +3 -1
  61. package/lib/datasource/adapter.js.map +1 -1
  62. package/lib/datasource/filesystemAdapter.js +3 -1
  63. package/lib/datasource/filesystemAdapter.js.map +1 -1
  64. package/lib/linter/testSchema.d.ts +5 -0
  65. package/lib/linter/testSchema.js +8 -0
  66. package/lib/linter/testSchema.js.map +1 -1
  67. package/lib/list/matrix.js +5 -0
  68. package/lib/list/matrix.js.map +1 -1
  69. package/lib/tester/testFeature.d.ts +5 -3
  70. package/lib/tester/testFeature.js +34 -9
  71. package/lib/tester/testFeature.js.map +1 -1
  72. package/lib/tester/testProject.d.ts +3 -2
  73. package/lib/tester/testProject.js +40 -6
  74. package/lib/tester/testProject.js.map +1 -1
  75. package/package.json +5 -5
  76. package/src/builder/buildDatafile.ts +47 -0
  77. package/src/builder/buildProject.ts +58 -3
  78. package/src/builder/buildScopedConditions.spec.ts +2659 -0
  79. package/src/builder/buildScopedConditions.ts +134 -0
  80. package/src/builder/buildScopedDatafile.spec.ts +2236 -0
  81. package/src/builder/buildScopedDatafile.ts +173 -0
  82. package/src/builder/buildScopedSegments.spec.ts +1573 -0
  83. package/src/builder/buildScopedSegments.ts +153 -0
  84. package/src/builder/index.ts +1 -0
  85. package/src/config/projectConfig.ts +11 -1
  86. package/src/datasource/adapter.ts +4 -1
  87. package/src/datasource/filesystemAdapter.ts +4 -1
  88. package/src/linter/testSchema.ts +12 -0
  89. package/src/list/matrix.ts +7 -0
  90. package/src/tester/testFeature.ts +50 -16
  91. package/src/tester/testProject.ts +68 -8
@@ -0,0 +1,1573 @@
1
+ import type { DatafileContent } from "@featurevisor/types";
2
+
3
+ import {
4
+ buildScopedSegments,
5
+ removeRedundantGroupSegments,
6
+ buildScopedGroupSegments,
7
+ } from "./buildScopedSegments";
8
+ import { DatafileReader, createLogger } from "@featurevisor/sdk";
9
+
10
+ describe("core: buildScopedSegments", function () {
11
+ describe("buildScopedSegments", function () {
12
+ const datafileWithSegments: DatafileContent = {
13
+ schemaVersion: "2",
14
+ revision: "unknown",
15
+ segments: {
16
+ web: {
17
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
18
+ },
19
+ mobile: {
20
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
21
+ },
22
+ chrome: {
23
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
24
+ },
25
+ safari: {
26
+ conditions: [{ attribute: "browser", operator: "equals", value: "safari" }],
27
+ },
28
+ premium: {
29
+ conditions: [
30
+ {
31
+ and: [
32
+ { attribute: "tier", operator: "equals", value: "premium" },
33
+ { attribute: "status", operator: "equals", value: "active" },
34
+ ],
35
+ },
36
+ ],
37
+ },
38
+ us: {
39
+ conditions: [{ attribute: "country", operator: "equals", value: "us" }],
40
+ },
41
+ eu: {
42
+ conditions: [{ attribute: "country", operator: "equals", value: "eu" }],
43
+ },
44
+ },
45
+ features: {},
46
+ };
47
+
48
+ const datafileReaderWithSegments = new DatafileReader({
49
+ datafile: datafileWithSegments,
50
+ logger: createLogger({ level: "fatal" }),
51
+ });
52
+
53
+ test("buildScopedSegments is a function", function () {
54
+ expect(buildScopedSegments).toBeInstanceOf(Function);
55
+ });
56
+
57
+ test("simple cases", function () {
58
+ // "*" remains "*"
59
+ expect(buildScopedSegments(datafileReaderWithSegments, "*", {})).toEqual("*");
60
+
61
+ // Plain segment that matches
62
+ expect(
63
+ buildScopedSegments(datafileReaderWithSegments, "web", {
64
+ platform: "web",
65
+ }),
66
+ ).toEqual("*");
67
+
68
+ // Plain segment that doesn't match
69
+ expect(
70
+ buildScopedSegments(datafileReaderWithSegments, "web", {
71
+ platform: "mobile",
72
+ }),
73
+ ).toEqual("web");
74
+
75
+ // Array of segments - partial match (redundant "*" removed)
76
+ expect(
77
+ buildScopedSegments(datafileReaderWithSegments, ["web", "chrome"], {
78
+ platform: "web",
79
+ }),
80
+ ).toEqual(["chrome"]);
81
+
82
+ // Array of segments - full match (all "*" becomes "*")
83
+ expect(
84
+ buildScopedSegments(datafileReaderWithSegments, ["web", "chrome"], {
85
+ platform: "web",
86
+ browser: "chrome",
87
+ }),
88
+ ).toEqual("*");
89
+
90
+ // Array of segments - no match
91
+ expect(
92
+ buildScopedSegments(datafileReaderWithSegments, ["web", "chrome"], {
93
+ platform: "mobile",
94
+ browser: "safari",
95
+ }),
96
+ ).toEqual(["web", "chrome"]);
97
+ });
98
+
99
+ test("AND group segments", function () {
100
+ // AND with all matching segments (all "*" becomes "*")
101
+ expect(
102
+ buildScopedSegments(
103
+ datafileReaderWithSegments,
104
+ {
105
+ and: ["web", "chrome"],
106
+ },
107
+ {
108
+ platform: "web",
109
+ browser: "chrome",
110
+ },
111
+ ),
112
+ ).toEqual("*");
113
+
114
+ // AND with partial match (redundant "*" removed)
115
+ expect(
116
+ buildScopedSegments(
117
+ datafileReaderWithSegments,
118
+ {
119
+ and: ["web", "chrome"],
120
+ },
121
+ {
122
+ platform: "web",
123
+ },
124
+ ),
125
+ ).toEqual({
126
+ and: ["chrome"],
127
+ });
128
+
129
+ // AND with no matches
130
+ expect(
131
+ buildScopedSegments(
132
+ datafileReaderWithSegments,
133
+ {
134
+ and: ["web", "chrome"],
135
+ },
136
+ {
137
+ platform: "mobile",
138
+ browser: "safari",
139
+ },
140
+ ),
141
+ ).toEqual({
142
+ and: ["web", "chrome"],
143
+ });
144
+
145
+ // AND with "*" in it (all "*" becomes "*")
146
+ expect(
147
+ buildScopedSegments(
148
+ datafileReaderWithSegments,
149
+ {
150
+ and: ["*", "chrome"],
151
+ },
152
+ {
153
+ browser: "chrome",
154
+ },
155
+ ),
156
+ ).toEqual("*");
157
+ });
158
+
159
+ test("OR group segments", function () {
160
+ // OR with all matching segments (all "*" becomes "*")
161
+ expect(
162
+ buildScopedSegments(
163
+ datafileReaderWithSegments,
164
+ {
165
+ or: ["web", "chrome"],
166
+ },
167
+ {
168
+ platform: "web",
169
+ browser: "chrome",
170
+ },
171
+ ),
172
+ ).toEqual("*");
173
+
174
+ // OR with partial match (redundant "*" removed, but OR structure remains)
175
+ expect(
176
+ buildScopedSegments(
177
+ datafileReaderWithSegments,
178
+ {
179
+ or: ["web", "chrome"],
180
+ },
181
+ {
182
+ platform: "web",
183
+ },
184
+ ),
185
+ ).toEqual({
186
+ or: ["chrome"],
187
+ });
188
+
189
+ // OR with no matches
190
+ expect(
191
+ buildScopedSegments(
192
+ datafileReaderWithSegments,
193
+ {
194
+ or: ["web", "mobile"],
195
+ },
196
+ {
197
+ platform: "desktop",
198
+ },
199
+ ),
200
+ ).toEqual({
201
+ or: ["web", "mobile"],
202
+ });
203
+ });
204
+
205
+ test("NOT group segments", function () {
206
+ // NOT with matching segment (all "*" becomes "*")
207
+ expect(
208
+ buildScopedSegments(
209
+ datafileReaderWithSegments,
210
+ {
211
+ not: ["web"],
212
+ },
213
+ {
214
+ platform: "web",
215
+ },
216
+ ),
217
+ ).toEqual("*");
218
+
219
+ // NOT with non-matching segment
220
+ expect(
221
+ buildScopedSegments(
222
+ datafileReaderWithSegments,
223
+ {
224
+ not: ["web"],
225
+ },
226
+ {
227
+ platform: "mobile",
228
+ },
229
+ ),
230
+ ).toEqual({
231
+ not: ["web"],
232
+ });
233
+
234
+ // NOT with multiple segments (all "*" becomes "*")
235
+ expect(
236
+ buildScopedSegments(
237
+ datafileReaderWithSegments,
238
+ {
239
+ not: ["web", "chrome"],
240
+ },
241
+ {
242
+ platform: "web",
243
+ browser: "chrome",
244
+ },
245
+ ),
246
+ ).toEqual("*");
247
+ });
248
+
249
+ test("nested AND group segments", function () {
250
+ // Nested AND with all matching (all "*" becomes "*")
251
+ expect(
252
+ buildScopedSegments(
253
+ datafileReaderWithSegments,
254
+ {
255
+ and: [
256
+ "web",
257
+ {
258
+ and: ["chrome", "us"],
259
+ },
260
+ ],
261
+ },
262
+ {
263
+ platform: "web",
264
+ browser: "chrome",
265
+ country: "us",
266
+ },
267
+ ),
268
+ ).toEqual("*");
269
+
270
+ // Nested AND with partial match (redundant "*" removed)
271
+ expect(
272
+ buildScopedSegments(
273
+ datafileReaderWithSegments,
274
+ {
275
+ and: [
276
+ "web",
277
+ {
278
+ and: ["chrome", "us"],
279
+ },
280
+ ],
281
+ },
282
+ {
283
+ platform: "web",
284
+ browser: "chrome",
285
+ },
286
+ ),
287
+ ).toEqual({
288
+ and: [
289
+ {
290
+ and: ["us"],
291
+ },
292
+ ],
293
+ });
294
+ });
295
+
296
+ test("nested OR group segments", function () {
297
+ // Nested OR with outer match (redundant "*" removed, but inner OR remains)
298
+ expect(
299
+ buildScopedSegments(
300
+ datafileReaderWithSegments,
301
+ {
302
+ or: [
303
+ "web",
304
+ {
305
+ or: ["mobile", "chrome"],
306
+ },
307
+ ],
308
+ },
309
+ {
310
+ platform: "web",
311
+ },
312
+ ),
313
+ ).toEqual({
314
+ or: [
315
+ {
316
+ or: ["mobile", "chrome"],
317
+ },
318
+ ],
319
+ });
320
+
321
+ // Nested OR with inner match (redundant "*" removed, but outer OR remains)
322
+ expect(
323
+ buildScopedSegments(
324
+ datafileReaderWithSegments,
325
+ {
326
+ or: [
327
+ "web",
328
+ {
329
+ or: ["chrome", "safari"],
330
+ },
331
+ ],
332
+ },
333
+ {
334
+ browser: "chrome",
335
+ },
336
+ ),
337
+ ).toEqual({
338
+ or: [
339
+ "web",
340
+ {
341
+ or: ["safari"],
342
+ },
343
+ ],
344
+ });
345
+ });
346
+
347
+ test("nested NOT group segments", function () {
348
+ // Nested NOT (all "*" becomes "*")
349
+ expect(
350
+ buildScopedSegments(
351
+ datafileReaderWithSegments,
352
+ {
353
+ not: [
354
+ "web",
355
+ {
356
+ not: ["chrome"],
357
+ },
358
+ ],
359
+ },
360
+ {
361
+ platform: "web",
362
+ browser: "chrome",
363
+ },
364
+ ),
365
+ ).toEqual("*");
366
+ });
367
+
368
+ test("mixed nested group segments", function () {
369
+ // AND with nested OR (redundant "*" removed, but OR structure remains if not all match)
370
+ expect(
371
+ buildScopedSegments(
372
+ datafileReaderWithSegments,
373
+ {
374
+ and: [
375
+ "web",
376
+ {
377
+ or: ["chrome", "safari"],
378
+ },
379
+ ],
380
+ },
381
+ {
382
+ platform: "web",
383
+ browser: "chrome",
384
+ },
385
+ ),
386
+ ).toEqual({
387
+ and: [
388
+ {
389
+ or: ["safari"],
390
+ },
391
+ ],
392
+ });
393
+
394
+ // OR with nested AND (redundant "*" removed, but OR structure remains if not all match)
395
+ expect(
396
+ buildScopedSegments(
397
+ datafileReaderWithSegments,
398
+ {
399
+ or: [
400
+ "web",
401
+ {
402
+ and: ["mobile", "chrome"],
403
+ },
404
+ ],
405
+ },
406
+ {
407
+ platform: "mobile",
408
+ browser: "chrome",
409
+ },
410
+ ),
411
+ ).toEqual({
412
+ or: ["web"],
413
+ });
414
+
415
+ // AND with nested NOT (redundant "*" removed)
416
+ expect(
417
+ buildScopedSegments(
418
+ datafileReaderWithSegments,
419
+ {
420
+ and: [
421
+ "web",
422
+ {
423
+ not: ["mobile"],
424
+ },
425
+ ],
426
+ },
427
+ {
428
+ platform: "web",
429
+ },
430
+ ),
431
+ ).toEqual({
432
+ and: [
433
+ {
434
+ not: ["mobile"],
435
+ },
436
+ ],
437
+ });
438
+
439
+ // Complex nested structure (redundant "*" removed)
440
+ expect(
441
+ buildScopedSegments(
442
+ datafileReaderWithSegments,
443
+ {
444
+ and: [
445
+ "web",
446
+ {
447
+ or: [
448
+ {
449
+ and: ["chrome", "us"],
450
+ },
451
+ "safari",
452
+ ],
453
+ },
454
+ ],
455
+ },
456
+ {
457
+ platform: "web",
458
+ browser: "chrome",
459
+ country: "us",
460
+ },
461
+ ),
462
+ ).toEqual({
463
+ and: [
464
+ {
465
+ or: ["safari"],
466
+ },
467
+ ],
468
+ });
469
+ });
470
+
471
+ test("arrays with nested group segments", function () {
472
+ // Array with AND group segment (redundant "*" removed)
473
+ expect(
474
+ buildScopedSegments(
475
+ datafileReaderWithSegments,
476
+ [
477
+ "web",
478
+ {
479
+ and: ["chrome", "us"],
480
+ },
481
+ ],
482
+ {
483
+ platform: "web",
484
+ browser: "chrome",
485
+ country: "us",
486
+ },
487
+ ),
488
+ ).toEqual("*");
489
+
490
+ // Array with OR group segment (redundant "*" removed, but OR structure remains if not all match)
491
+ expect(
492
+ buildScopedSegments(
493
+ datafileReaderWithSegments,
494
+ [
495
+ "web",
496
+ {
497
+ or: ["chrome", "safari"],
498
+ },
499
+ ],
500
+ {
501
+ platform: "web",
502
+ browser: "chrome",
503
+ },
504
+ ),
505
+ ).toEqual([
506
+ {
507
+ or: ["safari"],
508
+ },
509
+ ]);
510
+ });
511
+
512
+ test("segments with complex conditions", function () {
513
+ // Segment with AND conditions in its definition
514
+ expect(
515
+ buildScopedSegments(datafileReaderWithSegments, "premium", {
516
+ tier: "premium",
517
+ status: "active",
518
+ }),
519
+ ).toEqual("*");
520
+
521
+ // Segment with AND conditions that doesn't match
522
+ expect(
523
+ buildScopedSegments(datafileReaderWithSegments, "premium", {
524
+ tier: "premium",
525
+ status: "inactive",
526
+ }),
527
+ ).toEqual("premium");
528
+ });
529
+
530
+ test("edge cases", function () {
531
+ // Empty context
532
+ expect(buildScopedSegments(datafileReaderWithSegments, "web", {})).toEqual("web");
533
+
534
+ // Empty array (all "*" becomes "*")
535
+ expect(buildScopedSegments(datafileReaderWithSegments, [], {})).toEqual("*");
536
+
537
+ // "*" in array with matching segment (all "*" becomes "*")
538
+ expect(
539
+ buildScopedSegments(datafileReaderWithSegments, ["*", "web"], {
540
+ platform: "web",
541
+ }),
542
+ ).toEqual("*");
543
+
544
+ // AND with empty array (all "*" becomes "*")
545
+ expect(
546
+ buildScopedSegments(
547
+ datafileReaderWithSegments,
548
+ {
549
+ and: [],
550
+ },
551
+ {},
552
+ ),
553
+ ).toEqual("*");
554
+
555
+ // OR with empty array (all "*" becomes "*")
556
+ expect(
557
+ buildScopedSegments(
558
+ datafileReaderWithSegments,
559
+ {
560
+ or: [],
561
+ },
562
+ {},
563
+ ),
564
+ ).toEqual("*");
565
+ });
566
+ });
567
+
568
+ describe("removeRedundantGroupSegments", function () {
569
+ test("removeRedundantGroupSegments is a function", function () {
570
+ expect(removeRedundantGroupSegments).toBeInstanceOf(Function);
571
+ });
572
+
573
+ test("simple cases", function () {
574
+ // * is *
575
+ expect(removeRedundantGroupSegments("*")).toEqual("*");
576
+
577
+ // [*, *, *] is "*"
578
+ expect(removeRedundantGroupSegments(["*", "*", "*"])).toEqual("*");
579
+
580
+ // Array with mixed group segments
581
+ expect(removeRedundantGroupSegments(["*", "web", "*", "chrome"])).toEqual(["web", "chrome"]);
582
+ });
583
+
584
+ test("AND group segments", function () {
585
+ // All "*" in AND
586
+ expect(removeRedundantGroupSegments({ and: ["*", "*", "*"] })).toEqual("*");
587
+
588
+ // Mixed with "*" in AND
589
+ expect(
590
+ removeRedundantGroupSegments({
591
+ and: ["*", "web", "*"],
592
+ }),
593
+ ).toEqual({
594
+ and: ["web"],
595
+ });
596
+
597
+ // Multiple non-* group segments in AND
598
+ expect(
599
+ removeRedundantGroupSegments({
600
+ and: ["*", "web", "*", "chrome", "*"],
601
+ }),
602
+ ).toEqual({
603
+ and: ["web", "chrome"],
604
+ });
605
+ });
606
+
607
+ test("OR group segments", function () {
608
+ // All "*" in OR
609
+ expect(removeRedundantGroupSegments({ or: ["*", "*", "*"] })).toEqual("*");
610
+
611
+ // Mixed with "*" in OR
612
+ expect(
613
+ removeRedundantGroupSegments({
614
+ or: ["*", "web", "*"],
615
+ }),
616
+ ).toEqual({
617
+ or: ["web"],
618
+ });
619
+
620
+ // Multiple non-* group segments in OR
621
+ expect(
622
+ removeRedundantGroupSegments({
623
+ or: ["*", "web", "*", "mobile", "*"],
624
+ }),
625
+ ).toEqual({
626
+ or: ["web", "mobile"],
627
+ });
628
+ });
629
+
630
+ test("NOT group segments", function () {
631
+ // All "*" in NOT
632
+ expect(removeRedundantGroupSegments({ not: ["*", "*", "*"] })).toEqual("*");
633
+
634
+ // Mixed with "*" in NOT
635
+ expect(
636
+ removeRedundantGroupSegments({
637
+ not: ["*", "web", "*"],
638
+ }),
639
+ ).toEqual({
640
+ not: ["web"],
641
+ });
642
+ });
643
+
644
+ test("nested AND group segments", function () {
645
+ // Nested AND with all "*"
646
+ expect(
647
+ removeRedundantGroupSegments({
648
+ and: ["*", { and: ["*", "*", "*"] }, "*"],
649
+ }),
650
+ ).toEqual("*");
651
+
652
+ // Nested AND with mixed group segments
653
+ expect(
654
+ removeRedundantGroupSegments({
655
+ and: [
656
+ "*",
657
+ "web",
658
+ {
659
+ and: ["*", "chrome", "*"],
660
+ },
661
+ "*",
662
+ ],
663
+ }),
664
+ ).toEqual({
665
+ and: [
666
+ "web",
667
+ {
668
+ and: ["chrome"],
669
+ },
670
+ ],
671
+ });
672
+
673
+ // Deeply nested AND
674
+ expect(
675
+ removeRedundantGroupSegments({
676
+ and: [
677
+ "*",
678
+ {
679
+ and: [
680
+ "*",
681
+ {
682
+ and: ["*", "*", "*"],
683
+ },
684
+ "*",
685
+ ],
686
+ },
687
+ "*",
688
+ ],
689
+ }),
690
+ ).toEqual("*");
691
+ });
692
+
693
+ test("nested OR group segments", function () {
694
+ // Nested OR with all "*"
695
+ expect(
696
+ removeRedundantGroupSegments({
697
+ or: ["*", { or: ["*", "*", "*"] }, "*"],
698
+ }),
699
+ ).toEqual("*");
700
+
701
+ // Nested OR with mixed group segments
702
+ expect(
703
+ removeRedundantGroupSegments({
704
+ or: [
705
+ "*",
706
+ "web",
707
+ {
708
+ or: ["*", "mobile", "*"],
709
+ },
710
+ "*",
711
+ ],
712
+ }),
713
+ ).toEqual({
714
+ or: [
715
+ "web",
716
+ {
717
+ or: ["mobile"],
718
+ },
719
+ ],
720
+ });
721
+ });
722
+
723
+ test("nested NOT group segments", function () {
724
+ // Nested NOT with all "*"
725
+ expect(
726
+ removeRedundantGroupSegments({
727
+ not: ["*", { not: ["*", "*", "*"] }, "*"],
728
+ }),
729
+ ).toEqual("*");
730
+
731
+ // Nested NOT with mixed group segments
732
+ expect(
733
+ removeRedundantGroupSegments({
734
+ not: [
735
+ "*",
736
+ "web",
737
+ {
738
+ not: ["*", "chrome", "*"],
739
+ },
740
+ "*",
741
+ ],
742
+ }),
743
+ ).toEqual({
744
+ not: [
745
+ "web",
746
+ {
747
+ not: ["chrome"],
748
+ },
749
+ ],
750
+ });
751
+ });
752
+
753
+ test("mixed nested group segments", function () {
754
+ // AND with nested OR
755
+ expect(
756
+ removeRedundantGroupSegments({
757
+ and: [
758
+ "*",
759
+ "web",
760
+ {
761
+ or: ["*", "chrome", "*"],
762
+ },
763
+ "*",
764
+ ],
765
+ }),
766
+ ).toEqual({
767
+ and: [
768
+ "web",
769
+ {
770
+ or: ["chrome"],
771
+ },
772
+ ],
773
+ });
774
+
775
+ // OR with nested AND
776
+ expect(
777
+ removeRedundantGroupSegments({
778
+ or: [
779
+ "*",
780
+ "web",
781
+ {
782
+ and: ["*", "chrome", "*"],
783
+ },
784
+ "*",
785
+ ],
786
+ }),
787
+ ).toEqual({
788
+ or: [
789
+ "web",
790
+ {
791
+ and: ["chrome"],
792
+ },
793
+ ],
794
+ });
795
+
796
+ // AND with nested NOT
797
+ expect(
798
+ removeRedundantGroupSegments({
799
+ and: [
800
+ "*",
801
+ "web",
802
+ {
803
+ not: ["*", "mobile", "*"],
804
+ },
805
+ "*",
806
+ ],
807
+ }),
808
+ ).toEqual({
809
+ and: [
810
+ "web",
811
+ {
812
+ not: ["mobile"],
813
+ },
814
+ ],
815
+ });
816
+
817
+ // Complex nested structure
818
+ expect(
819
+ removeRedundantGroupSegments({
820
+ and: [
821
+ "*",
822
+ "web",
823
+ {
824
+ or: [
825
+ "*",
826
+ {
827
+ and: ["*", "chrome", "*"],
828
+ },
829
+ "*",
830
+ ],
831
+ },
832
+ "*",
833
+ ],
834
+ }),
835
+ ).toEqual({
836
+ and: [
837
+ "web",
838
+ {
839
+ or: [
840
+ {
841
+ and: ["chrome"],
842
+ },
843
+ ],
844
+ },
845
+ ],
846
+ });
847
+ });
848
+
849
+ test("arrays with nested group segments", function () {
850
+ // Array with nested AND
851
+ expect(
852
+ removeRedundantGroupSegments([
853
+ "*",
854
+ {
855
+ and: ["*", "web", "*"],
856
+ },
857
+ "*",
858
+ ]),
859
+ ).toEqual([
860
+ {
861
+ and: ["web"],
862
+ },
863
+ ]);
864
+
865
+ // Array with multiple nested group segments
866
+ expect(
867
+ removeRedundantGroupSegments([
868
+ "*",
869
+ "web",
870
+ {
871
+ or: ["*", "chrome", "*"],
872
+ },
873
+ "*",
874
+ ]),
875
+ ).toEqual([
876
+ "web",
877
+ {
878
+ or: ["chrome"],
879
+ },
880
+ ]);
881
+ });
882
+
883
+ test("edge cases", function () {
884
+ // Single group segment after filtering
885
+ expect(
886
+ removeRedundantGroupSegments({
887
+ and: ["*", "web", "*"],
888
+ }),
889
+ ).toEqual({
890
+ and: ["web"],
891
+ });
892
+
893
+ // Plain string group segment (no change)
894
+ expect(removeRedundantGroupSegments("web")).toEqual("web");
895
+
896
+ // Array with single non-* group segment
897
+ expect(removeRedundantGroupSegments(["*", "web", "*"])).toEqual(["web"]);
898
+ });
899
+ });
900
+
901
+ describe("buildScopedGroupSegments", function () {
902
+ const datafileWithSegments: DatafileContent = {
903
+ schemaVersion: "2",
904
+ revision: "unknown",
905
+ segments: {
906
+ web: {
907
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
908
+ },
909
+ mobile: {
910
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
911
+ },
912
+ chrome: {
913
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
914
+ },
915
+ safari: {
916
+ conditions: [{ attribute: "browser", operator: "equals", value: "safari" }],
917
+ },
918
+ premium: {
919
+ conditions: [
920
+ {
921
+ and: [
922
+ { attribute: "tier", operator: "equals", value: "premium" },
923
+ { attribute: "status", operator: "equals", value: "active" },
924
+ ],
925
+ },
926
+ ],
927
+ },
928
+ us: {
929
+ conditions: [{ attribute: "country", operator: "equals", value: "us" }],
930
+ },
931
+ eu: {
932
+ conditions: [{ attribute: "country", operator: "equals", value: "eu" }],
933
+ },
934
+ },
935
+ features: {},
936
+ };
937
+
938
+ const datafileReaderWithSegments = new DatafileReader({
939
+ datafile: datafileWithSegments,
940
+ logger: createLogger({ level: "fatal" }),
941
+ });
942
+
943
+ test("buildScopedGroupSegments is a function", function () {
944
+ expect(buildScopedGroupSegments).toBeInstanceOf(Function);
945
+ });
946
+
947
+ test("simple cases", function () {
948
+ // "*" remains "*"
949
+ expect(buildScopedGroupSegments(datafileReaderWithSegments, "*", {})).toEqual("*");
950
+
951
+ // Plain segment that matches
952
+ expect(
953
+ buildScopedGroupSegments(datafileReaderWithSegments, "web", {
954
+ platform: "web",
955
+ }),
956
+ ).toEqual("*");
957
+
958
+ // Plain segment that doesn't match
959
+ expect(
960
+ buildScopedGroupSegments(datafileReaderWithSegments, "web", {
961
+ platform: "mobile",
962
+ }),
963
+ ).toEqual("web");
964
+
965
+ // Array of segments - partial match
966
+ expect(
967
+ buildScopedGroupSegments(datafileReaderWithSegments, ["web", "chrome"], {
968
+ platform: "web",
969
+ }),
970
+ ).toEqual(["*", "chrome"]);
971
+
972
+ // Array of segments - full match (all "*" - will be cleaned by removeRedundantGroupSegments later)
973
+ expect(
974
+ buildScopedGroupSegments(datafileReaderWithSegments, ["web", "chrome"], {
975
+ platform: "web",
976
+ browser: "chrome",
977
+ }),
978
+ ).toEqual(["*", "*"]);
979
+
980
+ // Array of segments - no match
981
+ expect(
982
+ buildScopedGroupSegments(datafileReaderWithSegments, ["web", "chrome"], {
983
+ platform: "mobile",
984
+ browser: "safari",
985
+ }),
986
+ ).toEqual(["web", "chrome"]);
987
+ });
988
+
989
+ test("AND group segments", function () {
990
+ // AND with all matching segments (all "*" becomes "*")
991
+ expect(
992
+ buildScopedGroupSegments(
993
+ datafileReaderWithSegments,
994
+ {
995
+ and: ["web", "chrome"],
996
+ },
997
+ {
998
+ platform: "web",
999
+ browser: "chrome",
1000
+ },
1001
+ ),
1002
+ ).toEqual({
1003
+ and: ["*", "*"],
1004
+ });
1005
+
1006
+ // AND with partial match
1007
+ expect(
1008
+ buildScopedGroupSegments(
1009
+ datafileReaderWithSegments,
1010
+ {
1011
+ and: ["web", "chrome"],
1012
+ },
1013
+ {
1014
+ platform: "web",
1015
+ },
1016
+ ),
1017
+ ).toEqual({
1018
+ and: ["*", "chrome"],
1019
+ });
1020
+
1021
+ // AND with no matches
1022
+ expect(
1023
+ buildScopedGroupSegments(
1024
+ datafileReaderWithSegments,
1025
+ {
1026
+ and: ["web", "chrome"],
1027
+ },
1028
+ {
1029
+ platform: "mobile",
1030
+ browser: "safari",
1031
+ },
1032
+ ),
1033
+ ).toEqual({
1034
+ and: ["web", "chrome"],
1035
+ });
1036
+
1037
+ // AND with "*" in it
1038
+ expect(
1039
+ buildScopedGroupSegments(
1040
+ datafileReaderWithSegments,
1041
+ {
1042
+ and: ["*", "chrome"],
1043
+ },
1044
+ {
1045
+ browser: "chrome",
1046
+ },
1047
+ ),
1048
+ ).toEqual({
1049
+ and: ["*", "*"],
1050
+ });
1051
+ });
1052
+
1053
+ test("OR group segments", function () {
1054
+ // OR with all matching segments (all "*" becomes "*")
1055
+ expect(
1056
+ buildScopedGroupSegments(
1057
+ datafileReaderWithSegments,
1058
+ {
1059
+ or: ["web", "chrome"],
1060
+ },
1061
+ {
1062
+ platform: "web",
1063
+ browser: "chrome",
1064
+ },
1065
+ ),
1066
+ ).toEqual({
1067
+ or: ["*", "*"],
1068
+ });
1069
+
1070
+ // OR with partial match
1071
+ expect(
1072
+ buildScopedGroupSegments(
1073
+ datafileReaderWithSegments,
1074
+ {
1075
+ or: ["web", "chrome"],
1076
+ },
1077
+ {
1078
+ platform: "web",
1079
+ },
1080
+ ),
1081
+ ).toEqual({
1082
+ or: ["*", "chrome"],
1083
+ });
1084
+
1085
+ // OR with no matches
1086
+ expect(
1087
+ buildScopedGroupSegments(
1088
+ datafileReaderWithSegments,
1089
+ {
1090
+ or: ["web", "mobile"],
1091
+ },
1092
+ {
1093
+ platform: "desktop",
1094
+ },
1095
+ ),
1096
+ ).toEqual({
1097
+ or: ["web", "mobile"],
1098
+ });
1099
+ });
1100
+
1101
+ test("NOT group segments", function () {
1102
+ // NOT with matching segment
1103
+ expect(
1104
+ buildScopedGroupSegments(
1105
+ datafileReaderWithSegments,
1106
+ {
1107
+ not: ["web"],
1108
+ },
1109
+ {
1110
+ platform: "web",
1111
+ },
1112
+ ),
1113
+ ).toEqual({
1114
+ not: ["*"],
1115
+ });
1116
+
1117
+ // NOT with non-matching segment
1118
+ expect(
1119
+ buildScopedGroupSegments(
1120
+ datafileReaderWithSegments,
1121
+ {
1122
+ not: ["web"],
1123
+ },
1124
+ {
1125
+ platform: "mobile",
1126
+ },
1127
+ ),
1128
+ ).toEqual({
1129
+ not: ["web"],
1130
+ });
1131
+
1132
+ // NOT with multiple segments (all "*" becomes "*")
1133
+ expect(
1134
+ buildScopedGroupSegments(
1135
+ datafileReaderWithSegments,
1136
+ {
1137
+ not: ["web", "chrome"],
1138
+ },
1139
+ {
1140
+ platform: "web",
1141
+ browser: "chrome",
1142
+ },
1143
+ ),
1144
+ ).toEqual({
1145
+ not: ["*", "*"],
1146
+ });
1147
+ });
1148
+
1149
+ test("nested AND group segments", function () {
1150
+ // Nested AND with all matching (all "*" becomes "*")
1151
+ expect(
1152
+ buildScopedGroupSegments(
1153
+ datafileReaderWithSegments,
1154
+ {
1155
+ and: [
1156
+ "web",
1157
+ {
1158
+ and: ["chrome", "us"],
1159
+ },
1160
+ ],
1161
+ },
1162
+ {
1163
+ platform: "web",
1164
+ browser: "chrome",
1165
+ country: "us",
1166
+ },
1167
+ ),
1168
+ ).toEqual({
1169
+ and: [
1170
+ "*",
1171
+ {
1172
+ and: ["*", "*"],
1173
+ },
1174
+ ],
1175
+ });
1176
+
1177
+ // Nested AND with partial match
1178
+ expect(
1179
+ buildScopedGroupSegments(
1180
+ datafileReaderWithSegments,
1181
+ {
1182
+ and: [
1183
+ "web",
1184
+ {
1185
+ and: ["chrome", "us"],
1186
+ },
1187
+ ],
1188
+ },
1189
+ {
1190
+ platform: "web",
1191
+ browser: "chrome",
1192
+ },
1193
+ ),
1194
+ ).toEqual({
1195
+ and: [
1196
+ "*",
1197
+ {
1198
+ and: ["*", "us"],
1199
+ },
1200
+ ],
1201
+ });
1202
+ });
1203
+
1204
+ test("nested OR group segments", function () {
1205
+ // Nested OR with all matching
1206
+ expect(
1207
+ buildScopedGroupSegments(
1208
+ datafileReaderWithSegments,
1209
+ {
1210
+ or: [
1211
+ "web",
1212
+ {
1213
+ or: ["chrome", "safari"],
1214
+ },
1215
+ ],
1216
+ },
1217
+ {
1218
+ platform: "web",
1219
+ browser: "chrome",
1220
+ },
1221
+ ),
1222
+ ).toEqual({
1223
+ or: [
1224
+ "*",
1225
+ {
1226
+ or: ["*", "safari"],
1227
+ },
1228
+ ],
1229
+ });
1230
+
1231
+ // Nested OR with partial match
1232
+ expect(
1233
+ buildScopedGroupSegments(
1234
+ datafileReaderWithSegments,
1235
+ {
1236
+ or: [
1237
+ "web",
1238
+ {
1239
+ or: ["chrome", "safari"],
1240
+ },
1241
+ ],
1242
+ },
1243
+ {
1244
+ platform: "web",
1245
+ },
1246
+ ),
1247
+ ).toEqual({
1248
+ or: [
1249
+ "*",
1250
+ {
1251
+ or: ["chrome", "safari"],
1252
+ },
1253
+ ],
1254
+ });
1255
+ });
1256
+
1257
+ test("complex nested structures", function () {
1258
+ // Complex nested with AND, OR, NOT
1259
+ expect(
1260
+ buildScopedGroupSegments(
1261
+ datafileReaderWithSegments,
1262
+ {
1263
+ and: [
1264
+ "web",
1265
+ {
1266
+ or: ["chrome", "safari"],
1267
+ },
1268
+ {
1269
+ not: ["mobile"],
1270
+ },
1271
+ ],
1272
+ },
1273
+ {
1274
+ platform: "web",
1275
+ browser: "chrome",
1276
+ },
1277
+ ),
1278
+ ).toEqual({
1279
+ and: [
1280
+ "*",
1281
+ {
1282
+ or: ["*", "safari"],
1283
+ },
1284
+ {
1285
+ not: ["mobile"],
1286
+ },
1287
+ ],
1288
+ });
1289
+ });
1290
+
1291
+ test("segments with complex conditions", function () {
1292
+ // Segment with AND conditions in its definition
1293
+ expect(
1294
+ buildScopedGroupSegments(datafileReaderWithSegments, "premium", {
1295
+ tier: "premium",
1296
+ status: "active",
1297
+ }),
1298
+ ).toEqual("*");
1299
+
1300
+ // Segment with AND conditions that doesn't match
1301
+ expect(
1302
+ buildScopedGroupSegments(datafileReaderWithSegments, "premium", {
1303
+ tier: "premium",
1304
+ status: "inactive",
1305
+ }),
1306
+ ).toEqual("premium");
1307
+ });
1308
+
1309
+ test("removeSegments parameter", function () {
1310
+ // Simple segment in removeSegments
1311
+ expect(buildScopedGroupSegments(datafileReaderWithSegments, "web", {}, ["web"])).toEqual("*");
1312
+
1313
+ // Segment not in removeSegments
1314
+ expect(buildScopedGroupSegments(datafileReaderWithSegments, "web", {}, ["mobile"])).toEqual(
1315
+ "web",
1316
+ );
1317
+
1318
+ // Array with one segment in removeSegments
1319
+ expect(
1320
+ buildScopedGroupSegments(datafileReaderWithSegments, ["web", "chrome"], {}, ["web"]),
1321
+ ).toEqual(["*", "chrome"]);
1322
+
1323
+ // Array with all segments in removeSegments
1324
+ expect(
1325
+ buildScopedGroupSegments(datafileReaderWithSegments, ["web", "chrome"], {}, [
1326
+ "web",
1327
+ "chrome",
1328
+ ]),
1329
+ ).toEqual(["*", "*"]);
1330
+
1331
+ // Array with multiple segments, some in removeSegments
1332
+ expect(
1333
+ buildScopedGroupSegments(
1334
+ datafileReaderWithSegments,
1335
+ ["web", "chrome", "mobile", "safari"],
1336
+ {},
1337
+ ["web", "mobile"],
1338
+ ),
1339
+ ).toEqual(["*", "chrome", "*", "safari"]);
1340
+
1341
+ // AND group segment with removeSegments
1342
+ expect(
1343
+ buildScopedGroupSegments(
1344
+ datafileReaderWithSegments,
1345
+ {
1346
+ and: ["web", "chrome"],
1347
+ },
1348
+ {},
1349
+ ["web"],
1350
+ ),
1351
+ ).toEqual({
1352
+ and: ["*", "chrome"],
1353
+ });
1354
+
1355
+ // AND group segment with all segments in removeSegments
1356
+ expect(
1357
+ buildScopedGroupSegments(
1358
+ datafileReaderWithSegments,
1359
+ {
1360
+ and: ["web", "chrome"],
1361
+ },
1362
+ {},
1363
+ ["web", "chrome"],
1364
+ ),
1365
+ ).toEqual({
1366
+ and: ["*", "*"],
1367
+ });
1368
+
1369
+ // OR group segment with removeSegments
1370
+ expect(
1371
+ buildScopedGroupSegments(
1372
+ datafileReaderWithSegments,
1373
+ {
1374
+ or: ["web", "chrome"],
1375
+ },
1376
+ {},
1377
+ ["web"],
1378
+ ),
1379
+ ).toEqual({
1380
+ or: ["*", "chrome"],
1381
+ });
1382
+
1383
+ // OR group segment with all segments in removeSegments
1384
+ expect(
1385
+ buildScopedGroupSegments(
1386
+ datafileReaderWithSegments,
1387
+ {
1388
+ or: ["web", "chrome"],
1389
+ },
1390
+ {},
1391
+ ["web", "chrome"],
1392
+ ),
1393
+ ).toEqual({
1394
+ or: ["*", "*"],
1395
+ });
1396
+
1397
+ // NOT group segment with removeSegments
1398
+ expect(
1399
+ buildScopedGroupSegments(
1400
+ datafileReaderWithSegments,
1401
+ {
1402
+ not: ["web", "chrome"],
1403
+ },
1404
+ {},
1405
+ ["web"],
1406
+ ),
1407
+ ).toEqual({
1408
+ not: ["*", "chrome"],
1409
+ });
1410
+
1411
+ // NOT group segment with all segments in removeSegments
1412
+ expect(
1413
+ buildScopedGroupSegments(
1414
+ datafileReaderWithSegments,
1415
+ {
1416
+ not: ["web", "chrome"],
1417
+ },
1418
+ {},
1419
+ ["web", "chrome"],
1420
+ ),
1421
+ ).toEqual({
1422
+ not: ["*", "*"],
1423
+ });
1424
+
1425
+ // Nested AND with removeSegments
1426
+ expect(
1427
+ buildScopedGroupSegments(
1428
+ datafileReaderWithSegments,
1429
+ {
1430
+ and: [
1431
+ "web",
1432
+ {
1433
+ and: ["chrome", "us"],
1434
+ },
1435
+ ],
1436
+ },
1437
+ {},
1438
+ ["web"],
1439
+ ),
1440
+ ).toEqual({
1441
+ and: [
1442
+ "*",
1443
+ {
1444
+ and: ["chrome", "us"],
1445
+ },
1446
+ ],
1447
+ });
1448
+
1449
+ // Nested OR with removeSegments
1450
+ expect(
1451
+ buildScopedGroupSegments(
1452
+ datafileReaderWithSegments,
1453
+ {
1454
+ or: [
1455
+ "web",
1456
+ {
1457
+ or: ["chrome", "safari"],
1458
+ },
1459
+ ],
1460
+ },
1461
+ {},
1462
+ ["chrome"],
1463
+ ),
1464
+ ).toEqual({
1465
+ or: [
1466
+ "web",
1467
+ {
1468
+ or: ["*", "safari"],
1469
+ },
1470
+ ],
1471
+ });
1472
+
1473
+ // Mixed nested structure with removeSegments
1474
+ expect(
1475
+ buildScopedGroupSegments(
1476
+ datafileReaderWithSegments,
1477
+ {
1478
+ and: [
1479
+ "web",
1480
+ {
1481
+ or: ["chrome", "safari"],
1482
+ },
1483
+ {
1484
+ not: ["mobile"],
1485
+ },
1486
+ ],
1487
+ },
1488
+ {},
1489
+ ["web", "safari"],
1490
+ ),
1491
+ ).toEqual({
1492
+ and: [
1493
+ "*",
1494
+ {
1495
+ or: ["chrome", "*"],
1496
+ },
1497
+ {
1498
+ not: ["mobile"],
1499
+ },
1500
+ ],
1501
+ });
1502
+
1503
+ // removeSegments combined with matching context
1504
+ expect(
1505
+ buildScopedGroupSegments(
1506
+ datafileReaderWithSegments,
1507
+ ["web", "chrome"],
1508
+ {
1509
+ platform: "web",
1510
+ },
1511
+ ["chrome"],
1512
+ ),
1513
+ ).toEqual(["*", "*"]);
1514
+
1515
+ // removeSegments with segment that would match context
1516
+ expect(
1517
+ buildScopedGroupSegments(
1518
+ datafileReaderWithSegments,
1519
+ ["web", "chrome"],
1520
+ {
1521
+ platform: "web",
1522
+ browser: "chrome",
1523
+ },
1524
+ ["web"],
1525
+ ),
1526
+ ).toEqual(["*", "*"]);
1527
+
1528
+ // Complex: removeSegments, context matching, and nested structures
1529
+ expect(
1530
+ buildScopedGroupSegments(
1531
+ datafileReaderWithSegments,
1532
+ {
1533
+ and: [
1534
+ "web",
1535
+ {
1536
+ or: ["chrome", "safari", "mobile"],
1537
+ },
1538
+ "us",
1539
+ ],
1540
+ },
1541
+ {
1542
+ platform: "web",
1543
+ browser: "chrome",
1544
+ },
1545
+ ["us"],
1546
+ ),
1547
+ ).toEqual({
1548
+ and: [
1549
+ "*",
1550
+ {
1551
+ or: ["*", "safari", "mobile"],
1552
+ },
1553
+ "*",
1554
+ ],
1555
+ });
1556
+
1557
+ // Empty removeSegments array (should behave like no removeSegments)
1558
+ expect(buildScopedGroupSegments(datafileReaderWithSegments, "web", {}, [])).toEqual("web");
1559
+
1560
+ // removeSegments with segment that doesn't exist in datafile
1561
+ expect(
1562
+ buildScopedGroupSegments(datafileReaderWithSegments, ["web", "nonexistent"], {}, [
1563
+ "nonexistent",
1564
+ ]),
1565
+ ).toEqual(["web", "*"]);
1566
+
1567
+ // removeSegments with "*" in array (should still work)
1568
+ expect(
1569
+ buildScopedGroupSegments(datafileReaderWithSegments, ["*", "web", "chrome"], {}, ["web"]),
1570
+ ).toEqual(["*", "*", "chrome"]);
1571
+ });
1572
+ });
1573
+ });