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.
- package/.vscode/settings.json +1 -0
- package/core/index.js +24 -19
- package/core/utils.js +134 -77
- package/dungeons/.gitkeep +0 -0
- package/package.json +5 -5
- package/schemas/anon.js +2 -2
- package/schemas/complex.js +22 -22
- package/schemas/foobar.js +2 -2
- package/schemas/funnels.js +10 -10
- package/schemas/simple.js +16 -30
- package/scratch.mjs +4 -7
- package/scripts/new.sh +52 -0
- package/tests/coverage/.gitkeep +0 -0
- package/tests/e2e.test.js +4 -22
- package/tests/jest.config.js +3 -2
- package/tests/unit.test.js +327 -207
- package/types.d.ts +157 -80
- package/schemas/deepNest.js +0 -106
package/tests/unit.test.js
CHANGED
|
@@ -6,7 +6,17 @@ const u = require('ak-tools');
|
|
|
6
6
|
dayjs.extend(utc);
|
|
7
7
|
require('dotenv').config();
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
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('
|
|
114
|
+
describe('filenames', () => {
|
|
69
115
|
|
|
70
116
|
test('default config', () => {
|
|
71
117
|
const config = { simulationName: 'testSim' };
|
|
72
118
|
const result = buildFileNames(config);
|
|
73
|
-
|
|
74
|
-
expect(
|
|
75
|
-
expect(
|
|
76
|
-
expect(
|
|
77
|
-
expect(
|
|
78
|
-
expect(
|
|
79
|
-
expect(
|
|
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
|
-
|
|
86
|
-
expect(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
]);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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
|
-
|
|
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('
|
|
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
|
-
|
|
263
|
+
|
|
264
|
+
test('user: works', () => {
|
|
265
|
+
const uuid = { guid: jest.fn().mockReturnValue('uuid-123') };
|
|
217
266
|
const numDays = 30;
|
|
218
|
-
const user = generateUser(
|
|
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
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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('
|
|
289
|
-
const
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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:
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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('
|
|
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
|
-
|
|
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 =
|
|
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
|
|
489
|
+
test('hooks: filter', () => {
|
|
474
490
|
const arr = [];
|
|
475
491
|
const hook = (item) => item ? item.toString() : item;
|
|
476
|
-
const enrichedArray =
|
|
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('
|
|
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:
|
|
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
|
|
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
|
|
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
|
+
});
|