@lumjs/tests 1.3.0 → 1.6.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 +52 -1
- package/TODO.md +2 -0
- package/jsdoc.json +33 -0
- package/lib/functional.js +22 -9
- package/lib/harness.js +9 -0
- package/lib/index.js +17 -2
- package/lib/log.js +25 -11
- package/lib/test.js +341 -35
- package/package.json +13 -3
- package/test/data/people.js +58 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [1.6.0] - 2022-09-12
|
|
10
|
+
#### The *sub-classes* update
|
|
11
|
+
### Added
|
|
12
|
+
- `Test.new()` works like the `@lumjs/tests.new()`, but uses `this` so it's sub-classable.
|
|
13
|
+
- `Test.static()`, calls `@lumjs/tests.functional()`, and passes `this` as the `testClass`.
|
|
14
|
+
- `Test#TAP`, read-only accessor alias to `Test#tap()`.
|
|
15
|
+
- `Test.$METHODS.$meta`, a list of properties to skip in `$METHODS.all` output.
|
|
16
|
+
- `Test.$METHODS.extend()`, allow `Test` *sub-classes* to build upon the `$METHODS`
|
|
17
|
+
without changing the list in the original `Test` class.
|
|
18
|
+
### Changed
|
|
19
|
+
- Reworked a bunch of the DocBlocks to make the docs better.
|
|
20
|
+
- Modified the `functional()` method to accept a `testClass` parameter.
|
|
21
|
+
This allows *sub-classes* to make their own functional APIs.
|
|
22
|
+
- Changed `Log.tap()` to make the `details` structure more flexible.
|
|
23
|
+
The `wanted` property is now optional.
|
|
24
|
+
- The `Test` class saves the test method list for `call()` into `this.$testMethods`
|
|
25
|
+
instead of using the `.$METHODS.test` directly.
|
|
26
|
+
### Fixed
|
|
27
|
+
- Added some missing functions to the registered list of test methods.
|
|
28
|
+
|
|
29
|
+
## [1.5.0] - 2022-08-30
|
|
30
|
+
### Added
|
|
31
|
+
- Sample `data` used in some of the old tests.
|
|
32
|
+
### Fixed
|
|
33
|
+
- Mistakes in the changelog.
|
|
34
|
+
|
|
35
|
+
## [1.4.0] - 2022-07-29
|
|
36
|
+
### Added
|
|
37
|
+
- Configuration for JSDoc.
|
|
38
|
+
- A few module-level *docblocks*.
|
|
39
|
+
- Explicit `exports` section in `package.json` file.
|
|
40
|
+
- Added `test.done()` method to be used instead of `test.output()`.
|
|
41
|
+
- Added ability to configure the stringify depth.
|
|
42
|
+
- Added ability to detect if the script was ran directly.
|
|
43
|
+
- Added ability to check for a top-level `Harness` instance.
|
|
44
|
+
- Added four new *binary flag* comparitor tests to `cmp()` method.
|
|
45
|
+
- Added `not` alias for `!==` comparitor.
|
|
46
|
+
- Added `matches` method for using a regular expression to match a string.
|
|
47
|
+
- Added `callIs()` method that is like `call()` but takes a desired value and passes the function return value to `cmp()`, `isa()`, or other test methods.
|
|
48
|
+
- A new `test.ran` computed property.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
- Updated `@lumjs/core` dependency to `^1.0.0` (no more *beta* tags!)
|
|
52
|
+
- Updated various *docblocks* for documentation.
|
|
53
|
+
- Enhanced a lot of docblocks.
|
|
54
|
+
- Updated anything using `types.stringify()` to support the depth setting.
|
|
55
|
+
- Updated `run()` so it can use either `call()` or `callIs()` as the underlying test method when using a custom `function` test.
|
|
56
|
+
|
|
9
57
|
## [1.3.0] - 2022-07-27
|
|
10
58
|
### Added
|
|
11
59
|
- `$call()` function; powers `call()`, `lives()`, `dies()`, and `diesWith()`.
|
|
@@ -57,7 +105,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
57
105
|
- Ported from Lum.js v4 library set.
|
|
58
106
|
- Added a few more features from the PHP version.
|
|
59
107
|
|
|
60
|
-
[Unreleased]: https://github.com/supernovus/lum.tests.js/compare/v1.
|
|
108
|
+
[Unreleased]: https://github.com/supernovus/lum.tests.js/compare/v1.6.0...HEAD
|
|
109
|
+
[1.6.0]: https://github.com/supernovus/lum.tests.js/compare/v1.5.0...v1.6.0
|
|
110
|
+
[1.5.0]: https://github.com/supernovus/lum.tests.js/compare/v1.4.0...v1.5.0
|
|
111
|
+
[1.4.0]: https://github.com/supernovus/lum.tests.js/compare/v1.3.0...v1.4.0
|
|
61
112
|
[1.3.0]: https://github.com/supernovus/lum.tests.js/compare/v1.2.0...v1.3.0
|
|
62
113
|
[1.2.0]: https://github.com/supernovus/lum.tests.js/compare/v1.1.1...v1.2.0
|
|
63
114
|
[1.1.1]: https://github.com/supernovus/lum.tests.js/compare/v1.1.0...v1.1.1
|
package/TODO.md
CHANGED
package/jsdoc.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"tags":
|
|
3
|
+
{
|
|
4
|
+
"allowUnknownTags": true
|
|
5
|
+
},
|
|
6
|
+
"source":
|
|
7
|
+
{
|
|
8
|
+
"include": ["./lib"]
|
|
9
|
+
},
|
|
10
|
+
"opts":
|
|
11
|
+
{
|
|
12
|
+
"destination": "./docs/api",
|
|
13
|
+
"recurse": true
|
|
14
|
+
},
|
|
15
|
+
"plugins":
|
|
16
|
+
[
|
|
17
|
+
"plugins/markdown"
|
|
18
|
+
],
|
|
19
|
+
"templates":
|
|
20
|
+
{
|
|
21
|
+
"cleverLinks": false,
|
|
22
|
+
"monospaceLinks": false,
|
|
23
|
+
"default":
|
|
24
|
+
{
|
|
25
|
+
"outputSourceFiles": true
|
|
26
|
+
},
|
|
27
|
+
"path": "ink-docstrap",
|
|
28
|
+
"theme": "cerulean",
|
|
29
|
+
"navType": "vertical",
|
|
30
|
+
"linenums": true,
|
|
31
|
+
"dateFormat": "YYYY-MM-DD, hh:mm:ss"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/lib/functional.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
// A functional interface to the test library.
|
|
2
2
|
const Test = require('./test');
|
|
3
3
|
|
|
4
|
-
// A list of methods we can proxy directly.
|
|
5
|
-
const PROXY_METHODS = Test.$METHODS.all;
|
|
6
|
-
|
|
7
4
|
/**
|
|
8
5
|
* A new test instance and a set of functions wrapping it.
|
|
9
|
-
*
|
|
6
|
+
*
|
|
7
|
+
* @typedef {object} module:@lumjs/tests/functional.Functional
|
|
10
8
|
* @property {Test} test - The new test instance.
|
|
11
9
|
*
|
|
12
10
|
* All of the rest of the properties are functions that
|
|
13
11
|
* can be imported into a JS scope using destructuring, and
|
|
14
12
|
* which wrap the corresponding test instance method.
|
|
13
|
+
*
|
|
14
|
+
* Basically anything that is a method of the `Test` instance
|
|
15
|
+
* will become a wrapped `function` available in this structure.
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -21,11 +22,12 @@ const PROXY_METHODS = Test.$METHODS.all;
|
|
|
21
22
|
* Usage is like:
|
|
22
23
|
*
|
|
23
24
|
* ```js
|
|
24
|
-
* const {plan,ok,isa} = require('@lumjs/tests').functional({module});
|
|
25
|
+
* const {plan,ok,isa,done} = require('@lumjs/tests').functional({module});
|
|
25
26
|
*
|
|
26
27
|
* plan(2);
|
|
27
28
|
* ok(true, 'ok() works');
|
|
28
29
|
* isa(isa, 'function', 'isa is a function');
|
|
30
|
+
* done();
|
|
29
31
|
*
|
|
30
32
|
* ```
|
|
31
33
|
*
|
|
@@ -36,14 +38,25 @@ const PROXY_METHODS = Test.$METHODS.all;
|
|
|
36
38
|
*
|
|
37
39
|
* @param {object} [opts] Options to pass to the `Test` constructor.
|
|
38
40
|
*
|
|
39
|
-
* @
|
|
41
|
+
* @param {function} [testClass=Test] The class we're creating an instance of.
|
|
42
|
+
*
|
|
43
|
+
* This defaults to `Test`, but can be changed to be a sub-class such as
|
|
44
|
+
* `DOMTest` (defined in the `@lumjs/tests-dom` library.) This feature is
|
|
45
|
+
* likely not useful for most end users, as knowledge of the internals is
|
|
46
|
+
* required to make the functional API work.
|
|
47
|
+
*
|
|
48
|
+
* @returns {module:@lumjs/tests/functional.Functional}
|
|
40
49
|
*
|
|
50
|
+
* @exports module:@lumjs/tests/functional
|
|
41
51
|
*/
|
|
42
|
-
function functional(opts={})
|
|
52
|
+
function functional(opts={}, testClass=Test)
|
|
43
53
|
{
|
|
44
|
-
|
|
54
|
+
//console.debug("functional()", testClass, testClass.$METHODS);
|
|
55
|
+
// A list of methods we can proxy directly.
|
|
56
|
+
const proxyMethods = testClass.$METHODS.all;
|
|
57
|
+
const test = new testClass(opts);
|
|
45
58
|
const functions = { test };
|
|
46
|
-
for (const meth of
|
|
59
|
+
for (const meth of proxyMethods)
|
|
47
60
|
{
|
|
48
61
|
functions[meth] = function ()
|
|
49
62
|
{
|
package/lib/harness.js
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
const Test = require('./test');
|
|
3
3
|
const Log = require('./log');
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Module defining the Harness class.
|
|
7
|
+
* @module @lumjs/tests/harness
|
|
8
|
+
*/
|
|
9
|
+
|
|
5
10
|
/**
|
|
6
11
|
* A class that acts as a test harness for running other tests.
|
|
7
12
|
*
|
|
@@ -14,3 +19,7 @@ class Harness
|
|
|
14
19
|
throw new Error("Not yet implemented");
|
|
15
20
|
}
|
|
16
21
|
}
|
|
22
|
+
|
|
23
|
+
// Export it.
|
|
24
|
+
module.exports = Harness;
|
|
25
|
+
|
package/lib/index.js
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Several test related classes.
|
|
3
|
+
* @module @lumjs/tests
|
|
3
4
|
*/
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Test class.
|
|
8
|
+
* @alias module:@lumjs/tests.Test
|
|
9
|
+
* @see module:@lumjs/tests/test
|
|
10
|
+
*/
|
|
5
11
|
const Test = require('./test');
|
|
6
|
-
|
|
7
12
|
module.exports.Test = Test;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Test Harness class.
|
|
16
|
+
* @alias module:@lumjs/tests.Harness
|
|
17
|
+
* @see module:@lumjs/tests/harness
|
|
18
|
+
*/
|
|
8
19
|
module.exports.Harness = require('./harness');
|
|
9
20
|
|
|
10
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Functional API registration.
|
|
23
|
+
* @alias module:@lumjs/tests.functional
|
|
24
|
+
* @see module:@lumjs/tests/functional
|
|
25
|
+
*/
|
|
11
26
|
module.exports.functional = require('./functional');
|
|
12
27
|
|
|
13
28
|
/**
|
package/lib/log.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
|
|
2
2
|
const types = require('@lumjs/core').types;
|
|
3
|
-
const {
|
|
3
|
+
const {S,O,isArray,stringify,def} = types;
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* A log representing the results of a test.
|
|
7
7
|
*
|
|
8
|
+
* @alias module:@lumjs/tests/test~Log
|
|
9
|
+
*
|
|
8
10
|
* @property {boolean} ok - Did the test pass.
|
|
9
11
|
* @property {boolean} skipped - Was the test skipped?
|
|
10
12
|
* @property {string} skippedReason - If the test was skipped, why?
|
|
@@ -19,7 +21,11 @@ const {F,S,O,isArray,stringify} = types;
|
|
|
19
21
|
*/
|
|
20
22
|
class Log
|
|
21
23
|
{
|
|
22
|
-
|
|
24
|
+
/**
|
|
25
|
+
* (Internal Constructor)
|
|
26
|
+
* @param {module:@lumjs/tests/test} test - The parent `Test` instance.
|
|
27
|
+
*/
|
|
28
|
+
constructor (test)
|
|
23
29
|
{
|
|
24
30
|
this.ok = false;
|
|
25
31
|
this.skipped = false;
|
|
@@ -27,6 +33,8 @@ class Log
|
|
|
27
33
|
this.desc = null;
|
|
28
34
|
this.directive = null;
|
|
29
35
|
this.details = {};
|
|
36
|
+
this.stringifyDepth = test.stringifyDepth;
|
|
37
|
+
def(this, '$test$', test);
|
|
30
38
|
}
|
|
31
39
|
|
|
32
40
|
/**
|
|
@@ -40,6 +48,8 @@ class Log
|
|
|
40
48
|
*/
|
|
41
49
|
tap (num)
|
|
42
50
|
{
|
|
51
|
+
const SD = this.stringifyDepth;
|
|
52
|
+
|
|
43
53
|
var out;
|
|
44
54
|
if (this.ok)
|
|
45
55
|
out = 'ok ';
|
|
@@ -60,17 +70,21 @@ class Log
|
|
|
60
70
|
|
|
61
71
|
out += "\n";
|
|
62
72
|
|
|
63
|
-
if ('got' in this.details
|
|
73
|
+
if ('got' in this.details)
|
|
64
74
|
{
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
75
|
+
const stringy = this.details.stringify;
|
|
76
|
+
|
|
77
|
+
let got = this.details.got;
|
|
78
|
+
if (stringy) got = stringify(got, SD);
|
|
79
|
+
out += `# got: ${got}\n`;
|
|
80
|
+
|
|
81
|
+
if ('wanted' in this.details)
|
|
68
82
|
{
|
|
69
|
-
|
|
70
|
-
want = stringify(want);
|
|
83
|
+
let want = this.details.wanted;
|
|
84
|
+
if (stringy) want = stringify(want, SD);
|
|
85
|
+
out += `# expected: ${want}\n`;
|
|
71
86
|
}
|
|
72
|
-
|
|
73
|
-
out += `# expected: ${want}\n`;
|
|
87
|
+
|
|
74
88
|
if (typeof this.details.comparitor === S)
|
|
75
89
|
{
|
|
76
90
|
out += `# op: ${this.details.comparitor}\n`;
|
|
@@ -86,7 +100,7 @@ class Log
|
|
|
86
100
|
|
|
87
101
|
for (const i in info)
|
|
88
102
|
{
|
|
89
|
-
const line = (typeof info[i] === S) ? info[i] : stringify(info[i]);
|
|
103
|
+
const line = (typeof info[i] === S) ? info[i] : stringify(info[i], SD);
|
|
90
104
|
out += `## ${line}\n`;
|
|
91
105
|
}
|
|
92
106
|
}
|
package/lib/test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const core = require('@lumjs/core');
|
|
2
|
-
const types = core
|
|
3
|
-
const {F,S,N,isObj,isArray,def} = types;
|
|
2
|
+
const {types,obj} = core;
|
|
3
|
+
const {F,S,N,isObj,isArray,needs,def} = types;
|
|
4
4
|
|
|
5
5
|
// We use a separate class to represent test logs.
|
|
6
6
|
const Log = require('./log');
|
|
@@ -8,14 +8,14 @@ const Log = require('./log');
|
|
|
8
8
|
// A list of Test methods that return Log objects.
|
|
9
9
|
const TEST_METHODS =
|
|
10
10
|
[
|
|
11
|
-
'ok', 'call', 'fail', 'pass', 'dies', 'diesWith', 'lives', 'cmp',
|
|
12
|
-
'is', 'isnt', 'isa', 'nota', 'isJSON', 'isntJSON', 'skip',
|
|
11
|
+
'ok', 'call', 'callIs', 'fail', 'pass', 'dies', 'diesWith', 'lives', 'cmp',
|
|
12
|
+
'is', 'isnt', 'isa', 'nota', 'isJSON', 'isntJSON', 'matches', 'skip',
|
|
13
13
|
];
|
|
14
14
|
|
|
15
15
|
// A list of other methods to export that are not standard tests.
|
|
16
16
|
const META_METHODS =
|
|
17
17
|
[
|
|
18
|
-
'plan', 'diag', 'run', 'tap', 'output',
|
|
18
|
+
'plan', 'diag', 'run', 'tap', 'output', 'done',
|
|
19
19
|
];
|
|
20
20
|
|
|
21
21
|
// The function that powers `Test.call()` and friends.
|
|
@@ -39,6 +39,16 @@ function $call (testfunc, args)
|
|
|
39
39
|
* Based on Lum.php's Test library.
|
|
40
40
|
* Which itself was based on Perl 5's Test::More, and
|
|
41
41
|
* Raku's Test libraries.
|
|
42
|
+
*
|
|
43
|
+
* @exports module:@lumjs/tests/test
|
|
44
|
+
*
|
|
45
|
+
* @property {number} planned - Number of tests planned, `0` if unplanned.
|
|
46
|
+
* @property {number} failed - Number of tests that failed.
|
|
47
|
+
* @property {number} skipped - Number of tests that were skipped.
|
|
48
|
+
* @property {number} ran - Number of tests ran (*calculated*).
|
|
49
|
+
* @property {string} id - Unique test id used by `Harness` libary.
|
|
50
|
+
* @property {boolean} isTop - Test module was loaded from the command line.
|
|
51
|
+
* @property {?object} harness - The top-level `Harness` if one was found.
|
|
42
52
|
*/
|
|
43
53
|
class Test
|
|
44
54
|
{
|
|
@@ -58,6 +68,9 @@ class Test
|
|
|
58
68
|
* Also, if this is passed, and `opts.id` was not specified, and id
|
|
59
69
|
* will be auto-generated based on the filename of the module.
|
|
60
70
|
*
|
|
71
|
+
* @param {number} [opts.stringify=1] The depth `stringify()` should recurse
|
|
72
|
+
* objects and Arrays before switching to plain JSON stringification.
|
|
73
|
+
*
|
|
61
74
|
*/
|
|
62
75
|
constructor (opts={})
|
|
63
76
|
{
|
|
@@ -85,20 +98,72 @@ class Test
|
|
|
85
98
|
this.id = null;
|
|
86
99
|
}
|
|
87
100
|
|
|
101
|
+
this.stringifyDepth = opts.stringify ?? 1;
|
|
102
|
+
|
|
88
103
|
this.failed = 0;
|
|
89
104
|
this.skipped = 0;
|
|
90
105
|
this.planned = 0;
|
|
91
106
|
this.log = [];
|
|
92
107
|
|
|
108
|
+
// These three will be updated below if possible.
|
|
109
|
+
this.isTop = false;
|
|
110
|
+
this.harness = null;
|
|
111
|
+
|
|
112
|
+
// Methods that can be ran via run().
|
|
113
|
+
this.$testMethods = TEST_METHODS.slice();
|
|
114
|
+
|
|
93
115
|
if (typeof opts.plan === N)
|
|
94
116
|
{
|
|
95
117
|
this.plan(opts.plan);
|
|
96
118
|
}
|
|
97
119
|
|
|
98
120
|
if (hasModule)
|
|
99
|
-
{ //
|
|
121
|
+
{ // If a module was passed, its going to export this test.
|
|
100
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
|
+
}
|
|
101
136
|
}
|
|
137
|
+
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* A static shortcut for creating a new instance.
|
|
142
|
+
*
|
|
143
|
+
* @param {object} [opts] Options to pass to constructor.
|
|
144
|
+
* @returns {module:@lumjs/tests/test}
|
|
145
|
+
*/
|
|
146
|
+
static new(opts={})
|
|
147
|
+
{
|
|
148
|
+
return new this(opts);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A static shortcut for creating a `Functional` API object.
|
|
153
|
+
*
|
|
154
|
+
* @param {object} [opts] Options to pass to constructor.
|
|
155
|
+
* @returns {module:@lumjs/tests/functional.Functional}
|
|
156
|
+
*/
|
|
157
|
+
static functional(opts={})
|
|
158
|
+
{
|
|
159
|
+
//console.debug('functional this', this);
|
|
160
|
+
return require('./functional')(opts, this);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// A wrapper around types.stringify()
|
|
164
|
+
stringify(what)
|
|
165
|
+
{
|
|
166
|
+
return types.stringify(what, this.stringifyDepth);
|
|
102
167
|
}
|
|
103
168
|
|
|
104
169
|
/**
|
|
@@ -142,7 +207,7 @@ class Test
|
|
|
142
207
|
*/
|
|
143
208
|
ok (test, desc, directive, details)
|
|
144
209
|
{
|
|
145
|
-
const log = new Log();
|
|
210
|
+
const log = new Log(this);
|
|
146
211
|
|
|
147
212
|
if (test)
|
|
148
213
|
{
|
|
@@ -185,7 +250,7 @@ class Test
|
|
|
185
250
|
* @param {...any} [args] Arguments to pass to the test function.
|
|
186
251
|
* @returns {Log}
|
|
187
252
|
*/
|
|
188
|
-
call(testfunc, desc, ...args)
|
|
253
|
+
call (testfunc, desc, ...args)
|
|
189
254
|
{
|
|
190
255
|
const ret = $call(testfunc, args);
|
|
191
256
|
return this.ok(ret.val, desc, ret.err);
|
|
@@ -257,7 +322,7 @@ class Test
|
|
|
257
322
|
* @param {...any} [args]
|
|
258
323
|
* @returns {Log}
|
|
259
324
|
*/
|
|
260
|
-
diesWith(testfunc, testerr, desc, ...args)
|
|
325
|
+
diesWith (testfunc, testerr, desc, ...args)
|
|
261
326
|
{
|
|
262
327
|
let ok = false, details = {}, err = null;
|
|
263
328
|
|
|
@@ -320,15 +385,22 @@ class Test
|
|
|
320
385
|
* @param {string} comp - A comparitor to test with.
|
|
321
386
|
*
|
|
322
387
|
* - `===`, `is` (See also: `is()`)
|
|
323
|
-
* - `!==`, `isnt`
|
|
388
|
+
* - `!==`, `isnt`, `not` (See also: `isnt()`)
|
|
324
389
|
* - `==`, `eq`
|
|
325
390
|
* - `!=`, `ne`
|
|
326
391
|
* - `>`, `gt`
|
|
327
392
|
* - `<`, `lt`
|
|
328
393
|
* - `>=`, `ge`, `gte`
|
|
329
394
|
* - `<=`, `le`, `lte`
|
|
395
|
+
*
|
|
396
|
+
* A few special comparitors for *binary flag* testing:
|
|
397
|
+
*
|
|
398
|
+
* - `=&` → `((got & want) === want)`
|
|
399
|
+
* - `!&` → `((got & want) !== want)`
|
|
400
|
+
* - `+&` → `((got & want) !== 0)`
|
|
401
|
+
* - `-&` → `((got & want) === 0)`
|
|
330
402
|
*
|
|
331
|
-
* @param {string} desc
|
|
403
|
+
* @param {string} [desc]
|
|
332
404
|
* @param {boolean} [stringify=true] Stringify values in TAP output?
|
|
333
405
|
* @returns {Log}
|
|
334
406
|
*/
|
|
@@ -343,6 +415,7 @@ class Test
|
|
|
343
415
|
break;
|
|
344
416
|
case 'isnt':
|
|
345
417
|
case '!==':
|
|
418
|
+
case 'not':
|
|
346
419
|
test = (got !== want);
|
|
347
420
|
break;
|
|
348
421
|
case 'eq':
|
|
@@ -371,6 +444,16 @@ class Test
|
|
|
371
444
|
case '>=':
|
|
372
445
|
test = (got >= want);
|
|
373
446
|
break;
|
|
447
|
+
case '=&':
|
|
448
|
+
test = ((got&want)===want);
|
|
449
|
+
break;
|
|
450
|
+
case '!&':
|
|
451
|
+
test = ((got&want)!==want)
|
|
452
|
+
case '+&':
|
|
453
|
+
test = ((got&want)!==0);
|
|
454
|
+
break;
|
|
455
|
+
case '-&':
|
|
456
|
+
test = ((got&want)===0);
|
|
374
457
|
default:
|
|
375
458
|
test = false;
|
|
376
459
|
}
|
|
@@ -390,6 +473,39 @@ class Test
|
|
|
390
473
|
return this.ok(test, desc, null, details);
|
|
391
474
|
}
|
|
392
475
|
|
|
476
|
+
/**
|
|
477
|
+
* See if a string matches a value.
|
|
478
|
+
*
|
|
479
|
+
* @param {string} got
|
|
480
|
+
* @param {RegExp} want
|
|
481
|
+
* @param {string} [desc]
|
|
482
|
+
* @param {boolean} [stringify=true]
|
|
483
|
+
* @returns {Log}
|
|
484
|
+
*/
|
|
485
|
+
matches(got, want, desc, stringify=true)
|
|
486
|
+
{
|
|
487
|
+
const no = {error: "matches 'got' value must be a string"};
|
|
488
|
+
needs(got, no, S);
|
|
489
|
+
no.error = "matches 'want' value must be a RegExp";
|
|
490
|
+
needs(want, no, RegExp);
|
|
491
|
+
|
|
492
|
+
const test = want.test(got);
|
|
493
|
+
|
|
494
|
+
let details = null;
|
|
495
|
+
if (!test)
|
|
496
|
+
{ // The test failed, add the deets.
|
|
497
|
+
details =
|
|
498
|
+
{
|
|
499
|
+
got,
|
|
500
|
+
wanted: want,
|
|
501
|
+
stringify,
|
|
502
|
+
comparitor: 'matches',
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return this.ok(test, desc, null, details);
|
|
507
|
+
}
|
|
508
|
+
|
|
393
509
|
/**
|
|
394
510
|
* See if two values are equal.
|
|
395
511
|
*
|
|
@@ -486,13 +602,13 @@ class Test
|
|
|
486
602
|
*
|
|
487
603
|
* @param {*} got
|
|
488
604
|
* @param {*} want
|
|
489
|
-
* @param {string} desc
|
|
490
|
-
* @returns
|
|
605
|
+
* @param {string} [desc]
|
|
606
|
+
* @returns {Log}
|
|
491
607
|
*/
|
|
492
608
|
isJSON (got, want, desc)
|
|
493
609
|
{
|
|
494
|
-
got =
|
|
495
|
-
want =
|
|
610
|
+
got = this.stringify(got);
|
|
611
|
+
want = this.stringify(want);
|
|
496
612
|
return this.is(got, want, desc, false);
|
|
497
613
|
}
|
|
498
614
|
|
|
@@ -503,16 +619,99 @@ class Test
|
|
|
503
619
|
*
|
|
504
620
|
* @param {*} got
|
|
505
621
|
* @param {*} want
|
|
506
|
-
* @param {string} desc
|
|
507
|
-
* @returns
|
|
622
|
+
* @param {string} [desc]
|
|
623
|
+
* @returns {Log}
|
|
508
624
|
*/
|
|
509
625
|
isntJSON (got, want, desc)
|
|
510
626
|
{
|
|
511
|
-
got =
|
|
512
|
-
want =
|
|
627
|
+
got = this.stringify(got);
|
|
628
|
+
want = this.stringify(want);
|
|
513
629
|
return this.isnt(got, want, desc, false);
|
|
514
630
|
}
|
|
515
631
|
|
|
632
|
+
/**
|
|
633
|
+
* Run a function and see if it's return value is what we wanted.
|
|
634
|
+
*
|
|
635
|
+
* @param {function} testfunc - The function to run.
|
|
636
|
+
* The return value will be passed to `cmp()` or another appropriate
|
|
637
|
+
* testing method as determined by the options.
|
|
638
|
+
* How this handles error handling is determined by options as well.
|
|
639
|
+
*
|
|
640
|
+
* @param {*} want - The value we want.
|
|
641
|
+
* @param {(object|string)} [opts] Named options for further behaviour.
|
|
642
|
+
* If it is a string it's considered the `opts.desc` option.
|
|
643
|
+
* @param {string} [opts.desc] A description for `ok()`.
|
|
644
|
+
* @param {boolean} [opts.stringify=true]
|
|
645
|
+
* @param {Array} [opts.args] Arguments to pass to the test function.
|
|
646
|
+
* @param {string} [opts.comp="is"] - The comparitor to test with.
|
|
647
|
+
* In addition to all of the comparitors from `cmp()`, there are a few
|
|
648
|
+
* extra comparitors that will pass through to other methods:
|
|
649
|
+
* - `isa` → Use `isa()` to test return value.
|
|
650
|
+
* - `nota` → Use `nota()` to test return value.
|
|
651
|
+
* - `=json`, `isJSON` → Use `isJSON()` to test return value.
|
|
652
|
+
* - `!json`, `isntJSON` → Use `isntJSON()` to test return value.
|
|
653
|
+
* - `matches` → Use `matches()` to test return value.
|
|
654
|
+
*
|
|
655
|
+
* @param {boolean} [opts.thrown=false] How to handle thrown errors.
|
|
656
|
+
*
|
|
657
|
+
* If this is `true`, then anything thrown will be passed as if it was
|
|
658
|
+
* the return value from the function.
|
|
659
|
+
*
|
|
660
|
+
* If this is `false`, then any errors thrown will result in an immediate
|
|
661
|
+
* failure of the test without any further processing, and the error will
|
|
662
|
+
* be passed as the `directive` to the `ok()` method.
|
|
663
|
+
*
|
|
664
|
+
* @returns {Log}
|
|
665
|
+
*/
|
|
666
|
+
callIs (testfunc, want, opts={})
|
|
667
|
+
{
|
|
668
|
+
const args = opts.args ?? [];
|
|
669
|
+
const ret = $call(testfunc, args);
|
|
670
|
+
const desc = opts.desc;
|
|
671
|
+
|
|
672
|
+
let got;
|
|
673
|
+
|
|
674
|
+
if (ret.err)
|
|
675
|
+
{ // How to handle errors.
|
|
676
|
+
if (opts.thrown)
|
|
677
|
+
{ // We're going to test the error.
|
|
678
|
+
got = ret.err;
|
|
679
|
+
}
|
|
680
|
+
else
|
|
681
|
+
{ // This is an automatic failure.
|
|
682
|
+
return this.ok(false, desc, ret.err);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
else
|
|
686
|
+
{ // No errors, good, testing against the return value.
|
|
687
|
+
got = ret.val;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const CFUN =
|
|
691
|
+
{
|
|
692
|
+
'matches': 'matches',
|
|
693
|
+
'isa': 'isa',
|
|
694
|
+
'nota': 'nota',
|
|
695
|
+
'isJSON': 'isJSON',
|
|
696
|
+
'=json': 'isJSON',
|
|
697
|
+
'isntJSON': 'isntJSON',
|
|
698
|
+
'!json': 'isntJSON',
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
const comp = opts.comp ?? 'is';
|
|
702
|
+
const stringify = opts.stringify ?? true;
|
|
703
|
+
|
|
704
|
+
if (typeof CFUN[comp] === S)
|
|
705
|
+
{ // A function with a custom return value.
|
|
706
|
+
const meth = CFUN[comp];
|
|
707
|
+
return this[meth](got, want, desc, stringify);
|
|
708
|
+
}
|
|
709
|
+
else
|
|
710
|
+
{ // We're going to use the cmp() method.
|
|
711
|
+
return this.cmp(got, want, comp, desc, stringify);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
516
715
|
/**
|
|
517
716
|
* Skip a test.
|
|
518
717
|
*
|
|
@@ -522,7 +721,7 @@ class Test
|
|
|
522
721
|
*/
|
|
523
722
|
skip (reason, desc)
|
|
524
723
|
{
|
|
525
|
-
|
|
724
|
+
const log = this.ok(true, desc);
|
|
526
725
|
log.skipped = true;
|
|
527
726
|
if (typeof reason === S)
|
|
528
727
|
log.skippedReason = reason;
|
|
@@ -554,35 +753,55 @@ class Test
|
|
|
554
753
|
* methods in this class, it will be set as the *current* test method.
|
|
555
754
|
*
|
|
556
755
|
* If this is a `function`, it will be set as the *current* test method.
|
|
557
|
-
*
|
|
756
|
+
* By default function test methods are passed to `call()` with the test
|
|
757
|
+
* parameters. However, if the *previous* test method was `callIs` then
|
|
758
|
+
* the `callIs()` method will be used as long as the custom function is
|
|
759
|
+
* the *current* test method. Likewise to switch back to `call()` simply
|
|
760
|
+
* set the *current* test method to `call` before setting it to a new custom
|
|
761
|
+
* test `function`.
|
|
558
762
|
*
|
|
559
763
|
* 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
|
|
561
|
-
*
|
|
562
|
-
*
|
|
764
|
+
* method. If a custom `function` is in use, remember that it's the
|
|
765
|
+
* `call()` or `callIs()` methods that will be being called, with their
|
|
766
|
+
* first parameter always being the custom function.
|
|
563
767
|
*
|
|
564
768
|
* Any value other than one of those will throw a `TypeError`.
|
|
565
769
|
*
|
|
566
770
|
* @returns {Log[]} A `Log` item for each test that was ran.
|
|
567
771
|
*/
|
|
568
|
-
run(...tests)
|
|
772
|
+
run (...tests)
|
|
569
773
|
{
|
|
774
|
+
const CF = 'call';
|
|
775
|
+
const CI = 'callIs';
|
|
776
|
+
|
|
570
777
|
const logs = [];
|
|
778
|
+
let funcall = CF;
|
|
571
779
|
let current = 'ok';
|
|
572
780
|
|
|
573
781
|
for (const test of tests)
|
|
574
782
|
{
|
|
575
783
|
const tt = typeof test;
|
|
576
|
-
if (tt ===
|
|
577
|
-
{ // Set the current test.
|
|
784
|
+
if (tt === S && this.$testMethods.includes(test))
|
|
785
|
+
{ // Set the current test to a built-in.
|
|
578
786
|
current = test;
|
|
579
787
|
}
|
|
788
|
+
else if (tt === F)
|
|
789
|
+
{ // A custom test function for further tests.
|
|
790
|
+
if (current === CI)
|
|
791
|
+
{ // Last test was `callIs` using that for the custom function.
|
|
792
|
+
funcall = CI;
|
|
793
|
+
}
|
|
794
|
+
else if (current === CF)
|
|
795
|
+
{ // Last test was `call`, using that for the custom function.
|
|
796
|
+
funcall = CF;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
580
799
|
else if (isArray(test))
|
|
581
800
|
{ // A set of test parameters.
|
|
582
801
|
let log;
|
|
583
802
|
if (typeof current === F)
|
|
584
803
|
{ // A custom test function is in use.
|
|
585
|
-
log = this
|
|
804
|
+
log = this[funcall](current, ...test);
|
|
586
805
|
}
|
|
587
806
|
else
|
|
588
807
|
{ // A standard test is in use.
|
|
@@ -602,15 +821,14 @@ class Test
|
|
|
602
821
|
*/
|
|
603
822
|
tap ()
|
|
604
823
|
{
|
|
605
|
-
|
|
824
|
+
let out = '';
|
|
606
825
|
if (this.planned > 0)
|
|
607
826
|
{
|
|
608
827
|
out += '1..'+this.planned+"\n";
|
|
609
828
|
}
|
|
610
|
-
|
|
611
|
-
for (
|
|
829
|
+
let t = 1;
|
|
830
|
+
for (const log of this.log)
|
|
612
831
|
{
|
|
613
|
-
var log = this.log[i];
|
|
614
832
|
if (log instanceof Log)
|
|
615
833
|
{
|
|
616
834
|
out += log.tap(t++);
|
|
@@ -631,7 +849,7 @@ class Test
|
|
|
631
849
|
out += ' out of '+this.planned;
|
|
632
850
|
out += "\n";
|
|
633
851
|
}
|
|
634
|
-
|
|
852
|
+
const ran = t-1;
|
|
635
853
|
if (this.planned > 0 && this.planned != ran)
|
|
636
854
|
{
|
|
637
855
|
out += '# Looks like you planned '+this.planned+' but ran '+ran+" tests\n";
|
|
@@ -639,8 +857,38 @@ class Test
|
|
|
639
857
|
return out;
|
|
640
858
|
}
|
|
641
859
|
|
|
860
|
+
/**
|
|
861
|
+
* A read-only *accessor* property alias for `tap()`.
|
|
862
|
+
* @returns {string}
|
|
863
|
+
* @see {@link module:@lumjs/tests/test#tap}
|
|
864
|
+
*/
|
|
865
|
+
get TAP()
|
|
866
|
+
{
|
|
867
|
+
return this.tap();
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* A calculated property of the number of tests that were ran.
|
|
872
|
+
* @type {int}
|
|
873
|
+
*/
|
|
874
|
+
get ran ()
|
|
875
|
+
{
|
|
876
|
+
let ran = 0;
|
|
877
|
+
for (const log of this.log)
|
|
878
|
+
{
|
|
879
|
+
if (log instanceof Log)
|
|
880
|
+
{
|
|
881
|
+
ran++;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return ran;
|
|
885
|
+
}
|
|
886
|
+
|
|
642
887
|
/**
|
|
643
888
|
* Send the TAP output to the `console`.
|
|
889
|
+
*
|
|
890
|
+
* This is a low-level method and is no longer recommended for use.
|
|
891
|
+
* Instead call the `done()` method, which will *do the right thing*.
|
|
644
892
|
*/
|
|
645
893
|
output ()
|
|
646
894
|
{
|
|
@@ -648,6 +896,25 @@ class Test
|
|
|
648
896
|
return this;
|
|
649
897
|
}
|
|
650
898
|
|
|
899
|
+
/**
|
|
900
|
+
* We're done testing.
|
|
901
|
+
*
|
|
902
|
+
* This will mark the test-set as finished, so attempting to run further
|
|
903
|
+
* tests after will result in a `RangeError` being thrown.
|
|
904
|
+
*
|
|
905
|
+
* If no `Harness` is in use, this will also run `this.output()`.
|
|
906
|
+
*/
|
|
907
|
+
done ()
|
|
908
|
+
{
|
|
909
|
+
if (this.$done)
|
|
910
|
+
{
|
|
911
|
+
throw new RangeError('Test set is already done');
|
|
912
|
+
}
|
|
913
|
+
this.$done = true;
|
|
914
|
+
|
|
915
|
+
return (this.harness ? this : this.output());
|
|
916
|
+
}
|
|
917
|
+
|
|
651
918
|
} // class Test
|
|
652
919
|
|
|
653
920
|
// Should never need this, but...
|
|
@@ -661,12 +928,17 @@ def(Test, '$METHODS',
|
|
|
661
928
|
{
|
|
662
929
|
test: TEST_METHODS,
|
|
663
930
|
meta: META_METHODS,
|
|
931
|
+
$meta:
|
|
932
|
+
[ // A list of properties to skip in `all`.
|
|
933
|
+
'$meta', 'all', 'extend',
|
|
934
|
+
],
|
|
664
935
|
get all()
|
|
665
936
|
{
|
|
666
937
|
const list = [];
|
|
938
|
+
const skip = this.$meta;
|
|
667
939
|
for (const name in this)
|
|
668
940
|
{
|
|
669
|
-
if (name
|
|
941
|
+
if (skip.includes(name)) continue;
|
|
670
942
|
const prop = this[name];
|
|
671
943
|
if (isArray(prop))
|
|
672
944
|
{
|
|
@@ -675,7 +947,41 @@ def(Test, '$METHODS',
|
|
|
675
947
|
}
|
|
676
948
|
return list;
|
|
677
949
|
},
|
|
678
|
-
})
|
|
950
|
+
extend(newClass, opts={})
|
|
951
|
+
{
|
|
952
|
+
const mode = obj.CLONE.DEEP;
|
|
953
|
+
const clone = obj.clone(this, {mode});
|
|
954
|
+
|
|
955
|
+
//console.debug("extend:clone<pre>", clone);
|
|
956
|
+
|
|
957
|
+
if (isObj(opts.add))
|
|
958
|
+
{ // Add in the extra properties.
|
|
959
|
+
//console.debug("opts.add", opts.add);
|
|
960
|
+
obj.copyProps(opts.add, clone, opts.addOpts);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (isArray(opts.meta))
|
|
964
|
+
{ // Added properties to skip in 'all'.
|
|
965
|
+
for (const val of opts.meta)
|
|
966
|
+
{
|
|
967
|
+
if (!clone.$meta.includes(val))
|
|
968
|
+
{
|
|
969
|
+
clone.$meta.push(val);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
//console.debug("extend:clone<post>", clone);
|
|
975
|
+
|
|
976
|
+
// Add a new cloned and extended `$METHODS` property.
|
|
977
|
+
def(newClass, '$METHODS', {value: clone});
|
|
978
|
+
}, // extend()
|
|
979
|
+
}); // Test.$METHODS
|
|
679
980
|
|
|
680
981
|
// Export the class
|
|
681
982
|
module.exports = Test;
|
|
983
|
+
|
|
984
|
+
// Finally at the bottom after `module.exports` has been set, we will load
|
|
985
|
+
// the Harness class to avoid circular references failing.
|
|
986
|
+
const Harness = require('./harness');
|
|
987
|
+
|
package/package.json
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lumjs/tests",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"main": "lib/index.js",
|
|
5
|
+
"exports":
|
|
6
|
+
{
|
|
7
|
+
".": "./lib/index.js",
|
|
8
|
+
"./test": "./lib/test.js",
|
|
9
|
+
"./functional": "./lib/functional.js",
|
|
10
|
+
"./harness": "./lib/harness.js",
|
|
11
|
+
"./package.json": "./package.json",
|
|
12
|
+
"./data/*": "./test/data/*.js"
|
|
13
|
+
},
|
|
5
14
|
"license": "MIT",
|
|
6
15
|
"repository":
|
|
7
16
|
{
|
|
@@ -9,11 +18,12 @@
|
|
|
9
18
|
"url": "https://github.com/supernovus/lum.tests.js.git"
|
|
10
19
|
},
|
|
11
20
|
"dependencies": {
|
|
12
|
-
"@lumjs/core": "^1.
|
|
21
|
+
"@lumjs/core": "^1.3.0"
|
|
13
22
|
},
|
|
14
23
|
"scripts":
|
|
15
24
|
{
|
|
16
25
|
"-TODO-1": "When Harness and bin/lumtest are done, use that for 'test'",
|
|
17
|
-
"test": "prove -e node --ext js ./test"
|
|
26
|
+
"test": "prove -e node --ext js ./test",
|
|
27
|
+
"build-docs": "jsdoc -c ./jsdoc.json"
|
|
18
28
|
}
|
|
19
29
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module.exports = function (opts={})
|
|
2
|
+
{
|
|
3
|
+
let people =
|
|
4
|
+
[
|
|
5
|
+
{
|
|
6
|
+
name: 'Bob',
|
|
7
|
+
age: 40,
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
name: 'Lisa',
|
|
11
|
+
age: 25,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: 'Kevin',
|
|
15
|
+
age: 18,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Sarah',
|
|
19
|
+
age: 13,
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
if (opts.withRecursion)
|
|
24
|
+
{
|
|
25
|
+
people[0].kids = [people[2],people[3]];
|
|
26
|
+
people[1].kids = [people[3]];
|
|
27
|
+
people[2].kids = [];
|
|
28
|
+
people[3].kids = [];
|
|
29
|
+
people[0].parents = [];
|
|
30
|
+
people[1].parents = [];
|
|
31
|
+
people[2].parents = [people[0]];
|
|
32
|
+
people[3].parents = [people[0],people[1]];
|
|
33
|
+
}
|
|
34
|
+
else if (opts.withReferences)
|
|
35
|
+
{
|
|
36
|
+
people[0].kids = [2,3];
|
|
37
|
+
people[1].kids = [3];
|
|
38
|
+
people[2].kids = [];
|
|
39
|
+
people[3].kids = [];
|
|
40
|
+
people[0].parents = [];
|
|
41
|
+
people[1].parents = [];
|
|
42
|
+
people[2].parents = [0];
|
|
43
|
+
people[3].parents = [0,1];
|
|
44
|
+
}
|
|
45
|
+
else if (opts.withNames)
|
|
46
|
+
{
|
|
47
|
+
people[0].kids = [people[2].name,people[3].name];
|
|
48
|
+
people[1].kids = [people[3].name];
|
|
49
|
+
people[2].kids = [];
|
|
50
|
+
people[3].kids = [];
|
|
51
|
+
people[0].parents = [];
|
|
52
|
+
people[1].parents = [];
|
|
53
|
+
people[2].parents = [people[0].name];
|
|
54
|
+
people[3].parents = [people[0].name,people[1].name];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return people;
|
|
58
|
+
}
|