@lumjs/tests 1.1.1 → 1.3.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 +28 -1
- package/TODO.md +5 -2
- package/lib/functional.js +1 -19
- package/lib/log.js +23 -8
- package/lib/test.js +290 -52
- package/package.json +1 -1
- package/test/basics.js +21 -3
- 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/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.3.0] - 2022-07-27
|
|
10
|
+
### Added
|
|
11
|
+
- `$call()` function; powers `call()`, `lives()`, `dies()`, and `diesWith()`.
|
|
12
|
+
- `call()` method; test the result of a function call.
|
|
13
|
+
- `diesWith()` method; like `dies()` but also tests the thrown `Error`.
|
|
14
|
+
- `run()` method; for running a bunch of similar tests with a compact syntax.
|
|
15
|
+
### Changed
|
|
16
|
+
- How `functional` gets its list of test methods to proxy.
|
|
17
|
+
- Cleaned up a bunch of stuff in the `Log` class.
|
|
18
|
+
- Added a `details.info` structure to the `Log` class.
|
|
19
|
+
- Refactored `lives()` and `dies()` to use the new `$call()` function.
|
|
20
|
+
- Updated `ok()` to support passing `details` to it directly.
|
|
21
|
+
- Some further cleanups in the `Test` class.
|
|
22
|
+
|
|
23
|
+
## [1.2.0] - 2022-07-26
|
|
24
|
+
### Added
|
|
25
|
+
- `lives()` method; the inverse of `dies()`.
|
|
26
|
+
- `nota()` method; the inverse of `isa()`.
|
|
27
|
+
- `isntJSON()` method; the inverse of `isJSON()`.
|
|
28
|
+
- Tests for `isa()`, `isJSON()`, and all the new methods.
|
|
29
|
+
### Changed
|
|
30
|
+
- `isa()` uses `core.types.isa()` method now.
|
|
31
|
+
- `isa()` supports multiple `wants` values, just use an Array.
|
|
32
|
+
- Anything that used `JSON.stringify()` now uses `core.types.stringify()`.
|
|
33
|
+
|
|
9
34
|
## [1.1.1] - 2022-07-15
|
|
10
35
|
### Fixed
|
|
11
36
|
- Dependency issue in `package.json` with pre-release parent.
|
|
@@ -32,7 +57,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
32
57
|
- Ported from Lum.js v4 library set.
|
|
33
58
|
- Added a few more features from the PHP version.
|
|
34
59
|
|
|
35
|
-
[Unreleased]: https://github.com/supernovus/lum.tests.js/compare/v1.
|
|
60
|
+
[Unreleased]: https://github.com/supernovus/lum.tests.js/compare/v1.3.0...HEAD
|
|
61
|
+
[1.3.0]: https://github.com/supernovus/lum.tests.js/compare/v1.2.0...v1.3.0
|
|
62
|
+
[1.2.0]: https://github.com/supernovus/lum.tests.js/compare/v1.1.1...v1.2.0
|
|
36
63
|
[1.1.1]: https://github.com/supernovus/lum.tests.js/compare/v1.1.0...v1.1.1
|
|
37
64
|
[1.1.0]: https://github.com/supernovus/lum.tests.js/compare/v1.0.0...v1.1.0
|
|
38
65
|
[1.0.0]: https://github.com/supernovus/lum.tests.js/releases/tag/v1.0.0
|
package/TODO.md
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# TODO
|
|
2
2
|
|
|
3
|
-
-
|
|
3
|
+
- Write tests for:
|
|
4
|
+
- `call()`
|
|
5
|
+
- `diesWith()`
|
|
6
|
+
- `run()`
|
|
4
7
|
- Write the `Harness` class, based of the Lum.php version.
|
|
5
8
|
- Add tests for `Harness`.
|
|
6
|
-
- Create
|
|
9
|
+
- Create `lumtest` binary for running the harness without `prove` utility.
|
|
7
10
|
|
package/lib/functional.js
CHANGED
|
@@ -2,11 +2,7 @@
|
|
|
2
2
|
const Test = require('./test');
|
|
3
3
|
|
|
4
4
|
// A list of methods we can proxy directly.
|
|
5
|
-
const PROXY_METHODS =
|
|
6
|
-
[
|
|
7
|
-
'plan', 'ok', 'fail', 'pass', 'dies', 'cmp', 'isa', 'is', 'isnt',
|
|
8
|
-
'isJSON', 'skip', 'diag', 'tap', 'output',
|
|
9
|
-
];
|
|
5
|
+
const PROXY_METHODS = Test.$METHODS.all;
|
|
10
6
|
|
|
11
7
|
/**
|
|
12
8
|
* A new test instance and a set of functions wrapping it.
|
|
@@ -16,20 +12,6 @@ const PROXY_METHODS =
|
|
|
16
12
|
* All of the rest of the properties are functions that
|
|
17
13
|
* can be imported into a JS scope using destructuring, and
|
|
18
14
|
* which wrap the corresponding test instance method.
|
|
19
|
-
*
|
|
20
|
-
* @property {function} plan
|
|
21
|
-
* @property {function} ok
|
|
22
|
-
* @property {function} fail
|
|
23
|
-
* @property {function} pass
|
|
24
|
-
* @property {function} dies
|
|
25
|
-
* @property {function} cmp
|
|
26
|
-
* @property {function} isa
|
|
27
|
-
* @property {function} is
|
|
28
|
-
* @property {function} isnt
|
|
29
|
-
* @property {function} isJSON
|
|
30
|
-
* @property {function} skip
|
|
31
|
-
* @property {function} diag
|
|
32
|
-
* @property {function} tap
|
|
33
15
|
*/
|
|
34
16
|
|
|
35
17
|
/**
|
package/lib/log.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
const
|
|
2
|
+
const types = require('@lumjs/core').types;
|
|
3
|
+
const {F,S,O,isArray,stringify} = types;
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* A log representing the results of a test.
|
|
@@ -34,7 +35,7 @@ class Log
|
|
|
34
35
|
* Generally this is never needed to be used by outside code.
|
|
35
36
|
* The `Test.tap()` method uses it when generating a full log.
|
|
36
37
|
*
|
|
37
|
-
* @param {*} num - The test
|
|
38
|
+
* @param {*} num - The test number.
|
|
38
39
|
* @returns {string} The TAP log.
|
|
39
40
|
*/
|
|
40
41
|
tap (num)
|
|
@@ -53,7 +54,7 @@ class Log
|
|
|
53
54
|
if (typeof this.directive === S)
|
|
54
55
|
out += ' # ' + this.directive;
|
|
55
56
|
else if (typeof this.directive == O && this.directive instanceof Error)
|
|
56
|
-
out +=
|
|
57
|
+
out += ` # ${this.directive.name}: ${this.directive.message}`;
|
|
57
58
|
else if (this.skipped)
|
|
58
59
|
out += ' # SKIP ' + this.skippedReason;
|
|
59
60
|
|
|
@@ -65,14 +66,28 @@ class Log
|
|
|
65
66
|
var want = this.details.wanted;
|
|
66
67
|
if (this.details.stringify)
|
|
67
68
|
{
|
|
68
|
-
got =
|
|
69
|
-
want =
|
|
69
|
+
got = stringify(got);
|
|
70
|
+
want = stringify(want);
|
|
70
71
|
}
|
|
71
|
-
out +=
|
|
72
|
-
out +=
|
|
72
|
+
out += `# got: ${got}\n`;
|
|
73
|
+
out += `# expected: ${want}\n`;
|
|
73
74
|
if (typeof this.details.comparitor === S)
|
|
74
75
|
{
|
|
75
|
-
out +=
|
|
76
|
+
out += `# op: ${this.details.comparitor}\n`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if ('info' in this.details)
|
|
81
|
+
{ // Extended information. Usually an array, but can be whatever.
|
|
82
|
+
const info
|
|
83
|
+
= isArray(this.details.info)
|
|
84
|
+
? this.details.info
|
|
85
|
+
: [this.details.info];
|
|
86
|
+
|
|
87
|
+
for (const i in info)
|
|
88
|
+
{
|
|
89
|
+
const line = (typeof info[i] === S) ? info[i] : stringify(info[i]);
|
|
90
|
+
out += `## ${line}\n`;
|
|
76
91
|
}
|
|
77
92
|
}
|
|
78
93
|
|
package/lib/test.js
CHANGED
|
@@ -1,9 +1,38 @@
|
|
|
1
1
|
const core = require('@lumjs/core');
|
|
2
|
-
const
|
|
2
|
+
const types = core.types;
|
|
3
|
+
const {F,S,N,isObj,isArray,def} = types;
|
|
3
4
|
|
|
4
5
|
// We use a separate class to represent test logs.
|
|
5
6
|
const Log = require('./log');
|
|
6
7
|
|
|
8
|
+
// A list of Test methods that return Log objects.
|
|
9
|
+
const TEST_METHODS =
|
|
10
|
+
[
|
|
11
|
+
'ok', 'call', 'fail', 'pass', 'dies', 'diesWith', 'lives', 'cmp',
|
|
12
|
+
'is', 'isnt', 'isa', 'nota', 'isJSON', 'isntJSON', 'skip',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
// A list of other methods to export that are not standard tests.
|
|
16
|
+
const META_METHODS =
|
|
17
|
+
[
|
|
18
|
+
'plan', 'diag', 'run', 'tap', 'output',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// The function that powers `Test.call()` and friends.
|
|
22
|
+
function $call (testfunc, args)
|
|
23
|
+
{
|
|
24
|
+
const ret = {};
|
|
25
|
+
try
|
|
26
|
+
{
|
|
27
|
+
ret.val = testfunc(...args);
|
|
28
|
+
}
|
|
29
|
+
catch (e)
|
|
30
|
+
{
|
|
31
|
+
ret.err = e;
|
|
32
|
+
}
|
|
33
|
+
return ret;
|
|
34
|
+
}
|
|
35
|
+
|
|
7
36
|
/**
|
|
8
37
|
* A simple testing library with TAP support.
|
|
9
38
|
*
|
|
@@ -106,13 +135,12 @@ class Test
|
|
|
106
135
|
* If the value evaluates as `true` (aka a truthy value), the test passes.
|
|
107
136
|
* If it evaluates as `false` (a falsey value), the test fails.
|
|
108
137
|
*
|
|
109
|
-
* @param {string} desc
|
|
110
|
-
*
|
|
138
|
+
* @param {string} [desc] A short description of the test.
|
|
111
139
|
* @param {(string|Error)} [directive] Further information for the log.
|
|
112
|
-
*
|
|
140
|
+
* @param {object} [details] Extra details to add to the log.
|
|
113
141
|
* @returns {Log} The test log with the results.
|
|
114
142
|
*/
|
|
115
|
-
ok (test, desc, directive)
|
|
143
|
+
ok (test, desc, directive, details)
|
|
116
144
|
{
|
|
117
145
|
const log = new Log();
|
|
118
146
|
|
|
@@ -135,11 +163,34 @@ class Test
|
|
|
135
163
|
log.directive = directive;
|
|
136
164
|
}
|
|
137
165
|
|
|
166
|
+
if (isObj(details))
|
|
167
|
+
{
|
|
168
|
+
log.details = details;
|
|
169
|
+
}
|
|
170
|
+
|
|
138
171
|
this.log.push(log);
|
|
139
172
|
|
|
140
173
|
return log;
|
|
141
174
|
}
|
|
142
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Run a function and pass its return value to `ok()`.
|
|
178
|
+
*
|
|
179
|
+
* @param {function} testfunc - The function to run.
|
|
180
|
+
* If the function returns, the return value will be passed to `ok()`.
|
|
181
|
+
* If it throws an Error, the test will be marked as failed, and the
|
|
182
|
+
* Error will be passed as the `directive` to the `ok()` method.
|
|
183
|
+
*
|
|
184
|
+
* @param {string} [desc] A description for `ok()`.
|
|
185
|
+
* @param {...any} [args] Arguments to pass to the test function.
|
|
186
|
+
* @returns {Log}
|
|
187
|
+
*/
|
|
188
|
+
call(testfunc, desc, ...args)
|
|
189
|
+
{
|
|
190
|
+
const ret = $call(testfunc, args);
|
|
191
|
+
return this.ok(ret.val, desc, ret.err);
|
|
192
|
+
}
|
|
193
|
+
|
|
143
194
|
/**
|
|
144
195
|
* Mark a test as failed.
|
|
145
196
|
*
|
|
@@ -174,20 +225,91 @@ class Test
|
|
|
174
225
|
* call. If no error is caught the test will be considered to have failed.
|
|
175
226
|
*
|
|
176
227
|
* @param {function} testfunc
|
|
177
|
-
* @param {string} desc
|
|
228
|
+
* @param {string} [desc]
|
|
229
|
+
* @param {...any} [args]
|
|
230
|
+
* @returns {Log}
|
|
231
|
+
*/
|
|
232
|
+
dies (testfunc, desc, ...args)
|
|
233
|
+
{
|
|
234
|
+
const ret = $call(testfunc, args);
|
|
235
|
+
return this.ok(('err' in ret), desc, ret.err);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* See if a function throws an Error, and if the Error passes a second test.
|
|
240
|
+
*
|
|
241
|
+
* All the notes that apply to `dies()` apply here as well.
|
|
242
|
+
*
|
|
243
|
+
* @param {function} testfunc - Same as `dies()` and `call()`
|
|
244
|
+
* We don't care about the return value of this function.
|
|
245
|
+
* We only care that it *should* throw an Error.
|
|
246
|
+
*
|
|
247
|
+
* @param {(function|string)} testerr - A test to validate the Error.
|
|
248
|
+
*
|
|
249
|
+
* If this is a `function` it will be passed the thrown `Error` as the first
|
|
250
|
+
* parameter, and an `Array` of `args` as the second parameter. The return
|
|
251
|
+
* value from this will be passed to `ok()`.
|
|
252
|
+
*
|
|
253
|
+
* If this is a `string` then the test will pass only if the either the
|
|
254
|
+
* `Error.name` or `Error.message` is exactly equal to this value.
|
|
255
|
+
*
|
|
256
|
+
* @param {string} [desc]
|
|
257
|
+
* @param {...any} [args]
|
|
178
258
|
* @returns {Log}
|
|
179
259
|
*/
|
|
180
|
-
|
|
260
|
+
diesWith(testfunc, testerr, desc, ...args)
|
|
181
261
|
{
|
|
182
|
-
let ok = false;
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
262
|
+
let ok = false, details = {}, err = null;
|
|
263
|
+
|
|
264
|
+
const r1 = $call(testfunc, args);
|
|
265
|
+
|
|
266
|
+
if ('err' in r1)
|
|
186
267
|
{
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
268
|
+
err = r1.err;
|
|
269
|
+
|
|
270
|
+
if (typeof testerr === F)
|
|
271
|
+
{ // A secondary function to test the error with.
|
|
272
|
+
const r2 = $call(testerr, [err, args]);
|
|
273
|
+
if ('err' in r2)
|
|
274
|
+
{ // Second function threw an error, add it as diagnostic info.
|
|
275
|
+
details.info = r2.err;
|
|
276
|
+
}
|
|
277
|
+
else
|
|
278
|
+
{ // No error, use the function output as the test value.
|
|
279
|
+
ok = r2.val;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else if (typeof testerr === S)
|
|
283
|
+
{ // A simple name/message test.
|
|
284
|
+
if (err.name === testerr || err.message === testerr)
|
|
285
|
+
{ // Either the name or the message matched the string.
|
|
286
|
+
ok = true;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
} // if r1.err
|
|
291
|
+
|
|
292
|
+
return this.ok(ok, desc, err, details);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* See if a function runs without throwing an Error.
|
|
297
|
+
*
|
|
298
|
+
* The function will be called in a `try { } catch (err) { }` block.
|
|
299
|
+
*
|
|
300
|
+
* If an error is caught, the test will be considered to have failed,
|
|
301
|
+
* and the `Error` object will be used as the `directive` in the `ok()`
|
|
302
|
+
* call. If no error is caught the test will be considered to have passed.
|
|
303
|
+
*
|
|
304
|
+
* @param {function} testfunc
|
|
305
|
+
* @param {string} [desc]
|
|
306
|
+
* @param {...any} [args]
|
|
307
|
+
* @returns {Log}
|
|
308
|
+
*/
|
|
309
|
+
lives (testfunc, desc, ...args)
|
|
310
|
+
{
|
|
311
|
+
const ret = $call(testfunc, args);
|
|
312
|
+
return this.ok(!('err' in ret), desc, ret.err);
|
|
191
313
|
}
|
|
192
314
|
|
|
193
315
|
/**
|
|
@@ -253,15 +375,19 @@ class Test
|
|
|
253
375
|
test = false;
|
|
254
376
|
}
|
|
255
377
|
|
|
256
|
-
|
|
378
|
+
let details = null;
|
|
257
379
|
if (!test)
|
|
258
|
-
{
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
380
|
+
{ // The test failed, add the deets.
|
|
381
|
+
details =
|
|
382
|
+
{
|
|
383
|
+
got,
|
|
384
|
+
wanted: want,
|
|
385
|
+
stringify,
|
|
386
|
+
comparitor: comp,
|
|
387
|
+
};
|
|
263
388
|
}
|
|
264
|
-
|
|
389
|
+
|
|
390
|
+
return this.ok(test, desc, null, details);
|
|
265
391
|
}
|
|
266
392
|
|
|
267
393
|
/**
|
|
@@ -271,7 +397,7 @@ class Test
|
|
|
271
397
|
*
|
|
272
398
|
* @param {*} got
|
|
273
399
|
* @param {*} want
|
|
274
|
-
* @param {string} desc
|
|
400
|
+
* @param {string} [desc]
|
|
275
401
|
* @param {boolean} [stringify=true]
|
|
276
402
|
* @returns {Log}
|
|
277
403
|
*/
|
|
@@ -287,8 +413,8 @@ class Test
|
|
|
287
413
|
*
|
|
288
414
|
* @param {*} got - The result value from the test function.
|
|
289
415
|
* @param {*} want - The value we expected from the test function.
|
|
290
|
-
* @param {string} desc
|
|
291
|
-
* @param {boolean} [stringify=true]
|
|
416
|
+
* @param {string} [desc]
|
|
417
|
+
* @param {boolean} [stringify=true]
|
|
292
418
|
* @returns {Log}
|
|
293
419
|
*/
|
|
294
420
|
isnt (got, want, desc, stringify=true)
|
|
@@ -300,45 +426,64 @@ class Test
|
|
|
300
426
|
* See if a value is of a certain type.
|
|
301
427
|
*
|
|
302
428
|
* @param {*} got
|
|
303
|
-
* @param {
|
|
429
|
+
* @param {Array} wants - Type names or constructor functions.
|
|
304
430
|
*
|
|
305
|
-
*
|
|
306
|
-
* If this is a string, we use the `isType()` function.
|
|
307
|
-
* If this is a constructor function, we use the `isInstance()` function.
|
|
431
|
+
* Uses `@lumjs/core/types.isa()` to perform the test.
|
|
308
432
|
*
|
|
309
|
-
* @param {string} desc
|
|
433
|
+
* @param {string} [desc]
|
|
310
434
|
* @param {boolean} [stringify=true]
|
|
311
435
|
* @returns {Log}
|
|
312
436
|
*/
|
|
313
|
-
isa (got,
|
|
437
|
+
isa (got, wants, desc, stringify=true, not=false)
|
|
314
438
|
{
|
|
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);
|
|
439
|
+
if (!isArray(wants))
|
|
440
|
+
{
|
|
441
|
+
wants = [wants];
|
|
323
442
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
443
|
+
let res = types.isa(got, ...wants);
|
|
444
|
+
if (not)
|
|
445
|
+
{ // Inverse the result.
|
|
446
|
+
res = !res;
|
|
327
447
|
}
|
|
328
448
|
|
|
329
|
-
|
|
330
|
-
if (!
|
|
331
|
-
{
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
449
|
+
let details = null;
|
|
450
|
+
if (!res)
|
|
451
|
+
{ // The test failed, add the deets.
|
|
452
|
+
details =
|
|
453
|
+
{
|
|
454
|
+
got,
|
|
455
|
+
wanted: wants,
|
|
456
|
+
stringify,
|
|
457
|
+
comparitor: not ? 'nota()' : 'isa()',
|
|
458
|
+
};
|
|
335
459
|
}
|
|
336
|
-
|
|
460
|
+
|
|
461
|
+
return this.ok(res, desc, null, details);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* See if a value is NOT of a certain type.
|
|
466
|
+
*
|
|
467
|
+
* Just inverses the results of `isa()`.
|
|
468
|
+
*
|
|
469
|
+
* @param {*} got
|
|
470
|
+
* @param {Array} wants - Type names of constructor functions.
|
|
471
|
+
* @param {string} [desc]
|
|
472
|
+
* @param {boolean} [stringify=true]
|
|
473
|
+
* @returns {Log}
|
|
474
|
+
*/
|
|
475
|
+
nota (got, wants, desc, stringify=true)
|
|
476
|
+
{
|
|
477
|
+
return this.isa(got, wants, desc, stringify, true);
|
|
337
478
|
}
|
|
338
479
|
|
|
339
480
|
/**
|
|
340
481
|
* An `is()` test, but encode both values as JSON first.
|
|
341
482
|
*
|
|
483
|
+
* Actually uses `core.types.stringify()` so it supports more
|
|
484
|
+
* data types than standard JSON, and can stringify functions,
|
|
485
|
+
* symbols, and several extended object types.
|
|
486
|
+
*
|
|
342
487
|
* @param {*} got
|
|
343
488
|
* @param {*} want
|
|
344
489
|
* @param {string} desc
|
|
@@ -346,11 +491,28 @@ class Test
|
|
|
346
491
|
*/
|
|
347
492
|
isJSON (got, want, desc)
|
|
348
493
|
{
|
|
349
|
-
got =
|
|
350
|
-
want =
|
|
494
|
+
got = types.stringify(got);
|
|
495
|
+
want = types.stringify(want);
|
|
351
496
|
return this.is(got, want, desc, false);
|
|
352
497
|
}
|
|
353
498
|
|
|
499
|
+
/**
|
|
500
|
+
* An `isnt()` test, but encode both values as JSON first.
|
|
501
|
+
*
|
|
502
|
+
* Like `isJSON()` this uses `core.types.stringify()`.
|
|
503
|
+
*
|
|
504
|
+
* @param {*} got
|
|
505
|
+
* @param {*} want
|
|
506
|
+
* @param {string} desc
|
|
507
|
+
* @returns
|
|
508
|
+
*/
|
|
509
|
+
isntJSON (got, want, desc)
|
|
510
|
+
{
|
|
511
|
+
got = types.stringify(got);
|
|
512
|
+
want = types.stringify(want);
|
|
513
|
+
return this.isnt(got, want, desc, false);
|
|
514
|
+
}
|
|
515
|
+
|
|
354
516
|
/**
|
|
355
517
|
* Skip a test.
|
|
356
518
|
*
|
|
@@ -381,6 +543,58 @@ class Test
|
|
|
381
543
|
this.log.push(msg);
|
|
382
544
|
}
|
|
383
545
|
|
|
546
|
+
/**
|
|
547
|
+
* Run an assortment of tests using a map.
|
|
548
|
+
*
|
|
549
|
+
* The *current* test method defaults to `ok`.
|
|
550
|
+
*
|
|
551
|
+
* @param {...any} tests - The tests we're running.
|
|
552
|
+
*
|
|
553
|
+
* If this is a `string`, and is the name of one of the standard testing
|
|
554
|
+
* methods in this class, it will be set as the *current* test method.
|
|
555
|
+
*
|
|
556
|
+
* If this is a `function`, it will be set as the *current* test method.
|
|
557
|
+
* Function test methods are passed to `call()` with the test parameters.
|
|
558
|
+
*
|
|
559
|
+
* If this is an `Array` then it's the parameters for the *current* test
|
|
560
|
+
* method. If a custom `function` is in use, remember that the *first*
|
|
561
|
+
* parameter is **always** the `desc`, and any subsequent parameters will be
|
|
562
|
+
* passed to the custom `function` call.
|
|
563
|
+
*
|
|
564
|
+
* Any value other than one of those will throw a `TypeError`.
|
|
565
|
+
*
|
|
566
|
+
* @returns {Log[]} A `Log` item for each test that was ran.
|
|
567
|
+
*/
|
|
568
|
+
run(...tests)
|
|
569
|
+
{
|
|
570
|
+
const logs = [];
|
|
571
|
+
let current = 'ok';
|
|
572
|
+
|
|
573
|
+
for (const test of tests)
|
|
574
|
+
{
|
|
575
|
+
const tt = typeof test;
|
|
576
|
+
if (tt === F || (tt === S && TEST_METHODS.includes(test)))
|
|
577
|
+
{ // Set the current test.
|
|
578
|
+
current = test;
|
|
579
|
+
}
|
|
580
|
+
else if (isArray(test))
|
|
581
|
+
{ // A set of test parameters.
|
|
582
|
+
let log;
|
|
583
|
+
if (typeof current === F)
|
|
584
|
+
{ // A custom test function is in use.
|
|
585
|
+
log = this.call(current, ...test);
|
|
586
|
+
}
|
|
587
|
+
else
|
|
588
|
+
{ // A standard test is in use.
|
|
589
|
+
log = this[current](...test);
|
|
590
|
+
}
|
|
591
|
+
logs.push(log);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return logs;
|
|
596
|
+
}
|
|
597
|
+
|
|
384
598
|
/**
|
|
385
599
|
* Return TAP formatted output for all the tests.
|
|
386
600
|
*
|
|
@@ -403,7 +617,7 @@ class Test
|
|
|
403
617
|
}
|
|
404
618
|
else
|
|
405
619
|
{ // A comment.
|
|
406
|
-
out += '# ' + (typeof log === S ? log :
|
|
620
|
+
out += '# ' + (typeof log === S ? log : types.stringify(log)) + "\n";
|
|
407
621
|
}
|
|
408
622
|
}
|
|
409
623
|
if (this.skipped)
|
|
@@ -439,5 +653,29 @@ class Test
|
|
|
439
653
|
// Should never need this, but...
|
|
440
654
|
def(Test, 'Log', Log);
|
|
441
655
|
|
|
656
|
+
// Probably don't need this either, but...
|
|
657
|
+
def(Test, '$call', $call);
|
|
658
|
+
|
|
659
|
+
// Methods we're exporting for the 'functional' API.
|
|
660
|
+
def(Test, '$METHODS',
|
|
661
|
+
{
|
|
662
|
+
test: TEST_METHODS,
|
|
663
|
+
meta: META_METHODS,
|
|
664
|
+
get all()
|
|
665
|
+
{
|
|
666
|
+
const list = [];
|
|
667
|
+
for (const name in this)
|
|
668
|
+
{
|
|
669
|
+
if (name === 'all') continue;
|
|
670
|
+
const prop = this[name];
|
|
671
|
+
if (isArray(prop))
|
|
672
|
+
{
|
|
673
|
+
list.push(...prop);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return list;
|
|
677
|
+
},
|
|
678
|
+
});
|
|
679
|
+
|
|
442
680
|
// Export the class
|
|
443
681
|
module.exports = Test;
|
package/package.json
CHANGED
package/test/basics.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const Test = require('../lib').Test;
|
|
2
2
|
const def = require('./inc/basics.js');
|
|
3
|
+
const stringify = def.types.stringify;
|
|
3
4
|
|
|
4
5
|
const test = new Test();
|
|
5
6
|
test.plan(def.plan);
|
|
@@ -9,22 +10,39 @@ test.pass('pass()');
|
|
|
9
10
|
|
|
10
11
|
for (const is of def.isTests)
|
|
11
12
|
{
|
|
12
|
-
const t =
|
|
13
|
+
const t = stringify(is);
|
|
13
14
|
test.is(is, is, 'is('+t+','+t+')');
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
for (const [a,b] of def.isntTests)
|
|
17
18
|
{
|
|
18
|
-
const msg = `isnt(${
|
|
19
|
+
const msg = `isnt(${stringify(a)},${stringify(b)})`;
|
|
19
20
|
test.isnt(a, b, msg);
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
for (const args of def.cmpTests)
|
|
23
24
|
{
|
|
24
|
-
const msg = 'cmp('+
|
|
25
|
+
const msg = 'cmp('+stringify(args)+')';
|
|
25
26
|
test.cmp(...args, msg);
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
for (const func of def.livesTests)
|
|
30
|
+
{
|
|
31
|
+
test.lives(func, `lives(${stringify(func)})`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const [a,b] of def.isJsonTests)
|
|
35
|
+
{
|
|
36
|
+
const msg = `isJSON(${stringify(a)},${stringify(b)})`;
|
|
37
|
+
test.isJSON(a, b, msg);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (const [a,b] of def.isntJsonTests)
|
|
41
|
+
{
|
|
42
|
+
const msg = `isntJSON(${stringify(a)},${stringify(b)})`;
|
|
43
|
+
test.isntJSON(a, b, msg);
|
|
44
|
+
}
|
|
45
|
+
|
|
28
46
|
// We're done, output the log.
|
|
29
47
|
console.log(test.tap());
|
|
30
48
|
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// Testing the basic functions of the functional testing API.
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const
|
|
4
|
+
{
|
|
5
|
+
test,plan,ok,pass,is,isnt,cmp,tap,isJSON,isntJSON,lives,
|
|
6
|
+
} = require('../lib').functional();
|
|
4
7
|
const def = require('./inc/basics.js');
|
|
8
|
+
const stringify = def.types.stringify;
|
|
5
9
|
|
|
6
10
|
plan(def.plan);
|
|
7
11
|
|
|
@@ -10,22 +14,39 @@ pass('pass()');
|
|
|
10
14
|
|
|
11
15
|
for (const it of def.isTests)
|
|
12
16
|
{
|
|
13
|
-
const t =
|
|
17
|
+
const t = stringify(it);
|
|
14
18
|
is(it, it, 'is('+t+','+t+')');
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
for (const [a,b] of def.isntTests)
|
|
18
22
|
{
|
|
19
|
-
const msg = `isnt(${
|
|
23
|
+
const msg = `isnt(${stringify(a)},${stringify(b)})`;
|
|
20
24
|
isnt(a, b, msg);
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
for (const args of def.cmpTests)
|
|
24
28
|
{
|
|
25
|
-
const msg = 'cmp('+
|
|
29
|
+
const msg = 'cmp('+stringify(args)+')';
|
|
26
30
|
cmp(...args, msg);
|
|
27
31
|
}
|
|
28
32
|
|
|
33
|
+
for (const func of def.livesTests)
|
|
34
|
+
{
|
|
35
|
+
lives(func, `lives(${stringify(func)})`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const [a,b] of def.isJsonTests)
|
|
39
|
+
{
|
|
40
|
+
const msg = `isJSON(${stringify(a)},${stringify(b)})`;
|
|
41
|
+
isJSON(a, b, msg);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const [a,b] of def.isntJsonTests)
|
|
45
|
+
{
|
|
46
|
+
const msg = `isntJSON(${stringify(a)},${stringify(b)})`;
|
|
47
|
+
isntJSON(a, b, msg);
|
|
48
|
+
}
|
|
49
|
+
|
|
29
50
|
// We're done, output the log.
|
|
30
51
|
console.log(tap());
|
|
31
52
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const {test,plan,tap,isa,nota} = require('../lib').functional();
|
|
2
|
+
const def = require('./inc/isa.js');
|
|
3
|
+
const getLabel = def.label;
|
|
4
|
+
|
|
5
|
+
plan(def.plan);
|
|
6
|
+
|
|
7
|
+
for (const isaTest of def.isaTests)
|
|
8
|
+
{
|
|
9
|
+
const label = getLabel('isa',isaTest);
|
|
10
|
+
isa(isaTest[0], isaTest[1], label);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
for (const notaTest of def.notaTests)
|
|
14
|
+
{
|
|
15
|
+
const label = getLabel('nota',notaTest);
|
|
16
|
+
nota(notaTest[0], notaTest[1], label);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// We're done, output the log.
|
|
20
|
+
console.log(tap());
|
|
21
|
+
|
|
22
|
+
// And re-export the test.
|
|
23
|
+
module.exports = test;
|
package/test/inc/basics.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Common stuff for both regular and functional tests.
|
|
2
|
+
const types = require('@lumjs/core').types;
|
|
2
3
|
|
|
3
4
|
const okay = (1==1);
|
|
4
5
|
|
|
@@ -33,10 +34,39 @@ const cmpTests =
|
|
|
33
34
|
// TODO: finish this set of tests.
|
|
34
35
|
];
|
|
35
36
|
|
|
37
|
+
const livesTests =
|
|
38
|
+
[ // Tests for the lives() function.
|
|
39
|
+
() => true,
|
|
40
|
+
() => JSON.stringify({answer:42}),
|
|
41
|
+
() => JSON.parse('{"hello":"world"}'),
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const isJsonTests =
|
|
45
|
+
[
|
|
46
|
+
[[1,2,3], [1,2,3]],
|
|
47
|
+
[['hello','world'], ['hello','world']],
|
|
48
|
+
[{hello: 'world'}, {hello:'world'}],
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const isntJsonTests =
|
|
52
|
+
[
|
|
53
|
+
[[1,2,3], [3,2,1]],
|
|
54
|
+
[['hello','world'], ['goodbye','universe']],
|
|
55
|
+
[{hello: 'world'}, {hello:'darkness my old friend'}],
|
|
56
|
+
];
|
|
57
|
+
|
|
36
58
|
const plan
|
|
37
59
|
= 2
|
|
38
60
|
+ isTests.length
|
|
39
61
|
+ isntTests.length
|
|
40
|
-
+ cmpTests.length
|
|
62
|
+
+ cmpTests.length
|
|
63
|
+
+ livesTests.length
|
|
64
|
+
+ isJsonTests.length
|
|
65
|
+
+ isntJsonTests.length
|
|
66
|
+
;
|
|
41
67
|
|
|
42
|
-
module.exports =
|
|
68
|
+
module.exports =
|
|
69
|
+
{
|
|
70
|
+
isTests, isntTests, cmpTests, livesTests, isJsonTests, isntJsonTests,
|
|
71
|
+
okay, plan, types,
|
|
72
|
+
};
|
package/test/inc/isa.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Common stuff for isa() and nota() tests.
|
|
2
|
+
const types = require('@lumjs/core').types;
|
|
3
|
+
// A quick reference to the type names.
|
|
4
|
+
const TYP = types.TYPES;
|
|
5
|
+
|
|
6
|
+
// I based most of these off of the tests in `@lumjs/core`.
|
|
7
|
+
// Some almost verbatim, but others required restructuring.
|
|
8
|
+
|
|
9
|
+
class TypeClass {}
|
|
10
|
+
|
|
11
|
+
class SubtypeClass extends TypeClass {}
|
|
12
|
+
|
|
13
|
+
const typesInstance = new TypeClass();
|
|
14
|
+
const subtypeInstance = new SubtypeClass();
|
|
15
|
+
|
|
16
|
+
class DifferentClass {}
|
|
17
|
+
|
|
18
|
+
const differentInstance = new DifferentClass();
|
|
19
|
+
|
|
20
|
+
function getArguments() { return arguments; }
|
|
21
|
+
|
|
22
|
+
const MT = [TYP.S, TYP.N];
|
|
23
|
+
const MC = [SubtypeClass, DifferentClass];
|
|
24
|
+
const MM = [TYP.B, TypeClass];
|
|
25
|
+
|
|
26
|
+
const MCT = ',[SubtypeClass,DifferentClass]';
|
|
27
|
+
const MMT = ',["binary",TypeClass]';
|
|
28
|
+
|
|
29
|
+
const isaTests =
|
|
30
|
+
[
|
|
31
|
+
// Single types.
|
|
32
|
+
[{}, TYP.O],
|
|
33
|
+
[function(){}, TYP.F],
|
|
34
|
+
['hello', TYP.S],
|
|
35
|
+
[true, TYP.B],
|
|
36
|
+
[false, TYP.B],
|
|
37
|
+
[100, TYP.N],
|
|
38
|
+
[undefined, TYP.U],
|
|
39
|
+
[Symbol('foo'), TYP.SY],
|
|
40
|
+
[BigInt(12345), TYP.BI, 'BigInt(12345),'+TYP.BI],
|
|
41
|
+
[54321n, TYP.BI, '54321n,'+ TYP.BI],
|
|
42
|
+
[getArguments(), TYP.ARGS],
|
|
43
|
+
[[], TYP.ARRAY],
|
|
44
|
+
[null, TYP.NULL],
|
|
45
|
+
[new Int16Array(16), TYP.TYPEDARRAY, 'Int16Array(16),'+TYP.TYPEDARRAY],
|
|
46
|
+
[{value: true}, TYP.DESCRIPTOR],
|
|
47
|
+
[{get: function(){}}, TYP.DESCRIPTOR],
|
|
48
|
+
[{}, TYP.COMPLEX],
|
|
49
|
+
[function(){}, TYP.COMPLEX],
|
|
50
|
+
[true, TYP.SCALAR],
|
|
51
|
+
['hi', TYP.SCALAR],
|
|
52
|
+
['woah', TYP.PROP],
|
|
53
|
+
[Symbol('woah'), TYP.PROP],
|
|
54
|
+
// Single class.
|
|
55
|
+
[typesInstance, TypeClass, 'typesInstance,TypeClass'],
|
|
56
|
+
[subtypeInstance, SubtypeClass, 'subtypeInstance,SubtypeClass'],
|
|
57
|
+
[subtypeInstance, TypeClass, 'subtypeInstance,TypeClass'],
|
|
58
|
+
[differentInstance, DifferentClass, 'differentInstance,DifferentClass'],
|
|
59
|
+
// Multiples.
|
|
60
|
+
['hello', MT],
|
|
61
|
+
[42, MT],
|
|
62
|
+
[subtypeInstance, MC, 'subtypeInstance'+MCT],
|
|
63
|
+
[differentInstance, MC, 'differentInstance'+MCT],
|
|
64
|
+
[true, MM, 'true'+MMT],
|
|
65
|
+
[false, MM, 'false'+MMT],
|
|
66
|
+
[typesInstance, MM, 'typeInstance'+MMT],
|
|
67
|
+
[subtypeInstance, MM, 'subtypeInstance'+MMT],
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
const notaTests =
|
|
71
|
+
[
|
|
72
|
+
// Single types.
|
|
73
|
+
['foo', TYP.O],
|
|
74
|
+
[null, TYP.O],
|
|
75
|
+
[{}, TYP.F],
|
|
76
|
+
[0, TYP.B],
|
|
77
|
+
[1, TYP.B],
|
|
78
|
+
['0', TYP.N],
|
|
79
|
+
[null, TYP.U],
|
|
80
|
+
['foo', TYP.SY],
|
|
81
|
+
[12345, TYP.BI],
|
|
82
|
+
[{}, TYP.ARGS],
|
|
83
|
+
[{}, TYP.ARRAY],
|
|
84
|
+
[false, TYP.NULL],
|
|
85
|
+
[[], TYP.TYPEDARRAY],
|
|
86
|
+
[{value: true, get: function(){}}, TYP.DESCRIPTOR],
|
|
87
|
+
[{}, TYP.DESCRIPTOR],
|
|
88
|
+
['a string', TYP.COMPLEX],
|
|
89
|
+
[{}, TYP.SCALAR],
|
|
90
|
+
[null, TYP.PROP],
|
|
91
|
+
// Single class.
|
|
92
|
+
[typesInstance, SubtypeClass, 'typesInstance,SubtypeClass'],
|
|
93
|
+
[differentInstance, TypeClass, 'differentInstance,TypeClass'],
|
|
94
|
+
[typesInstance, DifferentClass, 'typesInstance,DifferentClass'],
|
|
95
|
+
// Multiples.
|
|
96
|
+
[{}, MT],
|
|
97
|
+
[typesInstance, MC, 'typesInstance'+MCT],
|
|
98
|
+
[null, MM, 'null'+MMT],
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
const plan = isaTests.length + notaTests.length;
|
|
102
|
+
|
|
103
|
+
function label (f,t)
|
|
104
|
+
{
|
|
105
|
+
return f+'('+((t.length === 3)
|
|
106
|
+
? t[2]
|
|
107
|
+
: types.stringify(t[0])+','+types.stringify(t[1]))+')';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = {types, label, plan, isaTests, notaTests};
|
package/test/isa.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const Test = require('../lib').Test;
|
|
2
|
+
const def = require('./inc/isa.js');
|
|
3
|
+
|
|
4
|
+
const test = new Test();
|
|
5
|
+
test.plan(def.plan);
|
|
6
|
+
|
|
7
|
+
for (const isaTest of def.isaTests)
|
|
8
|
+
{
|
|
9
|
+
const label = def.label('isa',isaTest);
|
|
10
|
+
test.isa(isaTest[0], isaTest[1], label);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
for (const notaTest of def.notaTests)
|
|
14
|
+
{
|
|
15
|
+
const label = def.label('nota',notaTest);
|
|
16
|
+
test.nota(notaTest[0], notaTest[1], label);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// We're done, output the log.
|
|
20
|
+
console.log(test.tap());
|
|
21
|
+
|
|
22
|
+
// Re-expect the test.
|
|
23
|
+
module.exports = test;
|