@lumjs/tests 1.0.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 +16 -0
- package/README.md +32 -0
- package/TODO.md +7 -0
- package/index.js +23 -0
- package/package.json +14 -0
- package/src/functional.js +77 -0
- package/src/harness.js +16 -0
- package/src/log.js +85 -0
- package/src/test.js +402 -0
- package/test/basics.js +32 -0
- package/test/dies.js +17 -0
- package/test/functional_basics.js +33 -0
- package/test/functional_dies.js +17 -0
- package/test/inc/basics.js +42 -0
- package/test/inc/dies.js +78 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
All notable changes to this project will be documented in this file.
|
|
3
|
+
|
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [1.0.0] - 2022-06-22
|
|
10
|
+
### Added
|
|
11
|
+
- Ported from Lum.js v4 library set.
|
|
12
|
+
- Added a few more features from the PHP version.
|
|
13
|
+
|
|
14
|
+
[Unreleased]: https://github.com/supernovus/simpledom/compare/v1.0.0...HEAD
|
|
15
|
+
[1.0.0]: https://github.com/supernovus/simpledom/releases/tag/v1.0.0
|
|
16
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# lum.tests.js
|
|
2
|
+
|
|
3
|
+
An extremely simple and minimal testing framework.
|
|
4
|
+
|
|
5
|
+
This is nowhere near as advanced as many of the other options out there,
|
|
6
|
+
but does what I need it to do, and works with the TAP testing protocol that
|
|
7
|
+
the Perl and Raku programming languages popularized.
|
|
8
|
+
|
|
9
|
+
## Exports
|
|
10
|
+
|
|
11
|
+
| Name | Description |
|
|
12
|
+
| -------------------- | ---------------------------------------------------- |
|
|
13
|
+
| `Test` | The core class for testing. |
|
|
14
|
+
| `Harness` | A class for running a bunch of tests together. |
|
|
15
|
+
| `functional()` | A function for using functional-style testing. |
|
|
16
|
+
| `new()` | A function for getting a new `Test` instance. |
|
|
17
|
+
|
|
18
|
+
## Official URLs
|
|
19
|
+
|
|
20
|
+
This library can be found in two places:
|
|
21
|
+
|
|
22
|
+
* [Github](https://github.com/supernovus/lum.tests.js)
|
|
23
|
+
* [NPM](https://www.npmjs.com/package/@lumjs/tests)
|
|
24
|
+
|
|
25
|
+
## Author
|
|
26
|
+
|
|
27
|
+
Timothy Totten <2010@totten.ca>
|
|
28
|
+
|
|
29
|
+
## License
|
|
30
|
+
|
|
31
|
+
[MIT](https://spdx.org/licenses/MIT.html)
|
|
32
|
+
|
package/TODO.md
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Several test related classes.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const Test = require('./src/test');
|
|
6
|
+
|
|
7
|
+
module.exports.Test = Test;
|
|
8
|
+
module.exports.Harness = require('./src/harness');
|
|
9
|
+
|
|
10
|
+
// This one is not a class, but a special function.
|
|
11
|
+
module.exports.functional = require('./src/functional');
|
|
12
|
+
|
|
13
|
+
// This one is a quick shortcut function.
|
|
14
|
+
module.exports.new = function({plan,module}={})
|
|
15
|
+
{
|
|
16
|
+
const test = new Test(plan);
|
|
17
|
+
if (module)
|
|
18
|
+
{
|
|
19
|
+
module.exports = test;
|
|
20
|
+
}
|
|
21
|
+
return test;
|
|
22
|
+
}
|
|
23
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// A functional interface to the test library.
|
|
2
|
+
const Test = require('./test');
|
|
3
|
+
|
|
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
|
+
];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A new test instance and a set of functions wrapping it.
|
|
13
|
+
* @typedef {object} Functional
|
|
14
|
+
* @property {Test} test - The new test instance.
|
|
15
|
+
*
|
|
16
|
+
* All of the rest of the properties are functions that
|
|
17
|
+
* can be imported into a JS scope using destructuring, and
|
|
18
|
+
* 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
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create a function that will export a bunch of wrapper
|
|
37
|
+
* functions that can be imported via object destructuring.
|
|
38
|
+
*
|
|
39
|
+
* Usage is like:
|
|
40
|
+
*
|
|
41
|
+
* ```js
|
|
42
|
+
* const {plan,ok,isa} = require('@lumjs/tests').functional({module});
|
|
43
|
+
*
|
|
44
|
+
* plan(2);
|
|
45
|
+
* ok(true, 'ok() works');
|
|
46
|
+
* isa(isa, 'function', 'isa is a function');
|
|
47
|
+
*
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* Because each time this function is run it creates a new test instance,
|
|
51
|
+
* and a new set of wrapper functions for the test instance, you could
|
|
52
|
+
* create multiple sets of functional tests in their own private scopes.
|
|
53
|
+
* Not sure why you'd want to do that, but in case you do, there ya go.
|
|
54
|
+
*
|
|
55
|
+
* @returns {Functional}
|
|
56
|
+
*
|
|
57
|
+
*/
|
|
58
|
+
function functional({plan,module}={})
|
|
59
|
+
{
|
|
60
|
+
const test = new Test(plan);
|
|
61
|
+
if (module)
|
|
62
|
+
{
|
|
63
|
+
module.exports = test;
|
|
64
|
+
}
|
|
65
|
+
const functions = { test };
|
|
66
|
+
for (const meth of PROXY_METHODS)
|
|
67
|
+
{
|
|
68
|
+
functions[meth] = function ()
|
|
69
|
+
{
|
|
70
|
+
return test[meth](...arguments);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return functions;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Export the function itself.
|
|
77
|
+
module.exports = functional;
|
package/src/harness.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
const Test = require('./test');
|
|
3
|
+
const Log = require('./log');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A class that acts as a test harness for running other tests.
|
|
7
|
+
*
|
|
8
|
+
* Based off of the Lum.php Test\Harness class.
|
|
9
|
+
*/
|
|
10
|
+
class Harness
|
|
11
|
+
{
|
|
12
|
+
construct()
|
|
13
|
+
{
|
|
14
|
+
throw new Error("Not yet implemented");
|
|
15
|
+
}
|
|
16
|
+
}
|
package/src/log.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
|
|
2
|
+
const {F,S,O} = require('@lumjs/core').types;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A log representing the results of a test.
|
|
6
|
+
*
|
|
7
|
+
* @property {boolean} ok - Did the test pass.
|
|
8
|
+
* @property {boolean} skipped - Was the test skipped?
|
|
9
|
+
* @property {string} skippedReason - If the test was skipped, why?
|
|
10
|
+
* @property {?string} desc - A short description of the test.
|
|
11
|
+
* @property {*} directive - Special directives describing the test.
|
|
12
|
+
* @property {object} details - Extra information about the test.
|
|
13
|
+
*
|
|
14
|
+
* @property {*} [details.got] The value that was received in an `is` test.
|
|
15
|
+
* @property {*} [details.wanted] The value that was expectged in an `is` test.
|
|
16
|
+
* @property {*} [details.stringify] If `true` then output the details as JSON.
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
class Log
|
|
20
|
+
{
|
|
21
|
+
constructor ()
|
|
22
|
+
{
|
|
23
|
+
this.ok = false;
|
|
24
|
+
this.skipped = false;
|
|
25
|
+
this.skippedReason = '';
|
|
26
|
+
this.desc = null;
|
|
27
|
+
this.directive = null;
|
|
28
|
+
this.details = {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Output this Log object in the TAP format.
|
|
33
|
+
*
|
|
34
|
+
* Generally this is never needed to be used by outside code.
|
|
35
|
+
* The `Test.tap()` method uses it when generating a full log.
|
|
36
|
+
*
|
|
37
|
+
* @param {*} num - The test #
|
|
38
|
+
* @returns {string} The TAP log.
|
|
39
|
+
*/
|
|
40
|
+
tap (num)
|
|
41
|
+
{
|
|
42
|
+
var out;
|
|
43
|
+
if (this.ok)
|
|
44
|
+
out = 'ok ';
|
|
45
|
+
else
|
|
46
|
+
out = 'not ok ';
|
|
47
|
+
|
|
48
|
+
out += num;
|
|
49
|
+
|
|
50
|
+
if (typeof this.desc === S)
|
|
51
|
+
out += ' - ' + this.desc;
|
|
52
|
+
|
|
53
|
+
if (typeof this.directive === S)
|
|
54
|
+
out += ' # ' + this.directive;
|
|
55
|
+
else if (typeof this.directive == O && this.directive instanceof Error)
|
|
56
|
+
out += ' # ' + this.directive.name + ': ' + this.directive.message;
|
|
57
|
+
else if (this.skipped)
|
|
58
|
+
out += ' # SKIP ' + this.skippedReason;
|
|
59
|
+
|
|
60
|
+
out += "\n";
|
|
61
|
+
|
|
62
|
+
if ('got' in this.details && 'wanted' in this.details)
|
|
63
|
+
{
|
|
64
|
+
var got = this.details.got;
|
|
65
|
+
var want = this.details.wanted;
|
|
66
|
+
if (this.details.stringify)
|
|
67
|
+
{
|
|
68
|
+
got = (typeof got === F) ? got.toString() : JSON.stringify(got);
|
|
69
|
+
want = (typeof want === F) ? want.toString() : JSON.stringify(want);
|
|
70
|
+
}
|
|
71
|
+
out += '# got: ' + got + "\n";
|
|
72
|
+
out += '# expected: ' + want + "\n";
|
|
73
|
+
if (typeof this.details.comparitor === S)
|
|
74
|
+
{
|
|
75
|
+
out += '# op: ' + this.details.comparitor + "\n";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return out;
|
|
80
|
+
} // Log.tap()
|
|
81
|
+
|
|
82
|
+
} // class Log
|
|
83
|
+
|
|
84
|
+
// Export the class itself.
|
|
85
|
+
module.exports = Log;
|
package/src/test.js
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
|
|
2
|
+
const {F,S,N,isType,isInstance,def} = require('@lumjs/core').types;
|
|
3
|
+
|
|
4
|
+
// We use a separate class to represent test logs.
|
|
5
|
+
const Log = require('./log');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A simple testing library with TAP support.
|
|
9
|
+
*
|
|
10
|
+
* Based on Lum.php's Test library.
|
|
11
|
+
* Which itself was based on Perl 5's Test::More, and
|
|
12
|
+
* Raku's Test libraries.
|
|
13
|
+
*/
|
|
14
|
+
class Test
|
|
15
|
+
{
|
|
16
|
+
/**
|
|
17
|
+
* Build a new Test instance.
|
|
18
|
+
*
|
|
19
|
+
* @param {number} [plan] If used, passed to `plan()` method.
|
|
20
|
+
*
|
|
21
|
+
*/
|
|
22
|
+
constructor (plan)
|
|
23
|
+
{
|
|
24
|
+
this.failed = 0;
|
|
25
|
+
this.skipped = 0;
|
|
26
|
+
this.planned = 0;
|
|
27
|
+
this.log = [];
|
|
28
|
+
if (plan)
|
|
29
|
+
{
|
|
30
|
+
this.plan(plan);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Indicate how many tests are planned to be run in this set.
|
|
36
|
+
*
|
|
37
|
+
* @param {number} num - The number of tests that should run.
|
|
38
|
+
*/
|
|
39
|
+
plan (num)
|
|
40
|
+
{
|
|
41
|
+
if (typeof num === N)
|
|
42
|
+
{
|
|
43
|
+
this.planned = num;
|
|
44
|
+
}
|
|
45
|
+
else if (num === false)
|
|
46
|
+
{
|
|
47
|
+
this.planned = 0;
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
{
|
|
51
|
+
throw new Error("Invalid value passed to plan()");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if a value is truthy, and add a log indicating the result.
|
|
57
|
+
*
|
|
58
|
+
* This is the absolute most basic method that every other testing method
|
|
59
|
+
* uses to log the results.
|
|
60
|
+
*
|
|
61
|
+
* I won't document the `desc` and `directive` parameters on any of the
|
|
62
|
+
* other methods as they are exactly the same as here.
|
|
63
|
+
*
|
|
64
|
+
* @param {*} test - Any value, usually the output of a test function.
|
|
65
|
+
* If the value evaluates as `true` (aka a truthy value), the test passes.
|
|
66
|
+
* If it evaluates as `false` (a falsey value), the test fails.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} desc - A short description of the test.
|
|
69
|
+
*
|
|
70
|
+
* @param {(string|Error)} [directive] Further information for the log.
|
|
71
|
+
*
|
|
72
|
+
* @returns {Log} The test log with the results.
|
|
73
|
+
*/
|
|
74
|
+
ok (test, desc, directive)
|
|
75
|
+
{
|
|
76
|
+
const log = new Log();
|
|
77
|
+
|
|
78
|
+
if (test)
|
|
79
|
+
{
|
|
80
|
+
log.ok = true;
|
|
81
|
+
}
|
|
82
|
+
else
|
|
83
|
+
{
|
|
84
|
+
this.failed++;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (typeof desc === S)
|
|
88
|
+
{
|
|
89
|
+
log.desc = desc;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (directive)
|
|
93
|
+
{
|
|
94
|
+
log.directive = directive;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
this.log.push(log);
|
|
98
|
+
|
|
99
|
+
return log;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Mark a test as failed.
|
|
104
|
+
*
|
|
105
|
+
* @param {string} desc
|
|
106
|
+
* @param {*} [directive]
|
|
107
|
+
* @returns {Log}
|
|
108
|
+
*/
|
|
109
|
+
fail (desc, directive)
|
|
110
|
+
{
|
|
111
|
+
return this.ok(false, desc, directive);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Mark a test as passed.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} desc
|
|
118
|
+
* @param {*} [directive]
|
|
119
|
+
* @returns {Log}
|
|
120
|
+
*/
|
|
121
|
+
pass (desc, directive)
|
|
122
|
+
{
|
|
123
|
+
return this.ok(true, desc, directive);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* See if a function throws an Error.
|
|
128
|
+
*
|
|
129
|
+
* The function will be called in a `try { } catch (err) { }` block.
|
|
130
|
+
*
|
|
131
|
+
* If an error is caught, the test will be considered to have passed,
|
|
132
|
+
* and the `Error` object will be used as the `directive` in the `ok()`
|
|
133
|
+
* call. If no error is caught the test will be considered to have failed.
|
|
134
|
+
*
|
|
135
|
+
* @param {function} testfunc
|
|
136
|
+
* @param {string} desc
|
|
137
|
+
* @returns {Log}
|
|
138
|
+
*/
|
|
139
|
+
dies (testfunc, desc)
|
|
140
|
+
{
|
|
141
|
+
let ok = false;
|
|
142
|
+
let err;
|
|
143
|
+
try { testfunc(); }
|
|
144
|
+
catch (e)
|
|
145
|
+
{
|
|
146
|
+
ok = true;
|
|
147
|
+
err = e;
|
|
148
|
+
}
|
|
149
|
+
return this.ok(ok, desc, err);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* See if a value is what we expect it to be.
|
|
154
|
+
*
|
|
155
|
+
* @param {*} got - The result value from the test function.
|
|
156
|
+
* @param {*} want - The value we expected from the test function.
|
|
157
|
+
* @param {string} comp - A comparitor to test with.
|
|
158
|
+
*
|
|
159
|
+
* - `===`, `is` (See also: `is()`)
|
|
160
|
+
* - `!==`, `isnt` (See also: `isnt()`)
|
|
161
|
+
* - `==`, `eq`
|
|
162
|
+
* - `!=`, `ne`
|
|
163
|
+
* - `>`, `gt`
|
|
164
|
+
* - `<`, `lt`
|
|
165
|
+
* - `>=`, `ge`, `gte`
|
|
166
|
+
* - `<=`, `le`, `lte`
|
|
167
|
+
*
|
|
168
|
+
* @param {string} desc
|
|
169
|
+
* @param {boolean} [stringify=true] Stringify values in TAP output?
|
|
170
|
+
* @returns {Log}
|
|
171
|
+
*/
|
|
172
|
+
cmp (got, want, comp, desc, stringify=true)
|
|
173
|
+
{
|
|
174
|
+
let test;
|
|
175
|
+
switch(comp)
|
|
176
|
+
{
|
|
177
|
+
case 'is':
|
|
178
|
+
case '===':
|
|
179
|
+
test = (got === want);
|
|
180
|
+
break;
|
|
181
|
+
case 'isnt':
|
|
182
|
+
case '!==':
|
|
183
|
+
test = (got !== want);
|
|
184
|
+
break;
|
|
185
|
+
case 'eq':
|
|
186
|
+
case '==':
|
|
187
|
+
test = (got == want);
|
|
188
|
+
break;
|
|
189
|
+
case 'ne':
|
|
190
|
+
case '!=':
|
|
191
|
+
test = (got != want);
|
|
192
|
+
break;
|
|
193
|
+
case 'lt':
|
|
194
|
+
case '<':
|
|
195
|
+
test = (got < want);
|
|
196
|
+
break;
|
|
197
|
+
case '>':
|
|
198
|
+
case 'gt':
|
|
199
|
+
test = (got > want);
|
|
200
|
+
break;
|
|
201
|
+
case 'le':
|
|
202
|
+
case 'lte':
|
|
203
|
+
case '<=':
|
|
204
|
+
test = (got <= want);
|
|
205
|
+
break;
|
|
206
|
+
case 'ge':
|
|
207
|
+
case 'gte':
|
|
208
|
+
case '>=':
|
|
209
|
+
test = (got >= want);
|
|
210
|
+
break;
|
|
211
|
+
default:
|
|
212
|
+
test = false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const log = this.ok(test, desc);
|
|
216
|
+
if (!test)
|
|
217
|
+
{
|
|
218
|
+
log.details.got = got;
|
|
219
|
+
log.details.wanted = want;
|
|
220
|
+
log.details.stringify = stringify;
|
|
221
|
+
log.details.comparitor = comp;
|
|
222
|
+
}
|
|
223
|
+
return log;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* See if two values are equal.
|
|
228
|
+
*
|
|
229
|
+
* The same as using `cmp()` with the `===` comparitor.
|
|
230
|
+
*
|
|
231
|
+
* @param {*} got
|
|
232
|
+
* @param {*} want
|
|
233
|
+
* @param {string} desc
|
|
234
|
+
* @param {boolean} [stringify=true]
|
|
235
|
+
* @returns {Log}
|
|
236
|
+
*/
|
|
237
|
+
is (got, want, desc, stringify=true)
|
|
238
|
+
{
|
|
239
|
+
return this.cmp(got, want, '===', desc, stringify);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* See if two values are NOT equal.
|
|
244
|
+
*
|
|
245
|
+
* The same as using `cmp()` with the `!==` comparitor.
|
|
246
|
+
*
|
|
247
|
+
* @param {*} got - The result value from the test function.
|
|
248
|
+
* @param {*} want - The value we expected from the test function.
|
|
249
|
+
* @param {string} desc
|
|
250
|
+
* @param {boolean} [stringify=true] Use JSON details in TAP output?
|
|
251
|
+
* @returns {Log}
|
|
252
|
+
*/
|
|
253
|
+
isnt (got, want, desc, stringify=true)
|
|
254
|
+
{
|
|
255
|
+
return this.cmp(got, want, '!==', desc, stringify);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* See if a value is of a certain type.
|
|
260
|
+
*
|
|
261
|
+
* @param {*} got
|
|
262
|
+
* @param {(string|function)} want - A type name, or a constructor function.
|
|
263
|
+
*
|
|
264
|
+
* This uses two type checking functions from `@lumsj/core`.
|
|
265
|
+
* If this is a string, we use the `isType()` function.
|
|
266
|
+
* If this is a constructor function, we use the `isInstance()` function.
|
|
267
|
+
*
|
|
268
|
+
* @param {string} desc
|
|
269
|
+
* @param {boolean} [stringify=true]
|
|
270
|
+
* @returns {Log}
|
|
271
|
+
*/
|
|
272
|
+
isa (got, want, desc, stringify=true)
|
|
273
|
+
{
|
|
274
|
+
let test;
|
|
275
|
+
if (typeof want === S)
|
|
276
|
+
{ // A string, we're going to use the isType function.
|
|
277
|
+
test = isType(want, got);
|
|
278
|
+
}
|
|
279
|
+
else if (typeof want === F)
|
|
280
|
+
{ // Assuming it's a constructor.
|
|
281
|
+
test = isInstance(got, want);
|
|
282
|
+
}
|
|
283
|
+
else
|
|
284
|
+
{ // That's not supported.
|
|
285
|
+
throw new TypeError("'want' must be a string or a constructor");
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const log = this.ok(test, desc);
|
|
289
|
+
if (!test)
|
|
290
|
+
{
|
|
291
|
+
log.details.got = got;
|
|
292
|
+
log.details.wanted = want;
|
|
293
|
+
log.details.stringify = stringify;
|
|
294
|
+
}
|
|
295
|
+
return log;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* An `is()` test, but encode both values as JSON first.
|
|
300
|
+
*
|
|
301
|
+
* @param {*} got
|
|
302
|
+
* @param {*} want
|
|
303
|
+
* @param {string} desc
|
|
304
|
+
* @returns
|
|
305
|
+
*/
|
|
306
|
+
isJSON (got, want, desc)
|
|
307
|
+
{
|
|
308
|
+
got = JSON.stringify(got);
|
|
309
|
+
want = JSON.stringify(want);
|
|
310
|
+
return this.is(got, want, desc, false);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Skip a test.
|
|
315
|
+
*
|
|
316
|
+
* @param {?string} reason - Why the test was skipped.
|
|
317
|
+
* @param {string} desc
|
|
318
|
+
* @returns {Log}
|
|
319
|
+
*/
|
|
320
|
+
skip (reason, desc)
|
|
321
|
+
{
|
|
322
|
+
var log = this.ok(true, desc);
|
|
323
|
+
log.skipped = true;
|
|
324
|
+
if (typeof reason === S)
|
|
325
|
+
log.skippedReason = reason;
|
|
326
|
+
this.skipped++;
|
|
327
|
+
return log;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Add diagnostics info directly to our test logs.
|
|
332
|
+
*
|
|
333
|
+
* @param {*} msg - The info to add.
|
|
334
|
+
* If this is a `string` it will be displayed as a comment as is.
|
|
335
|
+
* If it is anything else, it will be encoded as JSON first.
|
|
336
|
+
*
|
|
337
|
+
*/
|
|
338
|
+
diag (msg)
|
|
339
|
+
{
|
|
340
|
+
this.log.push(msg);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Return TAP formatted output for all the tests.
|
|
345
|
+
*
|
|
346
|
+
* @returns {string} The test logs, in TAP format.
|
|
347
|
+
*/
|
|
348
|
+
tap ()
|
|
349
|
+
{
|
|
350
|
+
var out = '';
|
|
351
|
+
if (this.planned > 0)
|
|
352
|
+
{
|
|
353
|
+
out += '1..'+this.planned+"\n";
|
|
354
|
+
}
|
|
355
|
+
var t = 1;
|
|
356
|
+
for (var i = 0; i < this.log.length; i++)
|
|
357
|
+
{
|
|
358
|
+
var log = this.log[i];
|
|
359
|
+
if (log instanceof Log)
|
|
360
|
+
{
|
|
361
|
+
out += log.tap(t++);
|
|
362
|
+
}
|
|
363
|
+
else
|
|
364
|
+
{ // A comment.
|
|
365
|
+
out += '# ' + (typeof log === S ? log : JSON.stringify(log)) + "\n";
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (this.skipped)
|
|
369
|
+
{
|
|
370
|
+
out += '# Skipped '+this.skipped+" tests\n";
|
|
371
|
+
}
|
|
372
|
+
if (this.failed)
|
|
373
|
+
{
|
|
374
|
+
out += '# Failed '+this.failed+(this.failed>1?' tests':' test');
|
|
375
|
+
if (this.planned)
|
|
376
|
+
out += ' out of '+this.planned;
|
|
377
|
+
out += "\n";
|
|
378
|
+
}
|
|
379
|
+
var ran = t-1;
|
|
380
|
+
if (this.planned > 0 && this.planned != ran)
|
|
381
|
+
{
|
|
382
|
+
out += '# Looks like you planned '+this.planned+' but ran '+ran+" tests\n";
|
|
383
|
+
}
|
|
384
|
+
return out;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Send the TAP output to the `console`.
|
|
389
|
+
*/
|
|
390
|
+
output ()
|
|
391
|
+
{
|
|
392
|
+
console.log(this.tap());
|
|
393
|
+
return this;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
} // class Test
|
|
397
|
+
|
|
398
|
+
// Should never need this, but...
|
|
399
|
+
def(Test, 'Log', Log);
|
|
400
|
+
|
|
401
|
+
// Export the class
|
|
402
|
+
module.exports = Test;
|
package/test/basics.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const Test = require('../index').Test;
|
|
2
|
+
const def = require('./inc/basics.js');
|
|
3
|
+
|
|
4
|
+
const test = new Test();
|
|
5
|
+
test.plan(def.plan);
|
|
6
|
+
|
|
7
|
+
test.ok(def.okay, 'ok()');
|
|
8
|
+
test.pass('pass()');
|
|
9
|
+
|
|
10
|
+
for (const is of def.isTests)
|
|
11
|
+
{
|
|
12
|
+
const t = JSON.stringify(is);
|
|
13
|
+
test.is(is, is, 'is('+t+','+t+')');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
for (const [a,b] of def.isntTests)
|
|
17
|
+
{
|
|
18
|
+
const msg = `isnt(${JSON.stringify(a)},${JSON.stringify(b)})`;
|
|
19
|
+
test.isnt(a, b, msg);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const args of def.cmpTests)
|
|
23
|
+
{
|
|
24
|
+
const msg = 'cmp('+JSON.stringify(args)+')';
|
|
25
|
+
test.cmp(...args, msg);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// We're done, output the log.
|
|
29
|
+
console.log(test.tap());
|
|
30
|
+
|
|
31
|
+
// Re-expect the test.
|
|
32
|
+
module.exports = test;
|
package/test/dies.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const def = require('./inc/dies.js');
|
|
2
|
+
// Using the 'new()' function.
|
|
3
|
+
const test = require('../index').new({module, plan: def.plan});
|
|
4
|
+
|
|
5
|
+
for (const dt of def.diesErrors)
|
|
6
|
+
{
|
|
7
|
+
test.dies(...dt);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
for (const dt of def.diesSyntaxErrors)
|
|
11
|
+
{
|
|
12
|
+
test.dies(...dt);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// We're done, output the log.
|
|
16
|
+
test.output();
|
|
17
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Testing the basic functions of the functional testing API.
|
|
2
|
+
|
|
3
|
+
const {test,plan,ok,pass,is,isnt,cmp,tap} = require('../index').functional();
|
|
4
|
+
const def = require('./inc/basics.js');
|
|
5
|
+
|
|
6
|
+
plan(def.plan);
|
|
7
|
+
|
|
8
|
+
ok(def.okay, 'ok()');
|
|
9
|
+
pass('pass()');
|
|
10
|
+
|
|
11
|
+
for (const it of def.isTests)
|
|
12
|
+
{
|
|
13
|
+
const t = JSON.stringify(it);
|
|
14
|
+
is(it, it, 'is('+t+','+t+')');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
for (const [a,b] of def.isntTests)
|
|
18
|
+
{
|
|
19
|
+
const msg = `isnt(${JSON.stringify(a)},${JSON.stringify(b)})`;
|
|
20
|
+
isnt(a, b, msg);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
for (const args of def.cmpTests)
|
|
24
|
+
{
|
|
25
|
+
const msg = 'cmp('+JSON.stringify(args)+')';
|
|
26
|
+
cmp(...args, msg);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// We're done, output the log.
|
|
30
|
+
console.log(tap());
|
|
31
|
+
|
|
32
|
+
// And re-export the test.
|
|
33
|
+
module.exports = test;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const def = require('./inc/dies.js');
|
|
2
|
+
|
|
3
|
+
const {dies,output} = require('../index').functional({module, plan: def.plan});
|
|
4
|
+
|
|
5
|
+
for (const dt of def.diesErrors)
|
|
6
|
+
{
|
|
7
|
+
dies(...dt);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
for (const dt of def.diesSyntaxErrors)
|
|
11
|
+
{
|
|
12
|
+
dies(...dt);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// We're done, output the log.
|
|
16
|
+
output();
|
|
17
|
+
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Common stuff for both regular and functional tests.
|
|
2
|
+
|
|
3
|
+
const okay = (1==1);
|
|
4
|
+
|
|
5
|
+
const isTests = [1, 1.1, 'a', true, false, [1,2,3], null];
|
|
6
|
+
const isntTests =
|
|
7
|
+
[ // Tests to compare strict non-equality.
|
|
8
|
+
[1, '1'],
|
|
9
|
+
[1.1, '1.1'],
|
|
10
|
+
[true, 1],
|
|
11
|
+
[false, 0],
|
|
12
|
+
[false, null],
|
|
13
|
+
['', null],
|
|
14
|
+
[[1,2], [2,1]],
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const cmpTests =
|
|
18
|
+
[ // Tests to use custom comparisons and aliases.
|
|
19
|
+
[1, 1, '==='],
|
|
20
|
+
[1, 1, 'is'],
|
|
21
|
+
[0, 1, '!=='],
|
|
22
|
+
[0, 1, 'isnt'],
|
|
23
|
+
[1, '1', 'eq'],
|
|
24
|
+
[0, false, 'eq'],
|
|
25
|
+
[1, true, '=='],
|
|
26
|
+
[1, false, 'ne'],
|
|
27
|
+
[0, true, '!='],
|
|
28
|
+
[1, 0, '>'],
|
|
29
|
+
[1, 0, 'gt'],
|
|
30
|
+
[1, 0, '>='],
|
|
31
|
+
[1, 0, 'ge'],
|
|
32
|
+
[1, 0, 'gte'],
|
|
33
|
+
// TODO: finish this set of tests.
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const plan
|
|
37
|
+
= 2
|
|
38
|
+
+ isTests.length
|
|
39
|
+
+ isntTests.length
|
|
40
|
+
+ cmpTests.length;
|
|
41
|
+
|
|
42
|
+
module.exports = {okay, isTests, isntTests, cmpTests, plan};
|
package/test/inc/dies.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Common include for testing the .dies() method.
|
|
2
|
+
|
|
3
|
+
// A custom error class.
|
|
4
|
+
class Exception extends Error
|
|
5
|
+
{
|
|
6
|
+
constructor(msg)
|
|
7
|
+
{
|
|
8
|
+
super(msg);
|
|
9
|
+
this.name = "Exception";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
exports.Exception = Exception;
|
|
14
|
+
|
|
15
|
+
// A simplistic class for testing.
|
|
16
|
+
class TestObject
|
|
17
|
+
{
|
|
18
|
+
static throwError()
|
|
19
|
+
{
|
|
20
|
+
throw new Error("error thrown statically");
|
|
21
|
+
}
|
|
22
|
+
static throwSyntaxError()
|
|
23
|
+
{
|
|
24
|
+
return this.noSuchMethod();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throwError()
|
|
28
|
+
{
|
|
29
|
+
throw new Error("error thrown");
|
|
30
|
+
}
|
|
31
|
+
throwSyntaxError()
|
|
32
|
+
{
|
|
33
|
+
return this.noSuchMethod();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
exports.TestObject = TestObject;
|
|
38
|
+
|
|
39
|
+
function makeException()
|
|
40
|
+
{
|
|
41
|
+
throw new Exception("exception made");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
exports.makeException = makeException;
|
|
45
|
+
|
|
46
|
+
function syntaxError()
|
|
47
|
+
{
|
|
48
|
+
noSuchFunction();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
exports.syntaxError = syntaxError;
|
|
52
|
+
|
|
53
|
+
const testObject = new TestObject();
|
|
54
|
+
|
|
55
|
+
const diesErrors =
|
|
56
|
+
[
|
|
57
|
+
[function() { throw new Error("001"); }, 'dies(thrown_from_inline_function)'],
|
|
58
|
+
[() => { throw new Error("002") }, 'dies(thrown_from_arrow_function)'],
|
|
59
|
+
[() => { throw new Exception("003")}, 'dies(threw_custom_error)'],
|
|
60
|
+
[makeException, 'dies(thrown_from_named_function)'],
|
|
61
|
+
[() => TestObject.throwError(), 'dies(thrown_from_static_method'],
|
|
62
|
+
[() => testObject.throwError(), 'dies(thrown_from_instance_method'],
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
exports.diesErrors = diesErrors;
|
|
66
|
+
|
|
67
|
+
const diesSyntaxErrors =
|
|
68
|
+
[
|
|
69
|
+
[function() { wrong(); }, 'dies(synax_error_from_inline_function)'],
|
|
70
|
+
[() => wrong(), 'dies(syntax_error_from_arrow_function)'],
|
|
71
|
+
[syntaxError, 'dies(syntax_error_from_named_function'],
|
|
72
|
+
[() => TestObject.throwSyntaxError(), 'dies(syntax_error_from_static_method)'],
|
|
73
|
+
[() => testObject.throwSyntaxError(), 'dies(syntax_error_from_instance_method)'],
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
exports.diesSyntaxErrors = diesSyntaxErrors;
|
|
77
|
+
|
|
78
|
+
exports.plan = diesErrors.length + diesSyntaxErrors.length;
|