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