@locustjs/test 1.0.2 → 1.1.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.
- package/README.md +7 -0
- package/index.cjs.js +312 -43
- package/index.esm.js +240 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,9 +23,15 @@ npm i @locustjs/test
|
|
|
23
23
|
- `toThrowAsync()`
|
|
24
24
|
- `notToThrow()`
|
|
25
25
|
- `notToThrowAsync()`
|
|
26
|
+
- `toBeTruthy()`
|
|
27
|
+
- `toBeFalsy()`
|
|
28
|
+
- `toBeNaN()`
|
|
29
|
+
- `notToBeNaN()`
|
|
26
30
|
|
|
27
31
|
Example:
|
|
28
32
|
```javascript
|
|
33
|
+
import TestRunner from '@locustjs/test';
|
|
34
|
+
|
|
29
35
|
const tests = [
|
|
30
36
|
['test 1', expect => expect(2 + 2).toBe(4)],
|
|
31
37
|
['test 2', expect => expect(undefined).toBeUndefined()],
|
|
@@ -37,4 +43,5 @@ const runner = new TestRunner();
|
|
|
37
43
|
await runner.run(tests);
|
|
38
44
|
|
|
39
45
|
runner.report();
|
|
46
|
+
runner.log();
|
|
40
47
|
```
|
package/index.cjs.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { isArray, isFunction } from '@locustjs/base';
|
|
1
|
+
import { isArray, isFunction, isNaN, isObject, isPrimitive, equals } from '@locustjs/base';
|
|
2
2
|
import { Exception } from '@locustjs/exception';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
3
5
|
class Expect {
|
|
4
6
|
constructor(value) {
|
|
5
7
|
this.value = value;
|
|
@@ -112,19 +114,65 @@ class Expect {
|
|
|
112
114
|
});
|
|
113
115
|
}
|
|
114
116
|
}
|
|
115
|
-
toThrow(
|
|
116
|
-
if (!isFunction(
|
|
117
|
+
toThrow(ex, shape = false, strict = false) {
|
|
118
|
+
if (!isFunction(this.value)) {
|
|
117
119
|
throw new Exception({
|
|
118
120
|
message: `given argument is not a function.`,
|
|
119
121
|
code: 1012,
|
|
120
|
-
status: '
|
|
122
|
+
status: 'not-func'
|
|
121
123
|
});
|
|
122
124
|
}
|
|
123
125
|
let ok = false;
|
|
124
126
|
try {
|
|
125
|
-
|
|
127
|
+
this.value();
|
|
126
128
|
ok = true;
|
|
127
|
-
} catch (e) {
|
|
129
|
+
} catch (e) {
|
|
130
|
+
if (ex !== undefined) {
|
|
131
|
+
if (isPrimitive(ex)) {
|
|
132
|
+
if (e !== ex) {
|
|
133
|
+
throw new Exception({
|
|
134
|
+
message: `given function threw incorrect error.`,
|
|
135
|
+
code: 1018,
|
|
136
|
+
status: 'incorrect-throw-error'
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
} else if (isFunction(ex)) {
|
|
140
|
+
if (!(e instanceof ex)) {
|
|
141
|
+
throw new Exception({
|
|
142
|
+
message: `given function threw incorrect instance.`,
|
|
143
|
+
code: 1019,
|
|
144
|
+
status: 'incorrect-throw-instance'
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
} else if (isObject(ex)) {
|
|
148
|
+
if (shape) {
|
|
149
|
+
if (!equals(e, ex, strict)) {
|
|
150
|
+
throw new Exception({
|
|
151
|
+
message: `given function threw incorrect object shape.`,
|
|
152
|
+
code: 1020,
|
|
153
|
+
status: 'incorrect-throw-shape'
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
if (e !== ex) {
|
|
158
|
+
throw new Exception({
|
|
159
|
+
message: `given function threw incorrect object.`,
|
|
160
|
+
code: 1021,
|
|
161
|
+
status: 'incorrect-throw-object'
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
if (e !== ex) {
|
|
167
|
+
throw new Exception({
|
|
168
|
+
message: `given function threw incorrect value.`,
|
|
169
|
+
code: 1022,
|
|
170
|
+
status: 'incorrect-throw-value'
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
128
176
|
if (ok) {
|
|
129
177
|
throw new Exception({
|
|
130
178
|
message: `given function ran without throwing any errors.`,
|
|
@@ -133,19 +181,65 @@ class Expect {
|
|
|
133
181
|
});
|
|
134
182
|
}
|
|
135
183
|
}
|
|
136
|
-
async toThrowAsync(
|
|
137
|
-
if (!isFunction(
|
|
184
|
+
async toThrowAsync(ex, shape = false, strict = false) {
|
|
185
|
+
if (!isFunction(this.value)) {
|
|
138
186
|
throw new Exception({
|
|
139
187
|
message: `given argument is not a function.`,
|
|
140
188
|
code: 1012,
|
|
141
|
-
status: '
|
|
189
|
+
status: 'not-func'
|
|
142
190
|
});
|
|
143
191
|
}
|
|
144
192
|
let ok = false;
|
|
145
193
|
try {
|
|
146
|
-
await
|
|
194
|
+
await this.value();
|
|
147
195
|
ok = true;
|
|
148
|
-
} catch (e) {
|
|
196
|
+
} catch (e) {
|
|
197
|
+
if (ex !== undefined) {
|
|
198
|
+
if (isPrimitive(ex)) {
|
|
199
|
+
if (e !== ex) {
|
|
200
|
+
throw new Exception({
|
|
201
|
+
message: `given function threw incorrect error.`,
|
|
202
|
+
code: 1018,
|
|
203
|
+
status: 'incorrect-throw-error'
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
} else if (isFunction(ex)) {
|
|
207
|
+
if (!(e instanceof ex)) {
|
|
208
|
+
throw new Exception({
|
|
209
|
+
message: `given function threw incorrect instance.`,
|
|
210
|
+
code: 1019,
|
|
211
|
+
status: 'incorrect-throw-instance'
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
} else if (isObject(ex)) {
|
|
215
|
+
if (shape) {
|
|
216
|
+
if (!equals(e, ex, strict)) {
|
|
217
|
+
throw new Exception({
|
|
218
|
+
message: `given function threw incorrect object shape.`,
|
|
219
|
+
code: 1020,
|
|
220
|
+
status: 'incorrect-throw-shape'
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
if (e !== ex) {
|
|
225
|
+
throw new Exception({
|
|
226
|
+
message: `given function threw incorrect object.`,
|
|
227
|
+
code: 1021,
|
|
228
|
+
status: 'incorrect-throw-object'
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
if (e !== ex) {
|
|
234
|
+
throw new Exception({
|
|
235
|
+
message: `given function threw incorrect value.`,
|
|
236
|
+
code: 1022,
|
|
237
|
+
status: 'incorrect-throw-value'
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
149
243
|
if (ok) {
|
|
150
244
|
throw new Exception({
|
|
151
245
|
message: `given function ran without throwing any errors.`,
|
|
@@ -154,21 +248,66 @@ class Expect {
|
|
|
154
248
|
});
|
|
155
249
|
}
|
|
156
250
|
}
|
|
157
|
-
notToThrow(
|
|
158
|
-
if (!isFunction(
|
|
251
|
+
notToThrow(ex, shape = false, strict = false) {
|
|
252
|
+
if (!isFunction(this.value)) {
|
|
159
253
|
throw new Exception({
|
|
160
254
|
message: `given argument is not a function.`,
|
|
161
255
|
code: 1012,
|
|
162
|
-
status: '
|
|
256
|
+
status: 'not-func'
|
|
163
257
|
});
|
|
164
258
|
}
|
|
165
259
|
let ok = true;
|
|
166
260
|
let error;
|
|
167
261
|
try {
|
|
168
|
-
|
|
262
|
+
this.value();
|
|
169
263
|
ok = false;
|
|
170
264
|
} catch (e) {
|
|
171
265
|
error = e;
|
|
266
|
+
if (ex !== undefined) {
|
|
267
|
+
if (isPrimitive(ex)) {
|
|
268
|
+
if (e === ex) {
|
|
269
|
+
throw new Exception({
|
|
270
|
+
message: `given function threw incorrect error.`,
|
|
271
|
+
code: 1018,
|
|
272
|
+
status: 'incorrect-throw-error'
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
} else if (isFunction(ex)) {
|
|
276
|
+
if (e instanceof ex) {
|
|
277
|
+
throw new Exception({
|
|
278
|
+
message: `given function threw incorrect instance.`,
|
|
279
|
+
code: 1019,
|
|
280
|
+
status: 'incorrect-throw-instance'
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
} else if (isObject(ex)) {
|
|
284
|
+
if (shape) {
|
|
285
|
+
if (equals(e, ex, strict)) {
|
|
286
|
+
throw new Exception({
|
|
287
|
+
message: `given function threw incorrect object shape.`,
|
|
288
|
+
code: 1020,
|
|
289
|
+
status: 'incorrect-throw-shape'
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
if (e === ex) {
|
|
294
|
+
throw new Exception({
|
|
295
|
+
message: `given function threw incorrect object.`,
|
|
296
|
+
code: 1021,
|
|
297
|
+
status: 'incorrect-throw-object'
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
if (e === ex) {
|
|
303
|
+
throw new Exception({
|
|
304
|
+
message: `given function threw incorrect value.`,
|
|
305
|
+
code: 1022,
|
|
306
|
+
status: 'incorrect-throw-value'
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
172
311
|
}
|
|
173
312
|
if (ok) {
|
|
174
313
|
throw new Exception({
|
|
@@ -179,21 +318,66 @@ class Expect {
|
|
|
179
318
|
});
|
|
180
319
|
}
|
|
181
320
|
}
|
|
182
|
-
async notToThrowAsync(
|
|
183
|
-
if (!isFunction(
|
|
321
|
+
async notToThrowAsync(ex, shape = false, strict = false) {
|
|
322
|
+
if (!isFunction(this.value)) {
|
|
184
323
|
throw new Exception({
|
|
185
324
|
message: `given argument is not a function.`,
|
|
186
325
|
code: 1012,
|
|
187
|
-
status: '
|
|
326
|
+
status: 'not-func'
|
|
188
327
|
});
|
|
189
328
|
}
|
|
190
329
|
let ok = true;
|
|
191
330
|
let error;
|
|
192
331
|
try {
|
|
193
|
-
await
|
|
332
|
+
await this.value();
|
|
194
333
|
ok = false;
|
|
195
334
|
} catch (e) {
|
|
196
335
|
error = e;
|
|
336
|
+
if (ex !== undefined) {
|
|
337
|
+
if (isPrimitive(ex)) {
|
|
338
|
+
if (e === ex) {
|
|
339
|
+
throw new Exception({
|
|
340
|
+
message: `given function threw incorrect error.`,
|
|
341
|
+
code: 1018,
|
|
342
|
+
status: 'incorrect-throw-error'
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
} else if (isFunction(ex)) {
|
|
346
|
+
if (e instanceof ex) {
|
|
347
|
+
throw new Exception({
|
|
348
|
+
message: `given function threw incorrect instance.`,
|
|
349
|
+
code: 1019,
|
|
350
|
+
status: 'incorrect-throw-instance'
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
} else if (isObject(ex)) {
|
|
354
|
+
if (shape) {
|
|
355
|
+
if (equals(e, ex, strict)) {
|
|
356
|
+
throw new Exception({
|
|
357
|
+
message: `given function threw incorrect object shape.`,
|
|
358
|
+
code: 1020,
|
|
359
|
+
status: 'incorrect-throw-shape'
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
} else {
|
|
363
|
+
if (e === ex) {
|
|
364
|
+
throw new Exception({
|
|
365
|
+
message: `given function threw incorrect object.`,
|
|
366
|
+
code: 1021,
|
|
367
|
+
status: 'incorrect-throw-object'
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
} else {
|
|
372
|
+
if (e === ex) {
|
|
373
|
+
throw new Exception({
|
|
374
|
+
message: `given function threw incorrect value.`,
|
|
375
|
+
code: 1022,
|
|
376
|
+
status: 'incorrect-throw-value'
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
197
381
|
}
|
|
198
382
|
if (ok) {
|
|
199
383
|
throw new Exception({
|
|
@@ -204,6 +388,42 @@ class Expect {
|
|
|
204
388
|
});
|
|
205
389
|
}
|
|
206
390
|
}
|
|
391
|
+
toBeTruthy() {
|
|
392
|
+
if (this.value) {} else {
|
|
393
|
+
throw new Exception({
|
|
394
|
+
message: `${this.value} is not truthy`,
|
|
395
|
+
code: 1015,
|
|
396
|
+
status: 'not-truthy'
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
toBeFalsy() {
|
|
401
|
+
if (!this.value) {} else {
|
|
402
|
+
throw new Exception({
|
|
403
|
+
message: `${this.value} is not falsy`,
|
|
404
|
+
code: 1016,
|
|
405
|
+
status: 'not-falsy'
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
toBeNaN() {
|
|
410
|
+
if (isNaN(this.value)) {} else {
|
|
411
|
+
throw new Exception({
|
|
412
|
+
message: `${this.value} is not NaN`,
|
|
413
|
+
code: 1017,
|
|
414
|
+
status: 'not-nan'
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
notToBeNaN() {
|
|
419
|
+
if (!isNaN(this.value)) {} else {
|
|
420
|
+
throw new Exception({
|
|
421
|
+
message: `${this.value} is NaN`,
|
|
422
|
+
code: 1023,
|
|
423
|
+
status: 'is-nan'
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
207
427
|
}
|
|
208
428
|
const expect = x => new Expect(x);
|
|
209
429
|
class Test {
|
|
@@ -290,46 +510,49 @@ const ConsoleColors = {
|
|
|
290
510
|
};
|
|
291
511
|
class TestRunner {
|
|
292
512
|
constructor() {
|
|
293
|
-
this.
|
|
294
|
-
this.
|
|
295
|
-
this.
|
|
296
|
-
this.
|
|
513
|
+
this._passed = 0;
|
|
514
|
+
this._failed = 0;
|
|
515
|
+
this._results = [];
|
|
516
|
+
this._errors = [];
|
|
297
517
|
}
|
|
298
518
|
async _runSingle(test, onProgress, i) {
|
|
299
519
|
if (isFunction(onProgress)) {
|
|
300
520
|
try {
|
|
301
521
|
onProgress(i, test);
|
|
302
522
|
} catch (ex) {
|
|
303
|
-
this.
|
|
523
|
+
this._errors.push({
|
|
524
|
+
index: i,
|
|
525
|
+
test,
|
|
304
526
|
err: new Exception({
|
|
305
527
|
message: `onProgress failed for test '${test.name} at index ${i}'.`,
|
|
306
528
|
code: 1500,
|
|
307
|
-
status: 'progress-failed'
|
|
529
|
+
status: 'progress-failed',
|
|
530
|
+
innerException: ex
|
|
308
531
|
})
|
|
309
532
|
});
|
|
310
533
|
}
|
|
311
534
|
}
|
|
312
535
|
const tr = await test.run();
|
|
313
|
-
this.
|
|
536
|
+
this._results.push(tr);
|
|
314
537
|
if (tr.success) {
|
|
315
|
-
this.
|
|
538
|
+
this._passed++;
|
|
316
539
|
} else {
|
|
317
|
-
this.
|
|
540
|
+
this._failed++;
|
|
318
541
|
}
|
|
319
542
|
}
|
|
320
543
|
get result() {
|
|
321
544
|
return {
|
|
322
|
-
passed: this.
|
|
323
|
-
failed: this.
|
|
324
|
-
results: this.
|
|
325
|
-
errors: this.
|
|
545
|
+
passed: this._passed,
|
|
546
|
+
failed: this._failed,
|
|
547
|
+
results: this._results,
|
|
548
|
+
errors: this._errors
|
|
326
549
|
};
|
|
327
550
|
}
|
|
328
551
|
run(tests, onProgress) {
|
|
329
|
-
this.
|
|
330
|
-
this.
|
|
331
|
-
this.
|
|
332
|
-
this.
|
|
552
|
+
this._passed = 0;
|
|
553
|
+
this._failed = 0;
|
|
554
|
+
this._results = [];
|
|
555
|
+
this._errors = [];
|
|
333
556
|
return new Promise(res => {
|
|
334
557
|
if (tests) {
|
|
335
558
|
if (tests instanceof Test) {
|
|
@@ -346,7 +569,7 @@ class TestRunner {
|
|
|
346
569
|
return _test;
|
|
347
570
|
}).filter(test => test instanceof Test).map((test, i) => this._runSingle(test, onProgress, i));
|
|
348
571
|
Promise.all(_tests).then(_ => res()).catch(ex => {
|
|
349
|
-
this.
|
|
572
|
+
this._errors.push({
|
|
350
573
|
err: new Exception({
|
|
351
574
|
message: `not all tests succeeded. check errors.`,
|
|
352
575
|
code: 1503,
|
|
@@ -357,7 +580,7 @@ class TestRunner {
|
|
|
357
580
|
res();
|
|
358
581
|
});
|
|
359
582
|
} else {
|
|
360
|
-
this.
|
|
583
|
+
this._errors.push({
|
|
361
584
|
err: new Exception({
|
|
362
585
|
message: `invalid tests. expected array or a single test.`,
|
|
363
586
|
code: 1502,
|
|
@@ -367,7 +590,7 @@ class TestRunner {
|
|
|
367
590
|
res();
|
|
368
591
|
}
|
|
369
592
|
} else {
|
|
370
|
-
this.
|
|
593
|
+
this._errors.push({
|
|
371
594
|
err: new Exception({
|
|
372
595
|
message: `no tests given to be ran.`,
|
|
373
596
|
code: 1501,
|
|
@@ -378,14 +601,60 @@ class TestRunner {
|
|
|
378
601
|
}
|
|
379
602
|
});
|
|
380
603
|
}
|
|
381
|
-
|
|
604
|
+
_getTime(time) {
|
|
605
|
+
return `${time / 1000} sec`;
|
|
606
|
+
}
|
|
607
|
+
report(detailed) {
|
|
382
608
|
let time = 0;
|
|
383
|
-
|
|
384
|
-
|
|
609
|
+
console.log('Finished.\n\n');
|
|
610
|
+
for (let i = 0; i < this._results.length; i++) {
|
|
611
|
+
const result = this._results[i];
|
|
612
|
+
if (detailed) {
|
|
613
|
+
let message;
|
|
614
|
+
if (result.success) {
|
|
615
|
+
message = `${i}. ${result.test.name}: \x1b[${ConsoleColors.ForeColor.Green}m passed ${ConsoleColors.Modifier.Reset} (${this._getTime(result.time)})`;
|
|
616
|
+
} else {
|
|
617
|
+
message = `${i}. ${result.test.name}: \x1b[${ConsoleColors.ForeColor.Red}m failed ${ConsoleColors.Modifier.Reset} (${this._getTime(result.time)})`;
|
|
618
|
+
message += '\n';
|
|
619
|
+
message += `\x1b[${ConsoleColors.ForeColor.White}m${result.err.code}: ${result.err.toString('\n')} ${ConsoleColors.Modifier.Reset}`;
|
|
620
|
+
message += '\n';
|
|
621
|
+
}
|
|
622
|
+
console.log(message);
|
|
623
|
+
}
|
|
624
|
+
time += result.time;
|
|
625
|
+
}
|
|
626
|
+
if (detailed && this._errors.length) {
|
|
627
|
+
console.log('Progress errors:');
|
|
628
|
+
for (let error of this._errors) {
|
|
629
|
+
console.log(`${error.index}. ${error.test.name}: ${error.err.innerException.toString()}`);
|
|
630
|
+
}
|
|
385
631
|
}
|
|
386
|
-
const text =
|
|
632
|
+
const text = (detailed ? '\n' : '') + (this._failed > 0 ? `\x1b[${ConsoleColors.ForeColor.Red}m ${this._failed} tests failed ${ConsoleColors.Modifier.Reset}` : '0 tests failed') + ', ' + (this._passed > 0 ? `\x1b[${ConsoleColors.ForeColor.Green}m ${this._passed} tests passed ${ConsoleColors.Modifier.Reset}` : '0 tests passed') + '\n' + `Tests: ${this._passed + this._failed}` + '\n' + `Time: ${time / 1000} sec` + '\n';
|
|
387
633
|
console.log(text);
|
|
388
634
|
}
|
|
635
|
+
log(filename) {
|
|
636
|
+
const content = JSON.stringify({
|
|
637
|
+
results: this._results,
|
|
638
|
+
errors: this._errors
|
|
639
|
+
});
|
|
640
|
+
if (filename === null) {
|
|
641
|
+
const d = new Date();
|
|
642
|
+
const year = d.getFullYear().toString().padStart(4, '0');
|
|
643
|
+
const month = (d.getMonth() + 1).toString().padStart(2, '0');
|
|
644
|
+
const day = d.getDate().toString().padStart(2, '0');
|
|
645
|
+
const hours = d.getHours().toString().padStart(2, '0');
|
|
646
|
+
const minutes = d.getMinutes().toString().padStart(2, '0');
|
|
647
|
+
const seconds = d.getSeconds().toString().padStart(2, '0');
|
|
648
|
+
filename = `test-${year}-${month}-${day}-${hours}${minutes}${seconds}.log`;
|
|
649
|
+
}
|
|
650
|
+
const filepath = path.join(process.cwd(), filename);
|
|
651
|
+
try {
|
|
652
|
+
fs.writeFileSync(filepath, content);
|
|
653
|
+
console.log(`tests outcome wrote in ${filename}.`);
|
|
654
|
+
} catch (ex) {
|
|
655
|
+
console.log('writing tests outcome failed.\n' + ex);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
389
658
|
}
|
|
390
659
|
export default TestRunner;
|
|
391
660
|
export { Test, Expect, expect };
|
package/index.esm.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { isArray, isFunction } from '@locustjs/base';
|
|
1
|
+
import { isArray, isFunction, isNaN, isObject, isPrimitive, equals } from '@locustjs/base';
|
|
2
2
|
import { Exception } from '@locustjs/exception';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
3
5
|
|
|
4
6
|
class Expect {
|
|
5
7
|
constructor(value) {
|
|
@@ -67,76 +69,208 @@ class Expect {
|
|
|
67
69
|
throw new Exception({ message: `value is null/undefined`, code: 1011, status: 'null-or-undefined' })
|
|
68
70
|
}
|
|
69
71
|
}
|
|
70
|
-
toThrow(
|
|
71
|
-
if (!isFunction(
|
|
72
|
-
throw new Exception({ message: `given argument is not a function.`, code: 1012, status: '
|
|
72
|
+
toThrow(ex, shape = false, strict = false) {
|
|
73
|
+
if (!isFunction(this.value)) {
|
|
74
|
+
throw new Exception({ message: `given argument is not a function.`, code: 1012, status: 'not-func' })
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
let ok = false;
|
|
76
78
|
|
|
77
79
|
try {
|
|
78
|
-
|
|
80
|
+
this.value();
|
|
79
81
|
|
|
80
82
|
ok = true;
|
|
81
|
-
} catch (e) {
|
|
83
|
+
} catch (e) {
|
|
84
|
+
if (ex !== undefined) {
|
|
85
|
+
if (isPrimitive(ex)) {
|
|
86
|
+
if (e !== ex) {
|
|
87
|
+
throw new Exception({ message: `given function threw incorrect error.`, code: 1018, status: 'incorrect-throw-error' })
|
|
88
|
+
}
|
|
89
|
+
} else if (isFunction(ex)) {
|
|
90
|
+
if (!(e instanceof ex)) {
|
|
91
|
+
throw new Exception({ message: `given function threw incorrect instance.`, code: 1019, status: 'incorrect-throw-instance' })
|
|
92
|
+
}
|
|
93
|
+
} else if (isObject(ex)) {
|
|
94
|
+
if (shape) {
|
|
95
|
+
if (!equals(e, ex, strict)) {
|
|
96
|
+
throw new Exception({ message: `given function threw incorrect object shape.`, code: 1020, status: 'incorrect-throw-shape' })
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
if (e !== ex) {
|
|
100
|
+
throw new Exception({ message: `given function threw incorrect object.`, code: 1021, status: 'incorrect-throw-object' })
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
if (e !== ex) {
|
|
105
|
+
throw new Exception({ message: `given function threw incorrect value.`, code: 1022, status: 'incorrect-throw-value' })
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
82
110
|
|
|
83
111
|
if (ok) {
|
|
84
112
|
throw new Exception({ message: `given function ran without throwing any errors.`, code: 1013, status: 'ran-to-completion' })
|
|
85
113
|
}
|
|
86
114
|
}
|
|
87
|
-
async toThrowAsync(
|
|
88
|
-
if (!isFunction(
|
|
89
|
-
throw new Exception({ message: `given argument is not a function.`, code: 1012, status: '
|
|
115
|
+
async toThrowAsync(ex, shape = false, strict = false) {
|
|
116
|
+
if (!isFunction(this.value)) {
|
|
117
|
+
throw new Exception({ message: `given argument is not a function.`, code: 1012, status: 'not-func' })
|
|
90
118
|
}
|
|
91
119
|
|
|
92
120
|
let ok = false;
|
|
93
121
|
|
|
94
122
|
try {
|
|
95
|
-
await
|
|
123
|
+
await this.value();
|
|
96
124
|
|
|
97
125
|
ok = true;
|
|
98
|
-
} catch (e) {
|
|
126
|
+
} catch (e) {
|
|
127
|
+
if (ex !== undefined) {
|
|
128
|
+
if (isPrimitive(ex)) {
|
|
129
|
+
if (e !== ex) {
|
|
130
|
+
throw new Exception({ message: `given function threw incorrect error.`, code: 1018, status: 'incorrect-throw-error' })
|
|
131
|
+
}
|
|
132
|
+
} else if (isFunction(ex)) {
|
|
133
|
+
if (!(e instanceof ex)) {
|
|
134
|
+
throw new Exception({ message: `given function threw incorrect instance.`, code: 1019, status: 'incorrect-throw-instance' })
|
|
135
|
+
}
|
|
136
|
+
} else if (isObject(ex)) {
|
|
137
|
+
if (shape) {
|
|
138
|
+
if (!equals(e, ex, strict)) {
|
|
139
|
+
throw new Exception({ message: `given function threw incorrect object shape.`, code: 1020, status: 'incorrect-throw-shape' })
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
if (e !== ex) {
|
|
143
|
+
throw new Exception({ message: `given function threw incorrect object.`, code: 1021, status: 'incorrect-throw-object' })
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
if (e !== ex) {
|
|
148
|
+
throw new Exception({ message: `given function threw incorrect value.`, code: 1022, status: 'incorrect-throw-value' })
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
99
153
|
|
|
100
154
|
if (ok) {
|
|
101
155
|
throw new Exception({ message: `given function ran without throwing any errors.`, code: 1013, status: 'ran-to-completion' })
|
|
102
156
|
}
|
|
103
157
|
}
|
|
104
|
-
notToThrow(
|
|
105
|
-
if (!isFunction(
|
|
106
|
-
throw new Exception({ message: `given argument is not a function.`, code: 1012, status: '
|
|
158
|
+
notToThrow(ex, shape = false, strict = false) {
|
|
159
|
+
if (!isFunction(this.value)) {
|
|
160
|
+
throw new Exception({ message: `given argument is not a function.`, code: 1012, status: 'not-func' })
|
|
107
161
|
}
|
|
108
162
|
|
|
109
163
|
let ok = true;
|
|
110
164
|
let error;
|
|
111
165
|
|
|
112
166
|
try {
|
|
113
|
-
|
|
167
|
+
this.value();
|
|
114
168
|
|
|
115
169
|
ok = false;
|
|
116
|
-
} catch (e) {
|
|
170
|
+
} catch (e) {
|
|
171
|
+
error = e;
|
|
172
|
+
|
|
173
|
+
if (ex !== undefined) {
|
|
174
|
+
if (isPrimitive(ex)) {
|
|
175
|
+
if (e === ex) {
|
|
176
|
+
throw new Exception({ message: `given function threw incorrect error.`, code: 1018, status: 'incorrect-throw-error' })
|
|
177
|
+
}
|
|
178
|
+
} else if (isFunction(ex)) {
|
|
179
|
+
if (e instanceof ex) {
|
|
180
|
+
throw new Exception({ message: `given function threw incorrect instance.`, code: 1019, status: 'incorrect-throw-instance' })
|
|
181
|
+
}
|
|
182
|
+
} else if (isObject(ex)) {
|
|
183
|
+
if (shape) {
|
|
184
|
+
if (equals(e, ex, strict)) {
|
|
185
|
+
throw new Exception({ message: `given function threw incorrect object shape.`, code: 1020, status: 'incorrect-throw-shape' })
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
if (e === ex) {
|
|
189
|
+
throw new Exception({ message: `given function threw incorrect object.`, code: 1021, status: 'incorrect-throw-object' })
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
if (e === ex) {
|
|
194
|
+
throw new Exception({ message: `given function threw incorrect value.`, code: 1022, status: 'incorrect-throw-value' })
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
117
199
|
|
|
118
200
|
if (ok) {
|
|
119
201
|
throw new Exception({ message: `given function threw an error.`, code: 1014, status: 'ran-to-error', innerException: error })
|
|
120
202
|
}
|
|
121
203
|
}
|
|
122
|
-
async notToThrowAsync(
|
|
123
|
-
if (!isFunction(
|
|
124
|
-
throw new Exception({ message: `given argument is not a function.`, code: 1012, status: '
|
|
204
|
+
async notToThrowAsync(ex, shape = false, strict = false) {
|
|
205
|
+
if (!isFunction(this.value)) {
|
|
206
|
+
throw new Exception({ message: `given argument is not a function.`, code: 1012, status: 'not-func' })
|
|
125
207
|
}
|
|
126
208
|
|
|
127
209
|
let ok = true;
|
|
128
210
|
let error;
|
|
129
211
|
|
|
130
212
|
try {
|
|
131
|
-
await
|
|
213
|
+
await this.value();
|
|
132
214
|
|
|
133
215
|
ok = false;
|
|
134
|
-
} catch (e) {
|
|
216
|
+
} catch (e) {
|
|
217
|
+
error = e;
|
|
218
|
+
|
|
219
|
+
if (ex !== undefined) {
|
|
220
|
+
if (isPrimitive(ex)) {
|
|
221
|
+
if (e === ex) {
|
|
222
|
+
throw new Exception({ message: `given function threw incorrect error.`, code: 1018, status: 'incorrect-throw-error' })
|
|
223
|
+
}
|
|
224
|
+
} else if (isFunction(ex)) {
|
|
225
|
+
if (e instanceof ex) {
|
|
226
|
+
throw new Exception({ message: `given function threw incorrect instance.`, code: 1019, status: 'incorrect-throw-instance' })
|
|
227
|
+
}
|
|
228
|
+
} else if (isObject(ex)) {
|
|
229
|
+
if (shape) {
|
|
230
|
+
if (equals(e, ex, strict)) {
|
|
231
|
+
throw new Exception({ message: `given function threw incorrect object shape.`, code: 1020, status: 'incorrect-throw-shape' })
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
if (e === ex) {
|
|
235
|
+
throw new Exception({ message: `given function threw incorrect object.`, code: 1021, status: 'incorrect-throw-object' })
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
if (e === ex) {
|
|
240
|
+
throw new Exception({ message: `given function threw incorrect value.`, code: 1022, status: 'incorrect-throw-value' })
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
135
245
|
|
|
136
246
|
if (ok) {
|
|
137
247
|
throw new Exception({ message: `given function threw an error.`, code: 1014, status: 'ran-to-error', innerException: error })
|
|
138
248
|
}
|
|
139
249
|
}
|
|
250
|
+
toBeTruthy() {
|
|
251
|
+
if (this.value) {
|
|
252
|
+
} else {
|
|
253
|
+
throw new Exception({ message: `${this.value} is not truthy`, code: 1015, status: 'not-truthy' })
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
toBeFalsy() {
|
|
257
|
+
if (!this.value) {
|
|
258
|
+
} else {
|
|
259
|
+
throw new Exception({ message: `${this.value} is not falsy`, code: 1016, status: 'not-falsy' })
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
toBeNaN() {
|
|
263
|
+
if (isNaN(this.value)) {
|
|
264
|
+
} else {
|
|
265
|
+
throw new Exception({ message: `${this.value} is not NaN`, code: 1017, status: 'not-nan' })
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
notToBeNaN() {
|
|
269
|
+
if (!isNaN(this.value)) {
|
|
270
|
+
} else {
|
|
271
|
+
throw new Exception({ message: `${this.value} is NaN`, code: 1023, status: 'is-nan' })
|
|
272
|
+
}
|
|
273
|
+
}
|
|
140
274
|
}
|
|
141
275
|
|
|
142
276
|
const expect = (x) => new Expect(x);
|
|
@@ -189,43 +323,43 @@ const ConsoleColors = {
|
|
|
189
323
|
|
|
190
324
|
class TestRunner {
|
|
191
325
|
constructor() {
|
|
192
|
-
this.
|
|
193
|
-
this.
|
|
194
|
-
this.
|
|
195
|
-
this.
|
|
326
|
+
this._passed = 0;
|
|
327
|
+
this._failed = 0;
|
|
328
|
+
this._results = []
|
|
329
|
+
this._errors = []
|
|
196
330
|
}
|
|
197
331
|
async _runSingle(test, onProgress, i) {
|
|
198
332
|
if (isFunction(onProgress)) {
|
|
199
333
|
try {
|
|
200
334
|
onProgress(i, test);
|
|
201
335
|
} catch (ex) {
|
|
202
|
-
this.
|
|
336
|
+
this._errors.push({ index: i, test, err: new Exception({ message: `onProgress failed for test '${test.name} at index ${i}'.`, code: 1500, status: 'progress-failed', innerException: ex }) })
|
|
203
337
|
}
|
|
204
338
|
}
|
|
205
339
|
|
|
206
340
|
const tr = await test.run();
|
|
207
341
|
|
|
208
|
-
this.
|
|
342
|
+
this._results.push(tr);
|
|
209
343
|
|
|
210
344
|
if (tr.success) {
|
|
211
|
-
this.
|
|
345
|
+
this._passed++;
|
|
212
346
|
} else {
|
|
213
|
-
this.
|
|
347
|
+
this._failed++;
|
|
214
348
|
}
|
|
215
349
|
}
|
|
216
350
|
get result() {
|
|
217
351
|
return {
|
|
218
|
-
passed: this.
|
|
219
|
-
failed: this.
|
|
220
|
-
results: this.
|
|
221
|
-
errors: this.
|
|
352
|
+
passed: this._passed,
|
|
353
|
+
failed: this._failed,
|
|
354
|
+
results: this._results,
|
|
355
|
+
errors: this._errors
|
|
222
356
|
}
|
|
223
357
|
}
|
|
224
358
|
run(tests, onProgress) {
|
|
225
|
-
this.
|
|
226
|
-
this.
|
|
227
|
-
this.
|
|
228
|
-
this.
|
|
359
|
+
this._passed = 0;
|
|
360
|
+
this._failed = 0;
|
|
361
|
+
this._results = [];
|
|
362
|
+
this._errors = [];
|
|
229
363
|
|
|
230
364
|
return new Promise(res => {
|
|
231
365
|
if (tests) {
|
|
@@ -250,42 +384,100 @@ class TestRunner {
|
|
|
250
384
|
.map((test, i) => this._runSingle(test, onProgress, i));
|
|
251
385
|
|
|
252
386
|
Promise.all(_tests).then(_ => res()).catch(ex => {
|
|
253
|
-
this.
|
|
387
|
+
this._errors.push({ err: new Exception({ message: `not all tests succeeded. check errors.`, code: 1503, status: 'partial-finished', innerException: ex }) });
|
|
254
388
|
|
|
255
389
|
res();
|
|
256
390
|
});
|
|
257
391
|
} else {
|
|
258
|
-
this.
|
|
392
|
+
this._errors.push({ err: new Exception({ message: `invalid tests. expected array or a single test.`, code: 1502, status: 'invalid-tests' }) });
|
|
259
393
|
|
|
260
394
|
res();
|
|
261
395
|
}
|
|
262
396
|
} else {
|
|
263
|
-
this.
|
|
397
|
+
this._errors.push({ err: new Exception({ message: `no tests given to be ran.`, code: 1501, status: 'no-tests' }) });
|
|
264
398
|
|
|
265
399
|
res();
|
|
266
400
|
}
|
|
267
401
|
})
|
|
268
402
|
}
|
|
269
|
-
|
|
403
|
+
_getTime(time) {
|
|
404
|
+
return `${time / 1000} sec`
|
|
405
|
+
}
|
|
406
|
+
report(detailed) {
|
|
270
407
|
let time = 0;
|
|
271
408
|
|
|
272
|
-
|
|
273
|
-
|
|
409
|
+
console.log('Finished.\n\n');
|
|
410
|
+
|
|
411
|
+
for (let i = 0; i < this._results.length; i++) {
|
|
412
|
+
const result = this._results[i];
|
|
413
|
+
|
|
414
|
+
if (detailed) {
|
|
415
|
+
let message;
|
|
416
|
+
|
|
417
|
+
if (result.success) {
|
|
418
|
+
message = `${i}. ${result.test.name}: \x1b[${ConsoleColors.ForeColor.Green}m passed ${ConsoleColors.Modifier.Reset} (${this._getTime(result.time)})`
|
|
419
|
+
} else {
|
|
420
|
+
message = `${i}. ${result.test.name}: \x1b[${ConsoleColors.ForeColor.Red}m failed ${ConsoleColors.Modifier.Reset} (${this._getTime(result.time)})`;
|
|
421
|
+
message += '\n';
|
|
422
|
+
message += `\x1b[${ConsoleColors.ForeColor.White}m${result.err.code}: ${result.err.toString('\n')} ${ConsoleColors.Modifier.Reset}`;
|
|
423
|
+
message += '\n';
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
console.log(message);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
time += result.time;
|
|
274
430
|
}
|
|
275
431
|
|
|
276
|
-
|
|
277
|
-
'
|
|
278
|
-
|
|
432
|
+
if (detailed && this._errors.length) {
|
|
433
|
+
console.log('Progress errors:');
|
|
434
|
+
|
|
435
|
+
for (let error of this._errors) {
|
|
436
|
+
console.log(`${error.index}. ${error.test.name}: ${error.err.innerException.toString()}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const text = (detailed ? '\n': '') +
|
|
441
|
+
(this._failed > 0 ? `\x1b[${ConsoleColors.ForeColor.Red}m ${this._failed} tests failed ${ConsoleColors.Modifier.Reset}` : '0 tests failed') +
|
|
279
442
|
', ' +
|
|
280
|
-
(this.
|
|
443
|
+
(this._passed > 0 ? `\x1b[${ConsoleColors.ForeColor.Green}m ${this._passed} tests passed ${ConsoleColors.Modifier.Reset}` : '0 tests passed') +
|
|
281
444
|
'\n' +
|
|
282
|
-
`Tests: ${this.
|
|
445
|
+
`Tests: ${this._passed + this._failed}` +
|
|
283
446
|
'\n' +
|
|
284
447
|
`Time: ${time / 1000} sec` +
|
|
285
448
|
'\n';
|
|
286
449
|
|
|
287
450
|
console.log(text);
|
|
288
451
|
}
|
|
452
|
+
log(filename) {
|
|
453
|
+
const content = JSON.stringify({
|
|
454
|
+
results: this._results,
|
|
455
|
+
errors: this._errors
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
if (filename === null) {
|
|
459
|
+
const d = new Date();
|
|
460
|
+
|
|
461
|
+
const year = d.getFullYear().toString().padStart(4, '0');
|
|
462
|
+
const month = (d.getMonth() + 1).toString().padStart(2, '0');
|
|
463
|
+
const day = d.getDate().toString().padStart(2, '0');
|
|
464
|
+
const hours = d.getHours().toString().padStart(2, '0');
|
|
465
|
+
const minutes = d.getMinutes().toString().padStart(2, '0');
|
|
466
|
+
const seconds = d.getSeconds().toString().padStart(2, '0');
|
|
467
|
+
|
|
468
|
+
filename = `test-${year}-${month}-${day}-${hours}${minutes}${seconds}.log`;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const filepath = path.join(process.cwd(), filename);
|
|
472
|
+
|
|
473
|
+
try {
|
|
474
|
+
fs.writeFileSync(filepath, content);
|
|
475
|
+
|
|
476
|
+
console.log(`tests outcome wrote in ${filename}.`);
|
|
477
|
+
} catch (ex) {
|
|
478
|
+
console.log('writing tests outcome failed.\n' + ex);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
289
481
|
}
|
|
290
482
|
|
|
291
483
|
export default TestRunner;
|