@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,1062 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const buildScopedSegments_1 = require("./buildScopedSegments");
4
+ const sdk_1 = require("@featurevisor/sdk");
5
+ describe("core: buildScopedSegments", function () {
6
+ describe("buildScopedSegments", function () {
7
+ const datafileWithSegments = {
8
+ schemaVersion: "2",
9
+ revision: "unknown",
10
+ segments: {
11
+ web: {
12
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
13
+ },
14
+ mobile: {
15
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
16
+ },
17
+ chrome: {
18
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
19
+ },
20
+ safari: {
21
+ conditions: [{ attribute: "browser", operator: "equals", value: "safari" }],
22
+ },
23
+ premium: {
24
+ conditions: [
25
+ {
26
+ and: [
27
+ { attribute: "tier", operator: "equals", value: "premium" },
28
+ { attribute: "status", operator: "equals", value: "active" },
29
+ ],
30
+ },
31
+ ],
32
+ },
33
+ us: {
34
+ conditions: [{ attribute: "country", operator: "equals", value: "us" }],
35
+ },
36
+ eu: {
37
+ conditions: [{ attribute: "country", operator: "equals", value: "eu" }],
38
+ },
39
+ },
40
+ features: {},
41
+ };
42
+ const datafileReaderWithSegments = new sdk_1.DatafileReader({
43
+ datafile: datafileWithSegments,
44
+ logger: (0, sdk_1.createLogger)({ level: "fatal" }),
45
+ });
46
+ test("buildScopedSegments is a function", function () {
47
+ expect(buildScopedSegments_1.buildScopedSegments).toBeInstanceOf(Function);
48
+ });
49
+ test("simple cases", function () {
50
+ // "*" remains "*"
51
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, "*", {})).toEqual("*");
52
+ // Plain segment that matches
53
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, "web", {
54
+ platform: "web",
55
+ })).toEqual("*");
56
+ // Plain segment that doesn't match
57
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, "web", {
58
+ platform: "mobile",
59
+ })).toEqual("web");
60
+ // Array of segments - partial match (redundant "*" removed)
61
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, ["web", "chrome"], {
62
+ platform: "web",
63
+ })).toEqual(["chrome"]);
64
+ // Array of segments - full match (all "*" becomes "*")
65
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, ["web", "chrome"], {
66
+ platform: "web",
67
+ browser: "chrome",
68
+ })).toEqual("*");
69
+ // Array of segments - no match
70
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, ["web", "chrome"], {
71
+ platform: "mobile",
72
+ browser: "safari",
73
+ })).toEqual(["web", "chrome"]);
74
+ });
75
+ test("AND group segments", function () {
76
+ // AND with all matching segments (all "*" becomes "*")
77
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
78
+ and: ["web", "chrome"],
79
+ }, {
80
+ platform: "web",
81
+ browser: "chrome",
82
+ })).toEqual("*");
83
+ // AND with partial match (redundant "*" removed)
84
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
85
+ and: ["web", "chrome"],
86
+ }, {
87
+ platform: "web",
88
+ })).toEqual({
89
+ and: ["chrome"],
90
+ });
91
+ // AND with no matches
92
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
93
+ and: ["web", "chrome"],
94
+ }, {
95
+ platform: "mobile",
96
+ browser: "safari",
97
+ })).toEqual({
98
+ and: ["web", "chrome"],
99
+ });
100
+ // AND with "*" in it (all "*" becomes "*")
101
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
102
+ and: ["*", "chrome"],
103
+ }, {
104
+ browser: "chrome",
105
+ })).toEqual("*");
106
+ });
107
+ test("OR group segments", function () {
108
+ // OR with all matching segments (all "*" becomes "*")
109
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
110
+ or: ["web", "chrome"],
111
+ }, {
112
+ platform: "web",
113
+ browser: "chrome",
114
+ })).toEqual("*");
115
+ // OR with partial match (redundant "*" removed, but OR structure remains)
116
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
117
+ or: ["web", "chrome"],
118
+ }, {
119
+ platform: "web",
120
+ })).toEqual({
121
+ or: ["chrome"],
122
+ });
123
+ // OR with no matches
124
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
125
+ or: ["web", "mobile"],
126
+ }, {
127
+ platform: "desktop",
128
+ })).toEqual({
129
+ or: ["web", "mobile"],
130
+ });
131
+ });
132
+ test("NOT group segments", function () {
133
+ // NOT with matching segment (all "*" becomes "*")
134
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
135
+ not: ["web"],
136
+ }, {
137
+ platform: "web",
138
+ })).toEqual("*");
139
+ // NOT with non-matching segment
140
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
141
+ not: ["web"],
142
+ }, {
143
+ platform: "mobile",
144
+ })).toEqual({
145
+ not: ["web"],
146
+ });
147
+ // NOT with multiple segments (all "*" becomes "*")
148
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
149
+ not: ["web", "chrome"],
150
+ }, {
151
+ platform: "web",
152
+ browser: "chrome",
153
+ })).toEqual("*");
154
+ });
155
+ test("nested AND group segments", function () {
156
+ // Nested AND with all matching (all "*" becomes "*")
157
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
158
+ and: [
159
+ "web",
160
+ {
161
+ and: ["chrome", "us"],
162
+ },
163
+ ],
164
+ }, {
165
+ platform: "web",
166
+ browser: "chrome",
167
+ country: "us",
168
+ })).toEqual("*");
169
+ // Nested AND with partial match (redundant "*" removed)
170
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
171
+ and: [
172
+ "web",
173
+ {
174
+ and: ["chrome", "us"],
175
+ },
176
+ ],
177
+ }, {
178
+ platform: "web",
179
+ browser: "chrome",
180
+ })).toEqual({
181
+ and: [
182
+ {
183
+ and: ["us"],
184
+ },
185
+ ],
186
+ });
187
+ });
188
+ test("nested OR group segments", function () {
189
+ // Nested OR with outer match (redundant "*" removed, but inner OR remains)
190
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
191
+ or: [
192
+ "web",
193
+ {
194
+ or: ["mobile", "chrome"],
195
+ },
196
+ ],
197
+ }, {
198
+ platform: "web",
199
+ })).toEqual({
200
+ or: [
201
+ {
202
+ or: ["mobile", "chrome"],
203
+ },
204
+ ],
205
+ });
206
+ // Nested OR with inner match (redundant "*" removed, but outer OR remains)
207
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
208
+ or: [
209
+ "web",
210
+ {
211
+ or: ["chrome", "safari"],
212
+ },
213
+ ],
214
+ }, {
215
+ browser: "chrome",
216
+ })).toEqual({
217
+ or: [
218
+ "web",
219
+ {
220
+ or: ["safari"],
221
+ },
222
+ ],
223
+ });
224
+ });
225
+ test("nested NOT group segments", function () {
226
+ // Nested NOT (all "*" becomes "*")
227
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
228
+ not: [
229
+ "web",
230
+ {
231
+ not: ["chrome"],
232
+ },
233
+ ],
234
+ }, {
235
+ platform: "web",
236
+ browser: "chrome",
237
+ })).toEqual("*");
238
+ });
239
+ test("mixed nested group segments", function () {
240
+ // AND with nested OR (redundant "*" removed, but OR structure remains if not all match)
241
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
242
+ and: [
243
+ "web",
244
+ {
245
+ or: ["chrome", "safari"],
246
+ },
247
+ ],
248
+ }, {
249
+ platform: "web",
250
+ browser: "chrome",
251
+ })).toEqual({
252
+ and: [
253
+ {
254
+ or: ["safari"],
255
+ },
256
+ ],
257
+ });
258
+ // OR with nested AND (redundant "*" removed, but OR structure remains if not all match)
259
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
260
+ or: [
261
+ "web",
262
+ {
263
+ and: ["mobile", "chrome"],
264
+ },
265
+ ],
266
+ }, {
267
+ platform: "mobile",
268
+ browser: "chrome",
269
+ })).toEqual({
270
+ or: ["web"],
271
+ });
272
+ // AND with nested NOT (redundant "*" removed)
273
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
274
+ and: [
275
+ "web",
276
+ {
277
+ not: ["mobile"],
278
+ },
279
+ ],
280
+ }, {
281
+ platform: "web",
282
+ })).toEqual({
283
+ and: [
284
+ {
285
+ not: ["mobile"],
286
+ },
287
+ ],
288
+ });
289
+ // Complex nested structure (redundant "*" removed)
290
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
291
+ and: [
292
+ "web",
293
+ {
294
+ or: [
295
+ {
296
+ and: ["chrome", "us"],
297
+ },
298
+ "safari",
299
+ ],
300
+ },
301
+ ],
302
+ }, {
303
+ platform: "web",
304
+ browser: "chrome",
305
+ country: "us",
306
+ })).toEqual({
307
+ and: [
308
+ {
309
+ or: ["safari"],
310
+ },
311
+ ],
312
+ });
313
+ });
314
+ test("arrays with nested group segments", function () {
315
+ // Array with AND group segment (redundant "*" removed)
316
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, [
317
+ "web",
318
+ {
319
+ and: ["chrome", "us"],
320
+ },
321
+ ], {
322
+ platform: "web",
323
+ browser: "chrome",
324
+ country: "us",
325
+ })).toEqual("*");
326
+ // Array with OR group segment (redundant "*" removed, but OR structure remains if not all match)
327
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, [
328
+ "web",
329
+ {
330
+ or: ["chrome", "safari"],
331
+ },
332
+ ], {
333
+ platform: "web",
334
+ browser: "chrome",
335
+ })).toEqual([
336
+ {
337
+ or: ["safari"],
338
+ },
339
+ ]);
340
+ });
341
+ test("segments with complex conditions", function () {
342
+ // Segment with AND conditions in its definition
343
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, "premium", {
344
+ tier: "premium",
345
+ status: "active",
346
+ })).toEqual("*");
347
+ // Segment with AND conditions that doesn't match
348
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, "premium", {
349
+ tier: "premium",
350
+ status: "inactive",
351
+ })).toEqual("premium");
352
+ });
353
+ test("edge cases", function () {
354
+ // Empty context
355
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, "web", {})).toEqual("web");
356
+ // Empty array (all "*" becomes "*")
357
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, [], {})).toEqual("*");
358
+ // "*" in array with matching segment (all "*" becomes "*")
359
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, ["*", "web"], {
360
+ platform: "web",
361
+ })).toEqual("*");
362
+ // AND with empty array (all "*" becomes "*")
363
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
364
+ and: [],
365
+ }, {})).toEqual("*");
366
+ // OR with empty array (all "*" becomes "*")
367
+ expect((0, buildScopedSegments_1.buildScopedSegments)(datafileReaderWithSegments, {
368
+ or: [],
369
+ }, {})).toEqual("*");
370
+ });
371
+ });
372
+ describe("removeRedundantGroupSegments", function () {
373
+ test("removeRedundantGroupSegments is a function", function () {
374
+ expect(buildScopedSegments_1.removeRedundantGroupSegments).toBeInstanceOf(Function);
375
+ });
376
+ test("simple cases", function () {
377
+ // * is *
378
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)("*")).toEqual("*");
379
+ // [*, *, *] is "*"
380
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)(["*", "*", "*"])).toEqual("*");
381
+ // Array with mixed group segments
382
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)(["*", "web", "*", "chrome"])).toEqual(["web", "chrome"]);
383
+ });
384
+ test("AND group segments", function () {
385
+ // All "*" in AND
386
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({ and: ["*", "*", "*"] })).toEqual("*");
387
+ // Mixed with "*" in AND
388
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
389
+ and: ["*", "web", "*"],
390
+ })).toEqual({
391
+ and: ["web"],
392
+ });
393
+ // Multiple non-* group segments in AND
394
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
395
+ and: ["*", "web", "*", "chrome", "*"],
396
+ })).toEqual({
397
+ and: ["web", "chrome"],
398
+ });
399
+ });
400
+ test("OR group segments", function () {
401
+ // All "*" in OR
402
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({ or: ["*", "*", "*"] })).toEqual("*");
403
+ // Mixed with "*" in OR
404
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
405
+ or: ["*", "web", "*"],
406
+ })).toEqual({
407
+ or: ["web"],
408
+ });
409
+ // Multiple non-* group segments in OR
410
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
411
+ or: ["*", "web", "*", "mobile", "*"],
412
+ })).toEqual({
413
+ or: ["web", "mobile"],
414
+ });
415
+ });
416
+ test("NOT group segments", function () {
417
+ // All "*" in NOT
418
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({ not: ["*", "*", "*"] })).toEqual("*");
419
+ // Mixed with "*" in NOT
420
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
421
+ not: ["*", "web", "*"],
422
+ })).toEqual({
423
+ not: ["web"],
424
+ });
425
+ });
426
+ test("nested AND group segments", function () {
427
+ // Nested AND with all "*"
428
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
429
+ and: ["*", { and: ["*", "*", "*"] }, "*"],
430
+ })).toEqual("*");
431
+ // Nested AND with mixed group segments
432
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
433
+ and: [
434
+ "*",
435
+ "web",
436
+ {
437
+ and: ["*", "chrome", "*"],
438
+ },
439
+ "*",
440
+ ],
441
+ })).toEqual({
442
+ and: [
443
+ "web",
444
+ {
445
+ and: ["chrome"],
446
+ },
447
+ ],
448
+ });
449
+ // Deeply nested AND
450
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
451
+ and: [
452
+ "*",
453
+ {
454
+ and: [
455
+ "*",
456
+ {
457
+ and: ["*", "*", "*"],
458
+ },
459
+ "*",
460
+ ],
461
+ },
462
+ "*",
463
+ ],
464
+ })).toEqual("*");
465
+ });
466
+ test("nested OR group segments", function () {
467
+ // Nested OR with all "*"
468
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
469
+ or: ["*", { or: ["*", "*", "*"] }, "*"],
470
+ })).toEqual("*");
471
+ // Nested OR with mixed group segments
472
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
473
+ or: [
474
+ "*",
475
+ "web",
476
+ {
477
+ or: ["*", "mobile", "*"],
478
+ },
479
+ "*",
480
+ ],
481
+ })).toEqual({
482
+ or: [
483
+ "web",
484
+ {
485
+ or: ["mobile"],
486
+ },
487
+ ],
488
+ });
489
+ });
490
+ test("nested NOT group segments", function () {
491
+ // Nested NOT with all "*"
492
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
493
+ not: ["*", { not: ["*", "*", "*"] }, "*"],
494
+ })).toEqual("*");
495
+ // Nested NOT with mixed group segments
496
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
497
+ not: [
498
+ "*",
499
+ "web",
500
+ {
501
+ not: ["*", "chrome", "*"],
502
+ },
503
+ "*",
504
+ ],
505
+ })).toEqual({
506
+ not: [
507
+ "web",
508
+ {
509
+ not: ["chrome"],
510
+ },
511
+ ],
512
+ });
513
+ });
514
+ test("mixed nested group segments", function () {
515
+ // AND with nested OR
516
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
517
+ and: [
518
+ "*",
519
+ "web",
520
+ {
521
+ or: ["*", "chrome", "*"],
522
+ },
523
+ "*",
524
+ ],
525
+ })).toEqual({
526
+ and: [
527
+ "web",
528
+ {
529
+ or: ["chrome"],
530
+ },
531
+ ],
532
+ });
533
+ // OR with nested AND
534
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
535
+ or: [
536
+ "*",
537
+ "web",
538
+ {
539
+ and: ["*", "chrome", "*"],
540
+ },
541
+ "*",
542
+ ],
543
+ })).toEqual({
544
+ or: [
545
+ "web",
546
+ {
547
+ and: ["chrome"],
548
+ },
549
+ ],
550
+ });
551
+ // AND with nested NOT
552
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
553
+ and: [
554
+ "*",
555
+ "web",
556
+ {
557
+ not: ["*", "mobile", "*"],
558
+ },
559
+ "*",
560
+ ],
561
+ })).toEqual({
562
+ and: [
563
+ "web",
564
+ {
565
+ not: ["mobile"],
566
+ },
567
+ ],
568
+ });
569
+ // Complex nested structure
570
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
571
+ and: [
572
+ "*",
573
+ "web",
574
+ {
575
+ or: [
576
+ "*",
577
+ {
578
+ and: ["*", "chrome", "*"],
579
+ },
580
+ "*",
581
+ ],
582
+ },
583
+ "*",
584
+ ],
585
+ })).toEqual({
586
+ and: [
587
+ "web",
588
+ {
589
+ or: [
590
+ {
591
+ and: ["chrome"],
592
+ },
593
+ ],
594
+ },
595
+ ],
596
+ });
597
+ });
598
+ test("arrays with nested group segments", function () {
599
+ // Array with nested AND
600
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)([
601
+ "*",
602
+ {
603
+ and: ["*", "web", "*"],
604
+ },
605
+ "*",
606
+ ])).toEqual([
607
+ {
608
+ and: ["web"],
609
+ },
610
+ ]);
611
+ // Array with multiple nested group segments
612
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)([
613
+ "*",
614
+ "web",
615
+ {
616
+ or: ["*", "chrome", "*"],
617
+ },
618
+ "*",
619
+ ])).toEqual([
620
+ "web",
621
+ {
622
+ or: ["chrome"],
623
+ },
624
+ ]);
625
+ });
626
+ test("edge cases", function () {
627
+ // Single group segment after filtering
628
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)({
629
+ and: ["*", "web", "*"],
630
+ })).toEqual({
631
+ and: ["web"],
632
+ });
633
+ // Plain string group segment (no change)
634
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)("web")).toEqual("web");
635
+ // Array with single non-* group segment
636
+ expect((0, buildScopedSegments_1.removeRedundantGroupSegments)(["*", "web", "*"])).toEqual(["web"]);
637
+ });
638
+ });
639
+ describe("buildScopedGroupSegments", function () {
640
+ const datafileWithSegments = {
641
+ schemaVersion: "2",
642
+ revision: "unknown",
643
+ segments: {
644
+ web: {
645
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
646
+ },
647
+ mobile: {
648
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
649
+ },
650
+ chrome: {
651
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
652
+ },
653
+ safari: {
654
+ conditions: [{ attribute: "browser", operator: "equals", value: "safari" }],
655
+ },
656
+ premium: {
657
+ conditions: [
658
+ {
659
+ and: [
660
+ { attribute: "tier", operator: "equals", value: "premium" },
661
+ { attribute: "status", operator: "equals", value: "active" },
662
+ ],
663
+ },
664
+ ],
665
+ },
666
+ us: {
667
+ conditions: [{ attribute: "country", operator: "equals", value: "us" }],
668
+ },
669
+ eu: {
670
+ conditions: [{ attribute: "country", operator: "equals", value: "eu" }],
671
+ },
672
+ },
673
+ features: {},
674
+ };
675
+ const datafileReaderWithSegments = new sdk_1.DatafileReader({
676
+ datafile: datafileWithSegments,
677
+ logger: (0, sdk_1.createLogger)({ level: "fatal" }),
678
+ });
679
+ test("buildScopedGroupSegments is a function", function () {
680
+ expect(buildScopedSegments_1.buildScopedGroupSegments).toBeInstanceOf(Function);
681
+ });
682
+ test("simple cases", function () {
683
+ // "*" remains "*"
684
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, "*", {})).toEqual("*");
685
+ // Plain segment that matches
686
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, "web", {
687
+ platform: "web",
688
+ })).toEqual("*");
689
+ // Plain segment that doesn't match
690
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, "web", {
691
+ platform: "mobile",
692
+ })).toEqual("web");
693
+ // Array of segments - partial match
694
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, ["web", "chrome"], {
695
+ platform: "web",
696
+ })).toEqual(["*", "chrome"]);
697
+ // Array of segments - full match (all "*" - will be cleaned by removeRedundantGroupSegments later)
698
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, ["web", "chrome"], {
699
+ platform: "web",
700
+ browser: "chrome",
701
+ })).toEqual(["*", "*"]);
702
+ // Array of segments - no match
703
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, ["web", "chrome"], {
704
+ platform: "mobile",
705
+ browser: "safari",
706
+ })).toEqual(["web", "chrome"]);
707
+ });
708
+ test("AND group segments", function () {
709
+ // AND with all matching segments (all "*" becomes "*")
710
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
711
+ and: ["web", "chrome"],
712
+ }, {
713
+ platform: "web",
714
+ browser: "chrome",
715
+ })).toEqual({
716
+ and: ["*", "*"],
717
+ });
718
+ // AND with partial match
719
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
720
+ and: ["web", "chrome"],
721
+ }, {
722
+ platform: "web",
723
+ })).toEqual({
724
+ and: ["*", "chrome"],
725
+ });
726
+ // AND with no matches
727
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
728
+ and: ["web", "chrome"],
729
+ }, {
730
+ platform: "mobile",
731
+ browser: "safari",
732
+ })).toEqual({
733
+ and: ["web", "chrome"],
734
+ });
735
+ // AND with "*" in it
736
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
737
+ and: ["*", "chrome"],
738
+ }, {
739
+ browser: "chrome",
740
+ })).toEqual({
741
+ and: ["*", "*"],
742
+ });
743
+ });
744
+ test("OR group segments", function () {
745
+ // OR with all matching segments (all "*" becomes "*")
746
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
747
+ or: ["web", "chrome"],
748
+ }, {
749
+ platform: "web",
750
+ browser: "chrome",
751
+ })).toEqual({
752
+ or: ["*", "*"],
753
+ });
754
+ // OR with partial match
755
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
756
+ or: ["web", "chrome"],
757
+ }, {
758
+ platform: "web",
759
+ })).toEqual({
760
+ or: ["*", "chrome"],
761
+ });
762
+ // OR with no matches
763
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
764
+ or: ["web", "mobile"],
765
+ }, {
766
+ platform: "desktop",
767
+ })).toEqual({
768
+ or: ["web", "mobile"],
769
+ });
770
+ });
771
+ test("NOT group segments", function () {
772
+ // NOT with matching segment
773
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
774
+ not: ["web"],
775
+ }, {
776
+ platform: "web",
777
+ })).toEqual({
778
+ not: ["*"],
779
+ });
780
+ // NOT with non-matching segment
781
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
782
+ not: ["web"],
783
+ }, {
784
+ platform: "mobile",
785
+ })).toEqual({
786
+ not: ["web"],
787
+ });
788
+ // NOT with multiple segments (all "*" becomes "*")
789
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
790
+ not: ["web", "chrome"],
791
+ }, {
792
+ platform: "web",
793
+ browser: "chrome",
794
+ })).toEqual({
795
+ not: ["*", "*"],
796
+ });
797
+ });
798
+ test("nested AND group segments", function () {
799
+ // Nested AND with all matching (all "*" becomes "*")
800
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
801
+ and: [
802
+ "web",
803
+ {
804
+ and: ["chrome", "us"],
805
+ },
806
+ ],
807
+ }, {
808
+ platform: "web",
809
+ browser: "chrome",
810
+ country: "us",
811
+ })).toEqual({
812
+ and: [
813
+ "*",
814
+ {
815
+ and: ["*", "*"],
816
+ },
817
+ ],
818
+ });
819
+ // Nested AND with partial match
820
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
821
+ and: [
822
+ "web",
823
+ {
824
+ and: ["chrome", "us"],
825
+ },
826
+ ],
827
+ }, {
828
+ platform: "web",
829
+ browser: "chrome",
830
+ })).toEqual({
831
+ and: [
832
+ "*",
833
+ {
834
+ and: ["*", "us"],
835
+ },
836
+ ],
837
+ });
838
+ });
839
+ test("nested OR group segments", function () {
840
+ // Nested OR with all matching
841
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
842
+ or: [
843
+ "web",
844
+ {
845
+ or: ["chrome", "safari"],
846
+ },
847
+ ],
848
+ }, {
849
+ platform: "web",
850
+ browser: "chrome",
851
+ })).toEqual({
852
+ or: [
853
+ "*",
854
+ {
855
+ or: ["*", "safari"],
856
+ },
857
+ ],
858
+ });
859
+ // Nested OR with partial match
860
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
861
+ or: [
862
+ "web",
863
+ {
864
+ or: ["chrome", "safari"],
865
+ },
866
+ ],
867
+ }, {
868
+ platform: "web",
869
+ })).toEqual({
870
+ or: [
871
+ "*",
872
+ {
873
+ or: ["chrome", "safari"],
874
+ },
875
+ ],
876
+ });
877
+ });
878
+ test("complex nested structures", function () {
879
+ // Complex nested with AND, OR, NOT
880
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
881
+ and: [
882
+ "web",
883
+ {
884
+ or: ["chrome", "safari"],
885
+ },
886
+ {
887
+ not: ["mobile"],
888
+ },
889
+ ],
890
+ }, {
891
+ platform: "web",
892
+ browser: "chrome",
893
+ })).toEqual({
894
+ and: [
895
+ "*",
896
+ {
897
+ or: ["*", "safari"],
898
+ },
899
+ {
900
+ not: ["mobile"],
901
+ },
902
+ ],
903
+ });
904
+ });
905
+ test("segments with complex conditions", function () {
906
+ // Segment with AND conditions in its definition
907
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, "premium", {
908
+ tier: "premium",
909
+ status: "active",
910
+ })).toEqual("*");
911
+ // Segment with AND conditions that doesn't match
912
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, "premium", {
913
+ tier: "premium",
914
+ status: "inactive",
915
+ })).toEqual("premium");
916
+ });
917
+ test("removeSegments parameter", function () {
918
+ // Simple segment in removeSegments
919
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, "web", {}, ["web"])).toEqual("*");
920
+ // Segment not in removeSegments
921
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, "web", {}, ["mobile"])).toEqual("web");
922
+ // Array with one segment in removeSegments
923
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, ["web", "chrome"], {}, ["web"])).toEqual(["*", "chrome"]);
924
+ // Array with all segments in removeSegments
925
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, ["web", "chrome"], {}, [
926
+ "web",
927
+ "chrome",
928
+ ])).toEqual(["*", "*"]);
929
+ // Array with multiple segments, some in removeSegments
930
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, ["web", "chrome", "mobile", "safari"], {}, ["web", "mobile"])).toEqual(["*", "chrome", "*", "safari"]);
931
+ // AND group segment with removeSegments
932
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
933
+ and: ["web", "chrome"],
934
+ }, {}, ["web"])).toEqual({
935
+ and: ["*", "chrome"],
936
+ });
937
+ // AND group segment with all segments in removeSegments
938
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
939
+ and: ["web", "chrome"],
940
+ }, {}, ["web", "chrome"])).toEqual({
941
+ and: ["*", "*"],
942
+ });
943
+ // OR group segment with removeSegments
944
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
945
+ or: ["web", "chrome"],
946
+ }, {}, ["web"])).toEqual({
947
+ or: ["*", "chrome"],
948
+ });
949
+ // OR group segment with all segments in removeSegments
950
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
951
+ or: ["web", "chrome"],
952
+ }, {}, ["web", "chrome"])).toEqual({
953
+ or: ["*", "*"],
954
+ });
955
+ // NOT group segment with removeSegments
956
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
957
+ not: ["web", "chrome"],
958
+ }, {}, ["web"])).toEqual({
959
+ not: ["*", "chrome"],
960
+ });
961
+ // NOT group segment with all segments in removeSegments
962
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
963
+ not: ["web", "chrome"],
964
+ }, {}, ["web", "chrome"])).toEqual({
965
+ not: ["*", "*"],
966
+ });
967
+ // Nested AND with removeSegments
968
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
969
+ and: [
970
+ "web",
971
+ {
972
+ and: ["chrome", "us"],
973
+ },
974
+ ],
975
+ }, {}, ["web"])).toEqual({
976
+ and: [
977
+ "*",
978
+ {
979
+ and: ["chrome", "us"],
980
+ },
981
+ ],
982
+ });
983
+ // Nested OR with removeSegments
984
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
985
+ or: [
986
+ "web",
987
+ {
988
+ or: ["chrome", "safari"],
989
+ },
990
+ ],
991
+ }, {}, ["chrome"])).toEqual({
992
+ or: [
993
+ "web",
994
+ {
995
+ or: ["*", "safari"],
996
+ },
997
+ ],
998
+ });
999
+ // Mixed nested structure with removeSegments
1000
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
1001
+ and: [
1002
+ "web",
1003
+ {
1004
+ or: ["chrome", "safari"],
1005
+ },
1006
+ {
1007
+ not: ["mobile"],
1008
+ },
1009
+ ],
1010
+ }, {}, ["web", "safari"])).toEqual({
1011
+ and: [
1012
+ "*",
1013
+ {
1014
+ or: ["chrome", "*"],
1015
+ },
1016
+ {
1017
+ not: ["mobile"],
1018
+ },
1019
+ ],
1020
+ });
1021
+ // removeSegments combined with matching context
1022
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, ["web", "chrome"], {
1023
+ platform: "web",
1024
+ }, ["chrome"])).toEqual(["*", "*"]);
1025
+ // removeSegments with segment that would match context
1026
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, ["web", "chrome"], {
1027
+ platform: "web",
1028
+ browser: "chrome",
1029
+ }, ["web"])).toEqual(["*", "*"]);
1030
+ // Complex: removeSegments, context matching, and nested structures
1031
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, {
1032
+ and: [
1033
+ "web",
1034
+ {
1035
+ or: ["chrome", "safari", "mobile"],
1036
+ },
1037
+ "us",
1038
+ ],
1039
+ }, {
1040
+ platform: "web",
1041
+ browser: "chrome",
1042
+ }, ["us"])).toEqual({
1043
+ and: [
1044
+ "*",
1045
+ {
1046
+ or: ["*", "safari", "mobile"],
1047
+ },
1048
+ "*",
1049
+ ],
1050
+ });
1051
+ // Empty removeSegments array (should behave like no removeSegments)
1052
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, "web", {}, [])).toEqual("web");
1053
+ // removeSegments with segment that doesn't exist in datafile
1054
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, ["web", "nonexistent"], {}, [
1055
+ "nonexistent",
1056
+ ])).toEqual(["web", "*"]);
1057
+ // removeSegments with "*" in array (should still work)
1058
+ expect((0, buildScopedSegments_1.buildScopedGroupSegments)(datafileReaderWithSegments, ["*", "web", "chrome"], {}, ["web"])).toEqual(["*", "*", "chrome"]);
1059
+ });
1060
+ });
1061
+ });
1062
+ //# sourceMappingURL=buildScopedSegments.spec.js.map