@lumjs/tests 1.8.2 → 2.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 CHANGED
@@ -11,6 +11,51 @@ and a reference to a property of a module will be in `@tests.propName` format.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [2.0.0] - 2024-08-14
15
+ ### Added
16
+ - The `Stats` class (and `Test` which extends it) now has async support.
17
+ - A new `.waiting` integer property indicates if async calls are in use.
18
+ - New `opts.async` nested options added to constructor options.
19
+ - The `async()` method takes an async function that returns a Promise,
20
+ increments the `.waiting` property, and then decrements it once the
21
+ returned Promise is either resolved or rejected.
22
+ - A `wait()` method is used by the `done()` method, and the Harness
23
+ to wait for all async calls to finish.
24
+ - A new `registerGlobal` option for the `Harness` constructor that if `true`
25
+ will make it assign the instance to a special global variable.
26
+ ### Changed
27
+ - The `Harness` constructor now requires a `Plugin` as a mandatory
28
+ parameter. No more auto-detection or other crufty magic.
29
+ - The `bin/lumtest.js` script was updated with the API changes.
30
+ - The `Plugin` constructor no longer takes the Harness instance as
31
+ a paramter. The `harness` property will be assigned directly by
32
+ the harness instance instead.
33
+ - `Plugin` class no longer extends the deprecated `core.AbstractClass`.
34
+ - `Harness` has support for the new async functionality.
35
+ - The `run()` method is now explicitly `async`, as are its sub-methods.
36
+ - Moved Harness Plugins into a `harness/plugin` sub-folder.
37
+ - `Stats` can find the harness via the `registerGlobal` feature.
38
+ It still supports the CommonJS `require.main` method, but no
39
+ longer depends on it.
40
+ - The `functional` module now supports using a different name
41
+ for the exported function than the underlying method. Useful
42
+ as the `async()` method is a reserved word and cannot be used
43
+ as a variable/function name (so it's renamed to `callAsync()`).
44
+ - A bunch of extra modules are exported directly now.
45
+ - Renamed some constructors for easier debugging.
46
+ ### Removed
47
+ - The `harness/browser` plugin that I never implemented.
48
+ That's better done in a separate package.
49
+
50
+ ## [1.9.0] - 2024-07-27
51
+ ### Changed
52
+ - Renamed `Harness` constructor name to `LumTestsHarness` to be more unique.
53
+ - The `Stats` class no longer requires the `../harness` module, but instead
54
+ checks if the object instance's constructor name is `LumTestsHarness`.
55
+ This will allow the `test` and `test/functional` modules to be bundled
56
+ by Webpack without bundling `harness` which currently has some issues.
57
+ - Minor bug fixes from `1.8.1` and `1.8.2` unlisted releases.
58
+
14
59
  ## [1.8.0] - 2023-01-06
15
60
  ### Changed
16
61
  - Bumped `@lumjs/core` to `1.8.0`.
@@ -140,7 +185,9 @@ and a reference to a property of a module will be in `@tests.propName` format.
140
185
  - Ported from Lum.js v4 library set.
141
186
  - Added a few more features from the PHP version.
142
187
 
143
- [Unreleased]: https://github.com/supernovus/lum.tests.js/compare/v1.8.0...HEAD
188
+ [Unreleased]: https://github.com/supernovus/lum.tests.js/compare/v2.0.0...HEAD
189
+ [2.0.0]: https://github.com/supernovus/lum.tests.js/compare/v1.9.0...v2.0.0
190
+ [1.9.0]: https://github.com/supernovus/lum.tests.js/compare/v1.8.0...v1.9.0
144
191
  [1.8.0]: https://github.com/supernovus/lum.tests.js/compare/v1.7.1...v1.8.0
145
192
  [1.7.1]: https://github.com/supernovus/lum.tests.js/compare/v1.7.0...v1.7.1
146
193
  [1.7.0]: https://github.com/supernovus/lum.tests.js/compare/v1.6.0...v1.7.0
package/README.md CHANGED
@@ -6,15 +6,6 @@ This is nowhere near as advanced as many of the other options out there,
6
6
  but does what I need it to do, and works with the TAP testing protocol that
7
7
  the Perl and Raku programming languages popularized.
8
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
9
  ## Official URLs
19
10
 
20
11
  This library can be found in two places:
package/TODO.md CHANGED
@@ -1,12 +1,11 @@
1
1
  # TODO
2
2
 
3
- - Update to properly support `async` tests; the `harness` will need some work.
4
3
  - Write tests for:
5
4
  - `call()`
6
5
  - `diesWith()`
7
6
  - `callIs()`
8
7
  - `matches()`
9
8
  - `run()`
9
+ - `async()` (currently used in `@lumjs/encode:v2.0.0` package).
10
10
  - Test the `harness/parser` and associated `grammar/tap` libraries.
11
11
  - Test the *external* test mode in `Harness`.
12
-
package/bin/lumtest.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  const process = require('node:process');
3
3
  const Harness = require('../lib/harness');
4
+ const NodePlugin = require('../lib/harness/plugin/node');
4
5
  const core = require('@lumjs/core');
5
6
  const {S} = core.types;
6
7
 
@@ -59,7 +60,8 @@ for (const arg of args)
59
60
  }
60
61
  }
61
62
 
62
- const harness = new Harness(hOpts); // Create our Harness instance.
63
- module.exports = harness; // Export it as the 'main' module.
64
- harness.addDir(aDir, aOpts); // Add all the files in the test dir.
65
- harness.run(rPlan); // Run all the tests.
63
+ // Create our Harness instance.
64
+ const harness = new Harness(NodePlugin, hOpts);
65
+ module.exports = harness; // Export it as the 'main' module.
66
+ harness.addDir(aDir, aOpts); // Add all the files in the test dir.
67
+ harness.run(rPlan); // Run all the tests.
@@ -1,5 +1,5 @@
1
1
  const core = require('@lumjs/core');
2
- const {def,lazy,context:ctx} = core;
2
+ const {def,lazy} = core;
3
3
  const {B,F} = core.types;
4
4
  const QueuedTest = require('./queuedtest');
5
5
  const Plugin = require('./plugin');
@@ -12,16 +12,13 @@ const Plugin = require('./plugin');
12
12
  *
13
13
  * @prop {object} opts - Options passed to constructor
14
14
  *
15
- * @prop {object} testSuite - A `Stats` (or `Test`) instance
15
+ * @prop {module:@lumjs/tests/test/stats} testSuite
16
16
  *
17
17
  * This meta-test will keep track of the success or failure of
18
18
  * all the other tests. So its `plan` will be equal to the number of
19
19
  * tests we're running, and so forth.
20
20
  *
21
- * @prop {object} plugin - A platform-specific `Plugin`
22
- *
23
- * This may be provided as a parameter to the constructor,
24
- * or auto-selected based on our Javascript environment.
21
+ * @prop {module:@lumjs/tests/harness/plugin} plugin - JS platform plugin
25
22
  *
26
23
  * @prop {Array} queue - An array of `QueuedTest` instances
27
24
  * representing tests that we want to run.
@@ -33,20 +30,17 @@ const Plugin = require('./plugin');
33
30
  *
34
31
  * @exports module:@lumjs/tests/harness
35
32
  */
36
- class Harness
33
+ class LumTestsHarness
37
34
  {
38
35
  /**
39
36
  * Build a new Harness
40
37
  *
41
- * @param {object} [opts] Options
42
- * @param {object} [opts.testSuite] A `Test` or `Stats` instance
43
- *
44
- * This is probably not needed. We'll generate a new `Stats` instance.
38
+ * @param {module:@lumjs/tests/harness/plugin} plugin
39
+ * The plugin for the current JS environment.
45
40
  *
46
- * @param {object} [opts.plugin] A `Plugin` instance.
41
+ * You can pass an instance `object`, or a class constructor `function`.
47
42
  *
48
- * This is probably not needed. We'll automatically determine a default
49
- * plugin to use based on the JS environment.
43
+ * @param {object} [opts] Advanced Options
50
44
  *
51
45
  * @param {boolean} [opts.plannedFailure=true] Is a broken plan a failure?
52
46
  *
@@ -63,9 +57,35 @@ class Harness
63
57
  *
64
58
  * If this is `false` then no warning will be shown.
65
59
  *
60
+ * @param {boolean} [opts.registerGlobal=false] Add a global variable?
61
+ *
62
+ * If this is `true`, a global variable called `__lum_tests_harness__`
63
+ * will be registered as a pointer to this instance.
64
+ *
65
+ * @param {module:@lumjs/tests/test/stats} [opts.testSuite] Meta-test
66
+ *
67
+ * This option is generally for internal (mostly debugging) use only.
68
+ * Don't use it, just let the constructor build a new `Stats` instance.
69
+ *
66
70
  */
67
- constructor(opts={})
71
+ constructor(plugin, opts={})
68
72
  {
73
+ if (typeof plugin === F && Plugin.isPrototypeOf(plugin))
74
+ { // Class constructor was passed, build an instance.
75
+ plugin = new plugin();
76
+ }
77
+
78
+ if (plugin instanceof Plugin)
79
+ { // An explicit plugin was specified.
80
+ this.plugin = plugin;
81
+ plugin.harness = this;
82
+ }
83
+ else
84
+ { // That's not valid...
85
+ console.error({plugin, opts, harness: this});
86
+ throw new TypeError('Invalid Plugin specified');
87
+ }
88
+
69
89
  // Save a reference to the options.
70
90
  this.opts = opts;
71
91
 
@@ -78,23 +98,6 @@ class Harness
78
98
  this.testSuite = new Stats(opts.testSuite);
79
99
  }
80
100
 
81
- if (opts.plugin instanceof Plugin)
82
- {
83
- this.plugin = opts.plugin;
84
- }
85
- else if (ctx.isBrowser)
86
- {
87
- this.plugin = require('./browser').new(this);
88
- }
89
- else if (ctx.isNode)
90
- {
91
- this.plugin = require('./node').new(this);
92
- }
93
- else
94
- {
95
- throw new Error('Could not determine plugin');
96
- }
97
-
98
101
  //console.debug("# ~ plugin", this.plugin, this);
99
102
 
100
103
  this.queue = []; // Tests to be ran.
@@ -105,6 +108,11 @@ class Harness
105
108
  this.plannedFailure = opts.plannedFailure ?? true;
106
109
  this.plannedWarning = opts.plannedWarning ?? true;
107
110
 
111
+ if (opts.registerGlobal)
112
+ { // Add a global variable.
113
+ globalThis[Stats.HARNESS_GLOBAL] = this;
114
+ }
115
+
108
116
  } // constructor()
109
117
 
110
118
  /**
@@ -177,9 +185,9 @@ class Harness
177
185
  * Run all the queued tests
178
186
  *
179
187
  * @param {boolean} [plan=true] Set a test plan for the entire suite
180
- * @returns {object} `this`
188
+ * @returns {Promise<object>} Resolves to `this`
181
189
  */
182
- run(plan=true)
190
+ async run(plan=true)
183
191
  {
184
192
  if (plan)
185
193
  {
@@ -188,15 +196,15 @@ class Harness
188
196
 
189
197
  for (const queued of this.queue)
190
198
  {
191
- this.runTest(queued);
199
+ await this.runTest(queued);
192
200
  }
193
201
 
194
202
  this.testSuite.done();
195
203
 
196
- return this;
204
+ return this
197
205
  }
198
206
 
199
- runTest(queued)
207
+ async runTest(queued)
200
208
  {
201
209
  const name = queued.filename;
202
210
  let test;
@@ -218,22 +226,42 @@ class Harness
218
226
  }
219
227
  else
220
228
  { // Evaluate if the test is successful or not.
221
- const ok = this.plugin.ok(test);
222
- if (typeof ok === B)
223
- { // A simple boolean response
224
- this.testSuite.ok(ok, name);
225
- }
226
- else if (ok instanceof Error)
227
- { // An error was returned
228
- this.testSuite.fail(name, ok);
229
+ if (test.waiting)
230
+ { // async tests running
231
+ try
232
+ {
233
+ await test.wait();
234
+ this.testTest(test, name);
235
+ }
236
+ catch (err)
237
+ {
238
+ this.testSuite.fail(name, err);
239
+ }
229
240
  }
230
- else
231
- { // This should never happen...
232
- throw new Error("Invalid result from plugin.ok() !!");
241
+ else
242
+ {
243
+ this.testTest(test, name);
233
244
  }
234
245
  }
235
246
 
236
- return this;
247
+ return test;
248
+ }
249
+
250
+ testTest(test, name)
251
+ {
252
+ const ok = this.plugin.ok(test);
253
+ if (typeof ok === B)
254
+ { // A simple boolean response
255
+ this.testSuite.ok(ok, name);
256
+ }
257
+ else if (ok instanceof Error)
258
+ { // An error was returned
259
+ this.testSuite.fail(name, ok);
260
+ }
261
+ else
262
+ { // This should never happen...
263
+ throw new Error("Invalid result from plugin.ok() !!");
264
+ }
237
265
  }
238
266
 
239
267
  tap()
@@ -248,12 +276,12 @@ class Harness
248
276
 
249
277
  } // Harness class
250
278
 
251
- def(Harness, 'Plugin', Plugin);
252
- def(Harness, 'QueuedTest', QueuedTest);
253
- def(Harness, 'Errors', require('./errors'));
279
+ def(LumTestsHarness, 'Plugin', Plugin);
280
+ def(LumTestsHarness, 'QueuedTest', QueuedTest);
281
+ def(LumTestsHarness, 'Errors', require('./errors'));
254
282
 
255
283
  // Export it.
256
- module.exports = Harness;
284
+ module.exports = LumTestsHarness;
257
285
 
258
286
  // Some classes required at end for recursive sanity reasons.
259
287
 
@@ -1,4 +1,4 @@
1
- const {S,N,needType,isArray,isObj} = require('@lumjs/core/types');
1
+ const {S,N,needType,isArray} = require('@lumjs/core/types');
2
2
  const Stats = require('../test/stats');
3
3
  const Tap = require('../grammar/tap');
4
4
 
@@ -21,7 +21,7 @@ const T = 'test',
21
21
  *
22
22
  * @exports module:@lumjs/tests/harness/parser
23
23
  */
24
- class Parser
24
+ class LumTestParser
25
25
  {
26
26
  /**
27
27
  * Build a TAP Parser.
@@ -145,9 +145,9 @@ class Parser
145
145
 
146
146
  static new(opts)
147
147
  {
148
- return new Parser(opts);
148
+ return new LumTestParser(opts);
149
149
  }
150
150
  }
151
151
 
152
152
  // Export the class as the parser.
153
- module.exports = Parser;
153
+ module.exports = LumTestParser;
@@ -1,34 +1,28 @@
1
1
  const core = require('@lumjs/core');
2
- const {AbstractClass} = core;
3
- const {TestFailure,PlanFailure} = require('./errors');
2
+ const {AbstractError} = core;
3
+ const {TestFailure,PlanFailure} = require('../errors');
4
4
 
5
5
  /**
6
6
  * An abstract base class for Harness plugins
7
7
  *
8
- * @exports module:@lumjs/tests/harness~Plugin
8
+ * @prop {module:@lumjs/tests/harness} harness
9
+ * The harness instance will assign itself to this property.
10
+ *
11
+ * @exports module:@lumjs/tests/harness/plugin
9
12
  */
10
- class Plugin extends AbstractClass
13
+ class Plugin
11
14
  {
12
- /**
13
- * (Internal class constructor)
14
- * @param {module:@lumjs/tests/harness} harness - Parent `Harness` instance
15
- */
16
- constructor(harness)
17
- {
18
- super();
19
- this.harness = harness;
20
- this.$needs('run');
21
- }
22
-
23
15
  /**
24
16
  * (ABSTRACT METHOD) Run a test
25
17
  *
26
18
  * This method must be implemented by every plugin.
27
19
  *
28
- * @name module:@lumjs/tests/harness~Plugin#run
29
- * @function
30
20
  * @param {object} queued - A queued test object
31
21
  */
22
+ run(queued)
23
+ {
24
+ throw new AbstractError("run() method not implemented");
25
+ }
32
26
 
33
27
  /**
34
28
  * See if a test had failures
@@ -67,11 +61,6 @@ class Plugin extends AbstractClass
67
61
  return true;
68
62
  }
69
63
 
70
- static new(harness)
71
- {
72
- return new this(harness);
73
- }
74
-
75
64
  }
76
65
 
77
66
  module.exports = Plugin;
@@ -1,7 +1,7 @@
1
1
  // Node.js harness plugin
2
2
  const core = require('@lumjs/core');
3
3
  const {N} = core.types;
4
- const Plugin = require('./plugin');
4
+ const Plugin = require('./index');
5
5
  const cp = require('node:child_process');
6
6
  const getCwd = require('node:process').cwd;
7
7
  const fs = require('node:fs');
@@ -10,8 +10,8 @@ const path = require('node:path');
10
10
  /**
11
11
  * Node.js Harness plugin
12
12
  *
13
- * @exports module:@lumjs/tests/harness~NodePlugin
14
- * @extends module:@lumjs/tetss/harness~Plugin
13
+ * @exports module:@lumjs/tests/harness/plugin/node
14
+ * @extends module:@lumjs/tetss/harness/plugin
15
15
  */
16
16
  class NodePlugin extends Plugin
17
17
  {
@@ -56,11 +56,22 @@ function functional(opts={}, testClass=Test)
56
56
  const proxyMethods = testClass.$METHODS.all;
57
57
  const test = new testClass(opts);
58
58
  const functions = { test };
59
- for (const meth of proxyMethods)
59
+ for (const mdef of proxyMethods)
60
60
  {
61
- functions[meth] = function ()
61
+ let sname, tname;
62
+ if (Array.isArray(mdef))
63
+ { // separate source and target names
64
+ sname = mdef[0];
65
+ tname = mdef[1];
66
+ }
67
+ else
68
+ { // same names for both
69
+ sname = tname = mdef;
70
+ }
71
+
72
+ functions[tname] = function ()
62
73
  {
63
- return test[meth](...arguments);
74
+ return test[sname](...arguments);
64
75
  }
65
76
  }
66
77
  return functions;
package/lib/test/index.js CHANGED
@@ -9,12 +9,13 @@ const TEST_METHODS =
9
9
  [
10
10
  'ok', 'call', 'callIs', 'fail', 'pass', 'dies', 'diesWith', 'lives', 'cmp',
11
11
  'is', 'isnt', 'isa', 'nota', 'isJSON', 'isntJSON', 'matches', 'skip',
12
+ ['async', 'callAsync'],
12
13
  ];
13
14
 
14
15
  // A list of other methods to export that are not standard tests.
15
16
  const META_METHODS =
16
17
  [
17
- 'plan', 'diag', 'run', 'tap', 'output', 'done',
18
+ 'plan', 'diag', 'run', 'tap', 'output', 'done', 'wait',
18
19
  ];
19
20
 
20
21
  // The function that powers `Test.call()` and friends.
@@ -40,10 +41,10 @@ function $call (testfunc, args)
40
41
  * Raku's Test libraries.
41
42
  *
42
43
  * @exports module:@lumjs/tests/test
43
- * @extends module:@lumjs/tests/test~Base
44
+ * @extends module:@lumjs/tests/test/stats
44
45
  *
45
46
  */
46
- class Test extends Stats
47
+ class LumTest extends Stats
47
48
  {
48
49
  /**
49
50
  * Build a new Test instance.
@@ -621,16 +622,16 @@ class Test extends Stats
621
622
  } // class Test
622
623
 
623
624
  // May want this for sub-classes.
624
- def(Test, 'Stats', Stats);
625
+ def(LumTest, 'Stats', Stats);
625
626
 
626
627
  // Should never need this, but...
627
- def(Test, 'Log', Log);
628
+ def(LumTest, 'Log', Log);
628
629
 
629
630
  // Probably don't need this either, but...
630
- def(Test, '$call', $call);
631
+ def(LumTest, '$call', $call);
631
632
 
632
633
  // Methods we're exporting for the 'functional' API.
633
- def(Test, '$METHODS',
634
+ def(LumTest, '$METHODS',
634
635
  {
635
636
  test: TEST_METHODS,
636
637
  meta: META_METHODS,
@@ -685,4 +686,4 @@ def(Test, '$METHODS',
685
686
  }); // Test.$METHODS
686
687
 
687
688
  // Export the class
688
- module.exports = Test;
689
+ module.exports = LumTest;
package/lib/test/log.js CHANGED
@@ -22,7 +22,7 @@ const {S,O,isArray,stringify,def} = types;
22
22
  * @property {object} [details.info] An optional array of extra information.
23
23
  *
24
24
  */
25
- class Log
25
+ class LumTestLog
26
26
  {
27
27
  /**
28
28
  * (Internal Constructor)
@@ -117,4 +117,4 @@ class Log
117
117
  } // class Log
118
118
 
119
119
  // Export the class itself.
120
- module.exports = Log;
120
+ module.exports = LumTestLog;
package/lib/test/stats.js CHANGED
@@ -1,8 +1,15 @@
1
1
  const core = require('@lumjs/core');
2
2
  const mods = require('@lumjs/core/modules');
3
3
  const {types} = core;
4
- const {S,N,isObj,def} = types;
4
+ const {S,N,F,isObj,def} = types;
5
5
  const Log = require('./log');
6
+ const HARNESS_GLOBAL = '__lum_tests_harness__';
7
+
8
+ const DEFAULT_ASYNC_OPTS =
9
+ {
10
+ timeout: 30000,
11
+ interval: 5,
12
+ }
6
13
 
7
14
  /**
8
15
  * Minimalistic base class for the `Test` library.
@@ -11,7 +18,7 @@ const Log = require('./log');
11
18
  * It's extremely minimal and does not provide the core testing methods.
12
19
  * See the [Test]{@link module:@lumjs/tests/test} class for that.
13
20
  *
14
- * @alias module:@lumjs/tests/test~Stats
21
+ * @alias module:@lumjs/tests/test/stats
15
22
  *
16
23
  * @property {number} planned - Number of tests planned, `0` if unplanned.
17
24
  * @property {number} failed - Number of tests that failed.
@@ -21,7 +28,7 @@ const Log = require('./log');
21
28
  * @property {boolean} isTop - Test module was loaded from the command line.
22
29
  * @property {?object} harness - The top-level `Harness` if one was found.
23
30
  */
24
- class Stats
31
+ class LumTestStats
25
32
  {
26
33
  /**
27
34
  * Build a new instance.
@@ -42,6 +49,10 @@ class Stats
42
49
  * @param {number} [opts.stringify=1] The depth `stringify()` should recurse
43
50
  * objects and Arrays before switching to plain JSON stringification.
44
51
  *
52
+ * @param {object} [opts.async] Options for async tests
53
+ * @param {number} [opts.async.timeout=30000] Max time to wait (in ms)
54
+ * @param {number} [opts.async.interval=5] Status check every (in ms)
55
+ *
45
56
  */
46
57
  constructor (opts={})
47
58
  {
@@ -74,6 +85,9 @@ class Stats
74
85
  this.failed = 0;
75
86
  this.skipped = 0;
76
87
  this.planned = 0;
88
+ this.waiting = 0;
89
+
90
+ this.asyncOpts = Object.assign({}, DEFAULT_ASYNC_OPTS, opts.async);
77
91
 
78
92
  // The test logs for each unit.
79
93
  this.log = [];
@@ -100,15 +114,18 @@ class Stats
100
114
  }
101
115
 
102
116
  if (!this.isTop)
103
- { // Try to find a Harness instance.
104
- if (isObj(require.main) && require.main.exports instanceof Harness)
117
+ { // Try to find a possible Harness instance using some obscure logic...
118
+ if (isObj(require.main) && isObj(require.main.exports)
119
+ && require.main.exports.constructor.name === 'LumTestsHarness')
105
120
  { // We found the Harness instance.
106
121
  this.harness = require.main.exports;
107
122
  }
123
+ else if (isObj(globalThis[HARNESS_GLOBAL]))
124
+ {
125
+ this.harness = globalThis[HARNESS_GLOBAL];
126
+ }
108
127
  }
109
-
110
- // Finally, mark the test stats as not generated yet.
111
- def(this, '$done', false);
128
+
112
129
  }
113
130
 
114
131
  // Internal method.
@@ -276,6 +293,83 @@ class Stats
276
293
  this.log.push(msg);
277
294
  }
278
295
 
296
+ /**
297
+ * Run async test code
298
+ *
299
+ * If a test needs to wait for the results of an async operation,
300
+ * then this is the way to do it. This method does not actually
301
+ * call any testing methods, that needs to be done inside the `call`.
302
+ *
303
+ * This increments `this.waiting`, which will be decremented when
304
+ * the `Promise` returned by the `call` is resolved or rejected.
305
+ *
306
+ * @param {(function|Promise)} call - Async code to wait for
307
+ *
308
+ * The `function` must be `async` or return a `Promise`.
309
+ * The function will have this Stats instance applied as `this`.
310
+ *
311
+ * @param {...any} args - Arguments for `call` function.
312
+ *
313
+ * @returns {Promise}
314
+ */
315
+ async async(call, ...args)
316
+ {
317
+ let testPromise;
318
+ this.waiting++;
319
+
320
+ if (typeof call === F)
321
+ { // It MUST be an async function, OR return a Promise.
322
+ testPromise = call.apply(this, args);
323
+ }
324
+ else
325
+ { // Assume the promise was passed directly.
326
+ testPromise = call;
327
+ }
328
+
329
+ if (testPromise instanceof Promise)
330
+ {
331
+ testPromise.finally(() => this.waiting--);
332
+ }
333
+ else
334
+ {
335
+ throw new TypeError("async() call must return a Promise");
336
+ }
337
+
338
+ return testPromise;
339
+ }
340
+
341
+ /**
342
+ * Called if `this.waiting` is greater than `0`
343
+ *
344
+ * @returns {Promise<object>}
345
+ *
346
+ * Will resolve to `this` when `this.waiting` becomes `0`.
347
+ * Will be rejected if the maximum wait timeout is reached.
348
+ */
349
+ wait()
350
+ {
351
+ const ao = this.asyncOpts;
352
+ return new Promise((resolve, reject) =>
353
+ {
354
+ const timeout = setTimeout(() =>
355
+ {
356
+ clearInterval(test);
357
+ clearTimeout(timeout);
358
+ reject(new Error("Timed out waiting for async encoding"));
359
+ }, ao.timeout);
360
+
361
+ const test = setInterval(() =>
362
+ {
363
+ if (this.waiting === 0)
364
+ {
365
+ clearInterval(test);
366
+ clearTimeout(timeout);
367
+ resolve(this);
368
+ }
369
+ }, ao.interval);
370
+ });
371
+ }
372
+
279
373
  /**
280
374
  * Return TAP formatted output for all the tests.
281
375
  *
@@ -359,28 +453,33 @@ class Stats
359
453
  }
360
454
 
361
455
  /**
362
- * We're done testing.
456
+ * Run this when you're done testing.
363
457
  *
364
- * This will mark the test-set as finished, so attempting to run further
365
- * tests after will result in a `RangeError` being thrown.
366
- *
367
- * If no `Harness` is in use, this will also run `this.output()`.
458
+ * It doesn't do much at all if a `Harness` is in use.
459
+ *
460
+ * If no `Harness` is in use, this will call `this.output()`.
461
+ * If there are any async calls we're waiting on this will wait
462
+ * until they are complete before calling `this.output()`.
368
463
  */
369
464
  done ()
370
465
  {
371
- if (this.$done)
466
+ if (this.harness)
467
+ { // The harness will handle the rest.
468
+ return this;
469
+ }
470
+ else if (this.waiting)
372
471
  {
373
- throw new RangeError('Test set is already done');
472
+ this.wait().finally(() => this.done());
473
+ }
474
+ else
475
+ {
476
+ this.output();
374
477
  }
375
- def(this, '$done', true);
376
- return (this.harness ? this : this.output());
377
478
  }
378
479
 
379
- } // class Test
480
+ } // class Stats
380
481
 
381
- // Export the class
382
- module.exports = Stats;
482
+ def(LumTestStats, 'HARNESS_GLOBAL', HARNESS_GLOBAL);
383
483
 
384
- // Finally at the bottom after `module.exports` has been set, we will load
385
- // the Harness class to avoid circular references failing.
386
- const Harness = require('../harness');
484
+ // Export the class
485
+ module.exports = LumTestStats;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumjs/tests",
3
- "version": "1.8.2",
3
+ "version": "2.0.0",
4
4
  "main": "lib/index.js",
5
5
  "bin":
6
6
  {
@@ -11,8 +11,11 @@
11
11
  ".": "./lib/index.js",
12
12
  "./test": "./lib/test/index.js",
13
13
  "./test/functional": "./lib/test/functional.js",
14
+ "./test/stats": "./lib/test/stats.js",
14
15
  "./harness": "./lib/harness/index.js",
15
16
  "./harness/parser": "./lib/harness/parser.js",
17
+ "./harness/plugin": "./lib/harness/plugin/index.js",
18
+ "./harness/plugin/node": "./lib/harness/plugin/node.js",
16
19
  "./package.json": "./package.json",
17
20
  "./data/*": "./test/data/*.js"
18
21
  },
@@ -24,7 +27,7 @@
24
27
  },
25
28
  "dependencies":
26
29
  {
27
- "@lumjs/core": "^1.8.0"
30
+ "@lumjs/core": "^1.26.0"
28
31
  },
29
32
  "scripts": {
30
33
  "test": "./bin/lumtest.js",
@@ -1,18 +0,0 @@
1
- // Browser harness plugin
2
- const Plugin = require('./plugin');
3
-
4
- /**
5
- * Browser Harness plugin
6
- *
7
- * @exports module:@lumjs/tests/harness~BrowserPlugin
8
- * @extends module:@lumjs/tetss/harness~Plugin
9
- */
10
- class BrowserPlugin extends Plugin
11
- {
12
- run(queued)
13
- {
14
- throw new Error("Browser tests not supported yet");
15
- }
16
- } // Node plugin class
17
-
18
- module.exports = BrowserPlugin;