@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 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(fn) {
116
- if (!isFunction(fn)) {
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: 'no-func'
122
+ status: 'not-func'
121
123
  });
122
124
  }
123
125
  let ok = false;
124
126
  try {
125
- fn();
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(fn) {
137
- if (!isFunction(fn)) {
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: 'no-func'
189
+ status: 'not-func'
142
190
  });
143
191
  }
144
192
  let ok = false;
145
193
  try {
146
- await fn();
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(fn) {
158
- if (!isFunction(fn)) {
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: 'no-func'
256
+ status: 'not-func'
163
257
  });
164
258
  }
165
259
  let ok = true;
166
260
  let error;
167
261
  try {
168
- fn();
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(fn) {
183
- if (!isFunction(fn)) {
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: 'no-func'
326
+ status: 'not-func'
188
327
  });
189
328
  }
190
329
  let ok = true;
191
330
  let error;
192
331
  try {
193
- await fn();
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.passed = 0;
294
- this.failed = 0;
295
- this.results = [];
296
- this.errors = [];
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.errors.push({
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.results.push(tr);
536
+ this._results.push(tr);
314
537
  if (tr.success) {
315
- this.passed++;
538
+ this._passed++;
316
539
  } else {
317
- this.failed++;
540
+ this._failed++;
318
541
  }
319
542
  }
320
543
  get result() {
321
544
  return {
322
- passed: this.passed,
323
- failed: this.failed,
324
- results: this.results,
325
- errors: this.errors
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.passed = 0;
330
- this.failed = 0;
331
- this.results = [];
332
- this.errors = [];
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.errors.push({
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.errors.push({
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.errors.push({
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
- report() {
604
+ _getTime(time) {
605
+ return `${time / 1000} sec`;
606
+ }
607
+ report(detailed) {
382
608
  let time = 0;
383
- for (let r of this.results) {
384
- time += r.time;
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 = 'Finished.' + '\n' + (this.failed > 0 ? `\x1b[${ConsoleColors.ForeColor.Red}m ${this.failed} tests failed` : '0 tests failed') + ConsoleColors.Modifier.Reset + ', ' + (this.passed > 0 ? `\x1b[${ConsoleColors.ForeColor.Green}m ${this.passed} tests passed` : '0 tests passed') + ConsoleColors.Modifier.Reset + '\n' + `Tests: ${this.passed + this.failed}` + '\n' + `Time: ${time / 1000} sec` + '\n';
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(fn) {
71
- if (!isFunction(fn)) {
72
- throw new Exception({ message: `given argument is not a function.`, code: 1012, status: 'no-func' })
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
- fn();
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(fn) {
88
- if (!isFunction(fn)) {
89
- throw new Exception({ message: `given argument is not a function.`, code: 1012, status: 'no-func' })
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 fn();
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(fn) {
105
- if (!isFunction(fn)) {
106
- throw new Exception({ message: `given argument is not a function.`, code: 1012, status: 'no-func' })
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
- fn();
167
+ this.value();
114
168
 
115
169
  ok = false;
116
- } catch (e) { error = 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(fn) {
123
- if (!isFunction(fn)) {
124
- throw new Exception({ message: `given argument is not a function.`, code: 1012, status: 'no-func' })
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 fn();
213
+ await this.value();
132
214
 
133
215
  ok = false;
134
- } catch (e) { error = 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.passed = 0;
193
- this.failed = 0;
194
- this.results = []
195
- this.errors = []
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.errors.push({ err: new Exception({ message: `onProgress failed for test '${test.name} at index ${i}'.`, code: 1500, status: 'progress-failed' }) })
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.results.push(tr);
342
+ this._results.push(tr);
209
343
 
210
344
  if (tr.success) {
211
- this.passed++;
345
+ this._passed++;
212
346
  } else {
213
- this.failed++;
347
+ this._failed++;
214
348
  }
215
349
  }
216
350
  get result() {
217
351
  return {
218
- passed: this.passed,
219
- failed: this.failed,
220
- results: this.results,
221
- errors: this.errors
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.passed = 0;
226
- this.failed = 0;
227
- this.results = [];
228
- this.errors = [];
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.errors.push({ err: new Exception({ message: `not all tests succeeded. check errors.`, code: 1503, status: 'partial-finished', innerException: ex }) });
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.errors.push({ err: new Exception({ message: `invalid tests. expected array or a single test.`, code: 1502, status: 'invalid-tests' }) });
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.errors.push({ err: new Exception({ message: `no tests given to be ran.`, code: 1501, status: 'no-tests' }) });
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
- report() {
403
+ _getTime(time) {
404
+ return `${time / 1000} sec`
405
+ }
406
+ report(detailed) {
270
407
  let time = 0;
271
408
 
272
- for (let r of this.results) {
273
- time += r.time;
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
- const text = 'Finished.' +
277
- '\n' +
278
- (this.failed > 0 ? `\x1b[${ConsoleColors.ForeColor.Red}m ${this.failed} tests failed` : '0 tests failed') + ConsoleColors.Modifier.Reset +
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.passed > 0 ? `\x1b[${ConsoleColors.ForeColor.Green}m ${this.passed} tests passed` : '0 tests passed') + ConsoleColors.Modifier.Reset +
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.passed + this.failed}` +
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locustjs/test",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "This library provides a simple test runner.",
5
5
  "main": "index.cjs.js",
6
6
  "module": "index.esm.js",