risei 1.3.4 → 2.0.1

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.
Files changed (44) hide show
  1. package/README.md +134 -479
  2. package/index.js +5 -5
  3. package/package.json +10 -11
  4. package/system/ASpoofingFixture.js +7 -8
  5. package/system/ATestCaller.js +3 -3
  6. package/system/ATestFinder.js +2 -2
  7. package/system/ATestReporter.js +2 -1
  8. package/system/ATestSource.js +5 -1
  9. package/system/ChosenTestFinder.js +4 -4
  10. package/system/ClassTestGroup.js +2 -2
  11. package/system/LocalCaller.js +5 -5
  12. package/system/{ClassMethodSpoofer.js → MethodSpoofer.js} +54 -48
  13. package/system/MethodTestGroup.js +2 -2
  14. package/system/Moment.js +1 -1
  15. package/system/NameAnalyzer.js +26 -0
  16. package/system/PropertySpoofer.js +156 -0
  17. package/system/Risei.js +5 -5
  18. package/system/SpoofDef.js +260 -0
  19. package/system/TerminalReporter.js +8 -8
  20. package/system/{TestDefinition.js → TestDef.js} +153 -107
  21. package/system/TestFinder.js +3 -3
  22. package/system/TestFrame.js +15 -52
  23. package/system/TestGroup.js +1 -1
  24. package/system/TestResult.js +2 -2
  25. package/system/TestRunner.js +23 -107
  26. package/system/TestStages.js +80 -76
  27. package/system/TestSummary.js +1 -1
  28. package/system/TotalComparer.js +60 -11
  29. package/system/TotalDisplayer.js +33 -12
  30. package/system/TypeAnalyzer.js +41 -79
  31. package/system/TypeIdentifier.js +18 -8
  32. package/system/Types.js +3 -1
  33. package/system/AComparer.js +0 -9
  34. package/system/ATestFixture.js +0 -44
  35. package/system/ClassPropertySpoofer.js +0 -185
  36. package/system/ObjectMethodSpoofer.js +0 -58
  37. package/system/ObjectPropertySpoofer.js +0 -136
  38. package/system/SpoofDefinition.js +0 -243
  39. package/system/TestFrameChooser.js +0 -54
  40. package/system/TestFrames.js +0 -232
  41. package/system/TotalCopier.js +0 -106
  42. package/usage-examples/Output-example.png +0 -0
  43. package/usage-examples/Summary-example.png +0 -0
  44. package/usage-examples/Syntax-example.png +0 -0
@@ -2,41 +2,24 @@
2
2
 
3
3
  /* Runs tests one by one as an ATestCaller requests them. */
4
4
 
5
- import { TestDefinition } from "./TestDefinition.js";
6
- import { TestGroup } from "./TestGroup.js";
7
- import { ClassTestGroup } from "./ClassTestGroup.js";
8
- import { MethodTestGroup } from "./MethodTestGroup.js";
9
- import { TestResult } from "./TestResult.js";
10
- import { TestSummary } from "./TestSummary.js";
5
+ import TestDef from "./TestDef.js";
6
+ import TestGroup from "./TestGroup.js";
7
+ import ClassTestGroup from "./ClassTestGroup.js";
8
+ import MethodTestGroup from "./MethodTestGroup.js";
9
+ import TestResult from "./TestResult.js";
10
+ import TestSummary from "./TestSummary.js";
11
11
 
12
- import { TestFrameChooser } from "./TestFrameChooser.js";
13
- import { TestFrames } from "./TestFrames.js";
12
+ import TestFrame from "./TestFrame.js";
14
13
 
15
- import { ClassMethodSpoofer } from "./ClassMethodSpoofer.js";
16
- import { ObjectMethodSpoofer } from "./ObjectMethodSpoofer.js";
17
- import { SpoofDefinition } from "./SpoofDefinition.js";
18
-
19
- export class TestRunner {
20
- // region Static definition fields
21
-
22
- // Used for a special test structural case;
23
- // classes have one constructor by this name.
24
- static #constructorName = "constructor";
25
-
26
- // endregion Static definition fields
14
+ import MethodSpoofer from "./MethodSpoofer.js";
15
+ import SpoofDef from "./SpoofDef.js";
27
16
 
17
+ export default class TestRunner {
28
18
  // region Private fields
29
19
 
30
20
  // Components and state.
31
21
  #tests;
32
-
33
- #classSpoofer;
34
- #instanceSpoofer;
35
-
36
- #frameSource;
37
- #frameChooser;
38
-
39
- #comparer;
22
+ #frame;
40
23
 
41
24
  // Results summary.
42
25
  #numberRun;
@@ -48,6 +31,8 @@ export class TestRunner {
48
31
 
49
32
  // region Properties
50
33
 
34
+ /* inputs */
35
+
51
36
  get tests() {
52
37
  return this.#tests;
53
38
  }
@@ -56,25 +41,7 @@ export class TestRunner {
56
41
  this.#tests = value;
57
42
  }
58
43
 
59
- // region Spoofing
60
-
61
- get classSpoofer() {
62
- return this.#classSpoofer;
63
- }
64
-
65
- set classSpoofer(value) {
66
- this.#classSpoofer = value;
67
- }
68
-
69
- get instanceSpoofer() {
70
- return this.#instanceSpoofer;
71
- }
72
-
73
- set instanceSpoofer(value) {
74
- this.#instanceSpoofer = value;
75
- }
76
-
77
- // endregion Spoofing
44
+ /* outputs */
78
45
 
79
46
  get numberRun() {
80
47
  return this.#numberRun;
@@ -112,11 +79,8 @@ export class TestRunner {
112
79
 
113
80
  constructor() {
114
81
  this.#tests = [];
115
- this.#classSpoofer = new ClassMethodSpoofer();
116
- this.#instanceSpoofer = new ObjectMethodSpoofer();
117
-
118
- this.#frameSource = new TestFrames();
119
- this.#frameChooser = new TestFrameChooser(this.#frameSource);
82
+
83
+ this.#frame = new TestFrame();
120
84
 
121
85
  this.#numberRun = 0;
122
86
  this.#numberPassed = 0;
@@ -143,7 +107,7 @@ export class TestRunner {
143
107
  }
144
108
 
145
109
  // Converting so that property names can be relied on.
146
- this.#tests = TestDefinition.fromNonceTuples(this.tests);
110
+ this.#tests = TestDef.fromNonceTuples(this.tests);
147
111
 
148
112
  // Needed for displaying classes and methods as groups.
149
113
  let classGroup = new ClassTestGroup();
@@ -160,10 +124,10 @@ export class TestRunner {
160
124
  yield classGroup;
161
125
  }
162
126
 
163
- // Each new method name should be a group,
164
- // and so should each method for a class.
165
- if (test.of !== methodGroup.group || atFirstClassMethod) {
166
- methodGroup.group = test.of;
127
+ // Each new method / prop name should be a group,
128
+ // and so should the first method for a class.
129
+ if (test.runName !== methodGroup.group || atFirstClassMethod) {
130
+ methodGroup.group = test.runName;
167
131
  atFirstClassMethod = false;
168
132
  yield methodGroup;
169
133
  }
@@ -181,35 +145,14 @@ export class TestRunner {
181
145
  test.didPass = false;
182
146
  test.anyThrow = null;
183
147
 
184
- // Spoofing based on any spoof
185
- // definitions in .with and .of.
186
- this.#anyObjectSpoofing(test);
187
-
188
148
  // Gathering facts defining the nature of the test,
189
149
  // some of which might change after the test is run.
190
150
  let result = new TestResult(test);
191
151
  result.setNature();
192
152
 
193
- // Aspects of the test determine the right test frame,
194
- // bound back to its class to use private class members.
195
- let testFrame = this.#frameChooser.supplyTestFrame(test);
196
- testFrame = testFrame.bind(this.#frameSource);
197
-
198
- // Done in every test.
199
- this.#anyMethodSpoofing(test);
200
-
201
153
  // Actually running the test.
202
- try {
203
- testFrame(test);
204
- }
205
- catch (thrown) {
206
- test.anyThrow = thrown;
207
- test.didPass = false;
208
- }
209
-
210
- // Done in every test, whether passed, failed, or thrown.
211
- this.#anyMethodRestoring(test);
212
-
154
+ this.#frame.run(test);
155
+
213
156
  // Gathering facts based on the test run.
214
157
  result.setResults();
215
158
 
@@ -222,33 +165,6 @@ export class TestRunner {
222
165
 
223
166
  // region Dependencies of runOneTest()
224
167
 
225
- // Spoofs objects as defined in TestDefinition's
226
- // .with and .in, which can contain spoofs.
227
- #anyObjectSpoofing(test) {
228
- // Spoofing objects may be skipped for self-testing.
229
- if (typeof test.and === "string") {
230
- if (test.and.includes("nospoof")) {
231
- return;
232
- }
233
- }
234
-
235
- // Actually spoofing, if any is defined.
236
- this.#instanceSpoofer.spoof(test);
237
- }
238
-
239
- #anyMethodSpoofing(test) {
240
- if (test.plus === undefined) {
241
- return;
242
- }
243
-
244
- this.#classSpoofer.spoof(test);
245
- }
246
-
247
- #anyMethodRestoring(test) {
248
- this.#classSpoofer.unspoof();
249
- this.#classSpoofer.removeDefinitions();
250
- }
251
-
252
168
  #retainTestResults(test) {
253
169
  this.#numberRun++;
254
170
 
@@ -1,22 +1,32 @@
1
1
  /**/
2
2
 
3
- import { TestDefinition } from "./TestDefinition.js";
4
- import { TotalComparer } from "./TotalComparer.js";
5
- import { TotalCopier } from "./TotalCopier.js";
3
+ import TestDef from "./TestDef.js";
4
+ import MethodSpoofer from "./MethodSpoofer.js";
5
+ import PropertySpoofer from "./PropertySpoofer.js";
6
+ import TypeAnalyzer from "./TypeAnalyzer.js";
7
+ import NameAnalyzer from "./NameAnalyzer.js";
8
+ import TotalComparer from "./TotalComparer.js";
6
9
 
7
- export class TestStages {
8
- /* &cruft, implement all of these under test, and eventually factor them */
10
+ export default class TestStages {
11
+ /* &cruft, ? factor these to more classes */
9
12
 
10
13
  /* These methods hold operations carried out in the test frame,
11
14
  or before / after all test frames, in the test runner.
12
15
  Not necessarily all test steps are found here. */
13
16
 
17
+ static #methodSpoofer = new MethodSpoofer();
18
+ static #propertySpoofer = new PropertySpoofer();
14
19
  static #comparer = new TotalComparer();
15
- static #copier = new TotalCopier();
16
20
 
17
- anyPreTargetingGroundwork(test) {
18
- // Spoofing of class members here.
19
- // Setting any static properties here.
21
+ anyPreTargetingGroundwork(test) /* passed */ {
22
+ // Spoofing methods; properties spoofed later.
23
+ TestStages.#methodSpoofer.spoof(test);
24
+
25
+ // Arbitrary user-defined actions.
26
+ // Only spoofed methods available.
27
+ if (test.doesHaveDoEarly) {
28
+ test.do.early(test);
29
+ }
20
30
  }
21
31
 
22
32
  setTarget(test) /* passed */ {
@@ -25,58 +35,50 @@ export class TestStages {
25
35
  static or instance properties. */
26
36
 
27
37
  if (test.isConstructorTest) {
28
- test.target = test.on.prototype;
38
+ test.target = new test.on.prototype.constructor(...test.in);
29
39
  return;
30
40
  }
31
41
 
32
- let callable = TestDefinition.plainNameOf(test.of);
33
-
34
42
  let target = test.isInstanceTest
35
- ? this.#supplyInstanceTarget(test, callable)
36
- : this.#supplyStaticTarget(test, callable);
43
+ ? new test.on.prototype.constructor(...test.with)
44
+ : test.on;
37
45
 
38
46
  test.target = target;
39
47
  }
40
48
 
41
- // region Dependencies of setTarget()
42
-
43
- #supplyStaticTarget(test, callable) /* verified */ {
44
- let type = test.on;
45
-
46
- // Methods are called on class directly.
47
- // Properties are called on class in nonce.
48
- let target = test.isMethodTest
49
- ? type
50
- : { localCallable: () => { return type[callable]; } };
49
+ supplyLocalTarget(test) /* passed */ {
50
+ /* Methods are called on instance / type / prototype directly.
51
+ Properties are called on instance / type in a nonce. */
51
52
 
52
- return target;
53
- }
53
+ let callable = NameAnalyzer.plainNameOf(test.method);
54
54
 
55
- #supplyInstanceTarget(test, callable) /* verified */ {
56
- let instance = new test.on.prototype.constructor(...test.with);
57
-
58
- // Methods are called on instance directly.
59
- // Properties are called on instance in nonce.
60
- let target = test.isMethodTest
61
- ? instance
62
- : { localCallable: () => { return instance[callable]; } };
63
-
64
- return target;
55
+ let localTarget = test.isMethodTest
56
+ ? test.target
57
+ : { nonce: () => { return test.target[callable]; } };
58
+
59
+ return localTarget;
65
60
  }
66
-
67
- // endregion Dependencies of setTarget()
68
61
 
69
- setLocalCallable(test) /* passed */ {
62
+ supplyCallableName(test) /* passed */ {
70
63
  /* Method under test is a method named in test,
71
64
  or a nonce method for a property test. */
72
65
 
73
- test.localCallable = test.isMethodTest
74
- ? TestDefinition.plainNameOf(test.method)
75
- : TestDefinition.nonceLocalCallableName;
66
+ let name = test.isMethodTest
67
+ ? NameAnalyzer.plainNameOf(test.method)
68
+ : TestDef.nonceLocalCallableName;
69
+
70
+ return name;
76
71
  }
77
72
 
78
- anyPostTargetingGroundwork(test) {
79
- // Setting instance properties.
73
+ anyPostTargetingGroundwork(test) /* passed */ {
74
+ // Spoofing properties; methods spoofed earlier.
75
+ TestStages.#propertySpoofer.spoof(test);
76
+
77
+ // Arbitrary user-defined actions.
78
+ // All spoofs normally available.
79
+ if (test.doesHaveDoLate) {
80
+ test.do.late(test);
81
+ }
80
82
  }
81
83
 
82
84
  anyModifyActual(test) /* passed */ {
@@ -97,52 +99,54 @@ export class TestStages {
97
99
  return TestStages.#comparer.compare(expected, actual);
98
100
  }
99
101
 
100
- anyGroundworkReversion(test) {
101
- // Unspoofing.
102
- // Unsetting static properties.
103
- // Undoing any other groundwork changes made.
102
+ anyGroundworkReversal(test) /* passed */ {
103
+ // Unspoofing methods and properties.
104
+ TestStages.#methodSpoofer.unspoof(test);
105
+ TestStages.#propertySpoofer.unspoof(test);
106
+
107
+ // Arbitrary user-defined operations.
108
+ // No spoofs are available here.
109
+ if (test.doesHaveUndo) {
110
+ test.undo(test);
111
+ }
104
112
  }
105
113
 
106
114
  // region Dependencies of test stages
107
115
 
108
- #compare(expected, actual) {
109
- return this.#comparer.compare(expected, actual);
110
- }
111
-
112
- supplyNonReturnActual(test) {
113
- // When there is a .from that's a string,
114
- // the actual is the named target member.
116
+ supplyNonReturnActual(test) /* passed */ {
117
+ // When .from is a string, the actual
118
+ // is the named target or type member.
115
119
  if (typeof test.from === "string") {
116
- /* &cruft, maybe explain how this differs from setting .target,
117
- which can be a nonce, or else maybe make them the same */
118
-
119
- // Common member host;
120
- // undefined if static.
121
- let host = test.target;
122
-
123
- // When .and defines static.
124
- if (test.isStaticTest) {
125
- host = test.on;
126
- }
127
-
128
- return host[test.from];
120
+ // Removing any sigil.
121
+ let name = NameAnalyzer.plainNameOf(test.from);
122
+
123
+ // Determining which exposes the property.
124
+ let host = TypeAnalyzer.isInstanceMember(test.type, name)
125
+ ? test.target
126
+ : test.type;
127
+
128
+ // Actually supplying value.
129
+ return host[name];
129
130
  }
130
-
131
- /* &cruft, possibly change all .from syntax to (actual, test),
132
- or maybe to (test), or else switch back args here */
131
+
132
+ /* &cruft, possibly change all .from syntax
133
+ to ? (actual, test), or ? (test) */
133
134
  // When there is a .from that's a function,
134
135
  // the actual is the result of calling it,
135
136
  // given everything that might be needed.
136
137
  if (test.from instanceof Function) {
137
- return test.from(test.actual, test.target);
138
+ // Actually supplying value.
139
+ return test.from(test.target, test);
138
140
  }
139
141
 
140
- /* &cruft, probably drop this, change maybe to a throw */
141
- // When there is any other .from, the actual is the
142
- // value of the code element provided as .from.
143
- return test.from;
142
+ // Only property names and functions make sense to support.
143
+ throw new Error("The test.from value was not a usable type. It must be either a property name or a function to work.");
144
144
  }
145
145
 
146
+ #compare(expected, actual) /* verified */ {
147
+ return this.#comparer.compare(expected, actual);
148
+ }
149
+
146
150
  // endregion Dependencies of test stages
147
151
 
148
152
  }
@@ -1,6 +1,6 @@
1
1
  /**/
2
2
 
3
- export class TestSummary {
3
+ export default class TestSummary {
4
4
  #summary;
5
5
  #allDidPass;
6
6
  #anyWereRun;
@@ -1,10 +1,10 @@
1
1
  /**/
2
2
 
3
- import { AComparer } from "./AComparer.js";
3
+ import ATestSource from "./ATestSource.js";
4
4
 
5
- export class TotalComparer extends AComparer {
5
+ export default class TotalComparer {
6
6
  constructor() {
7
- super();
7
+ /* No operations. */
8
8
  }
9
9
 
10
10
  compare(expected, actual) /* passed */ {
@@ -20,33 +20,43 @@ export class TotalComparer extends AComparer {
20
20
  /* Traverse / recurse to leaves. */
21
21
  return this.#recurseOverArrays(expected, actual);
22
22
  }
23
-
23
+
24
24
  if (expected instanceof Map) {
25
25
  /* Traverse / recurse to leaves. */
26
26
  return this.#recurseOverMaps(expected, actual);
27
27
  }
28
-
28
+
29
29
  if (expected instanceof Set) {
30
30
  /* Traverse / recurse to leaves. */
31
31
  return this.#recurseOverSets(expected, actual);
32
32
  }
33
-
33
+
34
34
  if (expected instanceof Function) {
35
35
  /* Leaf to compare directly. */
36
36
  return this.#compareFunctions(expected, actual);
37
37
  }
38
-
38
+
39
39
  if (expected instanceof Date) {
40
40
  return this.#compareDates(expected, actual);
41
41
  }
42
-
42
+
43
+ if (expected instanceof Error) {
44
+ return this.#compareErrors(expected, actual);
45
+ }
46
+
43
47
  if (expected instanceof Object) {
44
48
  /* Traverse / recurse to leaves. */
45
49
  return this.#recurseOverObjects(expected, actual);
46
50
  }
47
-
48
- /* If neither a function leaf nor any
49
- fork type, compare as a leaf value. */
51
+
52
+ /* If a special symbol to support undefined
53
+ as expected, see if actual is undefined. */
54
+ if (expected === ATestSource.undefSymbol) {
55
+ return this.#compareUndefs(expected, actual);
56
+ }
57
+
58
+ /* If none of the above things,
59
+ compare as a leaf value. */
50
60
  return this.#compareValues(expected, actual);
51
61
  }
52
62
 
@@ -203,6 +213,21 @@ export class TotalComparer extends AComparer {
203
213
  return false;
204
214
  }
205
215
 
216
+ #compareUndefs(expected, actual) /* verified */ {
217
+ // The rare case of comparing undef symbols.
218
+ if (actual === expected) {
219
+ return true;
220
+ }
221
+
222
+ // The common case of using the undef symbol
223
+ // to see if the actual is undefined.
224
+ if (actual === undefined) {
225
+ return true;
226
+ }
227
+
228
+ return false;
229
+ }
230
+
206
231
  #compareFunctions(expected, actual) /* verified */ {
207
232
  // `expected` is already known to be a function / method.
208
233
  if (!(actual instanceof Function)) {
@@ -235,6 +260,30 @@ export class TotalComparer extends AComparer {
235
260
  return expected.valueOf() === actual.valueOf();
236
261
  }
237
262
 
263
+ #compareErrors(expected, actual) /* verified */ {
264
+ /* Compares Errors and subclasses accurately apart from .stack,
265
+ which is ignored because it rarely makes sense to compare it. */
266
+
267
+ // Most common difference.
268
+ if (expected.message != actual.message) {
269
+ return false;
270
+ }
271
+
272
+ // Different Error subtypes.
273
+ if (expected.name !== actual.name) {
274
+ return false;
275
+ }
276
+
277
+ // Recursion for causes, which may not be Errors.
278
+ if (!this.compare(expected.cause, actual.cause)) {
279
+ return false;
280
+ }
281
+
282
+ // If nothing failed, they are the same
283
+ // (apart, probably from .stack).
284
+ return true;
285
+ }
286
+
238
287
  // endregion Dependencies of compare()
239
288
 
240
289
  }
@@ -1,17 +1,12 @@
1
1
  /**/
2
2
 
3
- import { TypeIdentifier } from "./TypeIdentifier.js";
4
- import { Types } from "./Types.js";
3
+ import TypeIdentifier from "./TypeIdentifier.js";
4
+ import Types from "./Types.js";
5
+ import ATestSource from "./ATestSource.js";
5
6
 
6
- export class TotalDisplayer {
7
- #identifier;
8
-
9
- constructor() /* verified */ {
10
- this.#identifier = new TypeIdentifier();
11
- }
12
-
13
- display(value) /* passed */ {
14
- let typeId = this.#identifier.identify(value);
7
+ export default class TotalDisplayer {
8
+ display(value) /* good */ {
9
+ let typeId = TypeIdentifier.identify(value);
15
10
 
16
11
  switch (typeId) {
17
12
  case Types.isValue: return this.displayValue(value);
@@ -19,11 +14,13 @@ export class TotalDisplayer {
19
14
  case Types.isArray: return this.displayArray(value);
20
15
  case Types.isMap: return this.displayMap(value);
21
16
  case Types.isSet: return this.displaySet(value);
17
+ case Types.isError: return this.displayError(value);
22
18
  case Types.isClass: return this.displayClass(value);
23
19
  case Types.isFunction: return this.displayFunction(value);
24
20
  case Types.isObject: return this.displayObject(value);
25
21
  case Types.isDate: return this.displayValue(value);
26
22
  case Types.isUndefined: return this.displayUndefined(value);
23
+ case Types.isUndefSymbol: return this.displayUndefined(value);
27
24
  case Types.isNull: return this.displayNull(value);
28
25
  }
29
26
  }
@@ -85,6 +82,30 @@ export class TotalDisplayer {
85
82
  return output;
86
83
  }
87
84
 
85
+ displayError(value) /* passed */ {
86
+ // Subtype can't be found with typeof.
87
+ let type = value.name;
88
+
89
+ // No "" when no message.
90
+ let message = value.message !== ""
91
+ ? `\"${ value.message }\"`
92
+ : "";
93
+
94
+ // Inter-property punctuation only when needed.
95
+ let inter
96
+ = value.message !== "" && value.cause !== undefined
97
+ ? ", "
98
+ : "";
99
+
100
+ // Normally an Error / subtype, but not necessarily.
101
+ let cause = value.cause !== undefined
102
+ ? `cause:${ this.display(value.cause) }`
103
+ : "";
104
+
105
+ let output = `${ type }{${ message }${ inter }${ cause }}`;
106
+ return output;
107
+ }
108
+
88
109
  displayClass(value) /* passed */ {
89
110
  // Class definitions have a .name.
90
111
  let output = value.name;
@@ -124,7 +145,7 @@ export class TotalDisplayer {
124
145
  }
125
146
 
126
147
  displayUndefined(value) /* passed */ {
127
- return "undefined";
148
+ return "undef";
128
149
  }
129
150
 
130
151
  displayNull(value) /* passed */ {