@featurevisor/core 2.3.3 → 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 +19 -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,1988 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const buildScopedDatafile_1 = require("./buildScopedDatafile");
4
+ describe("core: buildScopedDatafile", function () {
5
+ test("buildScopedDatafile is a function", function () {
6
+ expect(buildScopedDatafile_1.buildScopedDatafile).toBeInstanceOf(Function);
7
+ });
8
+ describe("basic functionality", function () {
9
+ test("empty datafile", function () {
10
+ const datafile = {
11
+ schemaVersion: "2",
12
+ revision: "unknown",
13
+ segments: {},
14
+ features: {},
15
+ };
16
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, {});
17
+ expect(result).toEqual(datafile);
18
+ });
19
+ test("datafile with no segments", function () {
20
+ const datafile = {
21
+ schemaVersion: "2",
22
+ revision: "unknown",
23
+ segments: {},
24
+ features: {
25
+ feature1: {
26
+ bucketBy: ["userId"],
27
+ traffic: [
28
+ {
29
+ key: "rule1",
30
+ segments: "*",
31
+ percentage: 100000,
32
+ },
33
+ ],
34
+ },
35
+ },
36
+ };
37
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, {});
38
+ expect(result.segments).toEqual({});
39
+ expect(result.features.feature1).toBeDefined();
40
+ });
41
+ test("datafile with no features", function () {
42
+ const datafile = {
43
+ schemaVersion: "2",
44
+ revision: "unknown",
45
+ segments: {
46
+ web: {
47
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
48
+ },
49
+ },
50
+ features: {},
51
+ };
52
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
53
+ expect(result.segments).toEqual({});
54
+ expect(result.features).toEqual({});
55
+ });
56
+ });
57
+ describe("segments processing", function () {
58
+ test("segment with matching condition becomes * and is removed", function () {
59
+ const datafile = {
60
+ schemaVersion: "2",
61
+ revision: "unknown",
62
+ segments: {
63
+ web: {
64
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
65
+ },
66
+ },
67
+ features: {},
68
+ };
69
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
70
+ expect(result.segments).toEqual({});
71
+ });
72
+ test("segment with non-matching condition remains", function () {
73
+ const datafile = {
74
+ schemaVersion: "2",
75
+ revision: "unknown",
76
+ segments: {
77
+ web: {
78
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
79
+ },
80
+ },
81
+ features: {},
82
+ };
83
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "mobile" });
84
+ expect(result.segments.web).toBeDefined();
85
+ expect(result.segments.web.conditions).toEqual([
86
+ { attribute: "platform", operator: "equals", value: "web" },
87
+ ]);
88
+ });
89
+ test("segment with * condition is removed", function () {
90
+ const datafile = {
91
+ schemaVersion: "2",
92
+ revision: "unknown",
93
+ segments: {
94
+ all: {
95
+ conditions: "*",
96
+ },
97
+ },
98
+ features: {},
99
+ };
100
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, {});
101
+ expect(result.segments).toEqual({});
102
+ });
103
+ test("multiple segments - some match, some don't", function () {
104
+ const datafile = {
105
+ schemaVersion: "2",
106
+ revision: "unknown",
107
+ segments: {
108
+ web: {
109
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
110
+ },
111
+ mobile: {
112
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
113
+ },
114
+ chrome: {
115
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
116
+ },
117
+ },
118
+ features: {},
119
+ };
120
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
121
+ expect(result.segments.web).toBeUndefined();
122
+ expect(result.segments.mobile).toBeDefined();
123
+ expect(result.segments.chrome).toBeUndefined();
124
+ });
125
+ test("segment with AND conditions - all match", function () {
126
+ const datafile = {
127
+ schemaVersion: "2",
128
+ revision: "unknown",
129
+ segments: {
130
+ premium: {
131
+ conditions: {
132
+ and: [
133
+ { attribute: "tier", operator: "equals", value: "premium" },
134
+ { attribute: "status", operator: "equals", value: "active" },
135
+ ],
136
+ },
137
+ },
138
+ },
139
+ features: {},
140
+ };
141
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { tier: "premium", status: "active" });
142
+ expect(result.segments).toEqual({});
143
+ });
144
+ test("segment with AND conditions - partial match", function () {
145
+ const datafile = {
146
+ schemaVersion: "2",
147
+ revision: "unknown",
148
+ segments: {
149
+ premium: {
150
+ conditions: {
151
+ and: [
152
+ { attribute: "tier", operator: "equals", value: "premium" },
153
+ { attribute: "status", operator: "equals", value: "active" },
154
+ ],
155
+ },
156
+ },
157
+ },
158
+ features: {},
159
+ };
160
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { tier: "premium" });
161
+ expect(result.segments.premium).toBeDefined();
162
+ expect(result.segments.premium.conditions).toEqual({
163
+ and: [{ attribute: "status", operator: "equals", value: "active" }],
164
+ });
165
+ });
166
+ test("segment with OR conditions - one matches", function () {
167
+ const datafile = {
168
+ schemaVersion: "2",
169
+ revision: "unknown",
170
+ segments: {
171
+ browser: {
172
+ conditions: {
173
+ or: [
174
+ { attribute: "browser", operator: "equals", value: "chrome" },
175
+ { attribute: "browser", operator: "equals", value: "firefox" },
176
+ ],
177
+ },
178
+ },
179
+ },
180
+ features: {},
181
+ };
182
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { browser: "chrome" });
183
+ expect(result.segments).toEqual({
184
+ browser: {
185
+ conditions: {
186
+ // @TODO: this OR (with only one child) can become a single (AND) condition
187
+ or: [
188
+ {
189
+ attribute: "browser",
190
+ operator: "equals",
191
+ value: "firefox",
192
+ },
193
+ ],
194
+ },
195
+ },
196
+ });
197
+ });
198
+ test("segment with array of conditions - all match", function () {
199
+ const datafile = {
200
+ schemaVersion: "2",
201
+ revision: "unknown",
202
+ segments: {
203
+ combined: {
204
+ conditions: [
205
+ { attribute: "platform", operator: "equals", value: "web" },
206
+ { attribute: "browser", operator: "equals", value: "chrome" },
207
+ ],
208
+ },
209
+ },
210
+ features: {},
211
+ };
212
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
213
+ expect(result.segments).toEqual({});
214
+ });
215
+ test("segment with array of conditions - partial match", function () {
216
+ const datafile = {
217
+ schemaVersion: "2",
218
+ revision: "unknown",
219
+ segments: {
220
+ combined: {
221
+ conditions: [
222
+ { attribute: "platform", operator: "equals", value: "web" },
223
+ { attribute: "browser", operator: "equals", value: "chrome" },
224
+ ],
225
+ },
226
+ },
227
+ features: {},
228
+ };
229
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
230
+ expect(result.segments.combined).toBeDefined();
231
+ expect(result.segments.combined.conditions).toEqual([
232
+ { attribute: "browser", operator: "equals", value: "chrome" },
233
+ ]);
234
+ });
235
+ });
236
+ describe("features with force entries", function () {
237
+ test("force with matching segments becomes *", function () {
238
+ const datafile = {
239
+ schemaVersion: "2",
240
+ revision: "unknown",
241
+ segments: {
242
+ web: {
243
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
244
+ },
245
+ },
246
+ features: {
247
+ feature1: {
248
+ bucketBy: ["userId"],
249
+ traffic: [],
250
+ force: [
251
+ {
252
+ segments: "web",
253
+ variation: "treatment",
254
+ },
255
+ ],
256
+ },
257
+ },
258
+ };
259
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
260
+ expect(result.features.feature1.force?.[0].segments).toEqual("*");
261
+ });
262
+ test("force with non-matching segments remains", function () {
263
+ const datafile = {
264
+ schemaVersion: "2",
265
+ revision: "unknown",
266
+ segments: {
267
+ web: {
268
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
269
+ },
270
+ },
271
+ features: {
272
+ feature1: {
273
+ bucketBy: ["userId"],
274
+ traffic: [],
275
+ force: [
276
+ {
277
+ segments: "web",
278
+ variation: "treatment",
279
+ },
280
+ ],
281
+ },
282
+ },
283
+ };
284
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "mobile" });
285
+ expect(result.features.feature1.force?.[0].segments).toEqual("web");
286
+ });
287
+ test("force with matching conditions becomes *", function () {
288
+ const datafile = {
289
+ schemaVersion: "2",
290
+ revision: "unknown",
291
+ segments: {},
292
+ features: {
293
+ feature1: {
294
+ bucketBy: ["userId"],
295
+ traffic: [],
296
+ force: [
297
+ {
298
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
299
+ variation: "treatment",
300
+ },
301
+ ],
302
+ },
303
+ },
304
+ };
305
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
306
+ expect(result.features.feature1.force?.[0].conditions).toEqual("*");
307
+ });
308
+ test("force with non-matching conditions remains", function () {
309
+ const datafile = {
310
+ schemaVersion: "2",
311
+ revision: "unknown",
312
+ segments: {},
313
+ features: {
314
+ feature1: {
315
+ bucketBy: ["userId"],
316
+ traffic: [],
317
+ force: [
318
+ {
319
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
320
+ variation: "treatment",
321
+ },
322
+ ],
323
+ },
324
+ },
325
+ };
326
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "mobile" });
327
+ expect(result.features.feature1.force?.[0].conditions).toEqual([
328
+ { attribute: "platform", operator: "equals", value: "web" },
329
+ ]);
330
+ });
331
+ test("force with array of segments - all match", function () {
332
+ const datafile = {
333
+ schemaVersion: "2",
334
+ revision: "unknown",
335
+ segments: {
336
+ web: {
337
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
338
+ },
339
+ chrome: {
340
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
341
+ },
342
+ },
343
+ features: {
344
+ feature1: {
345
+ bucketBy: ["userId"],
346
+ traffic: [],
347
+ force: [
348
+ {
349
+ segments: ["web", "chrome"],
350
+ variation: "treatment",
351
+ },
352
+ ],
353
+ },
354
+ },
355
+ };
356
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
357
+ expect(result.features.feature1.force?.[0].segments).toEqual("*");
358
+ });
359
+ test("force with array of segments - partial match", function () {
360
+ const datafile = {
361
+ schemaVersion: "2",
362
+ revision: "unknown",
363
+ segments: {
364
+ web: {
365
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
366
+ },
367
+ chrome: {
368
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
369
+ },
370
+ },
371
+ features: {
372
+ feature1: {
373
+ bucketBy: ["userId"],
374
+ traffic: [],
375
+ force: [
376
+ {
377
+ segments: ["web", "chrome"],
378
+ variation: "treatment",
379
+ },
380
+ ],
381
+ },
382
+ },
383
+ };
384
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
385
+ expect(result.features.feature1.force?.[0].segments).toEqual(["chrome"]);
386
+ });
387
+ test("force with AND group segments", function () {
388
+ const datafile = {
389
+ schemaVersion: "2",
390
+ revision: "unknown",
391
+ segments: {
392
+ web: {
393
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
394
+ },
395
+ chrome: {
396
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
397
+ },
398
+ },
399
+ features: {
400
+ feature1: {
401
+ bucketBy: ["userId"],
402
+ traffic: [],
403
+ force: [
404
+ {
405
+ segments: { and: ["web", "chrome"] },
406
+ variation: "treatment",
407
+ },
408
+ ],
409
+ },
410
+ },
411
+ };
412
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
413
+ expect(result.features.feature1.force?.[0].segments).toEqual("*");
414
+ });
415
+ test("force with OR group segments", function () {
416
+ const datafile = {
417
+ schemaVersion: "2",
418
+ revision: "unknown",
419
+ segments: {
420
+ web: {
421
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
422
+ },
423
+ mobile: {
424
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
425
+ },
426
+ },
427
+ features: {
428
+ feature1: {
429
+ bucketBy: ["userId"],
430
+ traffic: [],
431
+ force: [
432
+ {
433
+ segments: { or: ["web", "mobile"] },
434
+ variation: "treatment",
435
+ },
436
+ ],
437
+ },
438
+ },
439
+ };
440
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
441
+ expect(result.features.feature1.force?.[0].segments).toEqual({ or: ["mobile"] });
442
+ });
443
+ test("force with AND conditions", function () {
444
+ const datafile = {
445
+ schemaVersion: "2",
446
+ revision: "unknown",
447
+ segments: {},
448
+ features: {
449
+ feature1: {
450
+ bucketBy: ["userId"],
451
+ traffic: [],
452
+ force: [
453
+ {
454
+ conditions: {
455
+ and: [
456
+ { attribute: "platform", operator: "equals", value: "web" },
457
+ { attribute: "browser", operator: "equals", value: "chrome" },
458
+ ],
459
+ },
460
+ variation: "treatment",
461
+ },
462
+ ],
463
+ },
464
+ },
465
+ };
466
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
467
+ expect(result.features.feature1.force?.[0].conditions).toEqual("*");
468
+ });
469
+ test("force with OR conditions", function () {
470
+ const datafile = {
471
+ schemaVersion: "2",
472
+ revision: "unknown",
473
+ segments: {},
474
+ features: {
475
+ feature1: {
476
+ bucketBy: ["userId"],
477
+ traffic: [],
478
+ force: [
479
+ {
480
+ conditions: {
481
+ or: [
482
+ { attribute: "platform", operator: "equals", value: "web" },
483
+ { attribute: "platform", operator: "equals", value: "mobile" },
484
+ ],
485
+ },
486
+ variation: "treatment",
487
+ },
488
+ ],
489
+ },
490
+ },
491
+ };
492
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
493
+ expect(result.features.feature1.force?.[0].conditions).toEqual({
494
+ or: [{ attribute: "platform", operator: "equals", value: "mobile" }],
495
+ });
496
+ });
497
+ test("multiple force entries", function () {
498
+ const datafile = {
499
+ schemaVersion: "2",
500
+ revision: "unknown",
501
+ segments: {
502
+ web: {
503
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
504
+ },
505
+ mobile: {
506
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
507
+ },
508
+ },
509
+ features: {
510
+ feature1: {
511
+ bucketBy: ["userId"],
512
+ traffic: [],
513
+ force: [
514
+ {
515
+ segments: "web",
516
+ variation: "treatment",
517
+ },
518
+ {
519
+ segments: "mobile",
520
+ variation: "control",
521
+ },
522
+ ],
523
+ },
524
+ },
525
+ };
526
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
527
+ expect(result.features.feature1.force?.[0].segments).toEqual("*");
528
+ expect(result.features.feature1.force?.[1].segments).toEqual("mobile");
529
+ });
530
+ });
531
+ describe("features with traffic entries", function () {
532
+ test("traffic with matching segments becomes *", function () {
533
+ const datafile = {
534
+ schemaVersion: "2",
535
+ revision: "unknown",
536
+ segments: {
537
+ web: {
538
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
539
+ },
540
+ },
541
+ features: {
542
+ feature1: {
543
+ bucketBy: ["userId"],
544
+ traffic: [
545
+ {
546
+ key: "rule1",
547
+ segments: "web",
548
+ percentage: 100000,
549
+ },
550
+ ],
551
+ },
552
+ },
553
+ };
554
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
555
+ expect(result.features.feature1.traffic[0].segments).toEqual("*");
556
+ });
557
+ test("traffic with non-matching segments remains", function () {
558
+ const datafile = {
559
+ schemaVersion: "2",
560
+ revision: "unknown",
561
+ segments: {
562
+ web: {
563
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
564
+ },
565
+ },
566
+ features: {
567
+ feature1: {
568
+ bucketBy: ["userId"],
569
+ traffic: [
570
+ {
571
+ key: "rule1",
572
+ segments: "web",
573
+ percentage: 100000,
574
+ },
575
+ ],
576
+ },
577
+ },
578
+ };
579
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "mobile" });
580
+ expect(result.features.feature1.traffic[0].segments).toEqual("web");
581
+ });
582
+ test("traffic with array of segments - all match", function () {
583
+ const datafile = {
584
+ schemaVersion: "2",
585
+ revision: "unknown",
586
+ segments: {
587
+ web: {
588
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
589
+ },
590
+ chrome: {
591
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
592
+ },
593
+ },
594
+ features: {
595
+ feature1: {
596
+ bucketBy: ["userId"],
597
+ traffic: [
598
+ {
599
+ key: "rule1",
600
+ segments: ["web", "chrome"],
601
+ percentage: 100000,
602
+ },
603
+ ],
604
+ },
605
+ },
606
+ };
607
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
608
+ expect(result.features.feature1.traffic[0].segments).toEqual("*");
609
+ });
610
+ test("traffic with array of segments - partial match", function () {
611
+ const datafile = {
612
+ schemaVersion: "2",
613
+ revision: "unknown",
614
+ segments: {
615
+ web: {
616
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
617
+ },
618
+ chrome: {
619
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
620
+ },
621
+ },
622
+ features: {
623
+ feature1: {
624
+ bucketBy: ["userId"],
625
+ traffic: [
626
+ {
627
+ key: "rule1",
628
+ segments: ["web", "chrome"],
629
+ percentage: 100000,
630
+ },
631
+ ],
632
+ },
633
+ },
634
+ };
635
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
636
+ expect(result.features.feature1.traffic[0].segments).toEqual(["chrome"]);
637
+ });
638
+ test("traffic with AND group segments", function () {
639
+ const datafile = {
640
+ schemaVersion: "2",
641
+ revision: "unknown",
642
+ segments: {
643
+ web: {
644
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
645
+ },
646
+ chrome: {
647
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
648
+ },
649
+ },
650
+ features: {
651
+ feature1: {
652
+ bucketBy: ["userId"],
653
+ traffic: [
654
+ {
655
+ key: "rule1",
656
+ segments: { and: ["web", "chrome"] },
657
+ percentage: 100000,
658
+ },
659
+ ],
660
+ },
661
+ },
662
+ };
663
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
664
+ expect(result.features.feature1.traffic[0].segments).toEqual("*");
665
+ });
666
+ test("traffic with OR group segments", function () {
667
+ const datafile = {
668
+ schemaVersion: "2",
669
+ revision: "unknown",
670
+ segments: {
671
+ web: {
672
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
673
+ },
674
+ mobile: {
675
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
676
+ },
677
+ },
678
+ features: {
679
+ feature1: {
680
+ bucketBy: ["userId"],
681
+ traffic: [
682
+ {
683
+ key: "rule1",
684
+ segments: { or: ["web", "mobile"] },
685
+ percentage: 100000,
686
+ },
687
+ ],
688
+ },
689
+ },
690
+ };
691
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
692
+ expect(result.features.feature1.traffic[0].segments).toEqual({ or: ["mobile"] });
693
+ });
694
+ test("multiple traffic entries", function () {
695
+ const datafile = {
696
+ schemaVersion: "2",
697
+ revision: "unknown",
698
+ segments: {
699
+ web: {
700
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
701
+ },
702
+ mobile: {
703
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
704
+ },
705
+ },
706
+ features: {
707
+ feature1: {
708
+ bucketBy: ["userId"],
709
+ traffic: [
710
+ {
711
+ key: "rule1",
712
+ segments: "web",
713
+ percentage: 50000,
714
+ },
715
+ {
716
+ key: "rule2",
717
+ segments: "mobile",
718
+ percentage: 50000,
719
+ },
720
+ ],
721
+ },
722
+ },
723
+ };
724
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
725
+ expect(result.features.feature1.traffic[0].segments).toEqual("*");
726
+ expect(result.features.feature1.traffic[1].segments).toEqual("mobile");
727
+ });
728
+ test("consecutive traffic entries with * segments - second is removed", function () {
729
+ const datafile = {
730
+ schemaVersion: "2",
731
+ revision: "unknown",
732
+ segments: {
733
+ web: {
734
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
735
+ },
736
+ },
737
+ features: {
738
+ feature1: {
739
+ bucketBy: ["userId"],
740
+ traffic: [
741
+ {
742
+ key: "rule1",
743
+ segments: "*",
744
+ percentage: 50000,
745
+ },
746
+ {
747
+ key: "rule2",
748
+ segments: "*",
749
+ percentage: 50000,
750
+ },
751
+ ],
752
+ },
753
+ },
754
+ };
755
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, {});
756
+ expect(result.features.feature1.traffic).toHaveLength(1);
757
+ expect(result.features.feature1.traffic[0].key).toEqual("rule1");
758
+ });
759
+ test("consecutive traffic entries with * segments - multiple consecutive removed", function () {
760
+ const datafile = {
761
+ schemaVersion: "2",
762
+ revision: "unknown",
763
+ segments: {
764
+ web: {
765
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
766
+ },
767
+ },
768
+ features: {
769
+ feature1: {
770
+ bucketBy: ["userId"],
771
+ traffic: [
772
+ {
773
+ key: "rule1",
774
+ segments: "*",
775
+ percentage: 25000,
776
+ },
777
+ ],
778
+ },
779
+ },
780
+ };
781
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
782
+ expect(result.features.feature1.traffic).toHaveLength(1);
783
+ expect(result.features.feature1.traffic[0].key).toEqual("rule1");
784
+ expect(result.features.feature1.traffic[0].segments).toEqual("*");
785
+ });
786
+ test("non-consecutive traffic entries with * segments - both remain", function () {
787
+ const datafile = {
788
+ schemaVersion: "2",
789
+ revision: "unknown",
790
+ segments: {
791
+ web: {
792
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
793
+ },
794
+ mobile: {
795
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
796
+ },
797
+ },
798
+ features: {
799
+ feature1: {
800
+ bucketBy: ["userId"],
801
+ traffic: [
802
+ {
803
+ key: "rule1",
804
+ segments: "*",
805
+ percentage: 33333,
806
+ },
807
+ {
808
+ key: "rule2",
809
+ segments: "mobile",
810
+ percentage: 33333,
811
+ },
812
+ {
813
+ key: "rule3",
814
+ segments: "*",
815
+ percentage: 33334,
816
+ },
817
+ ],
818
+ },
819
+ },
820
+ };
821
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, {});
822
+ expect(result.features.feature1.traffic).toHaveLength(3);
823
+ });
824
+ test("traffic entry that becomes * after scoping - consecutive removal works", function () {
825
+ const datafile = {
826
+ schemaVersion: "2",
827
+ revision: "unknown",
828
+ segments: {
829
+ web: {
830
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
831
+ },
832
+ },
833
+ features: {
834
+ feature1: {
835
+ bucketBy: ["userId"],
836
+ traffic: [
837
+ {
838
+ key: "rule1",
839
+ segments: "*",
840
+ percentage: 50000,
841
+ },
842
+ {
843
+ key: "rule2",
844
+ segments: "web",
845
+ percentage: 50000,
846
+ },
847
+ ],
848
+ },
849
+ },
850
+ };
851
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
852
+ expect(result.features.feature1.traffic).toHaveLength(1);
853
+ expect(result.features.feature1.traffic[0].key).toEqual("rule1");
854
+ });
855
+ });
856
+ describe("features with variations and variableOverrides", function () {
857
+ test("variableOverride with matching segments becomes *", function () {
858
+ const datafile = {
859
+ schemaVersion: "2",
860
+ revision: "unknown",
861
+ segments: {
862
+ web: {
863
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
864
+ },
865
+ },
866
+ features: {
867
+ feature1: {
868
+ bucketBy: ["userId"],
869
+ traffic: [],
870
+ variations: [
871
+ {
872
+ value: "control",
873
+ variableOverrides: {
874
+ color: [
875
+ {
876
+ value: "red",
877
+ segments: "web",
878
+ },
879
+ ],
880
+ },
881
+ },
882
+ ],
883
+ },
884
+ },
885
+ };
886
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
887
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].segments).toEqual("*");
888
+ });
889
+ test("variableOverride with non-matching segments remains", function () {
890
+ const datafile = {
891
+ schemaVersion: "2",
892
+ revision: "unknown",
893
+ segments: {
894
+ web: {
895
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
896
+ },
897
+ },
898
+ features: {
899
+ feature1: {
900
+ bucketBy: ["userId"],
901
+ traffic: [],
902
+ variations: [
903
+ {
904
+ value: "control",
905
+ variableOverrides: {
906
+ color: [
907
+ {
908
+ value: "red",
909
+ segments: "web",
910
+ },
911
+ ],
912
+ },
913
+ },
914
+ ],
915
+ },
916
+ },
917
+ };
918
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "mobile" });
919
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].segments).toEqual("web");
920
+ });
921
+ test("variableOverride with matching conditions becomes *", function () {
922
+ const datafile = {
923
+ schemaVersion: "2",
924
+ revision: "unknown",
925
+ segments: {},
926
+ features: {
927
+ feature1: {
928
+ bucketBy: ["userId"],
929
+ traffic: [],
930
+ variations: [
931
+ {
932
+ value: "control",
933
+ variableOverrides: {
934
+ color: [
935
+ {
936
+ value: "red",
937
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
938
+ },
939
+ ],
940
+ },
941
+ },
942
+ ],
943
+ },
944
+ },
945
+ };
946
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
947
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].conditions).toEqual("*");
948
+ });
949
+ test("variableOverride with non-matching conditions remains", function () {
950
+ const datafile = {
951
+ schemaVersion: "2",
952
+ revision: "unknown",
953
+ segments: {},
954
+ features: {
955
+ feature1: {
956
+ bucketBy: ["userId"],
957
+ traffic: [],
958
+ variations: [
959
+ {
960
+ value: "control",
961
+ variableOverrides: {
962
+ color: [
963
+ {
964
+ value: "red",
965
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
966
+ },
967
+ ],
968
+ },
969
+ },
970
+ ],
971
+ },
972
+ },
973
+ };
974
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "mobile" });
975
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].conditions).toEqual([{ attribute: "platform", operator: "equals", value: "web" }]);
976
+ });
977
+ test("variableOverride with array of segments", function () {
978
+ const datafile = {
979
+ schemaVersion: "2",
980
+ revision: "unknown",
981
+ segments: {
982
+ web: {
983
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
984
+ },
985
+ chrome: {
986
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
987
+ },
988
+ },
989
+ features: {
990
+ feature1: {
991
+ bucketBy: ["userId"],
992
+ traffic: [],
993
+ variations: [
994
+ {
995
+ value: "control",
996
+ variableOverrides: {
997
+ color: [
998
+ {
999
+ value: "red",
1000
+ segments: ["web", "chrome"],
1001
+ },
1002
+ ],
1003
+ },
1004
+ },
1005
+ ],
1006
+ },
1007
+ },
1008
+ };
1009
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
1010
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].segments).toEqual("*");
1011
+ });
1012
+ test("variableOverride with AND group segments", function () {
1013
+ const datafile = {
1014
+ schemaVersion: "2",
1015
+ revision: "unknown",
1016
+ segments: {
1017
+ web: {
1018
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1019
+ },
1020
+ chrome: {
1021
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
1022
+ },
1023
+ },
1024
+ features: {
1025
+ feature1: {
1026
+ bucketBy: ["userId"],
1027
+ traffic: [],
1028
+ variations: [
1029
+ {
1030
+ value: "control",
1031
+ variableOverrides: {
1032
+ color: [
1033
+ {
1034
+ value: "red",
1035
+ segments: { and: ["web", "chrome"] },
1036
+ },
1037
+ ],
1038
+ },
1039
+ },
1040
+ ],
1041
+ },
1042
+ },
1043
+ };
1044
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
1045
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].segments).toEqual("*");
1046
+ });
1047
+ test("variableOverride with OR group segments", function () {
1048
+ const datafile = {
1049
+ schemaVersion: "2",
1050
+ revision: "unknown",
1051
+ segments: {
1052
+ web: {
1053
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1054
+ },
1055
+ mobile: {
1056
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
1057
+ },
1058
+ },
1059
+ features: {
1060
+ feature1: {
1061
+ bucketBy: ["userId"],
1062
+ traffic: [],
1063
+ variations: [
1064
+ {
1065
+ value: "control",
1066
+ variableOverrides: {
1067
+ color: [
1068
+ {
1069
+ value: "red",
1070
+ segments: { or: ["web", "mobile"] },
1071
+ },
1072
+ ],
1073
+ },
1074
+ },
1075
+ ],
1076
+ },
1077
+ },
1078
+ };
1079
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
1080
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].segments).toEqual({ or: ["mobile"] });
1081
+ });
1082
+ test("variableOverride with AND conditions", function () {
1083
+ const datafile = {
1084
+ schemaVersion: "2",
1085
+ revision: "unknown",
1086
+ segments: {},
1087
+ features: {
1088
+ feature1: {
1089
+ bucketBy: ["userId"],
1090
+ traffic: [],
1091
+ variations: [
1092
+ {
1093
+ value: "control",
1094
+ variableOverrides: {
1095
+ color: [
1096
+ {
1097
+ value: "red",
1098
+ conditions: {
1099
+ and: [
1100
+ { attribute: "platform", operator: "equals", value: "web" },
1101
+ { attribute: "browser", operator: "equals", value: "chrome" },
1102
+ ],
1103
+ },
1104
+ },
1105
+ ],
1106
+ },
1107
+ },
1108
+ ],
1109
+ },
1110
+ },
1111
+ };
1112
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
1113
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].conditions).toEqual("*");
1114
+ });
1115
+ test("variableOverride with OR conditions", function () {
1116
+ const datafile = {
1117
+ schemaVersion: "2",
1118
+ revision: "unknown",
1119
+ segments: {},
1120
+ features: {
1121
+ feature1: {
1122
+ bucketBy: ["userId"],
1123
+ traffic: [],
1124
+ variations: [
1125
+ {
1126
+ value: "control",
1127
+ variableOverrides: {
1128
+ color: [
1129
+ {
1130
+ value: "red",
1131
+ conditions: {
1132
+ or: [
1133
+ { attribute: "platform", operator: "equals", value: "web" },
1134
+ { attribute: "platform", operator: "equals", value: "mobile" },
1135
+ ],
1136
+ },
1137
+ },
1138
+ ],
1139
+ },
1140
+ },
1141
+ ],
1142
+ },
1143
+ },
1144
+ };
1145
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
1146
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].conditions).toEqual({
1147
+ or: [{ attribute: "platform", operator: "equals", value: "mobile" }],
1148
+ });
1149
+ });
1150
+ test("multiple variableOverrides for same variable", function () {
1151
+ const datafile = {
1152
+ schemaVersion: "2",
1153
+ revision: "unknown",
1154
+ segments: {
1155
+ web: {
1156
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1157
+ },
1158
+ mobile: {
1159
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
1160
+ },
1161
+ },
1162
+ features: {
1163
+ feature1: {
1164
+ bucketBy: ["userId"],
1165
+ traffic: [],
1166
+ variations: [
1167
+ {
1168
+ value: "control",
1169
+ variableOverrides: {
1170
+ color: [
1171
+ {
1172
+ value: "red",
1173
+ segments: "web",
1174
+ },
1175
+ {
1176
+ value: "blue",
1177
+ segments: "mobile",
1178
+ },
1179
+ ],
1180
+ },
1181
+ },
1182
+ ],
1183
+ },
1184
+ },
1185
+ };
1186
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
1187
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].segments).toEqual("*");
1188
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[1].segments).toEqual("mobile");
1189
+ });
1190
+ test("multiple variables with variableOverrides", function () {
1191
+ const datafile = {
1192
+ schemaVersion: "2",
1193
+ revision: "unknown",
1194
+ segments: {
1195
+ web: {
1196
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1197
+ },
1198
+ },
1199
+ features: {
1200
+ feature1: {
1201
+ bucketBy: ["userId"],
1202
+ traffic: [],
1203
+ variations: [
1204
+ {
1205
+ value: "control",
1206
+ variableOverrides: {
1207
+ color: [
1208
+ {
1209
+ value: "red",
1210
+ segments: "web",
1211
+ },
1212
+ ],
1213
+ size: [
1214
+ {
1215
+ value: "large",
1216
+ segments: "web",
1217
+ },
1218
+ ],
1219
+ },
1220
+ },
1221
+ ],
1222
+ },
1223
+ },
1224
+ };
1225
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
1226
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].segments).toEqual("*");
1227
+ expect(result.features.feature1.variations?.[0].variableOverrides?.size[0].segments).toEqual("*");
1228
+ });
1229
+ test("multiple variations with variableOverrides", function () {
1230
+ const datafile = {
1231
+ schemaVersion: "2",
1232
+ revision: "unknown",
1233
+ segments: {
1234
+ web: {
1235
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1236
+ },
1237
+ mobile: {
1238
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
1239
+ },
1240
+ },
1241
+ features: {
1242
+ feature1: {
1243
+ bucketBy: ["userId"],
1244
+ traffic: [],
1245
+ variations: [
1246
+ {
1247
+ value: "control",
1248
+ variableOverrides: {
1249
+ color: [
1250
+ {
1251
+ value: "red",
1252
+ segments: "web",
1253
+ },
1254
+ ],
1255
+ },
1256
+ },
1257
+ {
1258
+ value: "treatment",
1259
+ variableOverrides: {
1260
+ color: [
1261
+ {
1262
+ value: "blue",
1263
+ segments: "mobile",
1264
+ },
1265
+ ],
1266
+ },
1267
+ },
1268
+ ],
1269
+ },
1270
+ },
1271
+ };
1272
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
1273
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].segments).toEqual("*");
1274
+ expect(result.features.feature1.variations?.[1].variableOverrides?.color[0].segments).toEqual("mobile");
1275
+ });
1276
+ });
1277
+ describe("complex nested scenarios", function () {
1278
+ test("feature with force, traffic, and variations", function () {
1279
+ const datafile = {
1280
+ schemaVersion: "2",
1281
+ revision: "unknown",
1282
+ segments: {
1283
+ web: {
1284
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1285
+ },
1286
+ chrome: {
1287
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
1288
+ },
1289
+ },
1290
+ features: {
1291
+ feature1: {
1292
+ bucketBy: ["userId"],
1293
+ traffic: [
1294
+ {
1295
+ key: "rule1",
1296
+ segments: "web",
1297
+ percentage: 100000,
1298
+ },
1299
+ ],
1300
+ force: [
1301
+ {
1302
+ segments: "chrome",
1303
+ variation: "treatment",
1304
+ },
1305
+ ],
1306
+ variations: [
1307
+ {
1308
+ value: "control",
1309
+ variableOverrides: {
1310
+ color: [
1311
+ {
1312
+ value: "red",
1313
+ segments: "web",
1314
+ },
1315
+ ],
1316
+ },
1317
+ },
1318
+ ],
1319
+ },
1320
+ },
1321
+ };
1322
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
1323
+ expect(result.features.feature1.traffic[0].segments).toEqual("*");
1324
+ expect(result.features.feature1.force?.[0].segments).toEqual("*");
1325
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].segments).toEqual("*");
1326
+ });
1327
+ test("multiple features with different scoping results", function () {
1328
+ const datafile = {
1329
+ schemaVersion: "2",
1330
+ revision: "unknown",
1331
+ segments: {
1332
+ web: {
1333
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1334
+ },
1335
+ mobile: {
1336
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
1337
+ },
1338
+ },
1339
+ features: {
1340
+ feature1: {
1341
+ bucketBy: ["userId"],
1342
+ traffic: [
1343
+ {
1344
+ key: "rule1",
1345
+ segments: "web",
1346
+ percentage: 100000,
1347
+ },
1348
+ ],
1349
+ },
1350
+ feature2: {
1351
+ bucketBy: ["userId"],
1352
+ traffic: [
1353
+ {
1354
+ key: "rule1",
1355
+ segments: "mobile",
1356
+ percentage: 100000,
1357
+ },
1358
+ ],
1359
+ },
1360
+ },
1361
+ };
1362
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
1363
+ expect(result.features.feature1.traffic[0].segments).toEqual("*");
1364
+ expect(result.features.feature2.traffic[0].segments).toEqual("mobile");
1365
+ });
1366
+ test("segment used in multiple places - all scoped correctly", function () {
1367
+ const datafile = {
1368
+ schemaVersion: "2",
1369
+ revision: "unknown",
1370
+ segments: {
1371
+ web: {
1372
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1373
+ },
1374
+ },
1375
+ features: {
1376
+ feature1: {
1377
+ bucketBy: ["userId"],
1378
+ traffic: [
1379
+ {
1380
+ key: "rule1",
1381
+ segments: "web",
1382
+ percentage: 100000,
1383
+ },
1384
+ ],
1385
+ force: [
1386
+ {
1387
+ segments: "web",
1388
+ variation: "treatment",
1389
+ },
1390
+ ],
1391
+ variations: [
1392
+ {
1393
+ value: "control",
1394
+ variableOverrides: {
1395
+ color: [
1396
+ {
1397
+ value: "red",
1398
+ segments: "web",
1399
+ },
1400
+ ],
1401
+ },
1402
+ },
1403
+ ],
1404
+ },
1405
+ },
1406
+ };
1407
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
1408
+ expect(result.segments).toEqual({});
1409
+ expect(result.features.feature1.traffic[0].segments).toEqual("*");
1410
+ expect(result.features.feature1.force?.[0].segments).toEqual("*");
1411
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].segments).toEqual("*");
1412
+ });
1413
+ test("nested AND/OR segments in force and traffic", function () {
1414
+ const datafile = {
1415
+ schemaVersion: "2",
1416
+ revision: "unknown",
1417
+ segments: {
1418
+ web: {
1419
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1420
+ },
1421
+ chrome: {
1422
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
1423
+ },
1424
+ mobile: {
1425
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
1426
+ },
1427
+ },
1428
+ features: {
1429
+ feature1: {
1430
+ bucketBy: ["userId"],
1431
+ traffic: [
1432
+ {
1433
+ key: "rule1",
1434
+ segments: {
1435
+ and: ["web", "chrome"],
1436
+ },
1437
+ percentage: 100000,
1438
+ },
1439
+ ],
1440
+ force: [
1441
+ {
1442
+ segments: {
1443
+ or: ["web", "mobile"],
1444
+ },
1445
+ variation: "treatment",
1446
+ },
1447
+ ],
1448
+ },
1449
+ },
1450
+ };
1451
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
1452
+ expect(result.features.feature1.traffic[0].segments).toEqual("*");
1453
+ expect(result.features.feature1.force?.[0].segments).toEqual({ or: ["mobile"] });
1454
+ });
1455
+ test("nested AND/OR conditions in force and variableOverrides", function () {
1456
+ const datafile = {
1457
+ schemaVersion: "2",
1458
+ revision: "unknown",
1459
+ segments: {},
1460
+ features: {
1461
+ feature1: {
1462
+ bucketBy: ["userId"],
1463
+ traffic: [],
1464
+ force: [
1465
+ {
1466
+ conditions: {
1467
+ and: [
1468
+ { attribute: "platform", operator: "equals", value: "web" },
1469
+ { attribute: "browser", operator: "equals", value: "chrome" },
1470
+ ],
1471
+ },
1472
+ variation: "treatment",
1473
+ },
1474
+ ],
1475
+ variations: [
1476
+ {
1477
+ value: "control",
1478
+ variableOverrides: {
1479
+ color: [
1480
+ {
1481
+ value: "red",
1482
+ conditions: {
1483
+ or: [
1484
+ { attribute: "platform", operator: "equals", value: "web" },
1485
+ { attribute: "platform", operator: "equals", value: "mobile" },
1486
+ ],
1487
+ },
1488
+ },
1489
+ ],
1490
+ },
1491
+ },
1492
+ ],
1493
+ },
1494
+ },
1495
+ };
1496
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
1497
+ expect(result.features.feature1.force?.[0].conditions).toEqual("*");
1498
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].conditions).toEqual({
1499
+ or: [{ attribute: "platform", operator: "equals", value: "mobile" }],
1500
+ });
1501
+ });
1502
+ });
1503
+ describe("edge cases", function () {
1504
+ test("feature without force, traffic, or variations", function () {
1505
+ const datafile = {
1506
+ schemaVersion: "2",
1507
+ revision: "unknown",
1508
+ segments: {},
1509
+ features: {
1510
+ feature1: {
1511
+ bucketBy: ["userId"],
1512
+ traffic: [],
1513
+ },
1514
+ },
1515
+ };
1516
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, {});
1517
+ expect(result.features.feature1).toBeDefined();
1518
+ expect(result.features.feature1.traffic).toEqual([]);
1519
+ });
1520
+ test("feature with empty force array", function () {
1521
+ const datafile = {
1522
+ schemaVersion: "2",
1523
+ revision: "unknown",
1524
+ segments: {},
1525
+ features: {
1526
+ feature1: {
1527
+ bucketBy: ["userId"],
1528
+ traffic: [],
1529
+ force: [],
1530
+ },
1531
+ },
1532
+ };
1533
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, {});
1534
+ expect(result.features.feature1.force).toEqual([]);
1535
+ });
1536
+ test("feature with empty variations array", function () {
1537
+ const datafile = {
1538
+ schemaVersion: "2",
1539
+ revision: "unknown",
1540
+ segments: {},
1541
+ features: {
1542
+ feature1: {
1543
+ bucketBy: ["userId"],
1544
+ traffic: [],
1545
+ variations: [],
1546
+ },
1547
+ },
1548
+ };
1549
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, {});
1550
+ expect(result.features.feature1.variations).toEqual([]);
1551
+ });
1552
+ test("variation without variableOverrides", function () {
1553
+ const datafile = {
1554
+ schemaVersion: "2",
1555
+ revision: "unknown",
1556
+ segments: {},
1557
+ features: {
1558
+ feature1: {
1559
+ bucketBy: ["userId"],
1560
+ traffic: [],
1561
+ variations: [
1562
+ {
1563
+ value: "control",
1564
+ },
1565
+ ],
1566
+ },
1567
+ },
1568
+ };
1569
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, {});
1570
+ expect(result.features.feature1.variations?.[0].variableOverrides).toBeUndefined();
1571
+ });
1572
+ test("variation with empty variableOverrides object", function () {
1573
+ const datafile = {
1574
+ schemaVersion: "2",
1575
+ revision: "unknown",
1576
+ segments: {},
1577
+ features: {
1578
+ feature1: {
1579
+ bucketBy: ["userId"],
1580
+ traffic: [],
1581
+ variations: [
1582
+ {
1583
+ value: "control",
1584
+ variableOverrides: {},
1585
+ },
1586
+ ],
1587
+ },
1588
+ },
1589
+ };
1590
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, {});
1591
+ expect(result.features.feature1.variations?.[0].variableOverrides).toEqual({});
1592
+ });
1593
+ test("variableOverride with empty segments array", function () {
1594
+ const datafile = {
1595
+ schemaVersion: "2",
1596
+ revision: "unknown",
1597
+ segments: {},
1598
+ features: {
1599
+ feature1: {
1600
+ bucketBy: ["userId"],
1601
+ traffic: [],
1602
+ variations: [
1603
+ {
1604
+ value: "control",
1605
+ variableOverrides: {
1606
+ color: [
1607
+ {
1608
+ value: "red",
1609
+ segments: [],
1610
+ },
1611
+ ],
1612
+ },
1613
+ },
1614
+ ],
1615
+ },
1616
+ },
1617
+ };
1618
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, {});
1619
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].segments).toEqual("*");
1620
+ });
1621
+ test("force with both segments and conditions - segments take precedence", function () {
1622
+ const datafile = {
1623
+ schemaVersion: "2",
1624
+ revision: "unknown",
1625
+ segments: {
1626
+ web: {
1627
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1628
+ },
1629
+ },
1630
+ features: {
1631
+ feature1: {
1632
+ bucketBy: ["userId"],
1633
+ traffic: [],
1634
+ force: [
1635
+ {
1636
+ segments: "web",
1637
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
1638
+ variation: "treatment",
1639
+ },
1640
+ ],
1641
+ },
1642
+ },
1643
+ };
1644
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
1645
+ expect(result.features.feature1.force?.[0].segments).toEqual("*");
1646
+ expect(result.features.feature1.force?.[0].conditions).toBeDefined();
1647
+ });
1648
+ test("traffic with * segments initially - consecutive removal still works", function () {
1649
+ const datafile = {
1650
+ schemaVersion: "2",
1651
+ revision: "unknown",
1652
+ segments: {},
1653
+ features: {
1654
+ feature1: {
1655
+ bucketBy: ["userId"],
1656
+ traffic: [
1657
+ {
1658
+ key: "rule1",
1659
+ segments: "*",
1660
+ percentage: 50000,
1661
+ },
1662
+ {
1663
+ key: "rule2",
1664
+ segments: "*",
1665
+ percentage: 50000,
1666
+ },
1667
+ ],
1668
+ },
1669
+ },
1670
+ };
1671
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, {});
1672
+ expect(result.features.feature1.traffic).toHaveLength(1);
1673
+ });
1674
+ test("segment that becomes * is removed even if referenced elsewhere", function () {
1675
+ const datafile = {
1676
+ schemaVersion: "2",
1677
+ revision: "unknown",
1678
+ segments: {
1679
+ web: {
1680
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1681
+ },
1682
+ },
1683
+ features: {
1684
+ feature1: {
1685
+ bucketBy: ["userId"],
1686
+ traffic: [
1687
+ {
1688
+ key: "rule1",
1689
+ segments: "web",
1690
+ percentage: 100000,
1691
+ },
1692
+ ],
1693
+ },
1694
+ },
1695
+ };
1696
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
1697
+ expect(result.segments).toEqual({});
1698
+ expect(result.features.feature1.traffic[0].segments).toEqual("*");
1699
+ });
1700
+ test("context with extra attributes not in segments", function () {
1701
+ const datafile = {
1702
+ schemaVersion: "2",
1703
+ revision: "unknown",
1704
+ segments: {
1705
+ web: {
1706
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1707
+ },
1708
+ },
1709
+ features: {},
1710
+ };
1711
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, {
1712
+ platform: "web",
1713
+ extraAttribute: "extraValue",
1714
+ });
1715
+ expect(result.segments).toEqual({});
1716
+ });
1717
+ test("datafile with all segments matching context", function () {
1718
+ const datafile = {
1719
+ schemaVersion: "2",
1720
+ revision: "unknown",
1721
+ segments: {
1722
+ web: {
1723
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1724
+ },
1725
+ chrome: {
1726
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
1727
+ },
1728
+ },
1729
+ features: {},
1730
+ };
1731
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
1732
+ expect(result.segments).toEqual({});
1733
+ });
1734
+ test("datafile with no segments matching context", function () {
1735
+ const datafile = {
1736
+ schemaVersion: "2",
1737
+ revision: "unknown",
1738
+ segments: {
1739
+ web: {
1740
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1741
+ },
1742
+ chrome: {
1743
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
1744
+ },
1745
+ },
1746
+ features: {},
1747
+ };
1748
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "mobile", browser: "safari" });
1749
+ expect(result.segments.web).toBeDefined();
1750
+ expect(result.segments.chrome).toBeDefined();
1751
+ });
1752
+ test("feature with multiple traffic entries becoming * consecutively", function () {
1753
+ const datafile = {
1754
+ schemaVersion: "2",
1755
+ revision: "unknown",
1756
+ segments: {
1757
+ web: {
1758
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1759
+ },
1760
+ },
1761
+ features: {
1762
+ feature1: {
1763
+ bucketBy: ["userId"],
1764
+ traffic: [
1765
+ {
1766
+ key: "rule1",
1767
+ segments: "web",
1768
+ percentage: 25000,
1769
+ },
1770
+ {
1771
+ key: "rule2",
1772
+ segments: "web",
1773
+ percentage: 25000,
1774
+ },
1775
+ {
1776
+ key: "rule3",
1777
+ segments: "web",
1778
+ percentage: 25000,
1779
+ },
1780
+ {
1781
+ key: "rule4",
1782
+ segments: "web",
1783
+ percentage: 25000,
1784
+ },
1785
+ ],
1786
+ },
1787
+ },
1788
+ };
1789
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
1790
+ expect(result.features.feature1.traffic).toHaveLength(1);
1791
+ expect(result.features.feature1.traffic[0].key).toEqual("rule1");
1792
+ });
1793
+ test("variableOverride with segments that become * after segment removal", function () {
1794
+ const datafile = {
1795
+ schemaVersion: "2",
1796
+ revision: "unknown",
1797
+ segments: {
1798
+ web: {
1799
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1800
+ },
1801
+ },
1802
+ features: {
1803
+ feature1: {
1804
+ bucketBy: ["userId"],
1805
+ traffic: [],
1806
+ variations: [
1807
+ {
1808
+ value: "control",
1809
+ variableOverrides: {
1810
+ color: [
1811
+ {
1812
+ value: "red",
1813
+ segments: "web",
1814
+ },
1815
+ ],
1816
+ },
1817
+ },
1818
+ ],
1819
+ },
1820
+ },
1821
+ };
1822
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
1823
+ expect(result.segments).toEqual({});
1824
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].segments).toEqual("*");
1825
+ });
1826
+ });
1827
+ describe("integration scenarios", function () {
1828
+ test("complete datafile with all feature types", function () {
1829
+ const datafile = {
1830
+ schemaVersion: "2",
1831
+ revision: "unknown",
1832
+ segments: {
1833
+ web: {
1834
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1835
+ },
1836
+ chrome: {
1837
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
1838
+ },
1839
+ mobile: {
1840
+ conditions: [{ attribute: "platform", operator: "equals", value: "mobile" }],
1841
+ },
1842
+ },
1843
+ features: {
1844
+ feature1: {
1845
+ bucketBy: ["userId"],
1846
+ traffic: [
1847
+ {
1848
+ key: "rule1",
1849
+ segments: "web",
1850
+ percentage: 100000,
1851
+ },
1852
+ ],
1853
+ force: [
1854
+ {
1855
+ segments: "chrome",
1856
+ variation: "treatment",
1857
+ },
1858
+ ],
1859
+ variations: [
1860
+ {
1861
+ value: "control",
1862
+ variableOverrides: {
1863
+ color: [
1864
+ {
1865
+ value: "red",
1866
+ segments: "web",
1867
+ },
1868
+ ],
1869
+ },
1870
+ },
1871
+ ],
1872
+ },
1873
+ feature2: {
1874
+ bucketBy: ["userId"],
1875
+ traffic: [
1876
+ {
1877
+ key: "rule1",
1878
+ segments: "mobile",
1879
+ percentage: 100000,
1880
+ },
1881
+ ],
1882
+ },
1883
+ },
1884
+ };
1885
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
1886
+ expect(result.segments.web).toBeUndefined();
1887
+ expect(result.segments.chrome).toBeUndefined();
1888
+ expect(result.segments.mobile).toBeDefined();
1889
+ expect(result.features.feature1.traffic[0].segments).toEqual("*");
1890
+ expect(result.features.feature1.force?.[0].segments).toEqual("*");
1891
+ expect(result.features.feature1.variations?.[0].variableOverrides?.color[0].segments).toEqual("*");
1892
+ expect(result.features.feature2.traffic[0].segments).toEqual("mobile");
1893
+ });
1894
+ test("datafile with deeply nested conditions and segments", function () {
1895
+ const datafile = {
1896
+ schemaVersion: "2",
1897
+ revision: "unknown",
1898
+ segments: {
1899
+ web: {
1900
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1901
+ },
1902
+ chrome: {
1903
+ conditions: [{ attribute: "browser", operator: "equals", value: "chrome" }],
1904
+ },
1905
+ },
1906
+ features: {
1907
+ feature1: {
1908
+ bucketBy: ["userId"],
1909
+ traffic: [
1910
+ {
1911
+ key: "rule1",
1912
+ segments: {
1913
+ and: [
1914
+ "web",
1915
+ {
1916
+ or: ["chrome", "web"],
1917
+ },
1918
+ ],
1919
+ },
1920
+ percentage: 100000,
1921
+ },
1922
+ ],
1923
+ force: [
1924
+ {
1925
+ conditions: {
1926
+ and: [
1927
+ { attribute: "platform", operator: "equals", value: "web" },
1928
+ {
1929
+ or: [
1930
+ { attribute: "browser", operator: "equals", value: "chrome" },
1931
+ { attribute: "browser", operator: "equals", value: "firefox" },
1932
+ ],
1933
+ },
1934
+ ],
1935
+ },
1936
+ variation: "treatment",
1937
+ },
1938
+ ],
1939
+ },
1940
+ },
1941
+ };
1942
+ const result = (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web", browser: "chrome" });
1943
+ expect(result.features.feature1.traffic[0].segments).toEqual("*");
1944
+ expect(result.features.feature1.force?.[0].conditions).toEqual({
1945
+ and: [
1946
+ {
1947
+ or: [
1948
+ {
1949
+ attribute: "browser",
1950
+ operator: "equals",
1951
+ value: "firefox",
1952
+ },
1953
+ ],
1954
+ },
1955
+ ],
1956
+ });
1957
+ });
1958
+ test("datafile immutability - original not modified", function () {
1959
+ const datafile = {
1960
+ schemaVersion: "2",
1961
+ revision: "unknown",
1962
+ segments: {
1963
+ web: {
1964
+ conditions: [{ attribute: "platform", operator: "equals", value: "web" }],
1965
+ },
1966
+ },
1967
+ features: {
1968
+ feature1: {
1969
+ bucketBy: ["userId"],
1970
+ traffic: [
1971
+ {
1972
+ key: "rule1",
1973
+ segments: "web",
1974
+ percentage: 100000,
1975
+ },
1976
+ ],
1977
+ },
1978
+ },
1979
+ };
1980
+ const originalSegments = JSON.stringify(datafile.segments);
1981
+ const originalTraffic = JSON.stringify(datafile.features.feature1.traffic);
1982
+ (0, buildScopedDatafile_1.buildScopedDatafile)(datafile, { platform: "web" });
1983
+ expect(JSON.stringify(datafile.segments)).toEqual(originalSegments);
1984
+ expect(JSON.stringify(datafile.features.feature1.traffic)).toEqual(originalTraffic);
1985
+ });
1986
+ });
1987
+ });
1988
+ //# sourceMappingURL=buildScopedDatafile.spec.js.map