risei 3.0.0 → 3.1.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.
@@ -19,9 +19,19 @@ export default class TestResult {
19
19
  #expectedText;
20
20
  #actualText;
21
21
 
22
+ #thrown;
23
+ #trouble;
24
+
25
+ #thrownText;
26
+ #troubleText;
27
+
28
+ #wasThrowTest;
29
+
30
+ #doesHaveThrow;
31
+ #doesHaveTrouble;
32
+
22
33
  #didPass;
23
34
  #actual;
24
- #thrown;
25
35
 
26
36
  // endregion Fields
27
37
 
@@ -91,19 +101,39 @@ export default class TestResult {
91
101
  this.#didPass = value;
92
102
  }
93
103
 
104
+ get wasThrowTest() {
105
+ return this.#wasThrowTest;
106
+ }
107
+
108
+ get doesHaveThrow() {
109
+ return this.#doesHaveThrow;
110
+ }
111
+
112
+ get doesHaveTrouble() {
113
+ return this.#doesHaveTrouble;
114
+ }
115
+
94
116
  get anyThrow() {
95
117
  return this.#thrown;
96
118
  }
97
119
 
98
- set anyThrow(value) {
99
- this.#thrown = value;
120
+ get anyTrouble() {
121
+ return this.#trouble;
122
+ }
123
+
124
+ get anyThrowText() {
125
+ return this.#thrownText;
126
+ }
127
+
128
+ get anyTroubleText() {
129
+ return this.#troubleText;
100
130
  }
101
131
 
102
132
  // endregion Outcome properties
103
133
 
104
134
  // region Construction
105
135
 
106
- constructor(test) {
136
+ constructor(test) /* ok */ {
107
137
  this.#test = test;
108
138
  this.#displayer = new TotalDisplayer();
109
139
  }
@@ -112,56 +142,76 @@ export default class TestResult {
112
142
 
113
143
  // region Before run: setNature() and dependencies
114
144
 
115
- setNature() {
145
+ setNature() /* passed */ {
116
146
  this.#identityText = this.#calculateIdentityText();
117
147
  this.#initorsText = this.#calculateInitorsText();
118
148
  this.#inputsText = this.#calculateInputsText();
119
149
  this.#expectedText = this.#calculateExpectedText();
150
+ this.#wasThrowTest = this.#test.isThrowTest;
120
151
  }
121
152
 
122
- #calculateIdentityText() {
153
+ #calculateIdentityText() /* verified */ {
123
154
  let text = `${ this.test.for } `;
124
155
  return text;
125
156
  }
126
157
 
127
- #calculateInitorsText() {
158
+ #calculateInitorsText() /* verified */ {
128
159
  let text = this.#displayItemRow(this.test.with);
129
160
  text = `Initors: ${ text }. `;
130
161
  return text;
131
162
  }
132
163
 
133
- #calculateInputsText() {
164
+ #calculateInputsText() /* verified */ {
134
165
  let text = this.#displayItemRow(this.test.in);
135
166
  text = `Inputs: ${ text }. `;
136
167
  return text;
137
168
  }
138
169
 
139
- #calculateExpectedText() {
170
+ #calculateExpectedText() /* verified */ {
140
171
  let text = this.#displaySingleItem(this.test.out);
141
172
  text = `Expected: ${ text }. `;
142
173
  return text;
143
174
  }
144
-
175
+
145
176
  // endregion Before run: setNature() and dependencies
146
177
 
147
178
  // region After run: setResults() and dependencies
148
179
 
149
- setResults() {
180
+ setResults() /* passed */ {
150
181
  // Direct results.
151
182
  this.#didPass = this.test.didPass;
152
183
  this.#actual = this.test.actual;
153
- this.#thrown = this.test.anyThrow;
154
184
 
185
+ this.#thrown = this.test.thrown;
186
+ this.#trouble = this.test.trouble;
187
+
188
+ this.#doesHaveThrow = this.test.doesHaveUnsoughtThrow;
189
+ this.#doesHaveTrouble = this.test.doesHaveTrouble;
190
+
155
191
  // Derived displayable results.
156
192
  this.#actualText = this.#calculateActualText();
193
+ this.#thrownText = this.#calculateThrownText("Unsought throw in tested code", this.#thrown);
194
+ this.#troubleText = this.#calculateThrownText("Throw in framing code", this.#trouble);
157
195
  }
158
196
 
159
- #calculateActualText() {
197
+ #calculateActualText() /* verified */ {
160
198
  let text = this.#displaySingleItem(this.test.actual);
161
199
  text = `Actual: ${ text }. `;
162
200
  return text;
163
201
  }
164
202
 
203
+ #calculateThrownText(scope, thrown) /* verified */ {
204
+ // No throw to display.
205
+ if (thrown === undefined) {
206
+ return; // Undefined.
207
+ }
208
+
209
+ let text = `${ scope }: ${ this.#test.on.name }: ${ this.#test.runName }: `
210
+ + `\"${ this.#test.for }\": ${ thrown.message }`;
211
+
212
+ return text;
213
+ }
214
+
165
215
  // endregion After run: setResults() and dependencies
166
216
 
167
217
  // region Displaying test args
@@ -1,6 +1,6 @@
1
1
  /**/
2
2
 
3
- /* Runs tests one by one as an ATestCaller requests them. */
3
+ /* Runs tests one by one as TestCaller or equivalent requests them. */
4
4
 
5
5
  import TestDef from "./TestDef.js";
6
6
  import TestGroup from "./TestGroup.js";
@@ -25,6 +25,10 @@ export default class TestRunner {
25
25
  #numberRun;
26
26
  #numberPassed;
27
27
  #numberFailed;
28
+
29
+ #thrown;
30
+ #trouble;
31
+
28
32
  #allDidPass;
29
33
 
30
34
  // endregion Private fields
@@ -67,6 +71,14 @@ export default class TestRunner {
67
71
  this.#numberFailed = value;
68
72
  }
69
73
 
74
+ get thrown() {
75
+ return this.#thrown;
76
+ }
77
+
78
+ get trouble() {
79
+ return this.#trouble;
80
+ }
81
+
70
82
  get allDidPass() {
71
83
  return this.#allDidPass;
72
84
  }
@@ -86,6 +98,9 @@ export default class TestRunner {
86
98
  this.#numberPassed = 0;
87
99
  this.#numberFailed = 0;
88
100
 
101
+ this.#thrown = [];
102
+ this.#trouble = [];
103
+
89
104
  this.#allDidPass = true;
90
105
  }
91
106
 
@@ -103,7 +118,10 @@ export default class TestRunner {
103
118
  // including groups and summary.
104
119
  async * runTests() {
105
120
  if (!this.#tests) {
106
- throw new Error("No tests available to run. TestRunner's .tests must be set before an attempt to run is made.");
121
+ throw new Error(
122
+ "No tests available to run. TestRunner's .tests "
123
+ + "must be set before an attempt to run is made."
124
+ );
107
125
  }
108
126
 
109
127
  // Converting so that property names can be relied on.
@@ -134,15 +152,16 @@ export default class TestRunner {
134
152
  yield methodGroup;
135
153
  }
136
154
 
137
- let result = await this.runOneTest(test);
155
+ let result = await this.#runOneTest(test);
156
+
138
157
  yield result;
139
158
  }
140
159
 
141
160
  // Iterative returning of final results summary.
142
- yield this.summarize();
161
+ yield this.#summarize();
143
162
  }
144
163
 
145
- async runOneTest(test) {
164
+ async #runOneTest(test) {
146
165
  // Default outputs. Never left undefined.
147
166
  test.didPass = false;
148
167
  test.anyThrow = null;
@@ -159,7 +178,7 @@ export default class TestRunner {
159
178
  result.setResults();
160
179
 
161
180
  // For later summarizing.
162
- this.#retainTestResults(test);
181
+ this.#retainTestResults(result);
163
182
 
164
183
  // Results and test back to the caller.
165
184
  return result;
@@ -167,22 +186,30 @@ export default class TestRunner {
167
186
 
168
187
  // region Dependencies of runOneTest()
169
188
 
170
- #retainTestResults(test) {
189
+ #retainTestResults(result) {
171
190
  this.#numberRun++;
172
191
 
173
- if (test.didPass) {
192
+ if (result.doesHaveThrow) {
193
+ this.#thrown.push(result.anyThrowText);
194
+ }
195
+
196
+ if (result.doesHaveTrouble) {
197
+ this.#trouble.push(result.anyTroubleText);
198
+ }
199
+
200
+ if (result.didPass) {
174
201
  this.#numberPassed++;
175
202
  }
176
203
  else {
177
204
  this.#numberFailed++;
178
205
  }
179
206
 
180
- this.#allDidPass &&= test.didPass;
207
+ this.#allDidPass &&= result.didPass;
181
208
  }
182
209
 
183
210
  // endregion Dependencies of runOneTest()
184
211
 
185
- summarize() {
212
+ #summarize() {
186
213
  let summary = `Ran ${ this.#numberRun } test${ this.#numberRun !== 1 ? "s" : "" }. `
187
214
  + `${ this.#allDidPass ? "All tests passed. " : "" }`
188
215
  + `${ this.#numberPassed } passed. `
@@ -190,9 +217,8 @@ export default class TestRunner {
190
217
 
191
218
  let anyWereRun = this.#numberRun > 0;
192
219
 
193
- return new TestSummary(summary, this.#allDidPass, anyWereRun);
220
+ return new TestSummary(summary, this.#allDidPass, anyWereRun, this.#thrown, this.#trouble);
194
221
  }
195
222
 
196
223
  // endregion Running tests
197
-
198
224
  }
@@ -6,12 +6,11 @@ import PropertySpoofer from "./PropertySpoofer.js";
6
6
  import TypeAnalyzer from "./TypeAnalyzer.js";
7
7
  import NameAnalyzer from "./NameAnalyzer.js";
8
8
  import TotalComparer from "./TotalComparer.js";
9
+ import CallTypes from "./CallTypes.js";
9
10
 
10
11
  export default class TestStages {
11
- /* &cruft, ? factor these to more classes */
12
-
13
- /* These methods hold operations carried out in the test frame,
14
- or before / after all test frames, in the test runner.
12
+ /* These methods hold reentrant operations carried
13
+ out in the test frame in the test runner.
15
14
  Not necessarily all test steps are found here. */
16
15
 
17
16
  // region Components
@@ -39,7 +38,7 @@ export default class TestStages {
39
38
  static or instance properties. */
40
39
 
41
40
  if (test.isConstructorTest) {
42
- test.target = new test.on.prototype.constructor(...test.in);
41
+ test.target = test.on.prototype;
43
42
  return;
44
43
  }
45
44
 
@@ -57,20 +56,19 @@ export default class TestStages {
57
56
 
58
57
  let callable = NameAnalyzer.plainNameOf(test.method);
59
58
 
60
- // Two most common cases: mono-call
61
- // tests of methods and properties.
62
- let localTarget = test.isMethodTest
63
- ? test.target
64
- : { nonce: () => { return test.target[callable]; } };
65
-
66
- // Rare cases: poly-call tests
67
- // of methods and properties.
68
- if (test.isPolyCallTest) {
69
- let nonce = test.isMethodTest
70
- ? this.supplyPolyMethodNonce(test, callable)
71
- : this.supplyPolyPropNonce(test, callable);
72
-
73
- localTarget = { nonce };
59
+ switch (test.callType) {
60
+ case CallTypes.methodMono:
61
+ return test.target;
62
+ case CallTypes.propMono:
63
+ return { nonce: () => { return test.target[callable]; } };
64
+ case CallTypes.conMono:
65
+ return { nonce: (...args) => { return new test.target[callable](...args); } };
66
+ case CallTypes.methodPoly:
67
+ return { nonce: this.supplyPolyMethodNonce(test, callable) };
68
+ case CallTypes.propPoly:
69
+ return { nonce: this.supplyPolyPropNonce(test, callable) };
70
+ case CallTypes.conPoly:
71
+ return { nonce: this.supplyPolyConstructorNonce(test, callable) };
74
72
  }
75
73
 
76
74
  return localTarget;
@@ -78,21 +76,13 @@ export default class TestStages {
78
76
 
79
77
  supplyCallableName(test) /* passed */ {
80
78
  /* Name is of the method named in test,
81
- or a nonce method for a property or
82
- any poly-call test (method or prop). */
79
+ or of a nonce method for prop, con,
80
+ or poly-call test (of any target). */
83
81
 
84
- // Two most common cases: mono-call
85
- // test of a method or property.
86
- let name = test.isMethodTest
82
+ let name = test.isMethodTest && test.isMonoCallTest
87
83
  ? NameAnalyzer.plainNameOf(test.method)
88
84
  : TestDef.nonceLocalCallableName;
89
85
 
90
- // Rare cases: poly-call tests
91
- // of a method or property.
92
- if (test.isPolyCallTest) {
93
- name = TestDef.nonceLocalCallableName;
94
- }
95
-
96
86
  return name;
97
87
  }
98
88
 
@@ -169,6 +159,21 @@ export default class TestStages {
169
159
  return nonce;
170
160
  }
171
161
 
162
+ supplyPolyConstructorNonce(test, callable) /* verified */ {
163
+ let nonce = (...inputs) => {
164
+ let actuals = [];
165
+
166
+ for (let args of inputs) {
167
+ let local = new test.target[callable](...args);
168
+ actuals.push(local);
169
+ }
170
+
171
+ return actuals;
172
+ };
173
+
174
+ return nonce;
175
+ }
176
+
172
177
  supplyNonReturnActual(test) /* passed */ {
173
178
  // When .from is a string, the actual
174
179
  // is the named target or type member.
@@ -181,12 +186,15 @@ export default class TestStages {
181
186
  ? test.target
182
187
  : test.type;
183
188
 
189
+ // Constructors are different.
190
+ host = test.isConstructorTest
191
+ ? test.actual
192
+ : host;
193
+
184
194
  // Actually supplying value.
185
195
  return host[name];
186
196
  }
187
197
 
188
- /* &cruft, possibly change all .from syntax
189
- to ? (actual, test), or ? (test) */
190
198
  // When there is a .from that's a function,
191
199
  // the actual is the result of calling it,
192
200
  // given everything that might be needed.
@@ -199,10 +207,5 @@ export default class TestStages {
199
207
  throw new Error("The test.from value was not a usable type. It must be either a property name or a function to work.");
200
208
  }
201
209
 
202
- #compare(expected, actual) /* verified */ {
203
- return this.#comparer.compare(expected, actual);
204
- }
205
-
206
210
  // endregion Dependencies of test stages
207
-
208
211
  }
@@ -4,34 +4,35 @@ export default class TestSummary {
4
4
  #summary;
5
5
  #allDidPass;
6
6
  #anyWereRun;
7
+
8
+ #anyThrows;
9
+ #anyTroubles;
7
10
 
8
11
  get summary() {
9
12
  return this.#summary;
10
13
  }
11
14
 
12
- set summary(value) {
13
- this.#summary = value;
14
- }
15
-
16
15
  get allDidPass() {
17
16
  return this.#allDidPass;
18
17
  }
19
18
 
20
- set allDidPass(value) {
21
- this.#allDidPass = value;
22
- }
23
-
24
19
  get anyWereRun() {
25
20
  return this.#anyWereRun;
26
21
  }
27
22
 
28
- set anyWereRun(value) {
29
- this.#anyWereRun = value;
23
+ get anyThrows() {
24
+ return this.#anyThrows;
30
25
  }
31
-
32
- constructor(summary, allDidPass, anyWereRun) {
33
- this.summary = summary;
34
- this.allDidPass = allDidPass;
35
- this.anyWereRun = anyWereRun;
26
+
27
+ get anyTroubles() {
28
+ return this.#anyTroubles;
29
+ }
30
+
31
+ constructor(summary, allDidPass, anyWereRun, anyThrows, anyTroubles) {
32
+ this.#summary = summary;
33
+ this.#allDidPass = allDidPass;
34
+ this.#anyWereRun = anyWereRun;
35
+ this.#anyThrows = anyThrows;
36
+ this.#anyTroubles = anyTroubles;
36
37
  }
37
38
  }
@@ -1,6 +1,7 @@
1
1
  /**/
2
2
 
3
3
  import ATestSource from "./ATestSource.js";
4
+ import ObjectAnalyzer from "./ObjectAnalyzer.js";
4
5
 
5
6
  export default class TotalComparer {
6
7
  constructor() {
@@ -65,26 +66,31 @@ export default class TotalComparer {
65
66
  if (!(actual instanceof Object)) {
66
67
  return false;
67
68
  }
68
-
69
- // There must not be any extra members
70
- // of `actual`, its own or inherited.
71
- for (let propertyName in actual) {
72
- if (!(propertyName in expected)) {
73
- return false;
74
- }
69
+
70
+ let expectedNames = ObjectAnalyzer.allProperties(expected);
71
+ let actualNames = ObjectAnalyzer.allProperties(actual);
72
+
73
+ // Objects with different numbers of properties can't be the same.
74
+ if (expectedNames.length !== actualNames.length) {
75
+ return false;
75
76
  }
76
77
 
77
- // All members of `expected`, its own or inherited,
78
- // must exist in `actual` and match it recursively.
79
- for (let propertyName in expected) {
80
- // Members present must match.
81
- if (!(propertyName in actual)) {
78
+ // O(n) algorithm covers all other cases, including recursion.
79
+ for (let at = 0; at < expectedNames.length; at++) {
80
+ // Objects with differing property names can't be the same.
81
+ if (expectedNames[at] !== actualNames[at]) {
82
82
  return false;
83
83
  }
84
-
84
+
85
+ // Same property name in both objects.
86
+ let name = expectedNames[at];
87
+
88
+ // Individual props, or their throws if getters that fail.
89
+ let paired = this.#retrieveExpectedAndActualProps(name, expected, actual);
90
+
85
91
  // Cross-recursion.
86
- let areEqual = this.#recurse(expected[propertyName], actual[propertyName]);
87
-
92
+ let areEqual = this.#recurse(paired.expected, paired.actual);
93
+
88
94
  // Exit only at first false.
89
95
  switch (areEqual) {
90
96
  case true: continue;
@@ -95,7 +101,32 @@ export default class TotalComparer {
95
101
  // No mismatched or missing found.
96
102
  return true;
97
103
  }
98
-
104
+
105
+ /* Dependency of #recurseOverObjects(). */
106
+ #retrieveExpectedAndActualProps(name, expected, actual) {
107
+ /* Expected and actual prop values are retrieved in individual
108
+ try-catches because getters may fail, and this way,
109
+ any fails can be compared as indicators of overall state. */
110
+
111
+ let paired = { };
112
+
113
+ try {
114
+ paired.expected = expected[name];
115
+ }
116
+ catch (thrown) {
117
+ paired.expected = thrown;
118
+ }
119
+
120
+ try {
121
+ paired.actual = actual[name];
122
+ }
123
+ catch (thrown) {
124
+ paired.actual = thrown;
125
+ }
126
+
127
+ return paired;
128
+ }
129
+
99
130
  #recurseOverMaps(expected, actual) /* verified */ {
100
131
  // Both must be Maps.
101
132
  if (!(actual instanceof Map)) {
@@ -229,7 +260,7 @@ export default class TotalComparer {
229
260
  }
230
261
 
231
262
  #compareFunctions(expected, actual) /* verified */ {
232
- // `expected` is already known to be a function / method.
263
+ // 'expected' is already known to be a function / method.
233
264
  if (!(actual instanceof Function)) {
234
265
  return false;
235
266
  }
@@ -253,6 +284,7 @@ export default class TotalComparer {
253
284
  }
254
285
 
255
286
  #compareDates(expected, actual) /* verified */ {
287
+ // 'expected' is already known to be a Date.
256
288
  if (!(actual instanceof Date)) {
257
289
  return false;
258
290
  }
@@ -261,6 +293,11 @@ export default class TotalComparer {
261
293
  }
262
294
 
263
295
  #compareErrors(expected, actual) /* verified */ {
296
+ // 'expected' is already known to be an Error.
297
+ if (!(actual instanceof Error)) {
298
+ return false;
299
+ }
300
+
264
301
  /* Compares Errors and subclasses accurately apart from .stack,
265
302
  which is ignored because it rarely makes sense to compare it. */
266
303
 
@@ -3,23 +3,27 @@
3
3
  import TypeIdentifier from "./TypeIdentifier.js";
4
4
  import Types from "./Types.js";
5
5
  import ATestSource from "./ATestSource.js";
6
+ import ObjectAnalyzer from "./ObjectAnalyzer.js";
6
7
 
7
8
  export default class TotalDisplayer {
8
- display(value) /* good */ {
9
+ static defaultToString = "[object Object]";
10
+
11
+ display(value) /* passed */ {
9
12
  let typeId = TypeIdentifier.identify(value);
10
13
 
11
14
  switch (typeId) {
15
+ case Types.isUndefined: return this.displayUndefined(value);
12
16
  case Types.isValue: return this.displayValue(value);
13
17
  case Types.isString: return this.displayString(value);
14
18
  case Types.isArray: return this.displayArray(value);
15
19
  case Types.isMap: return this.displayMap(value);
16
20
  case Types.isSet: return this.displaySet(value);
21
+ case Types.isFile: return this.displayFile(value);
17
22
  case Types.isError: return this.displayError(value);
18
23
  case Types.isClass: return this.displayClass(value);
19
24
  case Types.isFunction: return this.displayFunction(value);
20
25
  case Types.isObject: return this.displayObject(value);
21
26
  case Types.isDate: return this.displayValue(value);
22
- case Types.isUndefined: return this.displayUndefined(value);
23
27
  case Types.isUndefSymbol: return this.displayUndefined(value);
24
28
  case Types.isNull: return this.displayNull(value);
25
29
  }
@@ -82,6 +86,10 @@ export default class TotalDisplayer {
82
86
  return output;
83
87
  }
84
88
 
89
+ displayFile(value) /* passed */ {
90
+ return `File{name:"${ value.name }",size:${ value.size }}`;
91
+ }
92
+
85
93
  displayError(value) /* passed */ {
86
94
  // Subtype can't be found with typeof.
87
95
  let type = value.name;
@@ -125,13 +133,28 @@ export default class TotalDisplayer {
125
133
  }
126
134
 
127
135
  displayObject(value) /* passed */ {
136
+ // If a custom toString() exists, use its output.
137
+ let asString = value.toString();
138
+
139
+ if (asString !== TotalDisplayer.defaultToString) {
140
+ return asString;
141
+ }
142
+
128
143
  // Each property of the object is handled individually.
129
144
  let items = [ ];
130
145
 
131
- for (let p in value) {
132
- // Cross-recursion.
133
- let item = this.display(value[p]);
134
- items.push(`${ p }:${ item }`);
146
+ let props = ObjectAnalyzer.allProperties(value);
147
+
148
+ for (let prop of props) {
149
+ // Cross-recursion. Try-catch because getters can fail.
150
+ try {
151
+ let item = this.display(value[prop]);
152
+ items.push(`${ prop }:${ item }`);
153
+ }
154
+ catch (e)
155
+ {
156
+ items.push(`${ prop }:(threw)`);
157
+ }
135
158
  }
136
159
 
137
160
  // Properties are all listed together.