make-mp-data 1.4.4 → 1.4.5

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.
@@ -6,7 +6,17 @@ const u = require('ak-tools');
6
6
  dayjs.extend(utc);
7
7
  require('dotenv').config();
8
8
 
9
- const { applySkew,
9
+ /** @typedef {import('../types').Config} Config */
10
+ /** @typedef {import('../types').EventConfig} EventConfig */
11
+ /** @typedef {import('../types').ValueValid} ValueValid */
12
+ /** @typedef {import('../types').EnrichedArray} hookArray */
13
+ /** @typedef {import('../types').hookArrayOptions} hookArrayOptions */
14
+ /** @typedef {import('../types').Person} Person */
15
+ /** @typedef {import('../types').Funnel} Funnel */
16
+
17
+
18
+ const {
19
+ applySkew,
10
20
  boxMullerRandom,
11
21
  choose,
12
22
  date,
@@ -21,8 +31,8 @@ const { applySkew,
21
31
  pick,
22
32
  range,
23
33
  pickAWinner,
24
- weightedRange,
25
- enrichArray,
34
+ weighNumRange,
35
+ hookArray,
26
36
  fixFirstAndLast,
27
37
  generateUser,
28
38
  openFinder,
@@ -45,63 +55,115 @@ const { applySkew,
45
55
  interruptArray,
46
56
  optimizedBoxMuller,
47
57
  inferFunnels,
48
- datesBetween
58
+ datesBetween,
59
+ weighChoices
49
60
  } = require('../core/utils.js');
50
61
 
51
62
 
52
63
  describe('timesoup', () => {
64
+
53
65
  test('always valid times', () => {
54
66
  const dates = [];
67
+ const earliest = dayjs().subtract(50, 'D').unix();
68
+ const latest = dayjs().subtract(1, "D").unix();
69
+ for (let i = 0; i < 10000; i++) {
70
+ dates.push(TimeSoup(earliest, latest));
71
+ }
72
+ const tooOld = dates.filter(d => dayjs(d).isBefore(dayjs.unix(0)));
73
+ const badYear = dates.filter(d => !d.startsWith('202'));
74
+ expect(dates.every(d => dayjs(d).isAfter(dayjs.unix(0)))).toBe(true);
75
+ expect(dates.every(d => d.startsWith('202'))).toBe(true);
76
+ expect(tooOld.length).toBe(0);
77
+ expect(badYear.length).toBe(0);
78
+ });
79
+
80
+ test('custom peaks', () => {
81
+ const dates = [];
82
+ const earliest = dayjs().subtract(50, 'D').unix();
83
+ const latest = dayjs().subtract(1, "D").unix();
55
84
  for (let i = 0; i < 10000; i++) {
56
- const earliest = dayjs().subtract(u.rand(5, 50), 'D');
57
- dates.push(TimeSoup());
85
+ dates.push(TimeSoup(earliest, latest, 10));
58
86
  }
59
87
  const tooOld = dates.filter(d => dayjs(d).isBefore(dayjs.unix(0)));
60
88
  const badYear = dates.filter(d => !d.startsWith('202'));
61
89
  expect(dates.every(d => dayjs(d).isAfter(dayjs.unix(0)))).toBe(true);
62
90
  expect(dates.every(d => d.startsWith('202'))).toBe(true);
91
+ expect(tooOld.length).toBe(0);
92
+ expect(badYear.length).toBe(0);
93
+ });
63
94
 
95
+ test('custom deviation', () => {
96
+ const dates = [];
97
+ const earliest = dayjs().subtract(50, 'D').unix();
98
+ const latest = dayjs().subtract(1, "D").unix();
99
+ for (let i = 0; i < 10000; i++) {
100
+ dates.push(TimeSoup(earliest, latest, 10, .5));
101
+ }
102
+ const tooOld = dates.filter(d => dayjs(d).isBefore(dayjs.unix(0)));
103
+ const badYear = dates.filter(d => !d.startsWith('202'));
104
+ expect(dates.every(d => dayjs(d).isAfter(dayjs.unix(0)))).toBe(true);
105
+ expect(dates.every(d => d.startsWith('202'))).toBe(true);
106
+ expect(tooOld.length).toBe(0);
107
+ expect(badYear.length).toBe(0);
64
108
  });
109
+
110
+
65
111
  });
66
112
 
67
113
 
68
- describe('names', () => {
114
+ describe('filenames', () => {
69
115
 
70
116
  test('default config', () => {
71
117
  const config = { simulationName: 'testSim' };
72
118
  const result = buildFileNames(config);
73
- expect(result.eventFiles).toEqual(['testSim-EVENTS.csv']);
74
- expect(result.userFiles).toEqual(['testSim-USERS.csv']);
75
- expect(result.scdFiles).toEqual([]);
76
- expect(result.groupFiles).toEqual([]);
77
- expect(result.lookupFiles).toEqual([]);
78
- expect(result.mirrorFiles).toEqual([]);
79
- expect(result.folder).toEqual('./');
119
+ const { eventFiles, folder, groupFiles, lookupFiles, mirrorFiles, scdFiles, userFiles, adSpendFiles } = result;
120
+ expect(eventFiles).toEqual(['testSim-EVENTS.csv']);
121
+ expect(userFiles).toEqual(['testSim-USERS.csv']);
122
+ expect(scdFiles).toEqual([]);
123
+ expect(groupFiles).toEqual([]);
124
+ expect(lookupFiles).toEqual([]);
125
+ expect(mirrorFiles).toEqual([]);
126
+ expect(adSpendFiles).toEqual([]);
127
+ expect(folder).toEqual('./');
80
128
  });
81
129
 
82
130
  test('json format', () => {
131
+ /** @type {Config} */
83
132
  const config = { simulationName: 'testSim', format: 'json' };
84
133
  const result = buildFileNames(config);
85
- expect(result.eventFiles).toEqual(['testSim-EVENTS.json']);
86
- expect(result.userFiles).toEqual(['testSim-USERS.json']);
87
- });
88
-
89
- test('with scdProps', () => {
134
+ const { eventFiles, folder, groupFiles, lookupFiles, mirrorFiles, scdFiles, userFiles, adSpendFiles } = result;
135
+ expect(eventFiles).toEqual(['testSim-EVENTS.json']);
136
+ expect(userFiles).toEqual(['testSim-USERS.json']);
137
+ expect(scdFiles).toEqual([]);
138
+ expect(groupFiles).toEqual([]);
139
+ expect(lookupFiles).toEqual([]);
140
+ expect(mirrorFiles).toEqual([]);
141
+ expect(adSpendFiles).toEqual([]);
142
+ expect(folder).toEqual('./');
143
+ });
144
+
145
+ test('scd tables', () => {
90
146
  const config = {
91
147
  simulationName: 'testSim',
92
148
  scdProps: { prop1: {}, prop2: {} }
93
149
  };
94
150
  const result = buildFileNames(config);
95
- expect(result.scdFiles).toEqual([
96
- 'testSim-prop1-SCD.csv',
97
- 'testSim-prop2-SCD.csv'
98
- ]);
99
- });
100
-
101
- test('with groupKeys', () => {
151
+ const { eventFiles, folder, groupFiles, lookupFiles, mirrorFiles, scdFiles, userFiles, adSpendFiles } = result;
152
+ expect(eventFiles).toEqual(['testSim-EVENTS.csv']);
153
+ expect(userFiles).toEqual(['testSim-USERS.csv']);
154
+ expect(scdFiles).toEqual(['testSim-prop1-SCD.csv', 'testSim-prop2-SCD.csv']);
155
+ expect(groupFiles).toEqual([]);
156
+ expect(lookupFiles).toEqual([]);
157
+ expect(mirrorFiles).toEqual([]);
158
+ expect(adSpendFiles).toEqual([]);
159
+ expect(folder).toEqual('./');
160
+ });
161
+
162
+ test('group keys', () => {
163
+ /** @type {Config} */
102
164
  const config = {
103
165
  simulationName: 'testSim',
104
- groupKeys: [['group1'], ['group2']]
166
+ groupKeys: [['group1', 10], ['group2', 20]]
105
167
  };
106
168
  const result = buildFileNames(config);
107
169
  expect(result.groupFiles).toEqual([
@@ -110,10 +172,11 @@ describe('names', () => {
110
172
  ]);
111
173
  });
112
174
 
113
- test('with lookupTables', () => {
175
+ test('lookup tables', () => {
176
+ /** @type {Config} */
114
177
  const config = {
115
178
  simulationName: 'testSim',
116
- lookupTables: [{ key: 'lookup1' }, { key: 'lookup2' }]
179
+ lookupTables: [{ key: 'lookup1', attributes: {}, entries: 10 }, { key: 'lookup2', attributes: {}, entries: 10 }]
117
180
  };
118
181
  const result = buildFileNames(config);
119
182
  expect(result.lookupFiles).toEqual([
@@ -122,43 +185,45 @@ describe('names', () => {
122
185
  ]);
123
186
  });
124
187
 
125
- test('with mirrorProps', () => {
188
+ test('mirror tables', () => {
189
+ /** @type {Config} */
126
190
  const config = {
127
191
  simulationName: 'testSim',
128
- mirrorProps: { prop1: {} }
192
+ mirrorProps: { prop1: { values: [] } }
129
193
  };
130
194
  const result = buildFileNames(config);
131
195
  expect(result.mirrorFiles).toEqual(['testSim-MIRROR.csv']);
132
196
  });
133
197
 
134
- test('writeToDisk', async () => {
198
+ test('validate disk path', async () => {
135
199
  const config = { simulationName: 'testSim', writeToDisk: true };
136
200
  const result = await buildFileNames(config);
137
201
  expect(result.folder).toBeDefined();
138
-
139
202
  });
140
203
 
141
204
 
142
- test('invalid simName', () => {
205
+ test('bad name', () => {
206
+ /** @type {Config} */
207
+ // @ts-ignore
143
208
  const config = { simulationName: 123 };
144
209
  expect(() => buildFileNames(config)).toThrow('simName must be a string');
145
210
  });
146
211
 
147
212
 
148
- test('streamJSON: writes to file', async () => {
213
+ test('JSON: writes', async () => {
149
214
  const path = 'test.json';
150
215
  const data = [{ a: 1, b: 2 }, { a: 3, b: 4 }];
151
- await streamJSON(path, data);
216
+ const streamed = await streamJSON(path, data);
152
217
  const content = fs.readFileSync(path, 'utf8');
153
218
  const lines = content.trim().split('\n').map(line => JSON.parse(line));
154
219
  expect(lines).toEqual(data);
155
220
  fs.unlinkSync(path);
156
221
  });
157
222
 
158
- test('streamCSV: writes to file', async () => {
223
+ test('CSV: writes', async () => {
159
224
  const path = 'test.csv';
160
225
  const data = [{ a: 1, b: 2 }, { a: 3, b: 4 }];
161
- await streamCSV(path, data);
226
+ const streamed = await streamCSV(path, data);
162
227
  const content = fs.readFileSync(path, 'utf8');
163
228
  const lines = content.trim().split('\n');
164
229
  expect(lines.length).toBe(3); // Including header
@@ -166,30 +231,12 @@ describe('names', () => {
166
231
  });
167
232
 
168
233
 
169
- test('generateUser: works', () => {
170
- const uuid = { guid: jest.fn().mockReturnValue('uuid-123') };
171
- const numDays = 30;
172
- const user = generateUser(numDays);
173
- expect(user).toHaveProperty('distinct_id');
174
- expect(user).toHaveProperty('name');
175
- expect(user).toHaveProperty('email');
176
- expect(user).toHaveProperty('avatar');
177
- });
178
-
179
- test('enrichArray: works', () => {
180
- const arr = [];
181
- const enrichedArray = enrichArray(arr);
182
- enrichedArray.hookPush(1);
183
- enrichedArray.hookPush(2);
184
- const match = JSON.stringify(enrichedArray) === JSON.stringify([1, 2]);
185
- expect(match).toEqual(true);
186
- });
187
-
188
234
  });
189
235
 
190
236
 
191
237
  describe('determinism', () => {
192
- test('initializes RNG with seed from environment variable', () => {
238
+
239
+ test('seed from env', () => {
193
240
  process.env.SEED = 'test-seed';
194
241
  // @ts-ignore
195
242
  initChance();
@@ -200,22 +247,24 @@ describe('determinism', () => {
200
247
 
201
248
  });
202
249
 
203
- test('initializes RNG only once', () => {
250
+ test('seed explicitly passed', () => {
204
251
  const seed = 'initial-seed';
205
252
  initChance(seed);
206
253
  const chance1 = getChance();
207
254
  initChance('new-seed');
208
255
  const chance2 = getChance();
209
256
  expect(chance1).toBe(chance2);
210
-
211
257
  });
258
+
212
259
  });
213
260
 
214
261
 
215
262
  describe('generation', () => {
216
- test('users: can make', () => {
263
+
264
+ test('user: works', () => {
265
+ const uuid = { guid: jest.fn().mockReturnValue('uuid-123') };
217
266
  const numDays = 30;
218
- const user = generateUser('uuid-123', numDays);
267
+ const user = generateUser(numDays);
219
268
  expect(user).toHaveProperty('distinct_id');
220
269
  expect(user).toHaveProperty('name');
221
270
  expect(user).toHaveProperty('email');
@@ -233,78 +282,34 @@ describe('generation', () => {
233
282
  expect(createdDate.isBefore(dayjs.unix(global.NOW))).toBeTruthy();
234
283
  });
235
284
 
236
- test('winner: return func', () => {
237
- const items = ['a', 'b', 'c'];
238
- const result = pickAWinner(items, 0);
239
- expect(typeof result).toBe('function');
240
- });
241
-
242
- test('winner: first most', () => {
243
- const items = ['a', 'b', 'c'];
244
- const mostChosenIndex = 0;
245
- const pickFunction = pickAWinner(items, mostChosenIndex);
246
- const weightedList = pickFunction();
247
-
248
- // Expect the most chosen item to appear at least once
249
- expect(weightedList.includes(items[mostChosenIndex])).toBeTruthy();
250
- });
251
-
252
- test('winner: second most', () => {
253
- const items = ['a', 'b', 'c'];
254
- const mostChosenIndex = 0;
255
- const pickFunction = pickAWinner(items, mostChosenIndex);
256
- const weightedList = pickFunction();
257
-
258
- const secondMostChosenIndex = (mostChosenIndex + 1) % items.length;
259
-
260
- // Expect the second most chosen item to appear at least once
261
- expect(weightedList.includes(items[secondMostChosenIndex])).toBeTruthy();
262
- });
263
-
264
- test('winner: third most', () => {
265
- const items = ['a', 'b', 'c'];
266
- const mostChosenIndex = 0;
267
- const pickFunction = pickAWinner(items, mostChosenIndex);
268
- const weightedList = pickFunction();
269
-
270
- const thirdMostChosenIndex = (mostChosenIndex + 2) % items.length;
271
-
272
- // Expect the third most chosen item to appear at least once
273
- expect(weightedList.includes(items[thirdMostChosenIndex])).toBeTruthy();
274
- });
275
-
276
- test('winner: exceed array bounds', () => {
277
- const items = ['a', 'b', 'c'];
278
- const mostChosenIndex = 0;
279
- const pickFunction = pickAWinner(items, mostChosenIndex);
280
- const weightedList = pickFunction();
281
285
 
282
- // Ensure all indices are within the bounds of the array
283
- weightedList.forEach(item => {
284
- expect(items.includes(item)).toBeTruthy();
285
- });
286
+ test('person: works', () => {
287
+ const numDays = 30;
288
+ const user = person('uuid-123', numDays, false);
289
+ expect(user).toHaveProperty('distinct_id');
290
+ expect(user.distinct_id).toBe('uuid-123');
291
+ expect(user).toHaveProperty('name');
292
+ expect(user).toHaveProperty('email');
293
+ expect(user).toHaveProperty('avatar');
294
+ expect(user).toHaveProperty('created');
295
+ expect(user).toHaveProperty('anonymousIds');
296
+ expect(user).toHaveProperty('sessionIds');
286
297
  });
287
298
 
288
- test('winner: single item array', () => {
289
- const items = ['a'];
290
- const mostChosenIndex = 0;
291
- const pickFunction = pickAWinner(items, mostChosenIndex);
292
- const weightedList = pickFunction();
293
-
294
- // Since there's only one item, all winner: he same
295
- weightedList.forEach(item => {
296
- expect(item).toBe('a');
297
- });
299
+ test('person: anon', () => {
300
+ const numDays = 30;
301
+ const user = person('uuid-123', numDays, true);
302
+ expect(user).toHaveProperty('distinct_id');
303
+ expect(user).toHaveProperty('name');
304
+ expect(user.name).toBe('Anonymous User')
305
+ expect(user).toHaveProperty('email');
306
+ expect(user.email.includes('*')).toBeTruthy();
307
+ expect(user).not.toHaveProperty('avatar');
308
+ expect(user).toHaveProperty('created');
309
+ expect(user).toHaveProperty('anonymousIds');
310
+ expect(user).toHaveProperty('sessionIds');
298
311
  });
299
312
 
300
- test('winner: empty array', () => {
301
- const items = [];
302
- const pickFunction = pickAWinner(items, 0);
303
- const weightedList = pickFunction();
304
-
305
- // Expect the result to be an empty array
306
- expect(weightedList.length).toBe(0);
307
- });
308
313
 
309
314
  test('dates: same start end', () => {
310
315
  const start = '2023-06-10';
@@ -380,13 +385,14 @@ describe('generation', () => {
380
385
 
381
386
  });
382
387
 
388
+
383
389
  describe('validation', () => {
384
390
 
385
391
  beforeAll(() => {
386
392
  global.NOW = 1672531200; // fixed point in time for testing
387
393
  });
388
394
 
389
- test('events: throws non array', () => {
395
+ test('events: non arrays', () => {
390
396
  // @ts-ignore
391
397
  expect(() => validateEventConfig("not an array")).toThrow("events must be an array");
392
398
  });
@@ -427,53 +433,63 @@ describe('validation', () => {
427
433
  expect(result[0].weight).toBeLessThanOrEqual(5);
428
434
  });
429
435
 
430
- test('dates: between', () => {
436
+ test('time: between', () => {
431
437
  const chosenTime = global.NOW - (60 * 60 * 24 * 15); // 15 days ago
432
438
  const earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
433
439
  const latestTime = global.NOW;
434
440
  expect(validateTime(chosenTime, earliestTime, latestTime)).toBe(true);
435
441
  });
436
442
 
437
- test('dates: outside earliest', () => {
443
+ test('time: outside earliest', () => {
438
444
  const chosenTime = global.NOW - (60 * 60 * 24 * 31); // 31 days ago
439
445
  const earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
440
446
  const latestTime = global.NOW;
441
447
  expect(validateTime(chosenTime, earliestTime, latestTime)).toBe(false);
442
448
  });
443
449
 
444
- test('dates: outside latest', () => {
450
+ test('time: outside latest', () => {
445
451
  const chosenTime = -1;
446
452
  const earliestTime = global.NOW - (60 * 60 * 24 * 30); // 30 days ago
447
453
  const latestTime = global.NOW;
448
454
  expect(validateTime(chosenTime, earliestTime, latestTime)).toBe(false);
449
455
  });
450
456
 
451
- test('dates: inference in', () => {
457
+ test('time: inference in', () => {
452
458
  const chosenTime = global.NOW - (60 * 60 * 24 * 15); // 15 days ago
453
459
  expect(validateTime(chosenTime)).toBe(true);
454
460
  });
455
461
 
456
- test('dates: inference out', () => {
462
+ test('time: inference out', () => {
457
463
  const chosenTime = global.NOW - (60 * 60 * 24 * 31); // 31 days ago
458
464
  expect(validateTime(chosenTime)).toBe(false);
459
465
  });
460
466
  });
461
467
 
462
468
  describe('enrichment', () => {
463
- test('hook works', () => {
469
+
470
+ test('hooks: noop', () => {
471
+ const arr = [];
472
+ const enrichedArray = hookArray(arr);
473
+ enrichedArray.hookPush(1);
474
+ enrichedArray.hookPush(2);
475
+ const match = JSON.stringify(enrichedArray) === JSON.stringify([1, 2]);
476
+ expect(match).toEqual(true);
477
+ });
478
+
479
+ test('hook: double', () => {
464
480
  const arr = [];
465
481
  const hook = (item) => item * 2;
466
- const enrichedArray = enrichArray(arr, { hook });
482
+ const enrichedArray = hookArray(arr, { hook });
467
483
  enrichedArray.hookPush(1);
468
484
  enrichedArray.hookPush(2);
469
485
  expect(enrichedArray.includes(2)).toBeTruthy();
470
486
  expect(enrichedArray.includes(4)).toBeTruthy();
471
487
  });
472
488
 
473
- test('filter empties', () => {
489
+ test('hooks: filter', () => {
474
490
  const arr = [];
475
491
  const hook = (item) => item ? item.toString() : item;
476
- const enrichedArray = enrichArray(arr, { hook });
492
+ const enrichedArray = hookArray(arr, { hook });
477
493
  enrichedArray.hookPush(null);
478
494
  enrichedArray.hookPush(undefined);
479
495
  enrichedArray.hookPush({});
@@ -505,7 +521,6 @@ describe('utilities', () => {
505
521
  });
506
522
 
507
523
 
508
-
509
524
  test('integer: diff', () => {
510
525
  const min = 5;
511
526
  const max = 10;
@@ -519,16 +534,6 @@ describe('utilities', () => {
519
534
  });
520
535
 
521
536
 
522
-
523
-
524
- test('person: fields', () => {
525
- const generatedPerson = person('myId');
526
- expect(generatedPerson).toHaveProperty('name');
527
- expect(generatedPerson).toHaveProperty('email');
528
- expect(generatedPerson).toHaveProperty('avatar');
529
- });
530
-
531
-
532
537
  test('date: past', () => {
533
538
  const pastDate = date(10, true, 'YYYY-MM-DD')();
534
539
  expect(dayjs(pastDate, 'YYYY-MM-DD').isValid()).toBeTruthy();
@@ -571,28 +576,6 @@ describe('utilities', () => {
571
576
  expect(result).toBe('nested');
572
577
  });
573
578
 
574
- test('weightedRange: within range', () => {
575
- const values = weightedRange(5, 15);
576
- expect(values.every(v => v >= 5 && v <= 15)).toBe(true);
577
- expect(values.length).toBe(50);
578
- });
579
-
580
- test('applySkew: skews', () => {
581
- const value = optimizedBoxMuller();
582
- const skewedValue = applySkew(value, .25);
583
- expect(Math.abs(skewedValue)).toBeLessThanOrEqual(Math.abs(value) + 1);
584
- });
585
-
586
- test('mapToRange: works', () => {
587
- const value = 0;
588
- const mean = 10;
589
- const sd = 5;
590
- const mappedValue = mapToRange(value, mean, sd);
591
- expect(mappedValue).toBe(10);
592
- });
593
-
594
-
595
-
596
579
  test('exhaust: elements', () => {
597
580
  const arr = [1, 2, 3];
598
581
  const exhaustFn = exhaust([...arr]);
@@ -611,25 +594,12 @@ describe('utilities', () => {
611
594
  });
612
595
 
613
596
 
614
- test('times', () => {
615
- const dates = [];
616
- for (let i = 0; i < 10000; i++) {
617
- const earliest = dayjs().subtract(u.rand(5, 50), 'D');
618
- dates.push(TimeSoup());
619
- }
620
- const tooOld = dates.filter(d => dayjs(d).isBefore(dayjs.unix(0)));
621
- const badYear = dates.filter(d => !d.startsWith('202'));
622
- expect(dates.every(d => dayjs(d).isAfter(dayjs.unix(0)))).toBe(true);
623
- expect(dates.every(d => d.startsWith('202'))).toBe(true);
624
-
625
- });
626
-
627
- test('date', () => {
597
+ test('date: valid', () => {
628
598
  const result = date();
629
599
  expect(dayjs(result()).isValid()).toBe(true);
630
600
  });
631
601
 
632
- test('dates', () => {
602
+ test('dates: valid', () => {
633
603
  const result = dates();
634
604
  expect(result).toBeInstanceOf(Array);
635
605
  expect(result.length).toBe(5); // Assuming default numPairs is 5
@@ -641,7 +611,7 @@ describe('utilities', () => {
641
611
  });
642
612
  });
643
613
 
644
- test('day', () => {
614
+ test('day: works', () => {
645
615
  const start = '2020-01-01';
646
616
  const end = '2020-01-30';
647
617
  const result = day(start, end);
@@ -650,7 +620,7 @@ describe('utilities', () => {
650
620
  expect(dayjs(dayResult.day).isBefore(dayjs(dayResult.end))).toBe(true);
651
621
  });
652
622
 
653
- test('exhaust', () => {
623
+ test('exhaust: works', () => {
654
624
  const arr = [1, 2, 3];
655
625
  const next = exhaust(arr);
656
626
  expect(next()).toBe(1);
@@ -684,20 +654,8 @@ describe('utilities', () => {
684
654
  });
685
655
 
686
656
 
687
- test('weighArray: works', () => {
688
- const arr = ['a', 'b', 'c'];
689
- const weightedArr = weighArray(arr);
690
- expect(weightedArr.length).toBeGreaterThanOrEqual(arr.length);
691
- });
692
-
693
- test('weighFunnels: works', () => {
694
- const acc = [];
695
- const funnel = { weight: 3 };
696
- const result = weighFunnels(acc, funnel);
697
- expect(result.length).toBe(3);
698
- });
699
657
 
700
- test('progress: outputs correctly', () => {
658
+ test('progress: output', () => {
701
659
  // @ts-ignore
702
660
  const mockStdoutWrite = jest.spyOn(process.stdout, 'write').mockImplementation(() => { });
703
661
  progress([['test', 50]]);
@@ -706,13 +664,11 @@ describe('utilities', () => {
706
664
  });
707
665
 
708
666
  test('range: works', () => {
709
- const result = [];
710
- range.call(result, 1, 5);
667
+ const result = range(1,5);
711
668
  expect(result).toEqual([1, 2, 3, 4, 5]);
712
669
  });
713
670
 
714
671
 
715
-
716
672
  test('shuffleArray: works', () => {
717
673
  const arr = [1, 2, 3, 4, 5];
718
674
  const shuffled = shuffleArray([...arr]);
@@ -756,7 +712,7 @@ describe('utilities', () => {
756
712
  expect(shuffled.slice(1, -1)).toEqual(arr.slice(1, -1));
757
713
  });
758
714
 
759
- test('box normal distribution', () => {
715
+ test('box: distribution', () => {
760
716
  const values = [];
761
717
  for (let i = 0; i < 10000; i++) {
762
718
  values.push(boxMullerRandom());
@@ -768,7 +724,7 @@ describe('utilities', () => {
768
724
  expect(stdDev).toBeCloseTo(1, 1);
769
725
  });
770
726
 
771
- test('optimized box normal distribution', () => {
727
+ test('optimized box: distribution', () => {
772
728
  const values = [];
773
729
  for (let i = 0; i < 10000; i++) {
774
730
  values.push(optimizedBoxMuller());
@@ -782,3 +738,167 @@ describe('utilities', () => {
782
738
 
783
739
 
784
740
  });
741
+
742
+
743
+ describe('weights', () => {
744
+ test('weighChoices: objects', () => {
745
+ const items = [
746
+ { value: 'foo', weight: 3 },
747
+ { value: 'bar', weight: 2 }
748
+ ];
749
+ const generateWeightedArray = weighChoices(items);
750
+ const result = generateWeightedArray();
751
+
752
+ expect(result.filter(item => item === 'foo').length).toBe(3);
753
+ expect(result.filter(item => item === 'bar').length).toBe(2);
754
+ });
755
+
756
+ test('weighChoices: strings', () => {
757
+ const items = ['foo', 'bar', 'baz'];
758
+ const generateWeightedArray = weighChoices(items);
759
+ const result = generateWeightedArray();
760
+
761
+ // Check that each item has a unique weight
762
+ const counts = items.map(item => result.filter(r => r === item).length);
763
+ const uniqueCounts = new Set(counts);
764
+
765
+ expect(uniqueCounts.size).toBe(items.length);
766
+ counts.forEach(count => {
767
+ expect(count).toBeGreaterThanOrEqual(1);
768
+ expect(count).toBeLessThanOrEqual(items.length);
769
+ });
770
+ });
771
+
772
+ test('weighChoices: empty', () => {
773
+ const items = [];
774
+ const generateWeightedArray = weighChoices(items);
775
+ const result = generateWeightedArray();
776
+
777
+ expect(result).toEqual([]);
778
+ });
779
+
780
+ test('weighChoices: one string', () => {
781
+ const items = ['foo'];
782
+ const generateWeightedArray = weighChoices(items);
783
+ const result = generateWeightedArray();
784
+
785
+ expect(result).toEqual(['foo']);
786
+ });
787
+
788
+ test('weighChoices: one obj', () => {
789
+ const items = [{ value: 'foo', weight: 5 }];
790
+ const generateWeightedArray = weighChoices(items);
791
+ const result = generateWeightedArray();
792
+
793
+ expect(result).toEqual(['foo', 'foo', 'foo', 'foo', 'foo']);
794
+ });
795
+
796
+
797
+ test('winner: return func', () => {
798
+ const items = ['a', 'b', 'c'];
799
+ const result = pickAWinner(items, 0);
800
+ expect(typeof result).toBe('function');
801
+ });
802
+
803
+ test('winner: first most', () => {
804
+ const items = ['a', 'b', 'c'];
805
+ const mostChosenIndex = 0;
806
+ const pickFunction = pickAWinner(items, mostChosenIndex);
807
+ const weightedList = pickFunction();
808
+
809
+ // Expect the most chosen item to appear at least once
810
+ expect(weightedList.includes(items[mostChosenIndex])).toBeTruthy();
811
+ });
812
+
813
+ test('winner: second most', () => {
814
+ const items = ['a', 'b', 'c'];
815
+ const mostChosenIndex = 0;
816
+ const pickFunction = pickAWinner(items, mostChosenIndex);
817
+ const weightedList = pickFunction();
818
+
819
+ const secondMostChosenIndex = (mostChosenIndex + 1) % items.length;
820
+
821
+ // Expect the second most chosen item to appear at least once
822
+ expect(weightedList.includes(items[secondMostChosenIndex])).toBeTruthy();
823
+ });
824
+
825
+ test('winner: third most', () => {
826
+ const items = ['a', 'b', 'c'];
827
+ const mostChosenIndex = 0;
828
+ const pickFunction = pickAWinner(items, mostChosenIndex);
829
+ const weightedList = pickFunction();
830
+
831
+ const thirdMostChosenIndex = (mostChosenIndex + 2) % items.length;
832
+
833
+ // Expect the third most chosen item to appear at least once
834
+ expect(weightedList.includes(items[thirdMostChosenIndex])).toBeTruthy();
835
+ });
836
+
837
+ test('winner: exceed array bounds', () => {
838
+ const items = ['a', 'b', 'c'];
839
+ const mostChosenIndex = 0;
840
+ const pickFunction = pickAWinner(items, mostChosenIndex);
841
+ const weightedList = pickFunction();
842
+
843
+ // Ensure all indices are within the bounds of the array
844
+ weightedList.forEach(item => {
845
+ expect(items.includes(item)).toBeTruthy();
846
+ });
847
+ });
848
+
849
+ test('winner: single item array', () => {
850
+ const items = ['a'];
851
+ const mostChosenIndex = 0;
852
+ const pickFunction = pickAWinner(items, mostChosenIndex);
853
+ const weightedList = pickFunction();
854
+
855
+ // Since there's only one item, all winner: he same
856
+ weightedList.forEach(item => {
857
+ expect(item).toBe('a');
858
+ });
859
+ });
860
+
861
+ test('winner: empty array', () => {
862
+ const items = [];
863
+ const pickFunction = pickAWinner(items, 0);
864
+ const weightedList = pickFunction();
865
+
866
+ // Expect the result to be an empty array
867
+ expect(weightedList.length).toBe(0);
868
+ });
869
+
870
+ test('weighNumRange: within range', () => {
871
+ const values = weighNumRange(5, 15);
872
+ expect(values.every(v => v >= 5 && v <= 15)).toBe(true);
873
+ expect(values.length).toBe(50);
874
+ });
875
+
876
+ test('applySkew: skews', () => {
877
+ const value = optimizedBoxMuller();
878
+ const skewedValue = applySkew(value, .25);
879
+ expect(Math.abs(skewedValue)).toBeLessThanOrEqual(Math.abs(value) + 1);
880
+ });
881
+
882
+ test('mapToRange: works', () => {
883
+ const value = 0;
884
+ const mean = 10;
885
+ const sd = 5;
886
+ const mappedValue = mapToRange(value, mean, sd);
887
+ expect(mappedValue).toBe(10);
888
+ });
889
+
890
+ test('weighArray: works', () => {
891
+ const arr = ['a', 'b', 'c'];
892
+ const weightedArr = weighArray(arr);
893
+ expect(weightedArr.length).toBeGreaterThanOrEqual(arr.length);
894
+ });
895
+
896
+ test('weighFunnels: works', () => {
897
+ const acc = [];
898
+ const funnel = { weight: 3 };
899
+ const result = weighFunnels(acc, funnel);
900
+ expect(result.length).toBe(3);
901
+ });
902
+
903
+
904
+ });