risei 3.0.0 → 3.1.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.
- package/README.md +38 -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 +109 -25
- package/system/TestDef.js +72 -5
- package/system/TestFinder.js +31 -29
- package/system/TestFrame.js +14 -3
- package/system/TestResult.js +63 -13
- package/system/TestRunner.js +75 -40
- package/system/TestStages.js +40 -37
- package/system/TestSummary.js +22 -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/TestFrame.js
CHANGED
|
@@ -36,6 +36,7 @@ export default class TestFrame {
|
|
|
36
36
|
}
|
|
37
37
|
catch (thrown) {
|
|
38
38
|
// Sometimes tests look for this.
|
|
39
|
+
// Otherwise, listed in gold bar.
|
|
39
40
|
test.thrown = thrown;
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -46,11 +47,21 @@ export default class TestFrame {
|
|
|
46
47
|
test.didPass = this.#stages.compareResults(test.output, test.actual);
|
|
47
48
|
}
|
|
48
49
|
catch (thrown) {
|
|
49
|
-
|
|
50
|
+
// Fail of the test system, or of code in .do or .from.
|
|
51
|
+
test.trouble = thrown;
|
|
50
52
|
}
|
|
51
53
|
finally {
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
+
// Always undoing any groundwork changes made.
|
|
55
|
+
try {
|
|
56
|
+
await this.#stages.anyGroundworkReversal(test);
|
|
57
|
+
}
|
|
58
|
+
catch (thrown) {
|
|
59
|
+
// Fail of the test system, or of code in .undo;
|
|
60
|
+
// only needed when no preceding throw.
|
|
61
|
+
if (test.trouble === undefined) {
|
|
62
|
+
test.trouble = thrown;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
54
65
|
}
|
|
55
66
|
}
|
|
56
67
|
}
|
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,12 @@ export default class TestRunner {
|
|
|
25
25
|
#numberRun;
|
|
26
26
|
#numberPassed;
|
|
27
27
|
#numberFailed;
|
|
28
|
+
|
|
29
|
+
// Problems in different scopes.
|
|
30
|
+
#thrown; // In tested code.
|
|
31
|
+
#trouble; // In framing code.
|
|
32
|
+
#caught; // In outer system.
|
|
33
|
+
|
|
28
34
|
#allDidPass;
|
|
29
35
|
|
|
30
36
|
// endregion Private fields
|
|
@@ -67,6 +73,14 @@ export default class TestRunner {
|
|
|
67
73
|
this.#numberFailed = value;
|
|
68
74
|
}
|
|
69
75
|
|
|
76
|
+
get thrown() {
|
|
77
|
+
return this.#thrown;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get trouble() {
|
|
81
|
+
return this.#trouble;
|
|
82
|
+
}
|
|
83
|
+
|
|
70
84
|
get allDidPass() {
|
|
71
85
|
return this.#allDidPass;
|
|
72
86
|
}
|
|
@@ -86,6 +100,10 @@ export default class TestRunner {
|
|
|
86
100
|
this.#numberPassed = 0;
|
|
87
101
|
this.#numberFailed = 0;
|
|
88
102
|
|
|
103
|
+
this.#thrown = [];
|
|
104
|
+
this.#trouble = [];
|
|
105
|
+
this.#caught = [];
|
|
106
|
+
|
|
89
107
|
this.#allDidPass = true;
|
|
90
108
|
}
|
|
91
109
|
|
|
@@ -103,46 +121,56 @@ export default class TestRunner {
|
|
|
103
121
|
// including groups and summary.
|
|
104
122
|
async * runTests() {
|
|
105
123
|
if (!this.#tests) {
|
|
106
|
-
throw new Error(
|
|
124
|
+
throw new Error(
|
|
125
|
+
"No tests available to run. TestRunner's .tests "
|
|
126
|
+
+ "must be set before an attempt to run is made."
|
|
127
|
+
);
|
|
107
128
|
}
|
|
108
129
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
try {
|
|
131
|
+
// Converting so that property names can be relied on.
|
|
132
|
+
this.#tests = TestDef.fromNonceTuples(this.tests);
|
|
133
|
+
|
|
134
|
+
// Needed for displaying classes and methods as groups.
|
|
135
|
+
let classGroup = new ClassTestGroup();
|
|
136
|
+
let methodGroup = new MethodTestGroup();
|
|
137
|
+
let atFirstClassMethod = false;
|
|
138
|
+
|
|
139
|
+
// Iterative running of all tests in current order.
|
|
140
|
+
for (let test of this.#tests) {
|
|
141
|
+
// Each new class should be a group, and its start
|
|
142
|
+
// should be retained for grouping of methods.
|
|
143
|
+
if (test.on.name !== classGroup.group) {
|
|
144
|
+
classGroup.group = test.on.name;
|
|
145
|
+
atFirstClassMethod = true;
|
|
146
|
+
yield classGroup;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Each new method / prop name should be a group,
|
|
150
|
+
// and so should the first method for a class.
|
|
151
|
+
let runName = test.runName;
|
|
152
|
+
|
|
153
|
+
if (runName !== methodGroup.group || atFirstClassMethod) {
|
|
154
|
+
methodGroup.group = runName;
|
|
155
|
+
atFirstClassMethod = false;
|
|
156
|
+
yield methodGroup;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let result = await this.#runOneTest(test);
|
|
160
|
+
|
|
161
|
+
yield result;
|
|
125
162
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (runName !== methodGroup.group || atFirstClassMethod) {
|
|
132
|
-
methodGroup.group = runName;
|
|
133
|
-
atFirstClassMethod = false;
|
|
134
|
-
yield methodGroup;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
let result = await this.runOneTest(test);
|
|
138
|
-
yield result;
|
|
163
|
+
}
|
|
164
|
+
catch (caught) {
|
|
165
|
+
// Top of .stack is .message.
|
|
166
|
+
this.#caught.push(caught.stack);
|
|
139
167
|
}
|
|
140
168
|
|
|
141
169
|
// Iterative returning of final results summary.
|
|
142
|
-
yield this
|
|
170
|
+
yield this.#summarize();
|
|
143
171
|
}
|
|
144
172
|
|
|
145
|
-
async runOneTest(test) {
|
|
173
|
+
async #runOneTest(test) {
|
|
146
174
|
// Default outputs. Never left undefined.
|
|
147
175
|
test.didPass = false;
|
|
148
176
|
test.anyThrow = null;
|
|
@@ -159,7 +187,7 @@ export default class TestRunner {
|
|
|
159
187
|
result.setResults();
|
|
160
188
|
|
|
161
189
|
// For later summarizing.
|
|
162
|
-
this.#retainTestResults(
|
|
190
|
+
this.#retainTestResults(result);
|
|
163
191
|
|
|
164
192
|
// Results and test back to the caller.
|
|
165
193
|
return result;
|
|
@@ -167,22 +195,30 @@ export default class TestRunner {
|
|
|
167
195
|
|
|
168
196
|
// region Dependencies of runOneTest()
|
|
169
197
|
|
|
170
|
-
#retainTestResults(
|
|
198
|
+
#retainTestResults(result) {
|
|
171
199
|
this.#numberRun++;
|
|
172
200
|
|
|
173
|
-
if (
|
|
201
|
+
if (result.doesHaveThrow) {
|
|
202
|
+
this.#thrown.push(result.anyThrowText);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (result.doesHaveTrouble) {
|
|
206
|
+
this.#trouble.push(result.anyTroubleText);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (result.didPass) {
|
|
174
210
|
this.#numberPassed++;
|
|
175
211
|
}
|
|
176
212
|
else {
|
|
177
213
|
this.#numberFailed++;
|
|
178
214
|
}
|
|
179
215
|
|
|
180
|
-
this.#allDidPass &&=
|
|
216
|
+
this.#allDidPass &&= result.didPass;
|
|
181
217
|
}
|
|
182
218
|
|
|
183
219
|
// endregion Dependencies of runOneTest()
|
|
184
220
|
|
|
185
|
-
summarize() {
|
|
221
|
+
#summarize() {
|
|
186
222
|
let summary = `Ran ${ this.#numberRun } test${ this.#numberRun !== 1 ? "s" : "" }. `
|
|
187
223
|
+ `${ this.#allDidPass ? "All tests passed. " : "" }`
|
|
188
224
|
+ `${ this.#numberPassed } passed. `
|
|
@@ -190,9 +226,8 @@ export default class TestRunner {
|
|
|
190
226
|
|
|
191
227
|
let anyWereRun = this.#numberRun > 0;
|
|
192
228
|
|
|
193
|
-
return new TestSummary(summary, this.#allDidPass, anyWereRun);
|
|
229
|
+
return new TestSummary(summary, this.#allDidPass, anyWereRun, this.#thrown, this.#trouble, this.#caught);
|
|
194
230
|
}
|
|
195
231
|
|
|
196
232
|
// endregion Running tests
|
|
197
|
-
|
|
198
233
|
}
|
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,41 @@ export default class TestSummary {
|
|
|
4
4
|
#summary;
|
|
5
5
|
#allDidPass;
|
|
6
6
|
#anyWereRun;
|
|
7
|
+
|
|
8
|
+
#anyThrows;
|
|
9
|
+
#anyTroubles;
|
|
10
|
+
#anyCaught;
|
|
7
11
|
|
|
8
12
|
get summary() {
|
|
9
13
|
return this.#summary;
|
|
10
14
|
}
|
|
11
15
|
|
|
12
|
-
set summary(value) {
|
|
13
|
-
this.#summary = value;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
16
|
get allDidPass() {
|
|
17
17
|
return this.#allDidPass;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
set allDidPass(value) {
|
|
21
|
-
this.#allDidPass = value;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
20
|
get anyWereRun() {
|
|
25
21
|
return this.#anyWereRun;
|
|
26
22
|
}
|
|
27
23
|
|
|
28
|
-
|
|
29
|
-
this.#
|
|
24
|
+
get anyThrows() {
|
|
25
|
+
return this.#anyThrows;
|
|
30
26
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
this
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
|
|
28
|
+
get anyTroubles() {
|
|
29
|
+
return this.#anyTroubles;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get anyCaught() {
|
|
33
|
+
return this.#anyCaught;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
constructor(summary, allDidPass, anyWereRun, anyThrows, anyTroubles, anyCaught) {
|
|
37
|
+
this.#summary = summary;
|
|
38
|
+
this.#allDidPass = allDidPass;
|
|
39
|
+
this.#anyWereRun = anyWereRun;
|
|
40
|
+
this.#anyThrows = anyThrows;
|
|
41
|
+
this.#anyTroubles = anyTroubles;
|
|
42
|
+
this.#anyCaught = anyCaught;
|
|
36
43
|
}
|
|
37
44
|
}
|
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
|
|