@lumjs/tests 1.1.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -1
- package/TODO.md +7 -2
- package/jsdoc.json +33 -0
- package/lib/functional.js +8 -20
- package/lib/harness.js +9 -0
- package/lib/index.js +17 -2
- package/lib/log.js +28 -9
- package/lib/test.js +541 -64
- package/package.json +13 -3
- package/test/basics.js +21 -3
- package/test/data/people.js +58 -0
- package/test/functional_basics.js +25 -4
- package/test/functional_isa.js +23 -0
- package/test/inc/basics.js +32 -2
- package/test/inc/isa.js +110 -0
- package/test/isa.js +23 -0
package/lib/test.js
CHANGED
|
@@ -1,15 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module defining the Test class.
|
|
3
|
+
* @module @lumjs/tests/test
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const core = require('@lumjs/core');
|
|
2
|
-
const
|
|
7
|
+
const types = core.types;
|
|
8
|
+
const {F,S,N,isObj,isArray,needs,def} = types;
|
|
3
9
|
|
|
4
10
|
// We use a separate class to represent test logs.
|
|
5
11
|
const Log = require('./log');
|
|
6
12
|
|
|
13
|
+
// A list of Test methods that return Log objects.
|
|
14
|
+
const TEST_METHODS =
|
|
15
|
+
[
|
|
16
|
+
'ok', 'call', 'fail', 'pass', 'dies', 'diesWith', 'lives', 'cmp',
|
|
17
|
+
'is', 'isnt', 'isa', 'nota', 'isJSON', 'isntJSON', 'skip',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
// A list of other methods to export that are not standard tests.
|
|
21
|
+
const META_METHODS =
|
|
22
|
+
[
|
|
23
|
+
'plan', 'diag', 'run', 'tap', 'output', 'done',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// The function that powers `Test.call()` and friends.
|
|
27
|
+
function $call (testfunc, args)
|
|
28
|
+
{
|
|
29
|
+
const ret = {};
|
|
30
|
+
try
|
|
31
|
+
{
|
|
32
|
+
ret.val = testfunc(...args);
|
|
33
|
+
}
|
|
34
|
+
catch (e)
|
|
35
|
+
{
|
|
36
|
+
ret.err = e;
|
|
37
|
+
}
|
|
38
|
+
return ret;
|
|
39
|
+
}
|
|
40
|
+
|
|
7
41
|
/**
|
|
8
42
|
* A simple testing library with TAP support.
|
|
9
43
|
*
|
|
10
44
|
* Based on Lum.php's Test library.
|
|
11
45
|
* Which itself was based on Perl 5's Test::More, and
|
|
12
46
|
* Raku's Test libraries.
|
|
47
|
+
*
|
|
48
|
+
* @property {number} planned - Number of tests planned, `0` if unplanned.
|
|
49
|
+
* @property {number} failed - Number of tests that failed.
|
|
50
|
+
* @property {number} skipped - Number of tests that were skipped.
|
|
51
|
+
* @property {number} ran - Number of tests ran (*calculated*).
|
|
52
|
+
* @property {string} id - Unique test id used by `Harness` libary.
|
|
53
|
+
* @property {boolean} isTop - Test module was loaded from the command line.
|
|
54
|
+
* @property {?object} harness - The top-level `Harness` if one was found.
|
|
13
55
|
*/
|
|
14
56
|
class Test
|
|
15
57
|
{
|
|
@@ -29,6 +71,9 @@ class Test
|
|
|
29
71
|
* Also, if this is passed, and `opts.id` was not specified, and id
|
|
30
72
|
* will be auto-generated based on the filename of the module.
|
|
31
73
|
*
|
|
74
|
+
* @param {number} [opts.stringify=1] The depth `stringify()` should recurse
|
|
75
|
+
* objects and Arrays before switching to plain JSON stringification.
|
|
76
|
+
*
|
|
32
77
|
*/
|
|
33
78
|
constructor (opts={})
|
|
34
79
|
{
|
|
@@ -56,20 +101,46 @@ class Test
|
|
|
56
101
|
this.id = null;
|
|
57
102
|
}
|
|
58
103
|
|
|
104
|
+
this.stringifyDepth = opts.stringify ?? 1;
|
|
105
|
+
|
|
59
106
|
this.failed = 0;
|
|
60
107
|
this.skipped = 0;
|
|
61
108
|
this.planned = 0;
|
|
62
109
|
this.log = [];
|
|
63
110
|
|
|
111
|
+
// These three will be updated below if possible.
|
|
112
|
+
this.isTop = false;
|
|
113
|
+
this.harness = null;
|
|
114
|
+
|
|
64
115
|
if (typeof opts.plan === N)
|
|
65
116
|
{
|
|
66
117
|
this.plan(opts.plan);
|
|
67
118
|
}
|
|
68
119
|
|
|
69
120
|
if (hasModule)
|
|
70
|
-
{ //
|
|
121
|
+
{ // If a module was passed, its going to export this test.
|
|
71
122
|
opts.module.exports = this;
|
|
123
|
+
// We'll also use the module to determine if we're Harnessed or not.
|
|
124
|
+
if (require.main === opts.module)
|
|
125
|
+
{ // Was called directly.
|
|
126
|
+
this.isTop = true;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!this.isTop)
|
|
131
|
+
{ // Try to find a Harness instance.
|
|
132
|
+
if (isObj(require.main) && require.main.exports instanceof Harness)
|
|
133
|
+
{ // We found the Harness instance.
|
|
134
|
+
this.harness = require.main.exports;
|
|
135
|
+
}
|
|
72
136
|
}
|
|
137
|
+
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// A wrapper around types.stringify()
|
|
141
|
+
stringify(what)
|
|
142
|
+
{
|
|
143
|
+
return types.stringify(what, this.stringifyDepth);
|
|
73
144
|
}
|
|
74
145
|
|
|
75
146
|
/**
|
|
@@ -106,15 +177,14 @@ class Test
|
|
|
106
177
|
* If the value evaluates as `true` (aka a truthy value), the test passes.
|
|
107
178
|
* If it evaluates as `false` (a falsey value), the test fails.
|
|
108
179
|
*
|
|
109
|
-
* @param {string} desc
|
|
110
|
-
*
|
|
180
|
+
* @param {string} [desc] A short description of the test.
|
|
111
181
|
* @param {(string|Error)} [directive] Further information for the log.
|
|
112
|
-
*
|
|
182
|
+
* @param {object} [details] Extra details to add to the log.
|
|
113
183
|
* @returns {Log} The test log with the results.
|
|
114
184
|
*/
|
|
115
|
-
ok (test, desc, directive)
|
|
185
|
+
ok (test, desc, directive, details)
|
|
116
186
|
{
|
|
117
|
-
const log = new Log();
|
|
187
|
+
const log = new Log(this);
|
|
118
188
|
|
|
119
189
|
if (test)
|
|
120
190
|
{
|
|
@@ -135,11 +205,34 @@ class Test
|
|
|
135
205
|
log.directive = directive;
|
|
136
206
|
}
|
|
137
207
|
|
|
208
|
+
if (isObj(details))
|
|
209
|
+
{
|
|
210
|
+
log.details = details;
|
|
211
|
+
}
|
|
212
|
+
|
|
138
213
|
this.log.push(log);
|
|
139
214
|
|
|
140
215
|
return log;
|
|
141
216
|
}
|
|
142
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Run a function and pass its return value to `ok()`.
|
|
220
|
+
*
|
|
221
|
+
* @param {function} testfunc - The function to run.
|
|
222
|
+
* If the function returns, the return value will be passed to `ok()`.
|
|
223
|
+
* If it throws an Error, the test will be marked as failed, and the
|
|
224
|
+
* Error will be passed as the `directive` to the `ok()` method.
|
|
225
|
+
*
|
|
226
|
+
* @param {string} [desc] A description for `ok()`.
|
|
227
|
+
* @param {...any} [args] Arguments to pass to the test function.
|
|
228
|
+
* @returns {Log}
|
|
229
|
+
*/
|
|
230
|
+
call (testfunc, desc, ...args)
|
|
231
|
+
{
|
|
232
|
+
const ret = $call(testfunc, args);
|
|
233
|
+
return this.ok(ret.val, desc, ret.err);
|
|
234
|
+
}
|
|
235
|
+
|
|
143
236
|
/**
|
|
144
237
|
* Mark a test as failed.
|
|
145
238
|
*
|
|
@@ -174,20 +267,91 @@ class Test
|
|
|
174
267
|
* call. If no error is caught the test will be considered to have failed.
|
|
175
268
|
*
|
|
176
269
|
* @param {function} testfunc
|
|
177
|
-
* @param {string} desc
|
|
270
|
+
* @param {string} [desc]
|
|
271
|
+
* @param {...any} [args]
|
|
178
272
|
* @returns {Log}
|
|
179
273
|
*/
|
|
180
|
-
dies (testfunc, desc)
|
|
274
|
+
dies (testfunc, desc, ...args)
|
|
181
275
|
{
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
276
|
+
const ret = $call(testfunc, args);
|
|
277
|
+
return this.ok(('err' in ret), desc, ret.err);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* See if a function throws an Error, and if the Error passes a second test.
|
|
282
|
+
*
|
|
283
|
+
* All the notes that apply to `dies()` apply here as well.
|
|
284
|
+
*
|
|
285
|
+
* @param {function} testfunc - Same as `dies()` and `call()`
|
|
286
|
+
* We don't care about the return value of this function.
|
|
287
|
+
* We only care that it *should* throw an Error.
|
|
288
|
+
*
|
|
289
|
+
* @param {(function|string)} testerr - A test to validate the Error.
|
|
290
|
+
*
|
|
291
|
+
* If this is a `function` it will be passed the thrown `Error` as the first
|
|
292
|
+
* parameter, and an `Array` of `args` as the second parameter. The return
|
|
293
|
+
* value from this will be passed to `ok()`.
|
|
294
|
+
*
|
|
295
|
+
* If this is a `string` then the test will pass only if the either the
|
|
296
|
+
* `Error.name` or `Error.message` is exactly equal to this value.
|
|
297
|
+
*
|
|
298
|
+
* @param {string} [desc]
|
|
299
|
+
* @param {...any} [args]
|
|
300
|
+
* @returns {Log}
|
|
301
|
+
*/
|
|
302
|
+
diesWith (testfunc, testerr, desc, ...args)
|
|
303
|
+
{
|
|
304
|
+
let ok = false, details = {}, err = null;
|
|
305
|
+
|
|
306
|
+
const r1 = $call(testfunc, args);
|
|
307
|
+
|
|
308
|
+
if ('err' in r1)
|
|
186
309
|
{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
310
|
+
err = r1.err;
|
|
311
|
+
|
|
312
|
+
if (typeof testerr === F)
|
|
313
|
+
{ // A secondary function to test the error with.
|
|
314
|
+
const r2 = $call(testerr, [err, args]);
|
|
315
|
+
if ('err' in r2)
|
|
316
|
+
{ // Second function threw an error, add it as diagnostic info.
|
|
317
|
+
details.info = r2.err;
|
|
318
|
+
}
|
|
319
|
+
else
|
|
320
|
+
{ // No error, use the function output as the test value.
|
|
321
|
+
ok = r2.val;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
else if (typeof testerr === S)
|
|
325
|
+
{ // A simple name/message test.
|
|
326
|
+
if (err.name === testerr || err.message === testerr)
|
|
327
|
+
{ // Either the name or the message matched the string.
|
|
328
|
+
ok = true;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
} // if r1.err
|
|
333
|
+
|
|
334
|
+
return this.ok(ok, desc, err, details);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* See if a function runs without throwing an Error.
|
|
339
|
+
*
|
|
340
|
+
* The function will be called in a `try { } catch (err) { }` block.
|
|
341
|
+
*
|
|
342
|
+
* If an error is caught, the test will be considered to have failed,
|
|
343
|
+
* and the `Error` object will be used as the `directive` in the `ok()`
|
|
344
|
+
* call. If no error is caught the test will be considered to have passed.
|
|
345
|
+
*
|
|
346
|
+
* @param {function} testfunc
|
|
347
|
+
* @param {string} [desc]
|
|
348
|
+
* @param {...any} [args]
|
|
349
|
+
* @returns {Log}
|
|
350
|
+
*/
|
|
351
|
+
lives (testfunc, desc, ...args)
|
|
352
|
+
{
|
|
353
|
+
const ret = $call(testfunc, args);
|
|
354
|
+
return this.ok(!('err' in ret), desc, ret.err);
|
|
191
355
|
}
|
|
192
356
|
|
|
193
357
|
/**
|
|
@@ -198,15 +362,22 @@ class Test
|
|
|
198
362
|
* @param {string} comp - A comparitor to test with.
|
|
199
363
|
*
|
|
200
364
|
* - `===`, `is` (See also: `is()`)
|
|
201
|
-
* - `!==`, `isnt`
|
|
365
|
+
* - `!==`, `isnt`, `not` (See also: `isnt()`)
|
|
202
366
|
* - `==`, `eq`
|
|
203
367
|
* - `!=`, `ne`
|
|
204
368
|
* - `>`, `gt`
|
|
205
369
|
* - `<`, `lt`
|
|
206
370
|
* - `>=`, `ge`, `gte`
|
|
207
371
|
* - `<=`, `le`, `lte`
|
|
372
|
+
*
|
|
373
|
+
* A few special comparitors for *binary flag* testing:
|
|
374
|
+
*
|
|
375
|
+
* - `=&` → `((got & want) === want)`
|
|
376
|
+
* - `!&` → `((got & want) !== want)`
|
|
377
|
+
* - `+&` → `((got & want) !== 0)`
|
|
378
|
+
* - `-&` → `((got & want) === 0)`
|
|
208
379
|
*
|
|
209
|
-
* @param {string} desc
|
|
380
|
+
* @param {string} [desc]
|
|
210
381
|
* @param {boolean} [stringify=true] Stringify values in TAP output?
|
|
211
382
|
* @returns {Log}
|
|
212
383
|
*/
|
|
@@ -221,6 +392,7 @@ class Test
|
|
|
221
392
|
break;
|
|
222
393
|
case 'isnt':
|
|
223
394
|
case '!==':
|
|
395
|
+
case 'not':
|
|
224
396
|
test = (got !== want);
|
|
225
397
|
break;
|
|
226
398
|
case 'eq':
|
|
@@ -249,19 +421,66 @@ class Test
|
|
|
249
421
|
case '>=':
|
|
250
422
|
test = (got >= want);
|
|
251
423
|
break;
|
|
424
|
+
case '=&':
|
|
425
|
+
test = ((got&want)===want);
|
|
426
|
+
break;
|
|
427
|
+
case '!&':
|
|
428
|
+
test = ((got&want)!==want)
|
|
429
|
+
case '+&':
|
|
430
|
+
test = ((got&want)!==0);
|
|
431
|
+
break;
|
|
432
|
+
case '-&':
|
|
433
|
+
test = ((got&want)===0);
|
|
252
434
|
default:
|
|
253
435
|
test = false;
|
|
254
436
|
}
|
|
255
437
|
|
|
256
|
-
|
|
438
|
+
let details = null;
|
|
257
439
|
if (!test)
|
|
258
|
-
{
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
440
|
+
{ // The test failed, add the deets.
|
|
441
|
+
details =
|
|
442
|
+
{
|
|
443
|
+
got,
|
|
444
|
+
wanted: want,
|
|
445
|
+
stringify,
|
|
446
|
+
comparitor: comp,
|
|
447
|
+
};
|
|
263
448
|
}
|
|
264
|
-
|
|
449
|
+
|
|
450
|
+
return this.ok(test, desc, null, details);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* See if a string matches a value.
|
|
455
|
+
*
|
|
456
|
+
* @param {string} got
|
|
457
|
+
* @param {RegExp} want
|
|
458
|
+
* @param {string} [desc]
|
|
459
|
+
* @param {boolean} [stringify=true]
|
|
460
|
+
* @returns {Log}
|
|
461
|
+
*/
|
|
462
|
+
matches(got, want, desc, stringify=true)
|
|
463
|
+
{
|
|
464
|
+
const no = {error: "matches 'got' value must be a string"};
|
|
465
|
+
needs(got, no, S);
|
|
466
|
+
no.error = "matches 'want' value must be a RegExp";
|
|
467
|
+
needs(want, no, RegExp);
|
|
468
|
+
|
|
469
|
+
const test = want.test(got);
|
|
470
|
+
|
|
471
|
+
let details = null;
|
|
472
|
+
if (!test)
|
|
473
|
+
{ // The test failed, add the deets.
|
|
474
|
+
details =
|
|
475
|
+
{
|
|
476
|
+
got,
|
|
477
|
+
wanted: want,
|
|
478
|
+
stringify,
|
|
479
|
+
comparitor: 'matches',
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return this.ok(test, desc, null, details);
|
|
265
484
|
}
|
|
266
485
|
|
|
267
486
|
/**
|
|
@@ -271,7 +490,7 @@ class Test
|
|
|
271
490
|
*
|
|
272
491
|
* @param {*} got
|
|
273
492
|
* @param {*} want
|
|
274
|
-
* @param {string} desc
|
|
493
|
+
* @param {string} [desc]
|
|
275
494
|
* @param {boolean} [stringify=true]
|
|
276
495
|
* @returns {Log}
|
|
277
496
|
*/
|
|
@@ -287,8 +506,8 @@ class Test
|
|
|
287
506
|
*
|
|
288
507
|
* @param {*} got - The result value from the test function.
|
|
289
508
|
* @param {*} want - The value we expected from the test function.
|
|
290
|
-
* @param {string} desc
|
|
291
|
-
* @param {boolean} [stringify=true]
|
|
509
|
+
* @param {string} [desc]
|
|
510
|
+
* @param {boolean} [stringify=true]
|
|
292
511
|
* @returns {Log}
|
|
293
512
|
*/
|
|
294
513
|
isnt (got, want, desc, stringify=true)
|
|
@@ -300,57 +519,176 @@ class Test
|
|
|
300
519
|
* See if a value is of a certain type.
|
|
301
520
|
*
|
|
302
521
|
* @param {*} got
|
|
303
|
-
* @param {
|
|
522
|
+
* @param {Array} wants - Type names or constructor functions.
|
|
304
523
|
*
|
|
305
|
-
*
|
|
306
|
-
* If this is a string, we use the `isType()` function.
|
|
307
|
-
* If this is a constructor function, we use the `isInstance()` function.
|
|
524
|
+
* Uses `@lumjs/core/types.isa()` to perform the test.
|
|
308
525
|
*
|
|
309
|
-
* @param {string} desc
|
|
526
|
+
* @param {string} [desc]
|
|
310
527
|
* @param {boolean} [stringify=true]
|
|
311
528
|
* @returns {Log}
|
|
312
529
|
*/
|
|
313
|
-
isa (got,
|
|
530
|
+
isa (got, wants, desc, stringify=true, not=false)
|
|
314
531
|
{
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
test = isType(want, got);
|
|
319
|
-
}
|
|
320
|
-
else if (typeof want === F)
|
|
321
|
-
{ // Assuming it's a constructor.
|
|
322
|
-
test = isInstance(got, want);
|
|
532
|
+
if (!isArray(wants))
|
|
533
|
+
{
|
|
534
|
+
wants = [wants];
|
|
323
535
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
536
|
+
let res = types.isa(got, ...wants);
|
|
537
|
+
if (not)
|
|
538
|
+
{ // Inverse the result.
|
|
539
|
+
res = !res;
|
|
327
540
|
}
|
|
328
541
|
|
|
329
|
-
|
|
330
|
-
if (!
|
|
331
|
-
{
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
542
|
+
let details = null;
|
|
543
|
+
if (!res)
|
|
544
|
+
{ // The test failed, add the deets.
|
|
545
|
+
details =
|
|
546
|
+
{
|
|
547
|
+
got,
|
|
548
|
+
wanted: wants,
|
|
549
|
+
stringify,
|
|
550
|
+
comparitor: not ? 'nota()' : 'isa()',
|
|
551
|
+
};
|
|
335
552
|
}
|
|
336
|
-
|
|
553
|
+
|
|
554
|
+
return this.ok(res, desc, null, details);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* See if a value is NOT of a certain type.
|
|
559
|
+
*
|
|
560
|
+
* Just inverses the results of `isa()`.
|
|
561
|
+
*
|
|
562
|
+
* @param {*} got
|
|
563
|
+
* @param {Array} wants - Type names of constructor functions.
|
|
564
|
+
* @param {string} [desc]
|
|
565
|
+
* @param {boolean} [stringify=true]
|
|
566
|
+
* @returns {Log}
|
|
567
|
+
*/
|
|
568
|
+
nota (got, wants, desc, stringify=true)
|
|
569
|
+
{
|
|
570
|
+
return this.isa(got, wants, desc, stringify, true);
|
|
337
571
|
}
|
|
338
572
|
|
|
339
573
|
/**
|
|
340
574
|
* An `is()` test, but encode both values as JSON first.
|
|
341
575
|
*
|
|
576
|
+
* Actually uses `core.types.stringify()` so it supports more
|
|
577
|
+
* data types than standard JSON, and can stringify functions,
|
|
578
|
+
* symbols, and several extended object types.
|
|
579
|
+
*
|
|
342
580
|
* @param {*} got
|
|
343
581
|
* @param {*} want
|
|
344
|
-
* @param {string} desc
|
|
345
|
-
* @returns
|
|
582
|
+
* @param {string} [desc]
|
|
583
|
+
* @returns {Log}
|
|
346
584
|
*/
|
|
347
585
|
isJSON (got, want, desc)
|
|
348
586
|
{
|
|
349
|
-
got =
|
|
350
|
-
want =
|
|
587
|
+
got = this.stringify(got);
|
|
588
|
+
want = this.stringify(want);
|
|
351
589
|
return this.is(got, want, desc, false);
|
|
352
590
|
}
|
|
353
591
|
|
|
592
|
+
/**
|
|
593
|
+
* An `isnt()` test, but encode both values as JSON first.
|
|
594
|
+
*
|
|
595
|
+
* Like `isJSON()` this uses `core.types.stringify()`.
|
|
596
|
+
*
|
|
597
|
+
* @param {*} got
|
|
598
|
+
* @param {*} want
|
|
599
|
+
* @param {string} [desc]
|
|
600
|
+
* @returns {Log}
|
|
601
|
+
*/
|
|
602
|
+
isntJSON (got, want, desc)
|
|
603
|
+
{
|
|
604
|
+
got = this.stringify(got);
|
|
605
|
+
want = this.stringify(want);
|
|
606
|
+
return this.isnt(got, want, desc, false);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* Run a function and see if it's return value is what we wanted.
|
|
611
|
+
*
|
|
612
|
+
* @param {function} testfunc - The function to run.
|
|
613
|
+
* The return value will be passed to `cmp()` or another appropriate
|
|
614
|
+
* testing method as determined by the options.
|
|
615
|
+
* How this handles error handling is determined by options as well.
|
|
616
|
+
*
|
|
617
|
+
* @param {*} want - The value we want.
|
|
618
|
+
* @param {(object|string)} [opts] Named options for further behaviour.
|
|
619
|
+
* If it is a string it's considered the `opts.desc` option.
|
|
620
|
+
* @param {string} [opts.desc] A description for `ok()`.
|
|
621
|
+
* @param {boolean} [opts.stringify=true]
|
|
622
|
+
* @param {Array} [opts.args] Arguments to pass to the test function.
|
|
623
|
+
* @param {string} [opts.comp="is"] - The comparitor to test with.
|
|
624
|
+
* In addition to all of the comparitors from `cmp()`, there are a few
|
|
625
|
+
* extra comparitors that will pass through to other methods:
|
|
626
|
+
* - `isa` → Use `isa()` to test return value.
|
|
627
|
+
* - `nota` → Use `nota()` to test return value.
|
|
628
|
+
* - `=json`, `isJSON` → Use `isJSON()` to test return value.
|
|
629
|
+
* - `!json`, `isntJSON` → Use `isntJSON()` to test return value.
|
|
630
|
+
* - `matches` → Use `matches()` to test return value.
|
|
631
|
+
*
|
|
632
|
+
* @param {boolean} [opts.thrown=false] How to handle thrown errors.
|
|
633
|
+
*
|
|
634
|
+
* If this is `true`, then anything thrown will be passed as if it was
|
|
635
|
+
* the return value from the function.
|
|
636
|
+
*
|
|
637
|
+
* If this is `false`, then any errors thrown will result in an immediate
|
|
638
|
+
* failure of the test without any further processing, and the error will
|
|
639
|
+
* be passed as the `directive` to the `ok()` method.
|
|
640
|
+
*
|
|
641
|
+
* @returns {Log}
|
|
642
|
+
*/
|
|
643
|
+
callIs (testfunc, want, opts={})
|
|
644
|
+
{
|
|
645
|
+
const args = opts.args ?? [];
|
|
646
|
+
const ret = $call(testfunc, args);
|
|
647
|
+
const desc = opts.desc;
|
|
648
|
+
|
|
649
|
+
let got;
|
|
650
|
+
|
|
651
|
+
if (ret.err)
|
|
652
|
+
{ // How to handle errors.
|
|
653
|
+
if (opts.thrown)
|
|
654
|
+
{ // We're going to test the error.
|
|
655
|
+
got = ret.err;
|
|
656
|
+
}
|
|
657
|
+
else
|
|
658
|
+
{ // This is an automatic failure.
|
|
659
|
+
return this.ok(false, desc, ret.err);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
else
|
|
663
|
+
{ // No errors, good, testing against the return value.
|
|
664
|
+
got = ret.val;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
const CFUN =
|
|
668
|
+
{
|
|
669
|
+
'matches': 'matches',
|
|
670
|
+
'isa': 'isa',
|
|
671
|
+
'nota': 'nota',
|
|
672
|
+
'isJSON': 'isJSON',
|
|
673
|
+
'=json': 'isJSON',
|
|
674
|
+
'isntJSON': 'isntJSON',
|
|
675
|
+
'!json': 'isntJSON',
|
|
676
|
+
};
|
|
677
|
+
|
|
678
|
+
const comp = opts.comp ?? 'is';
|
|
679
|
+
const stringify = opts.stringify ?? true;
|
|
680
|
+
|
|
681
|
+
if (typeof CFUN[comp] === S)
|
|
682
|
+
{ // A function with a custom return value.
|
|
683
|
+
const meth = CFUN[comp];
|
|
684
|
+
return this[meth](got, want, desc, stringify);
|
|
685
|
+
}
|
|
686
|
+
else
|
|
687
|
+
{ // We're going to use the cmp() method.
|
|
688
|
+
return this.cmp(got, want, comp, desc, stringify);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
354
692
|
/**
|
|
355
693
|
* Skip a test.
|
|
356
694
|
*
|
|
@@ -360,7 +698,7 @@ class Test
|
|
|
360
698
|
*/
|
|
361
699
|
skip (reason, desc)
|
|
362
700
|
{
|
|
363
|
-
|
|
701
|
+
const log = this.ok(true, desc);
|
|
364
702
|
log.skipped = true;
|
|
365
703
|
if (typeof reason === S)
|
|
366
704
|
log.skippedReason = reason;
|
|
@@ -381,6 +719,78 @@ class Test
|
|
|
381
719
|
this.log.push(msg);
|
|
382
720
|
}
|
|
383
721
|
|
|
722
|
+
/**
|
|
723
|
+
* Run an assortment of tests using a map.
|
|
724
|
+
*
|
|
725
|
+
* The *current* test method defaults to `ok`.
|
|
726
|
+
*
|
|
727
|
+
* @param {...any} tests - The tests we're running.
|
|
728
|
+
*
|
|
729
|
+
* If this is a `string`, and is the name of one of the standard testing
|
|
730
|
+
* methods in this class, it will be set as the *current* test method.
|
|
731
|
+
*
|
|
732
|
+
* If this is a `function`, it will be set as the *current* test method.
|
|
733
|
+
* By default function test methods are passed to `call()` with the test
|
|
734
|
+
* parameters. However, if the *previous* test method was `callIs` then
|
|
735
|
+
* the `callIs()` method will be used as long as the custom function is
|
|
736
|
+
* the *current* test method. Likewise to switch back to `call()` simply
|
|
737
|
+
* set the *current* test method to `call` before setting it to a new custom
|
|
738
|
+
* test `function`.
|
|
739
|
+
*
|
|
740
|
+
* If this is an `Array` then it's the parameters for the *current* test
|
|
741
|
+
* method. If a custom `function` is in use, remember that it's the
|
|
742
|
+
* `call()` or `callIs()` methods that will be being called, with their
|
|
743
|
+
* first parameter always being the custom function.
|
|
744
|
+
*
|
|
745
|
+
* Any value other than one of those will throw a `TypeError`.
|
|
746
|
+
*
|
|
747
|
+
* @returns {Log[]} A `Log` item for each test that was ran.
|
|
748
|
+
*/
|
|
749
|
+
run (...tests)
|
|
750
|
+
{
|
|
751
|
+
const CF = 'call';
|
|
752
|
+
const CI = 'callIs';
|
|
753
|
+
|
|
754
|
+
const logs = [];
|
|
755
|
+
let funcall = CF;
|
|
756
|
+
let current = 'ok';
|
|
757
|
+
|
|
758
|
+
for (const test of tests)
|
|
759
|
+
{
|
|
760
|
+
const tt = typeof test;
|
|
761
|
+
if (tt === S && TEST_METHODS.includes(test))
|
|
762
|
+
{ // Set the current test to a built-in.
|
|
763
|
+
current = test;
|
|
764
|
+
}
|
|
765
|
+
else if (tt === F)
|
|
766
|
+
{ // A custom test function for further tests.
|
|
767
|
+
if (current === CI)
|
|
768
|
+
{ // Last test was `callIs` using that for the custom function.
|
|
769
|
+
funcall = CI;
|
|
770
|
+
}
|
|
771
|
+
else if (current === CF)
|
|
772
|
+
{ // Last test was `call`, using that for the custom function.
|
|
773
|
+
funcall = CF;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
else if (isArray(test))
|
|
777
|
+
{ // A set of test parameters.
|
|
778
|
+
let log;
|
|
779
|
+
if (typeof current === F)
|
|
780
|
+
{ // A custom test function is in use.
|
|
781
|
+
log = this[funcall](current, ...test);
|
|
782
|
+
}
|
|
783
|
+
else
|
|
784
|
+
{ // A standard test is in use.
|
|
785
|
+
log = this[current](...test);
|
|
786
|
+
}
|
|
787
|
+
logs.push(log);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
return logs;
|
|
792
|
+
}
|
|
793
|
+
|
|
384
794
|
/**
|
|
385
795
|
* Return TAP formatted output for all the tests.
|
|
386
796
|
*
|
|
@@ -388,22 +798,21 @@ class Test
|
|
|
388
798
|
*/
|
|
389
799
|
tap ()
|
|
390
800
|
{
|
|
391
|
-
|
|
801
|
+
let out = '';
|
|
392
802
|
if (this.planned > 0)
|
|
393
803
|
{
|
|
394
804
|
out += '1..'+this.planned+"\n";
|
|
395
805
|
}
|
|
396
|
-
|
|
397
|
-
for (
|
|
806
|
+
let t = 1;
|
|
807
|
+
for (const log of this.log)
|
|
398
808
|
{
|
|
399
|
-
var log = this.log[i];
|
|
400
809
|
if (log instanceof Log)
|
|
401
810
|
{
|
|
402
811
|
out += log.tap(t++);
|
|
403
812
|
}
|
|
404
813
|
else
|
|
405
814
|
{ // A comment.
|
|
406
|
-
out += '# ' + (typeof log === S ? log :
|
|
815
|
+
out += '# ' + (typeof log === S ? log : types.stringify(log)) + "\n";
|
|
407
816
|
}
|
|
408
817
|
}
|
|
409
818
|
if (this.skipped)
|
|
@@ -417,7 +826,7 @@ class Test
|
|
|
417
826
|
out += ' out of '+this.planned;
|
|
418
827
|
out += "\n";
|
|
419
828
|
}
|
|
420
|
-
|
|
829
|
+
const ran = t-1;
|
|
421
830
|
if (this.planned > 0 && this.planned != ran)
|
|
422
831
|
{
|
|
423
832
|
out += '# Looks like you planned '+this.planned+' but ran '+ran+" tests\n";
|
|
@@ -425,8 +834,28 @@ class Test
|
|
|
425
834
|
return out;
|
|
426
835
|
}
|
|
427
836
|
|
|
837
|
+
/**
|
|
838
|
+
* A calculated property of the number of tests that were ran.
|
|
839
|
+
* @type {int}
|
|
840
|
+
*/
|
|
841
|
+
get ran ()
|
|
842
|
+
{
|
|
843
|
+
let ran = 0;
|
|
844
|
+
for (const log of this.log)
|
|
845
|
+
{
|
|
846
|
+
if (log instanceof Log)
|
|
847
|
+
{
|
|
848
|
+
ran++;
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return ran;
|
|
852
|
+
}
|
|
853
|
+
|
|
428
854
|
/**
|
|
429
855
|
* Send the TAP output to the `console`.
|
|
856
|
+
*
|
|
857
|
+
* This is a low-level method and is no longer recommended for use.
|
|
858
|
+
* Instead call the `done()` method, which will *do the right thing*.
|
|
430
859
|
*/
|
|
431
860
|
output ()
|
|
432
861
|
{
|
|
@@ -434,10 +863,58 @@ class Test
|
|
|
434
863
|
return this;
|
|
435
864
|
}
|
|
436
865
|
|
|
866
|
+
/**
|
|
867
|
+
* We're done testing.
|
|
868
|
+
*
|
|
869
|
+
* This will mark the test-set as finished, so attempting to run further
|
|
870
|
+
* tests after will result in a `RangeError` being thrown.
|
|
871
|
+
*
|
|
872
|
+
* If no `Harness` is in use, this will also run `this.output()`.
|
|
873
|
+
*/
|
|
874
|
+
done ()
|
|
875
|
+
{
|
|
876
|
+
if (this.$done)
|
|
877
|
+
{
|
|
878
|
+
throw new RangeError('Test set is already done');
|
|
879
|
+
}
|
|
880
|
+
this.$done = true;
|
|
881
|
+
|
|
882
|
+
return (this.harness ? this : this.output());
|
|
883
|
+
}
|
|
884
|
+
|
|
437
885
|
} // class Test
|
|
438
886
|
|
|
439
887
|
// Should never need this, but...
|
|
440
888
|
def(Test, 'Log', Log);
|
|
441
889
|
|
|
890
|
+
// Probably don't need this either, but...
|
|
891
|
+
def(Test, '$call', $call);
|
|
892
|
+
|
|
893
|
+
// Methods we're exporting for the 'functional' API.
|
|
894
|
+
def(Test, '$METHODS',
|
|
895
|
+
{
|
|
896
|
+
test: TEST_METHODS,
|
|
897
|
+
meta: META_METHODS,
|
|
898
|
+
get all()
|
|
899
|
+
{
|
|
900
|
+
const list = [];
|
|
901
|
+
for (const name in this)
|
|
902
|
+
{
|
|
903
|
+
if (name === 'all') continue;
|
|
904
|
+
const prop = this[name];
|
|
905
|
+
if (isArray(prop))
|
|
906
|
+
{
|
|
907
|
+
list.push(...prop);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
return list;
|
|
911
|
+
},
|
|
912
|
+
});
|
|
913
|
+
|
|
442
914
|
// Export the class
|
|
443
915
|
module.exports = Test;
|
|
916
|
+
|
|
917
|
+
// Finally at the bottom after `module.exports` has been set, we will load
|
|
918
|
+
// the Harness class to avoid circular references failing.
|
|
919
|
+
const Harness = require('./harness');
|
|
920
|
+
|