@lumjs/tests 1.5.0 → 1.7.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.
@@ -1,22 +1,18 @@
1
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 = Test.$METHODS.all;
6
-
7
- /**
8
- * Module defining the functional API.
9
- * @module @lumjs/tests/functional
10
- */
2
+ const Test = require('./index');
11
3
 
12
4
  /**
13
5
  * A new test instance and a set of functions wrapping it.
14
- * @typedef {object} Functional
6
+ *
7
+ * @typedef {object} module:@lumjs/tests/test/functional.Functional
15
8
  * @property {Test} test - The new test instance.
16
9
  *
17
10
  * All of the rest of the properties are functions that
18
11
  * can be imported into a JS scope using destructuring, and
19
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.
20
16
  */
21
17
 
22
18
  /**
@@ -42,14 +38,25 @@ const PROXY_METHODS = Test.$METHODS.all;
42
38
  *
43
39
  * @param {object} [opts] Options to pass to the `Test` constructor.
44
40
  *
45
- * @returns {Functional}
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/test/functional.Functional}
46
49
  *
50
+ * @exports module:@lumjs/tests/test/functional
47
51
  */
48
- function functional(opts={})
52
+ function functional(opts={}, testClass=Test)
49
53
  {
50
- const test = new Test(opts);
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);
51
58
  const functions = { test };
52
- for (const meth of PROXY_METHODS)
59
+ for (const meth of proxyMethods)
53
60
  {
54
61
  functions[meth] = function ()
55
62
  {
@@ -1,20 +1,14 @@
1
- /**
2
- * Module defining the Test class.
3
- * @module @lumjs/tests/test
4
- */
5
-
6
1
  const core = require('@lumjs/core');
7
- const types = core.types;
8
- const {F,S,N,isObj,isArray,needs,def} = types;
9
-
10
- // We use a separate class to represent test logs.
2
+ const {types,obj} = core;
3
+ const {F,S,isObj,isArray,needs,def} = types;
4
+ const Stats = require('./stats');
11
5
  const Log = require('./log');
12
6
 
13
7
  // A list of Test methods that return Log objects.
14
8
  const TEST_METHODS =
15
9
  [
16
- 'ok', 'call', 'fail', 'pass', 'dies', 'diesWith', 'lives', 'cmp',
17
- 'is', 'isnt', 'isa', 'nota', 'isJSON', 'isntJSON', 'skip',
10
+ 'ok', 'call', 'callIs', 'fail', 'pass', 'dies', 'diesWith', 'lives', 'cmp',
11
+ 'is', 'isnt', 'isa', 'nota', 'isJSON', 'isntJSON', 'matches', 'skip',
18
12
  ];
19
13
 
20
14
  // A list of other methods to export that are not standard tests.
@@ -45,15 +39,11 @@ function $call (testfunc, args)
45
39
  * Which itself was based on Perl 5's Test::More, and
46
40
  * Raku's Test libraries.
47
41
  *
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
+ * @exports module:@lumjs/tests/test
43
+ * @extends module:@lumjs/tests/test~Base
44
+ *
55
45
  */
56
- class Test
46
+ class Test extends Stats
57
47
  {
58
48
  /**
59
49
  * Build a new Test instance.
@@ -76,143 +66,34 @@ class Test
76
66
  *
77
67
  */
78
68
  constructor (opts={})
79
- {
80
- if (typeof opts === N)
81
- {
82
- opts = {plan: opts};
83
- }
84
- else if (typeof opts === S)
85
- {
86
- opts = {id: opts};
87
- }
88
-
89
- const hasModule = isObj(opts.module);
90
-
91
- if (typeof opts.id === S)
92
- { // A specific id was specified.
93
- this.id = opts.id;
94
- }
95
- else if (hasModule)
96
- { // We're going to generate a simple name.
97
- this.id = core.modules.name(opts.module, opts.moduleName);
98
- }
99
- else
100
- { // An anonymous test.
101
- this.id = null;
102
- }
103
-
104
- this.stringifyDepth = opts.stringify ?? 1;
105
-
106
- this.failed = 0;
107
- this.skipped = 0;
108
- this.planned = 0;
109
- this.log = [];
110
-
111
- // These three will be updated below if possible.
112
- this.isTop = false;
113
- this.harness = null;
114
-
115
- if (typeof opts.plan === N)
116
- {
117
- this.plan(opts.plan);
118
- }
119
-
120
- if (hasModule)
121
- { // If a module was passed, its going to export this test.
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
- }
136
- }
69
+ { // First call the Base constructor.
70
+ super(opts);
137
71
 
138
- }
139
-
140
- // A wrapper around types.stringify()
141
- stringify(what)
142
- {
143
- return types.stringify(what, this.stringifyDepth);
72
+ // Now register methods that can be ran via run().
73
+ this.$testMethods = TEST_METHODS.slice();
144
74
  }
145
75
 
146
76
  /**
147
- * Indicate how many tests are planned to be run in this set.
77
+ * A static shortcut for creating a new instance.
148
78
  *
149
- * @param {number} num - The number of tests that should run.
79
+ * @param {object} [opts] Options to pass to constructor.
80
+ * @returns {module:@lumjs/tests/test}
150
81
  */
151
- plan (num)
82
+ static new(opts={})
152
83
  {
153
- if (typeof num === N)
154
- {
155
- this.planned = num;
156
- }
157
- else if (num === false)
158
- {
159
- this.planned = 0;
160
- }
161
- else
162
- {
163
- throw new Error("Invalid value passed to plan()");
164
- }
84
+ return new this(opts);
165
85
  }
166
86
 
167
87
  /**
168
- * Check if a value is truthy, and add a log indicating the result.
169
- *
170
- * This is the absolute most basic method that every other testing method
171
- * uses to log the results.
172
- *
173
- * I won't document the `desc` and `directive` parameters on any of the
174
- * other methods as they are exactly the same as here.
88
+ * A static shortcut for creating a `Functional` API object.
175
89
  *
176
- * @param {*} test - Any value, usually the output of a test function.
177
- * If the value evaluates as `true` (aka a truthy value), the test passes.
178
- * If it evaluates as `false` (a falsey value), the test fails.
179
- *
180
- * @param {string} [desc] A short description of the test.
181
- * @param {(string|Error)} [directive] Further information for the log.
182
- * @param {object} [details] Extra details to add to the log.
183
- * @returns {Log} The test log with the results.
90
+ * @param {object} [opts] Options to pass to constructor.
91
+ * @returns {module:@lumjs/tests/functional.Functional}
184
92
  */
185
- ok (test, desc, directive, details)
93
+ static functional(opts={})
186
94
  {
187
- const log = new Log(this);
188
-
189
- if (test)
190
- {
191
- log.ok = true;
192
- }
193
- else
194
- {
195
- this.failed++;
196
- }
197
-
198
- if (typeof desc === S)
199
- {
200
- log.desc = desc;
201
- }
202
-
203
- if (directive)
204
- {
205
- log.directive = directive;
206
- }
207
-
208
- if (isObj(details))
209
- {
210
- log.details = details;
211
- }
212
-
213
- this.log.push(log);
214
-
215
- return log;
95
+ //console.debug('functional this', this);
96
+ return require('./functional')(opts, this);
216
97
  }
217
98
 
218
99
  /**
@@ -233,30 +114,6 @@ class Test
233
114
  return this.ok(ret.val, desc, ret.err);
234
115
  }
235
116
 
236
- /**
237
- * Mark a test as failed.
238
- *
239
- * @param {string} desc
240
- * @param {*} [directive]
241
- * @returns {Log}
242
- */
243
- fail (desc, directive)
244
- {
245
- return this.ok(false, desc, directive);
246
- }
247
-
248
- /**
249
- * Mark a test as passed.
250
- *
251
- * @param {string} desc
252
- * @param {*} [directive]
253
- * @returns {Log}
254
- */
255
- pass (desc, directive)
256
- {
257
- return this.ok(true, desc, directive);
258
- }
259
-
260
117
  /**
261
118
  * See if a function throws an Error.
262
119
  *
@@ -689,36 +546,6 @@ class Test
689
546
  }
690
547
  }
691
548
 
692
- /**
693
- * Skip a test.
694
- *
695
- * @param {?string} reason - Why the test was skipped.
696
- * @param {string} desc
697
- * @returns {Log}
698
- */
699
- skip (reason, desc)
700
- {
701
- const log = this.ok(true, desc);
702
- log.skipped = true;
703
- if (typeof reason === S)
704
- log.skippedReason = reason;
705
- this.skipped++;
706
- return log;
707
- }
708
-
709
- /**
710
- * Add diagnostics info directly to our test logs.
711
- *
712
- * @param {*} msg - The info to add.
713
- * If this is a `string` it will be displayed as a comment as is.
714
- * If it is anything else, it will be encoded as JSON first.
715
- *
716
- */
717
- diag (msg)
718
- {
719
- this.log.push(msg);
720
- }
721
-
722
549
  /**
723
550
  * Run an assortment of tests using a map.
724
551
  *
@@ -758,7 +585,7 @@ class Test
758
585
  for (const test of tests)
759
586
  {
760
587
  const tt = typeof test;
761
- if (tt === S && TEST_METHODS.includes(test))
588
+ if (tt === S && this.$testMethods.includes(test))
762
589
  { // Set the current test to a built-in.
763
590
  current = test;
764
591
  }
@@ -791,99 +618,11 @@ class Test
791
618
  return logs;
792
619
  }
793
620
 
794
- /**
795
- * Return TAP formatted output for all the tests.
796
- *
797
- * @returns {string} The test logs, in TAP format.
798
- */
799
- tap ()
800
- {
801
- let out = '';
802
- if (this.planned > 0)
803
- {
804
- out += '1..'+this.planned+"\n";
805
- }
806
- let t = 1;
807
- for (const log of this.log)
808
- {
809
- if (log instanceof Log)
810
- {
811
- out += log.tap(t++);
812
- }
813
- else
814
- { // A comment.
815
- out += '# ' + (typeof log === S ? log : types.stringify(log)) + "\n";
816
- }
817
- }
818
- if (this.skipped)
819
- {
820
- out += '# Skipped '+this.skipped+" tests\n";
821
- }
822
- if (this.failed)
823
- {
824
- out += '# Failed '+this.failed+(this.failed>1?' tests':' test');
825
- if (this.planned)
826
- out += ' out of '+this.planned;
827
- out += "\n";
828
- }
829
- const ran = t-1;
830
- if (this.planned > 0 && this.planned != ran)
831
- {
832
- out += '# Looks like you planned '+this.planned+' but ran '+ran+" tests\n";
833
- }
834
- return out;
835
- }
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
-
854
- /**
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*.
859
- */
860
- output ()
861
- {
862
- console.log(this.tap());
863
- return this;
864
- }
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
-
885
621
  } // class Test
886
622
 
623
+ // May want this for sub-classes.
624
+ def(Test, 'Stats', Stats);
625
+
887
626
  // Should never need this, but...
888
627
  def(Test, 'Log', Log);
889
628
 
@@ -895,12 +634,17 @@ def(Test, '$METHODS',
895
634
  {
896
635
  test: TEST_METHODS,
897
636
  meta: META_METHODS,
637
+ $meta:
638
+ [ // A list of properties to skip in `all`.
639
+ '$meta', 'all', 'extend',
640
+ ],
898
641
  get all()
899
642
  {
900
643
  const list = [];
644
+ const skip = this.$meta;
901
645
  for (const name in this)
902
646
  {
903
- if (name === 'all') continue;
647
+ if (skip.includes(name)) continue;
904
648
  const prop = this[name];
905
649
  if (isArray(prop))
906
650
  {
@@ -909,12 +653,36 @@ def(Test, '$METHODS',
909
653
  }
910
654
  return list;
911
655
  },
912
- });
656
+ extend(newClass, opts={})
657
+ {
658
+ const mode = obj.CLONE.DEEP;
659
+ const clone = obj.clone(this, {mode});
913
660
 
914
- // Export the class
915
- module.exports = Test;
661
+ //console.debug("extend:clone<pre>", clone);
662
+
663
+ if (isObj(opts.add))
664
+ { // Add in the extra properties.
665
+ //console.debug("opts.add", opts.add);
666
+ obj.copyProps(opts.add, clone, opts.addOpts);
667
+ }
916
668
 
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');
669
+ if (isArray(opts.meta))
670
+ { // Added properties to skip in 'all'.
671
+ for (const val of opts.meta)
672
+ {
673
+ if (!clone.$meta.includes(val))
674
+ {
675
+ clone.$meta.push(val);
676
+ }
677
+ }
678
+ }
920
679
 
680
+ //console.debug("extend:clone<post>", clone);
681
+
682
+ // Add a new cloned and extended `$METHODS` property.
683
+ def(newClass, '$METHODS', {value: clone});
684
+ }, // extend()
685
+ }); // Test.$METHODS
686
+
687
+ // Export the class
688
+ module.exports = Test;
@@ -5,25 +5,35 @@ const {S,O,isArray,stringify,def} = types;
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
- * @property {string} skippedReason - If the test was skipped, why?
12
+ * @property {boolean} todo - Was the test marked TODO?
13
+ * @property {string} reason - If the test was skipped or TODO, why?
11
14
  * @property {?string} desc - A short description of the test.
12
15
  * @property {*} directive - Special directives describing the test.
13
16
  * @property {object} details - Extra information about the test.
14
17
  *
15
- * @property {*} [details.got] The value that was received in an `is` test.
16
- * @property {*} [details.wanted] The value that was expectged in an `is` test.
17
- * @property {*} [details.stringify] If `true` then output the details as JSON.
18
+ * @property {*} [details.got] The value that was received in a test.
19
+ * @property {*} [details.wanted] The value that was expectged in a test.
20
+ * @property {string} [details.comparitor] The comparitor used in a test.
21
+ * @property {boolean} [details.stringify] If `true` then output the details as JSON.
22
+ * @property {object} [details.info] An optional array of extra information.
18
23
  *
19
24
  */
20
25
  class Log
21
26
  {
27
+ /**
28
+ * (Internal Constructor)
29
+ * @param {object} test - The parent `Test` or `Stats` instance.
30
+ */
22
31
  constructor (test)
23
32
  {
24
33
  this.ok = false;
25
34
  this.skipped = false;
26
- this.skippedReason = '';
35
+ this.todo = false;
36
+ this.reason = '';
27
37
  this.desc = null;
28
38
  this.directive = null;
29
39
  this.details = {};
@@ -60,21 +70,27 @@ class Log
60
70
  else if (typeof this.directive == O && this.directive instanceof Error)
61
71
  out += ` # ${this.directive.name}: ${this.directive.message}`;
62
72
  else if (this.skipped)
63
- out += ' # SKIP ' + this.skippedReason;
73
+ out += ' # SKIP ' + this.reason;
74
+ else if (this.todo)
75
+ out += ' # TODO ' + this.reason;
64
76
 
65
77
  out += "\n";
66
78
 
67
- if ('got' in this.details && 'wanted' in this.details)
79
+ if ('got' in this.details)
68
80
  {
69
- var got = this.details.got;
70
- var want = this.details.wanted;
71
- if (this.details.stringify)
81
+ const stringy = this.details.stringify;
82
+
83
+ let got = this.details.got;
84
+ if (stringy) got = stringify(got, SD);
85
+ out += `# got: ${got}\n`;
86
+
87
+ if ('wanted' in this.details)
72
88
  {
73
- got = stringify(got, SD);
74
- want = stringify(want, SD);
89
+ let want = this.details.wanted;
90
+ if (stringy) want = stringify(want, SD);
91
+ out += `# expected: ${want}\n`;
75
92
  }
76
- out += `# got: ${got}\n`;
77
- out += `# expected: ${want}\n`;
93
+
78
94
  if (typeof this.details.comparitor === S)
79
95
  {
80
96
  out += `# op: ${this.details.comparitor}\n`;
@@ -91,7 +107,7 @@ class Log
91
107
  for (const i in info)
92
108
  {
93
109
  const line = (typeof info[i] === S) ? info[i] : stringify(info[i], SD);
94
- out += `## ${line}\n`;
110
+ out += `# - ${line}\n`;
95
111
  }
96
112
  }
97
113