make-mp-data 2.0.1 → 2.0.12

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 (44) hide show
  1. package/dungeons/adspend.js +96 -0
  2. package/dungeons/anon.js +104 -0
  3. package/dungeons/big.js +224 -0
  4. package/dungeons/business.js +327 -0
  5. package/dungeons/complex.js +396 -0
  6. package/dungeons/experiments.js +124 -0
  7. package/dungeons/foobar.js +241 -0
  8. package/dungeons/funnels.js +272 -0
  9. package/dungeons/gaming.js +315 -0
  10. package/dungeons/mirror.js +129 -0
  11. package/dungeons/sanity.js +118 -0
  12. package/dungeons/scd.js +205 -0
  13. package/dungeons/simple.js +150 -0
  14. package/dungeons/userAgent.js +190 -0
  15. package/index.js +5 -1
  16. package/package.json +10 -1
  17. package/.claude/settings.local.json +0 -21
  18. package/.gcloudignore +0 -18
  19. package/.gitattributes +0 -2
  20. package/.prettierrc +0 -0
  21. package/.vscode/launch.json +0 -80
  22. package/.vscode/settings.json +0 -69
  23. package/.vscode/tasks.json +0 -12
  24. package/dungeons/customers/.gitkeep +0 -0
  25. package/env.yaml +0 -1
  26. package/scratch.mjs +0 -62
  27. package/scripts/dana.mjs +0 -137
  28. package/scripts/deploy.sh +0 -15
  29. package/scripts/jsdoctest.js +0 -5
  30. package/scripts/new-dungeon.sh +0 -98
  31. package/scripts/new-project.mjs +0 -14
  32. package/scripts/run-index.sh +0 -2
  33. package/scripts/update-deps.sh +0 -5
  34. package/tests/benchmark/concurrency.mjs +0 -52
  35. package/tests/cli.test.js +0 -124
  36. package/tests/coverage/.gitkeep +0 -0
  37. package/tests/e2e.test.js +0 -379
  38. package/tests/int.test.js +0 -715
  39. package/tests/testCases.mjs +0 -229
  40. package/tests/testSoup.mjs +0 -28
  41. package/tests/unit.test.js +0 -910
  42. package/tmp/.gitkeep +0 -0
  43. package/tsconfig.json +0 -18
  44. package/vitest.config.js +0 -47
@@ -1,910 +0,0 @@
1
- import generate from '../index.js';
2
- import dayjs from "dayjs";
3
- import utc from "dayjs/plugin/utc.js";
4
- import fs from 'fs';
5
- import * as u from 'ak-tools';
6
- dayjs.extend(utc);
7
- import 'dotenv/config';
8
-
9
- /** @typedef {import('../types').Dungeon} 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
- import {
19
- applySkew,
20
- boxMullerRandom,
21
- choose,
22
- date,
23
- dates,
24
- day,
25
- exhaust,
26
- generateEmoji,
27
- getUniqueKeys,
28
- integer,
29
- mapToRange,
30
- person,
31
- pick,
32
- range,
33
- pickAWinner,
34
- weighNumRange,
35
- fixFirstAndLast,
36
- generateUser,
37
- openFinder,
38
- progress,
39
- shuffleArray,
40
- shuffleExceptFirst,
41
- shuffleExceptLast,
42
- shuffleMiddle,
43
- shuffleOutside,
44
- streamCSV,
45
- streamJSON,
46
- weighArray,
47
- weighFunnels,
48
- buildFileNames,
49
- TimeSoup,
50
- getChance,
51
- initChance,
52
- validateEventConfig,
53
- validTime,
54
- interruptArray,
55
- optimizedBoxMuller,
56
- datesBetween,
57
- weighChoices
58
- } from '../lib/utils/utils.js';
59
-
60
- import main from '../index.js';
61
- import { createHookArray } from '../lib/core/storage.js';
62
- import { inferFunnels } from '../lib/core/config-validator.js';
63
-
64
- //todo: test for funnel inference
65
- const hookArray = createHookArray;
66
-
67
-
68
- describe('timesoup', () => {
69
-
70
- test('always valid times', () => {
71
- const dates = [];
72
- const earliest = dayjs().subtract(50, 'D').unix();
73
- const latest = dayjs().subtract(1, "D").unix();
74
- for (let i = 0; i < 10000; i++) {
75
- dates.push(TimeSoup(earliest, latest));
76
- }
77
- const tooOld = dates.filter(d => dayjs(d).isBefore(dayjs.unix(0)));
78
- const badYear = dates.filter(d => !d.startsWith('202'));
79
- expect(dates.every(d => dayjs(d).isAfter(dayjs.unix(0)))).toBe(true);
80
- expect(dates.every(d => d.startsWith('202'))).toBe(true);
81
- expect(tooOld.length).toBe(0);
82
- expect(badYear.length).toBe(0);
83
- });
84
-
85
- test('custom peaks', () => {
86
- const dates = [];
87
- const earliest = dayjs().subtract(50, 'D').unix();
88
- const latest = dayjs().subtract(1, "D").unix();
89
- for (let i = 0; i < 10000; i++) {
90
- dates.push(TimeSoup(earliest, latest, 10));
91
- }
92
- const tooOld = dates.filter(d => dayjs(d).isBefore(dayjs.unix(0)));
93
- const badYear = dates.filter(d => !d.startsWith('202'));
94
- expect(dates.every(d => dayjs(d).isAfter(dayjs.unix(0)))).toBe(true);
95
- expect(dates.every(d => d.startsWith('202'))).toBe(true);
96
- expect(tooOld.length).toBe(0);
97
- expect(badYear.length).toBe(0);
98
- });
99
-
100
- test('custom deviation', () => {
101
- const dates = [];
102
- const earliest = dayjs().subtract(50, 'D').unix();
103
- const latest = dayjs().subtract(1, "D").unix();
104
- for (let i = 0; i < 10000; i++) {
105
- dates.push(TimeSoup(earliest, latest, 10, .5));
106
- }
107
- const tooOld = dates.filter(d => dayjs(d).isBefore(dayjs.unix(0)));
108
- const badYear = dates.filter(d => !d.startsWith('202'));
109
- expect(dates.every(d => dayjs(d).isAfter(dayjs.unix(0)))).toBe(true);
110
- expect(dates.every(d => d.startsWith('202'))).toBe(true);
111
- expect(tooOld.length).toBe(0);
112
- expect(badYear.length).toBe(0);
113
- });
114
-
115
-
116
- });
117
-
118
-
119
- describe('filenames', () => {
120
-
121
- test('default config', () => {
122
- const config = { simulationName: 'testSim' };
123
- const result = buildFileNames(config);
124
- const { eventFiles, folder, groupFiles, lookupFiles, mirrorFiles, scdFiles, userFiles, adSpendFiles } = result;
125
- expect(eventFiles).toEqual(['testSim-EVENTS.csv']);
126
- expect(userFiles).toEqual(['testSim-USERS.csv']);
127
- expect(scdFiles).toEqual([]);
128
- expect(groupFiles).toEqual([]);
129
- expect(lookupFiles).toEqual([]);
130
- expect(mirrorFiles).toEqual([]);
131
- expect(adSpendFiles).toEqual([]);
132
- expect(folder).toEqual('./');
133
- });
134
-
135
- test('json format', () => {
136
- /** @type {Config} */
137
- const config = { simulationName: 'testSim', format: 'json' };
138
- const result = buildFileNames(config);
139
- const { eventFiles, folder, groupFiles, lookupFiles, mirrorFiles, scdFiles, userFiles, adSpendFiles } = result;
140
- expect(eventFiles).toEqual(['testSim-EVENTS.json']);
141
- expect(userFiles).toEqual(['testSim-USERS.json']);
142
- expect(scdFiles).toEqual([]);
143
- expect(groupFiles).toEqual([]);
144
- expect(lookupFiles).toEqual([]);
145
- expect(mirrorFiles).toEqual([]);
146
- expect(adSpendFiles).toEqual([]);
147
- expect(folder).toEqual('./');
148
- });
149
-
150
- test('scd tables', () => {
151
- const config = {
152
- simulationName: 'testSim',
153
- scdProps: { prop1: {}, prop2: {} }
154
- };
155
- const result = buildFileNames(config);
156
- const { eventFiles, folder, groupFiles, lookupFiles, mirrorFiles, scdFiles, userFiles, adSpendFiles } = result;
157
- expect(eventFiles).toEqual(['testSim-EVENTS.csv']);
158
- expect(userFiles).toEqual(['testSim-USERS.csv']);
159
- expect(scdFiles).toEqual(['testSim-prop1-SCD.csv', 'testSim-prop2-SCD.csv']);
160
- expect(groupFiles).toEqual([]);
161
- expect(lookupFiles).toEqual([]);
162
- expect(mirrorFiles).toEqual([]);
163
- expect(adSpendFiles).toEqual([]);
164
- expect(folder).toEqual('./');
165
- });
166
-
167
- test('group keys', () => {
168
- /** @type {Config} */
169
- const config = {
170
- simulationName: 'testSim',
171
- groupKeys: [['group1', 10], ['group2', 20]]
172
- };
173
- const result = buildFileNames(config);
174
- expect(result.groupFiles).toEqual([
175
- 'testSim-group1-GROUP.csv',
176
- 'testSim-group2-GROUP.csv'
177
- ]);
178
- });
179
-
180
- test('lookup tables', () => {
181
- /** @type {Config} */
182
- const config = {
183
- simulationName: 'testSim',
184
- lookupTables: [{ key: 'lookup1', attributes: {}, entries: 10 }, { key: 'lookup2', attributes: {}, entries: 10 }]
185
- };
186
- const result = buildFileNames(config);
187
- expect(result.lookupFiles).toEqual([
188
- 'testSim-lookup1-LOOKUP.csv',
189
- 'testSim-lookup2-LOOKUP.csv'
190
- ]);
191
- });
192
-
193
- test('mirror tables', () => {
194
- /** @type {Config} */
195
- const config = {
196
- simulationName: 'testSim',
197
- mirrorProps: { prop1: { values: [] } }
198
- };
199
- const result = buildFileNames(config);
200
- expect(result.mirrorFiles).toEqual(['testSim-MIRROR.csv']);
201
- });
202
-
203
- test('validate disk path', async () => {
204
- const config = { simulationName: 'testSim', writeToDisk: true };
205
- const result = await buildFileNames(config);
206
- expect(result.folder).toBeDefined();
207
- });
208
-
209
-
210
- test('bad name', () => {
211
- /** @type {Config} */
212
- // @ts-ignore
213
- const config = { simulationName: 123 };
214
- expect(() => buildFileNames(config)).toThrow('simName must be a string');
215
- });
216
-
217
-
218
- test('JSON: writes', async () => {
219
- const path = 'test.json';
220
- const data = [{ a: 1, b: 2 }, { a: 3, b: 4 }];
221
- const streamed = await streamJSON(path, data);
222
- const content = fs.readFileSync(path, 'utf8');
223
- const lines = content.trim().split('\n').map(line => JSON.parse(line));
224
- expect(lines).toEqual(data);
225
- fs.unlinkSync(path);
226
- });
227
-
228
- test('CSV: writes', async () => {
229
- const path = 'test.csv';
230
- const data = [{ a: 1, b: 2 }, { a: 3, b: 4 }];
231
- const streamed = await streamCSV(path, data);
232
- const content = fs.readFileSync(path, 'utf8');
233
- const lines = content.trim().split('\n');
234
- expect(lines.length).toBe(3); // Including header
235
- fs.unlinkSync(path);
236
- });
237
-
238
-
239
- });
240
-
241
-
242
- describe('determinism', () => {
243
-
244
- test('seed from env', () => {
245
- process.env.SEED = 'test-seed';
246
- // @ts-ignore
247
- initChance();
248
- const chance = getChance();
249
- expect(chance).toBeDefined();
250
- expect(chance.random()).toBeGreaterThanOrEqual(0);
251
- expect(chance.random()).toBeLessThanOrEqual(1);
252
-
253
- });
254
-
255
- test('seed explicitly passed', () => {
256
- const seed = 'initial-seed';
257
- initChance(seed);
258
- const chance1 = getChance();
259
- initChance('new-seed');
260
- const chance2 = getChance();
261
- expect(chance1).toBe(chance2);
262
- });
263
-
264
- });
265
-
266
-
267
- describe('generation', () => {
268
-
269
- test('user: works', () => {
270
- const uuid = { guid: vi.fn().mockReturnValue('uuid-123') };
271
- const numDays = 30;
272
- const user = generateUser('123', { numDays });
273
- expect(user).toHaveProperty('distinct_id');
274
- expect(user).toHaveProperty('name');
275
- expect(user).toHaveProperty('email');
276
- expect(user).not.toHaveProperty('avatar');
277
- expect(user).toHaveProperty('created');
278
- expect(user).not.toHaveProperty('anonymousIds');
279
- expect(user).not.toHaveProperty('sessionIds');
280
- });
281
-
282
- test('user: in time range', () => {
283
- const numDays = 30;
284
- const user = generateUser('uuid-123', { numDays });
285
- const createdDate = dayjs(user.created, 'YYYY-MM-DD');
286
- expect(createdDate.isValid()).toBeTruthy();
287
- expect(createdDate.isBefore(dayjs())).toBeTruthy();
288
- });
289
-
290
-
291
- test('person: works', () => {
292
- const numDays = 30;
293
- const user = person('uuid-123', numDays, false);
294
- expect(user).toHaveProperty('distinct_id');
295
- expect(user.distinct_id).toBe('uuid-123');
296
- expect(user).toHaveProperty('name');
297
- expect(user).toHaveProperty('email');
298
- expect(user).not.toHaveProperty('avatar');
299
- expect(user).toHaveProperty('created');
300
- expect(user).not.toHaveProperty('anonymousIds');
301
- expect(user).not.toHaveProperty('sessionIds');
302
- });
303
-
304
- test('person: anon', () => {
305
- const numDays = 30;
306
- const user = person('uuid-123', numDays, true);
307
- expect(user).toHaveProperty('distinct_id');
308
- expect(user).toHaveProperty('name');
309
- expect(user.name).toBe('Anonymous User');
310
- expect(user).toHaveProperty('email');
311
- expect(user.email.includes('*')).toBeTruthy();
312
- expect(user).not.toHaveProperty('avatar');
313
- expect(user).toHaveProperty('created');
314
- expect(user).not.toHaveProperty('anonymousIds');
315
- expect(user).not.toHaveProperty('sessionIds');
316
- });
317
-
318
-
319
- test('dates: same start end', () => {
320
- const start = '2023-06-10';
321
- const end = '2023-06-10';
322
- const result = datesBetween(start, end);
323
- expect(result).toEqual([]);
324
- });
325
-
326
- test('dates: start after end', () => {
327
- const start = '2023-06-12';
328
- const end = '2023-06-10';
329
- const result = datesBetween(start, end);
330
- expect(result).toEqual([]);
331
- });
332
-
333
- test('dates: correct', () => {
334
- const start = '2023-06-10';
335
- const end = '2023-06-13';
336
- const result = datesBetween(start, end);
337
- expect(result).toEqual([
338
- '2023-06-10T12:00:00.000Z',
339
- '2023-06-11T12:00:00.000Z',
340
- '2023-06-12T12:00:00.000Z'
341
- ]);
342
- });
343
-
344
- test('dates: unix times', () => {
345
- const start = dayjs('2023-06-10').unix();
346
- const end = dayjs('2023-06-13').unix();
347
- const result = datesBetween(start, end);
348
- expect(result).toEqual([
349
- '2023-06-10T12:00:00.000Z',
350
- '2023-06-11T12:00:00.000Z',
351
- '2023-06-12T12:00:00.000Z'
352
- ]);
353
- });
354
-
355
- test('dates: mixed formats', () => {
356
- const start = '2023-06-10';
357
- const end = dayjs('2023-06-13').unix();
358
- const result = datesBetween(start, end);
359
- expect(result).toEqual([
360
- '2023-06-10T12:00:00.000Z',
361
- '2023-06-11T12:00:00.000Z',
362
- '2023-06-12T12:00:00.000Z'
363
- ]);
364
- });
365
-
366
- test('dates: invalid dates', () => {
367
- const start = 'invalid-date';
368
- const end = '2023-06-13';
369
- const result = datesBetween(start, end);
370
- expect(result).toEqual([]);
371
- });
372
-
373
- test('dates: same day', () => {
374
- const start = '2023-06-10T08:00:00.000Z';
375
- const end = '2023-06-10T20:00:00.000Z';
376
- const result = datesBetween(start, end);
377
- expect(result).toEqual([]);
378
- });
379
-
380
- test('dates: leap years', () => {
381
- const start = '2024-02-28';
382
- const end = '2024-03-02';
383
- const result = datesBetween(start, end);
384
- expect(result).toEqual([
385
- '2024-02-28T12:00:00.000Z',
386
- '2024-02-29T12:00:00.000Z',
387
- '2024-03-01T12:00:00.000Z'
388
- ]);
389
- });
390
-
391
- });
392
-
393
-
394
- describe('validation', () => {
395
-
396
- beforeAll(() => {
397
- global.FIXED_NOW = 1672531200; // fixed point in time for testing
398
- global.FIXED_BEGIN = global.FIXED_NOW - (60 * 60 * 24 * 30); // 30 days ago
399
- });
400
-
401
- test('events: non arrays', () => {
402
- // @ts-ignore
403
- expect(() => validateEventConfig("not an array")).toThrow("events must be an array");
404
- });
405
-
406
- test('events: strings', () => {
407
- const events = ["event1", "event2"];
408
- const result = validateEventConfig(events);
409
-
410
- expect(result).toEqual([
411
- { event: "event1", isFirstEvent: false, properties: {}, weight: expect.any(Number) },
412
- { event: "event2", isFirstEvent: false, properties: {}, weight: expect.any(Number) },
413
- ]);
414
-
415
- result.forEach(event => {
416
- expect(event.weight).toBeGreaterThanOrEqual(1);
417
- expect(event.weight).toBeLessThanOrEqual(5);
418
- });
419
- });
420
-
421
- test('events: objects', () => {
422
- const events = [{ event: "event1", properties: { a: 1 } }, { event: "event2", properties: { b: 2 } }];
423
- const result = validateEventConfig(events);
424
-
425
- expect(result).toEqual(events);
426
- });
427
-
428
- test('events: mix', () => {
429
- const events = ["event1", { event: "event2", properties: { b: 2 } }];
430
- // @ts-ignore
431
- const result = validateEventConfig(events);
432
-
433
- expect(result).toEqual([
434
- { event: "event1", isFirstEvent: false, properties: {}, weight: expect.any(Number) },
435
- { event: "event2", properties: { b: 2 } }
436
- ]);
437
-
438
- expect(result[0].weight).toBeGreaterThanOrEqual(1);
439
- expect(result[0].weight).toBeLessThanOrEqual(5);
440
- });
441
-
442
- test('time: between', () => {
443
- const chosenTime = global.FIXED_NOW - (60 * 60 * 24 * 15); // 15 days ago
444
- const earliestTime = global.FIXED_NOW - (60 * 60 * 24 * 30); // 30 days ago
445
- const latestTime = global.FIXED_NOW;
446
- expect(validTime(chosenTime, earliestTime, latestTime)).toBe(true);
447
- });
448
-
449
- test('time: outside earliest', () => {
450
- const chosenTime = global.FIXED_NOW - (60 * 60 * 24 * 31); // 31 days ago
451
- const earliestTime = global.FIXED_NOW - (60 * 60 * 24 * 30); // 30 days ago
452
- const latestTime = global.FIXED_NOW;
453
- expect(validTime(chosenTime, earliestTime, latestTime)).toBe(false);
454
- });
455
-
456
- test('time: outside latest', () => {
457
- const chosenTime = -1;
458
- const earliestTime = global.FIXED_NOW - (60 * 60 * 24 * 30); // 30 days ago
459
- const latestTime = global.FIXED_NOW;
460
- expect(validTime(chosenTime, earliestTime, latestTime)).toBe(false);
461
- });
462
-
463
- test('time: inference in', () => {
464
- const chosenTime = global.FIXED_NOW - (60 * 60 * 24 * 15); // 15 days ago
465
- expect(validTime(chosenTime)).toBe(true);
466
- });
467
-
468
- test('time: inference out', () => {
469
- const chosenTime = global.FIXED_NOW - (60 * 60 * 24 * 31); // 31 days ago
470
- expect(validTime(chosenTime)).toBe(false);
471
- });
472
- });
473
-
474
- describe('enrichment', () => {
475
-
476
- test('hooks: noop', async () => {
477
- const arr = [];
478
- const enrichedArray = await hookArray(arr);
479
- await enrichedArray.hookPush(1);
480
- await enrichedArray.hookPush(2);
481
- const match = JSON.stringify(enrichedArray) === JSON.stringify([1, 2]);
482
- expect(match).toEqual(true);
483
- });
484
-
485
- test('hook: double', async () => {
486
- const arr = [];
487
- const hook = (item) => item * 2;
488
- const enrichedArray = await hookArray(arr, { hook });
489
- await enrichedArray.hookPush(1);
490
- await enrichedArray.hookPush(2);
491
- expect(enrichedArray.includes(2)).toBeTruthy();
492
- expect(enrichedArray.includes(4)).toBeTruthy();
493
- });
494
-
495
- test('hooks: filter', async () => {
496
- const arr = [];
497
- const hook = (item) => item ? item.toString() : item;
498
- const enrichedArray = await hookArray(arr, { hook });
499
- await enrichedArray.hookPush(null);
500
- await enrichedArray.hookPush(undefined);
501
- await enrichedArray.hookPush({});
502
- await enrichedArray.hookPush({ a: 1 });
503
- await enrichedArray.hookPush([1, 2]);
504
- expect(enrichedArray).toHaveLength(3);
505
- expect(enrichedArray.includes('null')).toBeFalsy();
506
- expect(enrichedArray.includes('undefined')).toBeFalsy();
507
- expect(enrichedArray.includes('[object Object]')).toBeTruthy();
508
- expect(enrichedArray.includes('1')).toBeTruthy();
509
- expect(enrichedArray.includes('2')).toBeTruthy();
510
-
511
- });
512
-
513
-
514
- });
515
-
516
-
517
- describe('utilities', () => {
518
-
519
- test('pick: works', () => {
520
- const array = [1, 2, 3];
521
- const item = pick(array);
522
- expect(array).toContain(item);
523
- });
524
-
525
- test('pick: null', () => {
526
- expect(pick(123)).toBe(123);
527
- });
528
-
529
-
530
- test('integer: diff', () => {
531
- const min = 5;
532
- const max = 10;
533
- const result = integer(min, max);
534
- expect(result).toBeGreaterThanOrEqual(min);
535
- expect(result).toBeLessThanOrEqual(max);
536
- });
537
-
538
- test('integer: same', () => {
539
- expect(integer(7, 7)).toBe(7);
540
- });
541
-
542
-
543
- test('date: past', () => {
544
- const pastDate = date(10, true, 'YYYY-MM-DD')();
545
- expect(dayjs(pastDate, 'YYYY-MM-DD').isValid()).toBeTruthy();
546
- expect(dayjs(pastDate).isBefore(dayjs())).toBeTruthy();
547
- });
548
-
549
- test('date: future', () => {
550
- const futureDate = date(10, false, 'YYYY-MM-DD')();
551
- expect(dayjs(futureDate, 'YYYY-MM-DD').isValid()).toBeTruthy();
552
- expect(dayjs(futureDate).isAfter(dayjs.unix(global.FIXED_NOW))).toBeTruthy();
553
- });
554
-
555
- test('dates: pairs', () => {
556
- const datePairs = dates(10, 3, 'YYYY-MM-DD');
557
- expect(datePairs).toBeInstanceOf(Array);
558
- expect(datePairs).toHaveLength(3);
559
- datePairs.forEach(pair => {
560
- expect(pair).toHaveLength(2);
561
- });
562
- });
563
-
564
- test('choose: array', () => {
565
- const options = ['apple', 'banana', 'cherry'];
566
- const choice = choose(options);
567
- expect(options).toContain(choice);
568
- });
569
-
570
- test('choose: function', () => {
571
- const result = choose(() => 'test');
572
- expect(result).toBe('test');
573
- });
574
-
575
- test('choose: non-function / non-array', () => {
576
- expect(choose('test')).toBe('test');
577
- expect(choose(123)).toBe(123);
578
- });
579
-
580
- test('choose: nested functions', () => {
581
- const result = choose(() => () => () => 'nested');
582
- expect(result).toBe('nested');
583
- });
584
-
585
- test('exhaust: elements', () => {
586
- const arr = [1, 2, 3];
587
- const exhaustFn = exhaust([...arr]);
588
- expect(exhaustFn()).toBe(1);
589
- expect(exhaustFn()).toBe(2);
590
- expect(exhaustFn()).toBe(3);
591
- expect(exhaustFn()).toBeUndefined();
592
- });
593
-
594
-
595
-
596
- test('unique keys', () => {
597
- const objects = [{ a: 1, b: 2 }, { a: 3, c: 4 }, { a: 5, b: 6 }];
598
- const uniqueKeys = getUniqueKeys(objects);
599
- expect(uniqueKeys).toEqual(expect.arrayContaining(['a', 'b', 'c']));
600
- });
601
-
602
-
603
- test('date: valid', () => {
604
- const result = date();
605
- expect(dayjs(result()).isValid()).toBe(true);
606
- });
607
-
608
- test('dates: valid', () => {
609
- const result = dates();
610
- expect(result).toBeInstanceOf(Array);
611
- expect(result.length).toBe(5); // Assuming default numPairs is 5
612
- result.forEach(pair => {
613
- expect(pair).toBeInstanceOf(Array);
614
- expect(pair.length).toBe(2);
615
- expect(dayjs(pair[0]()).isValid()).toBe(true);
616
- expect(dayjs(pair[1]()).isValid()).toBe(true);
617
- });
618
- });
619
-
620
- test('day: works', () => {
621
- const start = '2020-01-01';
622
- const end = '2020-01-30';
623
- const result = day(start, end);
624
- const dayResult = result(0, 9);
625
- expect(dayjs(dayResult.day).isAfter(dayjs(dayResult.start))).toBe(true);
626
- expect(dayjs(dayResult.day).isBefore(dayjs(dayResult.end))).toBe(true);
627
- });
628
-
629
- test('exhaust: works', () => {
630
- const arr = [1, 2, 3];
631
- const next = exhaust(arr);
632
- expect(next()).toBe(1);
633
- expect(next()).toBe(2);
634
- expect(next()).toBe(3);
635
- expect(next()).toBe(undefined); // or whatever your implementation does after array is exhausted
636
- });
637
-
638
- test('emoji: works', () => {
639
- const emojis = generateEmoji(5)();
640
- expect(typeof emojis).toBe('string');
641
- if (!Array.isArray(emojis)) {
642
- expect(emojis.split(', ').length).toBeLessThanOrEqual(5);
643
- }
644
- if (Array.isArray(emojis)) {
645
- expect(emojis.length).toBeLessThanOrEqual(5);
646
- }
647
- });
648
-
649
- test('emoji: length', () => {
650
- const result = generateEmoji();
651
- const emojis = result();
652
- expect(typeof emojis).toBe('string');
653
- if (!Array.isArray(emojis)) {
654
- expect(emojis.split(', ').length).toBeLessThanOrEqual(10);
655
- }
656
- if (Array.isArray(emojis)) {
657
- expect(emojis.length).toBeLessThanOrEqual(10);
658
- }
659
-
660
- });
661
-
662
-
663
-
664
- test('progress: output', () => {
665
- // @ts-ignore
666
- const mockStdoutWrite = vi.spyOn(process.stdout, 'write').mockImplementation(() => { });
667
- progress([['test', 50]]);
668
- expect(mockStdoutWrite).toHaveBeenCalled();
669
- mockStdoutWrite.mockRestore();
670
- });
671
-
672
- test('range: works', () => {
673
- const result = range(1, 5);
674
- expect(result).toEqual([1, 2, 3, 4, 5]);
675
- });
676
-
677
-
678
- test('shuffleArray: works', () => {
679
- const arr = [1, 2, 3, 4, 5];
680
- const shuffled = shuffleArray([...arr]);
681
- expect(shuffled).not.toEqual(arr);
682
- expect(shuffled.sort()).toEqual(arr.sort());
683
- });
684
-
685
- test('shuffleExceptFirst: works', () => {
686
- const arr = [1, 2, 3, 4, 5];
687
- const shuffled = shuffleExceptFirst([...arr]);
688
- expect(shuffled[0]).toBe(arr[0]);
689
- expect(shuffled.slice(1).sort()).toEqual(arr.slice(1).sort());
690
- });
691
-
692
- test('shuffleExceptLast: works', () => {
693
- const arr = [1, 2, 3, 4, 5];
694
- const shuffled = shuffleExceptLast([...arr]);
695
- expect(shuffled[shuffled.length - 1]).toBe(arr[arr.length - 1]);
696
- expect(shuffled.slice(0, -1).sort()).toEqual(arr.slice(0, -1).sort());
697
- });
698
-
699
- test('fixFirstAndLast: works', () => {
700
- const arr = [1, 2, 3, 4, 5];
701
- const shuffled = fixFirstAndLast([...arr]);
702
- expect(shuffled[0]).toBe(arr[0]);
703
- expect(shuffled[shuffled.length - 1]).toBe(arr[arr.length - 1]);
704
- expect(shuffled.slice(1, -1).sort()).toEqual(arr.slice(1, -1).sort());
705
- });
706
-
707
- test('shuffleMiddle: works', () => {
708
- const arr = [1, 2, 3, 4, 5];
709
- const shuffled = shuffleMiddle([...arr]);
710
- expect(shuffled[0]).toBe(arr[0]);
711
- expect(shuffled[shuffled.length - 1]).toBe(arr[arr.length - 1]);
712
- expect(shuffled.slice(1, -1).sort()).toEqual(arr.slice(1, -1).sort());
713
- });
714
-
715
- test('shuffleOutside: works', () => {
716
- const arr = [1, 2, 3, 4, 5];
717
- const shuffled = shuffleOutside([...arr]);
718
- expect(shuffled.slice(1, -1)).toEqual(arr.slice(1, -1));
719
- });
720
-
721
- test('box: distribution', () => {
722
- const values = [];
723
- for (let i = 0; i < 10000; i++) {
724
- values.push(boxMullerRandom());
725
- }
726
- const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
727
- const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
728
- const stdDev = Math.sqrt(variance);
729
- expect(mean).toBeCloseTo(0, 1);
730
- expect(stdDev).toBeCloseTo(1, 1);
731
- });
732
-
733
- test('optimized box: distribution', () => {
734
- const values = [];
735
- for (let i = 0; i < 10000; i++) {
736
- values.push(optimizedBoxMuller());
737
- }
738
- const mean = values.reduce((sum, val) => sum + val, 0) / values.length;
739
- const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;
740
- const stdDev = Math.sqrt(variance);
741
- expect(mean).toBeLessThan(1);
742
- expect(stdDev).toBeLessThan(1);
743
- });
744
-
745
-
746
- });
747
-
748
-
749
- describe('weights', () => {
750
- test('weighChoices: objects', () => {
751
- const items = [
752
- { value: 'foo', weight: 3 },
753
- { value: 'bar', weight: 2 }
754
- ];
755
- const generateWeightedArray = weighChoices(items);
756
- const result = generateWeightedArray();
757
-
758
- expect(result.filter(item => item === 'foo').length).toBe(3);
759
- expect(result.filter(item => item === 'bar').length).toBe(2);
760
- });
761
-
762
- test('weighChoices: strings', () => {
763
- const items = ['foo', 'bar', 'baz'];
764
- const generateWeightedArray = weighChoices(items);
765
- const result = generateWeightedArray();
766
-
767
- // Check that each item has a unique weight
768
- const counts = items.map(item => result.filter(r => r === item).length);
769
- const uniqueCounts = new Set(counts);
770
-
771
- expect(uniqueCounts.size).toBe(items.length);
772
- counts.forEach(count => {
773
- expect(count).toBeGreaterThanOrEqual(1);
774
- expect(count).toBeLessThanOrEqual(items.length);
775
- });
776
- });
777
-
778
- test('weighChoices: empty', () => {
779
- const items = [];
780
- const generateWeightedArray = weighChoices(items);
781
- const result = generateWeightedArray();
782
-
783
- expect(result).toEqual([]);
784
- });
785
-
786
- test('weighChoices: one string', () => {
787
- const items = ['foo'];
788
- const generateWeightedArray = weighChoices(items);
789
- const result = generateWeightedArray();
790
-
791
- expect(result).toEqual(['foo']);
792
- });
793
-
794
- test('weighChoices: one obj', () => {
795
- const items = [{ value: 'foo', weight: 5 }];
796
- const generateWeightedArray = weighChoices(items);
797
- const result = generateWeightedArray();
798
-
799
- expect(result).toEqual(['foo', 'foo', 'foo', 'foo', 'foo']);
800
- });
801
-
802
-
803
- test('winner: return func', () => {
804
- const items = ['a', 'b', 'c'];
805
- const result = pickAWinner(items, 0);
806
- expect(typeof result).toBe('function');
807
- });
808
-
809
- test('winner: first most', () => {
810
- const items = ['a', 'b', 'c'];
811
- const mostChosenIndex = 0;
812
- const pickFunction = pickAWinner(items, mostChosenIndex);
813
- const weightedList = pickFunction();
814
-
815
- // Expect the most chosen item to appear at least once
816
- expect(weightedList.includes(items[mostChosenIndex])).toBeTruthy();
817
- });
818
-
819
- test('winner: second most', () => {
820
- const items = ['a', 'b', 'c'];
821
- const mostChosenIndex = 0;
822
- const pickFunction = pickAWinner(items, mostChosenIndex);
823
- const weightedList = pickFunction();
824
-
825
- const secondMostChosenIndex = (mostChosenIndex + 1) % items.length;
826
-
827
- // Expect the second most chosen item to appear at least once
828
- expect(weightedList.includes(items[secondMostChosenIndex])).toBeTruthy();
829
- });
830
-
831
- test('winner: third most', () => {
832
- const items = ['a', 'b', 'c'];
833
- const mostChosenIndex = 0;
834
- const pickFunction = pickAWinner(items, mostChosenIndex);
835
- const weightedList = pickFunction();
836
-
837
- const thirdMostChosenIndex = (mostChosenIndex + 2) % items.length;
838
-
839
- // Expect the third most chosen item to appear at least once
840
- expect(weightedList.includes(items[thirdMostChosenIndex])).toBeTruthy();
841
- });
842
-
843
- test('winner: exceed array bounds', () => {
844
- const items = ['a', 'b', 'c'];
845
- const mostChosenIndex = 0;
846
- const pickFunction = pickAWinner(items, mostChosenIndex);
847
- const weightedList = pickFunction();
848
-
849
- // Ensure all indices are within the bounds of the array
850
- weightedList.forEach(item => {
851
- expect(items.includes(item)).toBeTruthy();
852
- });
853
- });
854
-
855
- test('winner: single item array', () => {
856
- const items = ['a'];
857
- const mostChosenIndex = 0;
858
- const pickFunction = pickAWinner(items, mostChosenIndex);
859
- const weightedList = pickFunction();
860
-
861
- // Since there's only one item, all winner: he same
862
- weightedList.forEach(item => {
863
- expect(item).toBe('a');
864
- });
865
- });
866
-
867
- test('winner: empty array', () => {
868
- const items = [];
869
- const pickFunction = pickAWinner(items, 0);
870
- const weightedList = pickFunction();
871
-
872
- // Expect the result to be an empty array
873
- expect(weightedList.length).toBe(0);
874
- });
875
-
876
- test('weighNumRange: within range', () => {
877
- const values = weighNumRange(5, 15);
878
- expect(values.every(v => v >= 5 && v <= 15)).toBe(true);
879
- expect(values.length).toBe(50);
880
- });
881
-
882
- test('applySkew: skews', () => {
883
- const value = optimizedBoxMuller();
884
- const skewedValue = applySkew(value, .25);
885
- expect(Math.abs(skewedValue)).toBeLessThanOrEqual(Math.abs(value) + 1);
886
- });
887
-
888
- test('mapToRange: works', () => {
889
- const value = 0;
890
- const mean = 10;
891
- const sd = 5;
892
- const mappedValue = mapToRange(value, mean, sd);
893
- expect(mappedValue).toBe(10);
894
- });
895
-
896
- test('weighArray: works', () => {
897
- const arr = ['a', 'b', 'c'];
898
- const weightedArr = weighArray(arr);
899
- expect(weightedArr.length).toBeGreaterThanOrEqual(arr.length);
900
- });
901
-
902
- test('weighFunnels: works', () => {
903
- const acc = [];
904
- const funnel = { weight: 3 };
905
- const result = weighFunnels(acc, funnel);
906
- expect(result.length).toBe(3);
907
- });
908
-
909
-
910
- });