@lumjs/tests 1.3.0 → 1.4.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
@@ -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.4.0] - 2022-07-29
10
+ ## Added
11
+ - Explicit `exports` section in `package.json` file.
12
+ - Added `test.done()` method to be used instead of `test.output()`.
13
+ - Added ability to configure the stringify depth.
14
+ - Added ability to detect if the script was ran directly.
15
+ - Added ability to check for a top-level `Harness` instance.
16
+ - Added four new *binary flag* comparitor tests to `cmp()` method.
17
+ - Added `not` alias for `!==` comparitor.
18
+ - Added `matches` method for using a regular expression to match a string.
19
+ - 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.
20
+ - A new `test.ran` computed property.
21
+
22
+ ## Changed
23
+ - Enhanced a lot of docblocks.
24
+ - Updated anything using `types.stringify()` to support the depth setting.
25
+ - Updated `run()` so it can use either `call()` or `callIs()` as the underlying test method when using a custom `function` test.
26
+
27
+ ### Added
28
+ - Configuration for JSDoc.
29
+ - A few module-level *docblocks*.
30
+ ### Changed
31
+ - Updated `@lumjs/core` dependency to `^1.0.0` (no more *beta* tags!)
32
+ - Updated various *docblocks* for documentation.
33
+
9
34
  ## [1.3.0] - 2022-07-27
10
35
  ### Added
11
36
  - `$call()` function; powers `call()`, `lives()`, `dies()`, and `diesWith()`.
@@ -57,7 +82,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
57
82
  - Ported from Lum.js v4 library set.
58
83
  - Added a few more features from the PHP version.
59
84
 
60
- [Unreleased]: https://github.com/supernovus/lum.tests.js/compare/v1.3.0...HEAD
85
+ [Unreleased]: https://github.com/supernovus/lum.tests.js/compare/v1.4.0...HEAD
86
+ [1.4.0]: https://github.com/supernovus/lum.tests.js/compare/v1.3.0...v1.4.0
61
87
  [1.3.0]: https://github.com/supernovus/lum.tests.js/compare/v1.2.0...v1.3.0
62
88
  [1.2.0]: https://github.com/supernovus/lum.tests.js/compare/v1.1.1...v1.2.0
63
89
  [1.1.1]: https://github.com/supernovus/lum.tests.js/compare/v1.1.0...v1.1.1
package/TODO.md CHANGED
@@ -3,6 +3,8 @@
3
3
  - Write tests for:
4
4
  - `call()`
5
5
  - `diesWith()`
6
+ - `callIs()`
7
+ - `matches()`
6
8
  - `run()`
7
9
  - Write the `Harness` class, based of the Lum.php version.
8
10
  - Add tests for `Harness`.
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
@@ -4,6 +4,11 @@ const Test = require('./test');
4
4
  // A list of methods we can proxy directly.
5
5
  const PROXY_METHODS = Test.$METHODS.all;
6
6
 
7
+ /**
8
+ * Module defining the functional API.
9
+ * @module @lumjs/tests/functional
10
+ */
11
+
7
12
  /**
8
13
  * A new test instance and a set of functions wrapping it.
9
14
  * @typedef {object} Functional
@@ -21,11 +26,12 @@ const PROXY_METHODS = Test.$METHODS.all;
21
26
  * Usage is like:
22
27
  *
23
28
  * ```js
24
- * const {plan,ok,isa} = require('@lumjs/tests').functional({module});
29
+ * const {plan,ok,isa,done} = require('@lumjs/tests').functional({module});
25
30
  *
26
31
  * plan(2);
27
32
  * ok(true, 'ok() works');
28
33
  * isa(isa, 'function', 'isa is a function');
34
+ * done();
29
35
  *
30
36
  * ```
31
37
  *
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
- // This one is not a class, but a special function.
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,6 +1,6 @@
1
1
 
2
2
  const types = require('@lumjs/core').types;
3
- const {F,S,O,isArray,stringify} = types;
3
+ const {S,O,isArray,stringify,def} = types;
4
4
 
5
5
  /**
6
6
  * A log representing the results of a test.
@@ -19,7 +19,7 @@ const {F,S,O,isArray,stringify} = types;
19
19
  */
20
20
  class Log
21
21
  {
22
- constructor ()
22
+ constructor (test)
23
23
  {
24
24
  this.ok = false;
25
25
  this.skipped = false;
@@ -27,6 +27,8 @@ class Log
27
27
  this.desc = null;
28
28
  this.directive = null;
29
29
  this.details = {};
30
+ this.stringifyDepth = test.stringifyDepth;
31
+ def(this, '$test$', test);
30
32
  }
31
33
 
32
34
  /**
@@ -40,6 +42,8 @@ class Log
40
42
  */
41
43
  tap (num)
42
44
  {
45
+ const SD = this.stringifyDepth;
46
+
43
47
  var out;
44
48
  if (this.ok)
45
49
  out = 'ok ';
@@ -66,8 +70,8 @@ class Log
66
70
  var want = this.details.wanted;
67
71
  if (this.details.stringify)
68
72
  {
69
- got = stringify(got);
70
- want = stringify(want);
73
+ got = stringify(got, SD);
74
+ want = stringify(want, SD);
71
75
  }
72
76
  out += `# got: ${got}\n`;
73
77
  out += `# expected: ${want}\n`;
@@ -86,7 +90,7 @@ class Log
86
90
 
87
91
  for (const i in info)
88
92
  {
89
- const line = (typeof info[i] === S) ? info[i] : stringify(info[i]);
93
+ const line = (typeof info[i] === S) ? info[i] : stringify(info[i], SD);
90
94
  out += `## ${line}\n`;
91
95
  }
92
96
  }
package/lib/test.js CHANGED
@@ -1,6 +1,11 @@
1
+ /**
2
+ * Module defining the Test class.
3
+ * @module @lumjs/tests/test
4
+ */
5
+
1
6
  const core = require('@lumjs/core');
2
7
  const types = core.types;
3
- const {F,S,N,isObj,isArray,def} = types;
8
+ const {F,S,N,isObj,isArray,needs,def} = types;
4
9
 
5
10
  // We use a separate class to represent test logs.
6
11
  const Log = require('./log');
@@ -15,7 +20,7 @@ const TEST_METHODS =
15
20
  // A list of other methods to export that are not standard tests.
16
21
  const META_METHODS =
17
22
  [
18
- 'plan', 'diag', 'run', 'tap', 'output',
23
+ 'plan', 'diag', 'run', 'tap', 'output', 'done',
19
24
  ];
20
25
 
21
26
  // The function that powers `Test.call()` and friends.
@@ -39,6 +44,14 @@ function $call (testfunc, args)
39
44
  * Based on Lum.php's Test library.
40
45
  * Which itself was based on Perl 5's Test::More, and
41
46
  * Raku's Test libraries.
47
+ *
48
+ * @property {number} planned - Number of tests planned, `0` if unplanned.
49
+ * @property {number} failed - Number of tests that failed.
50
+ * @property {number} skipped - Number of tests that were skipped.
51
+ * @property {number} ran - Number of tests ran (*calculated*).
52
+ * @property {string} id - Unique test id used by `Harness` libary.
53
+ * @property {boolean} isTop - Test module was loaded from the command line.
54
+ * @property {?object} harness - The top-level `Harness` if one was found.
42
55
  */
43
56
  class Test
44
57
  {
@@ -58,6 +71,9 @@ class Test
58
71
  * Also, if this is passed, and `opts.id` was not specified, and id
59
72
  * will be auto-generated based on the filename of the module.
60
73
  *
74
+ * @param {number} [opts.stringify=1] The depth `stringify()` should recurse
75
+ * objects and Arrays before switching to plain JSON stringification.
76
+ *
61
77
  */
62
78
  constructor (opts={})
63
79
  {
@@ -85,20 +101,46 @@ class Test
85
101
  this.id = null;
86
102
  }
87
103
 
104
+ this.stringifyDepth = opts.stringify ?? 1;
105
+
88
106
  this.failed = 0;
89
107
  this.skipped = 0;
90
108
  this.planned = 0;
91
109
  this.log = [];
92
110
 
111
+ // These three will be updated below if possible.
112
+ this.isTop = false;
113
+ this.harness = null;
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
- { // Finally, if a module was passed, its going to export this test.
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
+ }
101
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
+ }
136
+ }
137
+
138
+ }
139
+
140
+ // A wrapper around types.stringify()
141
+ stringify(what)
142
+ {
143
+ return types.stringify(what, this.stringifyDepth);
102
144
  }
103
145
 
104
146
  /**
@@ -142,7 +184,7 @@ class Test
142
184
  */
143
185
  ok (test, desc, directive, details)
144
186
  {
145
- const log = new Log();
187
+ const log = new Log(this);
146
188
 
147
189
  if (test)
148
190
  {
@@ -185,7 +227,7 @@ class Test
185
227
  * @param {...any} [args] Arguments to pass to the test function.
186
228
  * @returns {Log}
187
229
  */
188
- call(testfunc, desc, ...args)
230
+ call (testfunc, desc, ...args)
189
231
  {
190
232
  const ret = $call(testfunc, args);
191
233
  return this.ok(ret.val, desc, ret.err);
@@ -257,7 +299,7 @@ class Test
257
299
  * @param {...any} [args]
258
300
  * @returns {Log}
259
301
  */
260
- diesWith(testfunc, testerr, desc, ...args)
302
+ diesWith (testfunc, testerr, desc, ...args)
261
303
  {
262
304
  let ok = false, details = {}, err = null;
263
305
 
@@ -320,15 +362,22 @@ class Test
320
362
  * @param {string} comp - A comparitor to test with.
321
363
  *
322
364
  * - `===`, `is` (See also: `is()`)
323
- * - `!==`, `isnt` (See also: `isnt()`)
365
+ * - `!==`, `isnt`, `not` (See also: `isnt()`)
324
366
  * - `==`, `eq`
325
367
  * - `!=`, `ne`
326
368
  * - `>`, `gt`
327
369
  * - `<`, `lt`
328
370
  * - `>=`, `ge`, `gte`
329
371
  * - `<=`, `le`, `lte`
372
+ *
373
+ * A few special comparitors for *binary flag* testing:
374
+ *
375
+ * - `=&` → `((got & want) === want)`
376
+ * - `!&` → `((got & want) !== want)`
377
+ * - `+&` → `((got & want) !== 0)`
378
+ * - `-&` → `((got & want) === 0)`
330
379
  *
331
- * @param {string} desc
380
+ * @param {string} [desc]
332
381
  * @param {boolean} [stringify=true] Stringify values in TAP output?
333
382
  * @returns {Log}
334
383
  */
@@ -343,6 +392,7 @@ class Test
343
392
  break;
344
393
  case 'isnt':
345
394
  case '!==':
395
+ case 'not':
346
396
  test = (got !== want);
347
397
  break;
348
398
  case 'eq':
@@ -371,6 +421,16 @@ class Test
371
421
  case '>=':
372
422
  test = (got >= want);
373
423
  break;
424
+ case '=&':
425
+ test = ((got&want)===want);
426
+ break;
427
+ case '!&':
428
+ test = ((got&want)!==want)
429
+ case '+&':
430
+ test = ((got&want)!==0);
431
+ break;
432
+ case '-&':
433
+ test = ((got&want)===0);
374
434
  default:
375
435
  test = false;
376
436
  }
@@ -390,6 +450,39 @@ class Test
390
450
  return this.ok(test, desc, null, details);
391
451
  }
392
452
 
453
+ /**
454
+ * See if a string matches a value.
455
+ *
456
+ * @param {string} got
457
+ * @param {RegExp} want
458
+ * @param {string} [desc]
459
+ * @param {boolean} [stringify=true]
460
+ * @returns {Log}
461
+ */
462
+ matches(got, want, desc, stringify=true)
463
+ {
464
+ const no = {error: "matches 'got' value must be a string"};
465
+ needs(got, no, S);
466
+ no.error = "matches 'want' value must be a RegExp";
467
+ needs(want, no, RegExp);
468
+
469
+ const test = want.test(got);
470
+
471
+ let details = null;
472
+ if (!test)
473
+ { // The test failed, add the deets.
474
+ details =
475
+ {
476
+ got,
477
+ wanted: want,
478
+ stringify,
479
+ comparitor: 'matches',
480
+ };
481
+ }
482
+
483
+ return this.ok(test, desc, null, details);
484
+ }
485
+
393
486
  /**
394
487
  * See if two values are equal.
395
488
  *
@@ -486,13 +579,13 @@ class Test
486
579
  *
487
580
  * @param {*} got
488
581
  * @param {*} want
489
- * @param {string} desc
490
- * @returns
582
+ * @param {string} [desc]
583
+ * @returns {Log}
491
584
  */
492
585
  isJSON (got, want, desc)
493
586
  {
494
- got = types.stringify(got);
495
- want = types.stringify(want);
587
+ got = this.stringify(got);
588
+ want = this.stringify(want);
496
589
  return this.is(got, want, desc, false);
497
590
  }
498
591
 
@@ -503,16 +596,99 @@ class Test
503
596
  *
504
597
  * @param {*} got
505
598
  * @param {*} want
506
- * @param {string} desc
507
- * @returns
599
+ * @param {string} [desc]
600
+ * @returns {Log}
508
601
  */
509
602
  isntJSON (got, want, desc)
510
603
  {
511
- got = types.stringify(got);
512
- want = types.stringify(want);
604
+ got = this.stringify(got);
605
+ want = this.stringify(want);
513
606
  return this.isnt(got, want, desc, false);
514
607
  }
515
608
 
609
+ /**
610
+ * Run a function and see if it's return value is what we wanted.
611
+ *
612
+ * @param {function} testfunc - The function to run.
613
+ * The return value will be passed to `cmp()` or another appropriate
614
+ * testing method as determined by the options.
615
+ * How this handles error handling is determined by options as well.
616
+ *
617
+ * @param {*} want - The value we want.
618
+ * @param {(object|string)} [opts] Named options for further behaviour.
619
+ * If it is a string it's considered the `opts.desc` option.
620
+ * @param {string} [opts.desc] A description for `ok()`.
621
+ * @param {boolean} [opts.stringify=true]
622
+ * @param {Array} [opts.args] Arguments to pass to the test function.
623
+ * @param {string} [opts.comp="is"] - The comparitor to test with.
624
+ * In addition to all of the comparitors from `cmp()`, there are a few
625
+ * extra comparitors that will pass through to other methods:
626
+ * - `isa` → Use `isa()` to test return value.
627
+ * - `nota` → Use `nota()` to test return value.
628
+ * - `=json`, `isJSON` → Use `isJSON()` to test return value.
629
+ * - `!json`, `isntJSON` → Use `isntJSON()` to test return value.
630
+ * - `matches` → Use `matches()` to test return value.
631
+ *
632
+ * @param {boolean} [opts.thrown=false] How to handle thrown errors.
633
+ *
634
+ * If this is `true`, then anything thrown will be passed as if it was
635
+ * the return value from the function.
636
+ *
637
+ * If this is `false`, then any errors thrown will result in an immediate
638
+ * failure of the test without any further processing, and the error will
639
+ * be passed as the `directive` to the `ok()` method.
640
+ *
641
+ * @returns {Log}
642
+ */
643
+ callIs (testfunc, want, opts={})
644
+ {
645
+ const args = opts.args ?? [];
646
+ const ret = $call(testfunc, args);
647
+ const desc = opts.desc;
648
+
649
+ let got;
650
+
651
+ if (ret.err)
652
+ { // How to handle errors.
653
+ if (opts.thrown)
654
+ { // We're going to test the error.
655
+ got = ret.err;
656
+ }
657
+ else
658
+ { // This is an automatic failure.
659
+ return this.ok(false, desc, ret.err);
660
+ }
661
+ }
662
+ else
663
+ { // No errors, good, testing against the return value.
664
+ got = ret.val;
665
+ }
666
+
667
+ const CFUN =
668
+ {
669
+ 'matches': 'matches',
670
+ 'isa': 'isa',
671
+ 'nota': 'nota',
672
+ 'isJSON': 'isJSON',
673
+ '=json': 'isJSON',
674
+ 'isntJSON': 'isntJSON',
675
+ '!json': 'isntJSON',
676
+ };
677
+
678
+ const comp = opts.comp ?? 'is';
679
+ const stringify = opts.stringify ?? true;
680
+
681
+ if (typeof CFUN[comp] === S)
682
+ { // A function with a custom return value.
683
+ const meth = CFUN[comp];
684
+ return this[meth](got, want, desc, stringify);
685
+ }
686
+ else
687
+ { // We're going to use the cmp() method.
688
+ return this.cmp(got, want, comp, desc, stringify);
689
+ }
690
+ }
691
+
516
692
  /**
517
693
  * Skip a test.
518
694
  *
@@ -522,7 +698,7 @@ class Test
522
698
  */
523
699
  skip (reason, desc)
524
700
  {
525
- var log = this.ok(true, desc);
701
+ const log = this.ok(true, desc);
526
702
  log.skipped = true;
527
703
  if (typeof reason === S)
528
704
  log.skippedReason = reason;
@@ -554,35 +730,55 @@ class Test
554
730
  * methods in this class, it will be set as the *current* test method.
555
731
  *
556
732
  * 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.
733
+ * By default function test methods are passed to `call()` with the test
734
+ * parameters. However, if the *previous* test method was `callIs` then
735
+ * the `callIs()` method will be used as long as the custom function is
736
+ * the *current* test method. Likewise to switch back to `call()` simply
737
+ * set the *current* test method to `call` before setting it to a new custom
738
+ * test `function`.
558
739
  *
559
740
  * 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.
741
+ * method. If a custom `function` is in use, remember that it's the
742
+ * `call()` or `callIs()` methods that will be being called, with their
743
+ * first parameter always being the custom function.
563
744
  *
564
745
  * Any value other than one of those will throw a `TypeError`.
565
746
  *
566
747
  * @returns {Log[]} A `Log` item for each test that was ran.
567
748
  */
568
- run(...tests)
749
+ run (...tests)
569
750
  {
751
+ const CF = 'call';
752
+ const CI = 'callIs';
753
+
570
754
  const logs = [];
755
+ let funcall = CF;
571
756
  let current = 'ok';
572
757
 
573
758
  for (const test of tests)
574
759
  {
575
760
  const tt = typeof test;
576
- if (tt === F || (tt === S && TEST_METHODS.includes(test)))
577
- { // Set the current test.
761
+ if (tt === S && TEST_METHODS.includes(test))
762
+ { // Set the current test to a built-in.
578
763
  current = test;
579
764
  }
765
+ else if (tt === F)
766
+ { // A custom test function for further tests.
767
+ if (current === CI)
768
+ { // Last test was `callIs` using that for the custom function.
769
+ funcall = CI;
770
+ }
771
+ else if (current === CF)
772
+ { // Last test was `call`, using that for the custom function.
773
+ funcall = CF;
774
+ }
775
+ }
580
776
  else if (isArray(test))
581
777
  { // A set of test parameters.
582
778
  let log;
583
779
  if (typeof current === F)
584
780
  { // A custom test function is in use.
585
- log = this.call(current, ...test);
781
+ log = this[funcall](current, ...test);
586
782
  }
587
783
  else
588
784
  { // A standard test is in use.
@@ -602,15 +798,14 @@ class Test
602
798
  */
603
799
  tap ()
604
800
  {
605
- var out = '';
801
+ let out = '';
606
802
  if (this.planned > 0)
607
803
  {
608
804
  out += '1..'+this.planned+"\n";
609
805
  }
610
- var t = 1;
611
- for (var i = 0; i < this.log.length; i++)
806
+ let t = 1;
807
+ for (const log of this.log)
612
808
  {
613
- var log = this.log[i];
614
809
  if (log instanceof Log)
615
810
  {
616
811
  out += log.tap(t++);
@@ -631,7 +826,7 @@ class Test
631
826
  out += ' out of '+this.planned;
632
827
  out += "\n";
633
828
  }
634
- var ran = t-1;
829
+ const ran = t-1;
635
830
  if (this.planned > 0 && this.planned != ran)
636
831
  {
637
832
  out += '# Looks like you planned '+this.planned+' but ran '+ran+" tests\n";
@@ -639,8 +834,28 @@ class Test
639
834
  return out;
640
835
  }
641
836
 
837
+ /**
838
+ * A calculated property of the number of tests that were ran.
839
+ * @type {int}
840
+ */
841
+ get ran ()
842
+ {
843
+ let ran = 0;
844
+ for (const log of this.log)
845
+ {
846
+ if (log instanceof Log)
847
+ {
848
+ ran++;
849
+ }
850
+ }
851
+ return ran;
852
+ }
853
+
642
854
  /**
643
855
  * Send the TAP output to the `console`.
856
+ *
857
+ * This is a low-level method and is no longer recommended for use.
858
+ * Instead call the `done()` method, which will *do the right thing*.
644
859
  */
645
860
  output ()
646
861
  {
@@ -648,6 +863,25 @@ class Test
648
863
  return this;
649
864
  }
650
865
 
866
+ /**
867
+ * We're done testing.
868
+ *
869
+ * This will mark the test-set as finished, so attempting to run further
870
+ * tests after will result in a `RangeError` being thrown.
871
+ *
872
+ * If no `Harness` is in use, this will also run `this.output()`.
873
+ */
874
+ done ()
875
+ {
876
+ if (this.$done)
877
+ {
878
+ throw new RangeError('Test set is already done');
879
+ }
880
+ this.$done = true;
881
+
882
+ return (this.harness ? this : this.output());
883
+ }
884
+
651
885
  } // class Test
652
886
 
653
887
  // Should never need this, but...
@@ -679,3 +913,8 @@ def(Test, '$METHODS',
679
913
 
680
914
  // Export the class
681
915
  module.exports = Test;
916
+
917
+ // Finally at the bottom after `module.exports` has been set, we will load
918
+ // the Harness class to avoid circular references failing.
919
+ const Harness = require('./harness');
920
+
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "@lumjs/tests",
3
- "version": "1.3.0",
3
+ "version": "1.4.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
+ },
5
13
  "license": "MIT",
6
14
  "repository":
7
15
  {
@@ -9,11 +17,12 @@
9
17
  "url": "https://github.com/supernovus/lum.tests.js.git"
10
18
  },
11
19
  "dependencies": {
12
- "@lumjs/core": "^1.0.0-beta.4"
20
+ "@lumjs/core": "^1.1.0"
13
21
  },
14
22
  "scripts":
15
23
  {
16
24
  "-TODO-1": "When Harness and bin/lumtest are done, use that for 'test'",
17
- "test": "prove -e node --ext js ./test"
25
+ "test": "prove -e node --ext js ./test",
26
+ "build-docs": "jsdoc -c ./jsdoc.json"
18
27
  }
19
28
  }