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.
- package/README.md +34 -23
- package/index.js +18 -66
- package/package.json +7 -6
- package/system/CallTypes.js +10 -0
- package/system/Choices.js +92 -0
- package/system/LocalCaller.js +45 -5
- package/system/ObjectAnalyzer.js +36 -0
- package/system/SpoofDef.js +73 -58
- package/system/TerminalReporter.js +102 -25
- package/system/TestDef.js +62 -5
- package/system/TestFinder.js +31 -29
- package/system/TestFrame.js +14 -3
- package/system/TestResult.js +63 -13
- package/system/TestRunner.js +38 -12
- package/system/TestStages.js +40 -37
- package/system/TestSummary.js +16 -15
- package/system/TotalComparer.js +54 -17
- package/system/TotalDisplayer.js +29 -6
- package/system/TypeAnalyzer.js +52 -14
- package/system/TypeIdentifier.js +10 -6
- package/system/Types.js +1 -0
- package/system/ATestCaller.js +0 -61
- package/system/ATestFinder.js +0 -38
- package/system/ATestReporter.js +0 -26
package/system/TestResult.js
CHANGED
|
@@ -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
|
-
|
|
99
|
-
this.#
|
|
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
|
package/system/TestRunner.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**/
|
|
2
2
|
|
|
3
|
-
/* Runs tests one by one as
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
189
|
+
#retainTestResults(result) {
|
|
171
190
|
this.#numberRun++;
|
|
172
191
|
|
|
173
|
-
if (
|
|
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 &&=
|
|
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
|
}
|
package/system/TestStages.js
CHANGED
|
@@ -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
|
-
/*
|
|
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 =
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
82
|
-
|
|
79
|
+
or of a nonce method for prop, con,
|
|
80
|
+
or poly-call test (of any target). */
|
|
83
81
|
|
|
84
|
-
|
|
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
|
}
|
package/system/TestSummary.js
CHANGED
|
@@ -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
|
-
|
|
29
|
-
this.#
|
|
23
|
+
get anyThrows() {
|
|
24
|
+
return this.#anyThrows;
|
|
30
25
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this
|
|
34
|
-
|
|
35
|
-
|
|
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
|
}
|
package/system/TotalComparer.js
CHANGED
|
@@ -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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
|
package/system/TotalDisplayer.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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.
|