risei 1.0.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/LICENSE.txt +8 -0
- package/README.md +642 -0
- package/index.js +122 -0
- package/package.json +39 -0
- package/public/javascript/AComparer.js +9 -0
- package/public/javascript/ASpoofingFixture.js +84 -0
- package/public/javascript/ATestCaller.js +61 -0
- package/public/javascript/ATestFinder.js +25 -0
- package/public/javascript/ATestFixture.js +44 -0
- package/public/javascript/ATestReporter.js +25 -0
- package/public/javascript/ATestSource.js +8 -0
- package/public/javascript/ChosenTestFinder.js +30 -0
- package/public/javascript/ClassTestGroup.js +8 -0
- package/public/javascript/LocalCaller.js +22 -0
- package/public/javascript/MethodTestGroup.js +8 -0
- package/public/javascript/Moment.js +29 -0
- package/public/javascript/Risei.js +88 -0
- package/public/javascript/SpoofClassMethodsFixture.js +165 -0
- package/public/javascript/SpoofObjectMethodsFixture.js +52 -0
- package/public/javascript/SpoofTuple.js +238 -0
- package/public/javascript/TerminalReporter.js +222 -0
- package/public/javascript/TestFinder.js +140 -0
- package/public/javascript/TestGroup.js +25 -0
- package/public/javascript/TestResult.js +338 -0
- package/public/javascript/TestRunner.js +476 -0
- package/public/javascript/TestSummary.js +37 -0
- package/public/javascript/TestTuple.js +244 -0
- package/public/javascript/TotalComparer.js +229 -0
- package/usage-examples/Output-example.png +0 -0
- package/usage-examples/Summary-example.png +0 -0
- package/usage-examples/Syntax-example.png +0 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
import { ASpoofingFixture } from "./ASpoofingFixture.js";
|
|
4
|
+
import { SpoofTuple } from "./SpoofTuple.js";
|
|
5
|
+
|
|
6
|
+
export class SpoofClassMethodsFixture extends ASpoofingFixture {
|
|
7
|
+
// region Private fields
|
|
8
|
+
|
|
9
|
+
#spoofsByClass = new Map();
|
|
10
|
+
#originalsByClass = new Map();
|
|
11
|
+
|
|
12
|
+
// endregion Private fields
|
|
13
|
+
|
|
14
|
+
// region Properties
|
|
15
|
+
|
|
16
|
+
get spoofsByClass() {
|
|
17
|
+
return this.#spoofsByClass;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
set spoofsByClass(value) {
|
|
21
|
+
this.#spoofsByClass = value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
get originalsByClass() {
|
|
25
|
+
return this.#originalsByClass;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
set originalsByClass(value) {
|
|
29
|
+
this.#originalsByClass = value;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// endregion Properties
|
|
33
|
+
|
|
34
|
+
constructor() {
|
|
35
|
+
super("SpoofClassMethodsFixture", "Construction");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/* Spoofs methods of classes as defined in nonce
|
|
39
|
+
spoof-tuple definitions in any test.plus. */
|
|
40
|
+
spoof(test) {
|
|
41
|
+
// Localizing.
|
|
42
|
+
let type = test.on;
|
|
43
|
+
let spoofNonces = test.plus;
|
|
44
|
+
|
|
45
|
+
// Spoofing definitions are always in an array.
|
|
46
|
+
if (!Array.isArray(spoofNonces)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Converting to scope object type.
|
|
51
|
+
let spoofTuples = SpoofTuple.fromNonceTuples(spoofNonces);
|
|
52
|
+
|
|
53
|
+
// Storing spoofing definitions.
|
|
54
|
+
this.#targetThese(type, spoofTuples);
|
|
55
|
+
|
|
56
|
+
// Storing originals for restoring later.
|
|
57
|
+
this.#reserveOriginalsForSpoofs();
|
|
58
|
+
|
|
59
|
+
// Actually spoofing.
|
|
60
|
+
this.#spoofAll();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
#targetThese(type, spoofTuples) {
|
|
64
|
+
// Map of Maps: by type, then by method.
|
|
65
|
+
for (let tuple of spoofTuples) {
|
|
66
|
+
// If no .target, use `type`.
|
|
67
|
+
if (!tuple.target) {
|
|
68
|
+
tuple.target = type;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Either the existing Map for this type, or a new one if none yet.
|
|
72
|
+
let spoofsByTarget = this.#spoofsByClass.get(tuple.target);
|
|
73
|
+
|
|
74
|
+
if (!spoofsByTarget) {
|
|
75
|
+
spoofsByTarget = new Map();
|
|
76
|
+
this.#spoofsByClass.set(tuple.target, spoofsByTarget);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// A single-method spoof is set for later use.
|
|
80
|
+
if (tuple.method) {
|
|
81
|
+
spoofsByTarget.set(tuple.method, tuple.output);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Two or more method spoofs are set for later use.
|
|
86
|
+
if (tuple.output) {
|
|
87
|
+
let ofAsPairs = tuple.output;
|
|
88
|
+
|
|
89
|
+
for (let pair of ofAsPairs) {
|
|
90
|
+
spoofsByTarget.set(pair.of, pair.as);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#reserveOriginalsForSpoofs() {
|
|
99
|
+
/* Any spoofing of multiple methods on each class has already been
|
|
100
|
+
split up into Map elements, so this code can use those naively. */
|
|
101
|
+
|
|
102
|
+
let types = this.#spoofsByClass.keys();
|
|
103
|
+
|
|
104
|
+
for (let type of types) {
|
|
105
|
+
let originalsByTarget = this.#originalsByClass.get(type);
|
|
106
|
+
|
|
107
|
+
if (!originalsByTarget) {
|
|
108
|
+
originalsByTarget = new Map();
|
|
109
|
+
this.#originalsByClass.set(type, originalsByTarget);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let spoofsByTarget = this.#spoofsByClass.get(type);
|
|
113
|
+
let names = spoofsByTarget.keys();
|
|
114
|
+
|
|
115
|
+
for (let name of names) {
|
|
116
|
+
let original = type.prototype[name];
|
|
117
|
+
originalsByTarget.set(name, original);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#spoofAll() {
|
|
123
|
+
/* Any spoofing of multiple methods on each class has already been
|
|
124
|
+
split up into Map elements, so this code can use those naively. */
|
|
125
|
+
|
|
126
|
+
let types = this.#spoofsByClass.keys();
|
|
127
|
+
|
|
128
|
+
for (let type of types) {
|
|
129
|
+
let spoofsByTarget = this.#spoofsByClass.get(type);
|
|
130
|
+
let names = spoofsByTarget.keys();
|
|
131
|
+
|
|
132
|
+
for (let name of names) {
|
|
133
|
+
let spoofSource = spoofsByTarget.get(name);
|
|
134
|
+
let spoof = super.spoofMethod(spoofSource);
|
|
135
|
+
type.prototype[name] = spoof;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Spoofed methods on any targeted classes
|
|
141
|
+
// are restored to their original forms.
|
|
142
|
+
unspoof() {
|
|
143
|
+
let types = this.#originalsByClass.keys();
|
|
144
|
+
|
|
145
|
+
for (let target of types) {
|
|
146
|
+
let originalsByTarget = this.#originalsByClass.get(target);
|
|
147
|
+
|
|
148
|
+
let prototype = target.prototype;
|
|
149
|
+
|
|
150
|
+
let names = originalsByTarget.keys();
|
|
151
|
+
|
|
152
|
+
for (let name of names) {
|
|
153
|
+
let original = originalsByTarget.get(name);
|
|
154
|
+
target.prototype[name] = original;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Removes definitions, useful if this instance is reused,
|
|
160
|
+
// or else they might be applied when they shouldn't be.
|
|
161
|
+
removeDefinitions() {
|
|
162
|
+
this.#spoofsByClass.clear();
|
|
163
|
+
this.#originalsByClass.clear();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/* */
|
|
2
|
+
|
|
3
|
+
import { ASpoofingFixture } from "./ASpoofingFixture.js";
|
|
4
|
+
import { SpoofTuple } from "./SpoofTuple.js";
|
|
5
|
+
|
|
6
|
+
export class SpoofObjectMethodsFixture extends ASpoofingFixture {
|
|
7
|
+
constructor() {
|
|
8
|
+
super("SpoofObjectMethodsFixture", "Construction");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* Spoofs methods of any objects in test.with and test.in
|
|
12
|
+
for which nonce spoof-tuple definitions are provided. */
|
|
13
|
+
spoof(test) {
|
|
14
|
+
// These array test aspects may contain object-spoof definitions.
|
|
15
|
+
let topics = [ test.with, test.in ];
|
|
16
|
+
|
|
17
|
+
// Spoofing each element, or leaving it in place if not a spoof.
|
|
18
|
+
for (let topic of topics) {
|
|
19
|
+
for (let at = 0; at < topic.length; at++) {
|
|
20
|
+
let target = topic[at];
|
|
21
|
+
topic[at] = this.spoofTarget(target);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
spoofTarget(target) {
|
|
27
|
+
// Non-spoofs are returned unchanged.
|
|
28
|
+
if (SpoofTuple.isNotASpoof(target)) {
|
|
29
|
+
return target;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Spoofs are converted to their type.
|
|
33
|
+
let tuple = SpoofTuple.fromNonceTuple(target);
|
|
34
|
+
|
|
35
|
+
// Actually spoofing.
|
|
36
|
+
let spoof = this.#supplySpoof(tuple);
|
|
37
|
+
return spoof;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#supplySpoof(target) {
|
|
41
|
+
// Common case.
|
|
42
|
+
let ofAsPairs = [{ of: target.method, as: target.output }];
|
|
43
|
+
|
|
44
|
+
// Complex case.
|
|
45
|
+
if (!target.method) {
|
|
46
|
+
ofAsPairs = target.output;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let spoof = super.spoofObjectTree(ofAsPairs);
|
|
50
|
+
return spoof;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
/* Defines what is found in a spoof-definition tuple (SDT). Actual SDTs don't need to be instances of this class. */
|
|
4
|
+
|
|
5
|
+
export class SpoofTuple {
|
|
6
|
+
// region Static fields
|
|
7
|
+
|
|
8
|
+
/* Properties can have short or long names. */
|
|
9
|
+
static #longsByShort = new Map([
|
|
10
|
+
[ "on", "target" ],
|
|
11
|
+
[ "of", "method" ],
|
|
12
|
+
[ "as", "output" ],
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
/* Objects with either of these property names aren't
|
|
16
|
+
converted to SpoofTuples by from-nonce methods. */
|
|
17
|
+
static skipNames = [ "not", "skip" ];
|
|
18
|
+
|
|
19
|
+
// endregion Static fields
|
|
20
|
+
|
|
21
|
+
// region Public fields (long names)
|
|
22
|
+
|
|
23
|
+
/* These long names are clearer and match the constructor's parameter names. */
|
|
24
|
+
|
|
25
|
+
/* The test-definition field names use the longer, possibly clearer forms.
|
|
26
|
+
Equivalent short-name and long-name properties use the same fields. */
|
|
27
|
+
|
|
28
|
+
target;
|
|
29
|
+
method;
|
|
30
|
+
output;
|
|
31
|
+
|
|
32
|
+
// endregion Public fields (long names)
|
|
33
|
+
|
|
34
|
+
// region Properties
|
|
35
|
+
|
|
36
|
+
// region Spoof definition, short names
|
|
37
|
+
|
|
38
|
+
/* These short names make JSON-like definitions easier. */
|
|
39
|
+
|
|
40
|
+
get on() {
|
|
41
|
+
return this.target;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
set on(value) {
|
|
45
|
+
this.target = value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get of() {
|
|
49
|
+
return this.method;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
set of(value) {
|
|
53
|
+
this.method = value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get as() {
|
|
57
|
+
return this.output;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
set as(value) {
|
|
61
|
+
this.output = value;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// endregion Spoof definition, short names
|
|
65
|
+
|
|
66
|
+
// region Tuple state
|
|
67
|
+
|
|
68
|
+
/* Returns true if instance provides enough to spoof with.
|
|
69
|
+
A spoof must define at least either a method to spoof, or an output
|
|
70
|
+
containing an array of methods to spoof, with optional outputs.
|
|
71
|
+
See .notASpoof() for a contrasting condition. */
|
|
72
|
+
isSpoofable() /* passed */ {
|
|
73
|
+
let isSpoofable
|
|
74
|
+
= this.method !== undefined
|
|
75
|
+
|| this.output !== undefined;
|
|
76
|
+
|
|
77
|
+
return isSpoofable;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// endregion Tuple state
|
|
81
|
+
|
|
82
|
+
// endregion Properties
|
|
83
|
+
|
|
84
|
+
// region Initing, including constructor()
|
|
85
|
+
|
|
86
|
+
/* Constructor can't use param names like `of`, because when not .-prefixed, they are keywords. */
|
|
87
|
+
|
|
88
|
+
constructor(target, method, output) /* passed */ {
|
|
89
|
+
this.target = target;
|
|
90
|
+
this.method = method;
|
|
91
|
+
this.output = output;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
static fromNonceTuples(nonces) {
|
|
95
|
+
// Throughput and output.
|
|
96
|
+
let full = new SpoofTuple();
|
|
97
|
+
let output = [];
|
|
98
|
+
|
|
99
|
+
// Looping over all.
|
|
100
|
+
for (let nonce of nonces) {
|
|
101
|
+
// Don't convert non-tuples, but output
|
|
102
|
+
// them unchanged for the caller.
|
|
103
|
+
if (SpoofTuple.isNotASpoof(nonce)) {
|
|
104
|
+
output.push(nonce);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Get latest spoof definition.
|
|
109
|
+
let latest = SpoofTuple.fromNonceTuple(nonce);
|
|
110
|
+
|
|
111
|
+
// Restarting spoof definitions when desired.
|
|
112
|
+
full = SpoofTuple.maybeRestartFull(full, latest);
|
|
113
|
+
|
|
114
|
+
// Merge any previous
|
|
115
|
+
// values into latest.
|
|
116
|
+
latest.combineWith(full);
|
|
117
|
+
|
|
118
|
+
// Make latest the tuple for
|
|
119
|
+
// combining with next time.
|
|
120
|
+
full = latest;
|
|
121
|
+
|
|
122
|
+
// Add latest only if it's
|
|
123
|
+
// ready to use to spoof.
|
|
124
|
+
if (latest.isSpoofable) {
|
|
125
|
+
output.push(latest);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Back to caller.
|
|
130
|
+
return output;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Returns true if arg should not be used for spoofing.
|
|
134
|
+
See isSpoofable() for a contrasting condition. */
|
|
135
|
+
static isNotASpoof(nonce) /* passed */ {
|
|
136
|
+
/* Any nonce that is null or undefined is not a SpoofTuple. */
|
|
137
|
+
if (nonce === undefined || nonce === null) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* Any nonce with .not or .skip isn't a SpoofTuple, even if it has SpoofTuple properties. */
|
|
142
|
+
for (let non of SpoofTuple.skipNames) {
|
|
143
|
+
if (nonce[non] !== undefined) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* Any other nonce with any SpoofTuple properties is a full or partial SpoofTuple. */
|
|
149
|
+
for (let key of SpoofTuple.#longsByShort.keys()) {
|
|
150
|
+
let value = SpoofTuple.#longsByShort.get(key);
|
|
151
|
+
|
|
152
|
+
if (nonce[key] !== undefined) {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (nonce[value] !== undefined) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* Any nonce without SpoofTuple properties is not a spoof. */
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* &cruft, test or drop */
|
|
166
|
+
static isASpoof(nonce) {
|
|
167
|
+
return !this.isNotASpoof(nonce);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
static fromNonceTuple(nonce) /* passed */ {
|
|
171
|
+
// Empty, since properties can be set
|
|
172
|
+
// by either of two nonce naming styles.
|
|
173
|
+
let tuple = new SpoofTuple();
|
|
174
|
+
|
|
175
|
+
let shortNames = SpoofTuple.#longsByShort.keys();
|
|
176
|
+
|
|
177
|
+
// Traversing matching pairs of names and applying
|
|
178
|
+
// whichever one is present as the tuple property;
|
|
179
|
+
// if neither is present, the property is undefined.
|
|
180
|
+
for (let shortName of shortNames) {
|
|
181
|
+
let longName = SpoofTuple.#longsByShort.get(shortName);
|
|
182
|
+
tuple[shortName] = shortName in nonce ? nonce[shortName] : nonce[longName];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Back to caller.
|
|
186
|
+
return tuple;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// region Dependencies of nonce-tuple initing methods
|
|
190
|
+
|
|
191
|
+
static maybeRestartFull(full, latest) /* passed */ {
|
|
192
|
+
// When a (new) spoof target is named, return the new
|
|
193
|
+
// SpoofTuple to wipe out all reused spoof values.
|
|
194
|
+
if (latest.target !== undefined) {
|
|
195
|
+
full = latest;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return full;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
combineWith(other) {
|
|
202
|
+
let shortNames = SpoofTuple.#longsByShort.keys();
|
|
203
|
+
|
|
204
|
+
for (let shortName of shortNames) {
|
|
205
|
+
SpoofTuple.combineValues(this, other, shortName);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
static combineValues(self, other, name) /* passed */ {
|
|
210
|
+
// Property may still end up undefined.
|
|
211
|
+
if (self[name] === undefined) {
|
|
212
|
+
self[name] = other[name];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// endregion Dependencies of nonce-tuple initing methods
|
|
217
|
+
|
|
218
|
+
// endregion Initing, including constructor()
|
|
219
|
+
|
|
220
|
+
// region Overrides and dependencies
|
|
221
|
+
|
|
222
|
+
toString() /* passed */ {
|
|
223
|
+
let text = `SpoofTuple:{ target:${ this.#asRawOrString(this.target) }, `
|
|
224
|
+
+ `method:${ this.#asRawOrString(this.method) }, `
|
|
225
|
+
+ `output:${ this.#asRawOrString(this.output) } }`;
|
|
226
|
+
return text;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
#asRawOrString(value) /* verified */ {
|
|
230
|
+
if (typeof value === "string") {
|
|
231
|
+
return `"${ value }"`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return value;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// endregion Overrides and dependencies
|
|
238
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
/* TerminalReporter is an ATestReporter that sends each result to the terminal / console for display. */
|
|
4
|
+
|
|
5
|
+
import { ATestReporter } from "./ATestReporter.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
|
+
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
|
|
14
|
+
export class TerminalReporter extends ATestReporter {
|
|
15
|
+
// region Private fields
|
|
16
|
+
|
|
17
|
+
/* Foreground-colors using dependency calls and hex. */
|
|
18
|
+
#trueWhite = chalk.hex("FFFFFF");
|
|
19
|
+
#trueBlack = chalk.hex("000000");
|
|
20
|
+
|
|
21
|
+
/* Background colors as hex for dependency
|
|
22
|
+
calls to use, needed syntactically. */
|
|
23
|
+
#blue = "0000CD";
|
|
24
|
+
#aqua = "00BFFF";
|
|
25
|
+
#paleGreen = "90EE90";
|
|
26
|
+
#paleRed = "F08080";
|
|
27
|
+
|
|
28
|
+
/* Full fore- and background color output styles. */
|
|
29
|
+
|
|
30
|
+
#classGroup = this.#trueWhite.bgHex(this.#blue).bold;
|
|
31
|
+
#methodGroup = this.#trueWhite.bgHex(this.#aqua).bold;
|
|
32
|
+
#passed = this.#trueBlack.bgHex(this.#paleGreen);
|
|
33
|
+
#failed = this.#trueBlack.bgHex(this.#paleRed);
|
|
34
|
+
#passedSummary = this.#trueBlack.bgHex(this.#paleGreen).bold;
|
|
35
|
+
#failedSummary = this.#trueBlack.bgHex(this.#paleRed).bold;
|
|
36
|
+
#summary = chalk.hex("000080").bgHex("FAFAD2").bold;
|
|
37
|
+
|
|
38
|
+
// Count of characters used for indent
|
|
39
|
+
// and text to the left of each test.
|
|
40
|
+
#preUsedWidth = 20;
|
|
41
|
+
|
|
42
|
+
// endregion PrivateFields
|
|
43
|
+
|
|
44
|
+
// region ATestReporter
|
|
45
|
+
|
|
46
|
+
reportNext(result) {
|
|
47
|
+
// The next result introduces a group of tests.
|
|
48
|
+
if (result instanceof TestGroup) {
|
|
49
|
+
this.reportGroup(result);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The next result is from one test.
|
|
54
|
+
if (result instanceof TestResult) {
|
|
55
|
+
this.#reportTestResult(result);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The next result is the summary of all tests.
|
|
60
|
+
if (result instanceof TestSummary) {
|
|
61
|
+
this.reportSummary(result);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
reportGroup(result) {
|
|
67
|
+
if (result instanceof ClassTestGroup) {
|
|
68
|
+
console.log(this.#classGroup(` ${ result.group }: `));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (result instanceof MethodTestGroup) {
|
|
72
|
+
console.log(" " + this.#methodGroup(` ${ result.group }(): `));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// region Private dependencies of reportNext()
|
|
77
|
+
|
|
78
|
+
#reportTestResult(result) {
|
|
79
|
+
if (result.didPass) {
|
|
80
|
+
this.reportPassed(result);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!result.didPass) {
|
|
84
|
+
this.reportFailed(result);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// endregion Private dependencies of reportNext()
|
|
89
|
+
|
|
90
|
+
reportPassed(result) {
|
|
91
|
+
this.#reportOneTestResult(result, "Passed", this.#passed);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
reportFailed(result) {
|
|
95
|
+
this.#reportOneTestResult(result, "Failed", this.#failed);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// region Private dependencies of reportPassed() and reportFailed()
|
|
99
|
+
|
|
100
|
+
/* cruft, changes here */
|
|
101
|
+
#reportOneTestResult(result, net, styler) {
|
|
102
|
+
let { lineOne, lineTwo, lineThree } = this.#calculateResultDisplay(result);
|
|
103
|
+
|
|
104
|
+
console.log(
|
|
105
|
+
"\t" + styler(` ${ net }: ${ lineOne }`)
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (lineTwo) {
|
|
109
|
+
console.log(
|
|
110
|
+
"\t\t " + styler(` ${ lineTwo }`)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (lineThree) {
|
|
115
|
+
console.log(
|
|
116
|
+
"\t\t " + styler(` ${ lineThree }`)
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* &cruft, changes here and/or factoring
|
|
122
|
+
to lengths and tactics branching */
|
|
123
|
+
#calculateResultDisplay(result) {
|
|
124
|
+
/* Getting lengths to determine if output should
|
|
125
|
+
* be split to multiple lines to fit the window. */
|
|
126
|
+
|
|
127
|
+
let forInputsLength
|
|
128
|
+
= this.#preUsedWidth
|
|
129
|
+
+ result.identityText.length
|
|
130
|
+
+ result.inputsText.length;
|
|
131
|
+
|
|
132
|
+
let allLength
|
|
133
|
+
= forInputsLength
|
|
134
|
+
+ result.expectedText.length
|
|
135
|
+
+ result.actualText.length;
|
|
136
|
+
|
|
137
|
+
/* Splitting as much as is needed. */
|
|
138
|
+
|
|
139
|
+
let identityText = result.identityText;
|
|
140
|
+
let inputsText = result.inputsText;
|
|
141
|
+
let expectedText = result.expectedText;
|
|
142
|
+
let actualText = result.actualText;
|
|
143
|
+
|
|
144
|
+
if (forInputsLength >= process.stdout.columns) {
|
|
145
|
+
return this.#threeLineTactic(identityText, inputsText, expectedText, actualText);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (allLength >= process.stdout.columns) {
|
|
149
|
+
return this.#twoLineTactic(identityText, inputsText, expectedText, actualText);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return this.#oneLineTactic(identityText, inputsText, expectedText, actualText);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// region Private dependencies of #calculateResultDisplay()
|
|
156
|
+
|
|
157
|
+
#threeLineTactic(identityText, inputsText, expectedText, actualText) {
|
|
158
|
+
// Each line, with two spaces between phrases on same line.
|
|
159
|
+
let lineOne = identityText;
|
|
160
|
+
let lineTwo = inputsText;
|
|
161
|
+
let lineThree = expectedText + " " + actualText;
|
|
162
|
+
|
|
163
|
+
return { lineOne, lineTwo, lineThree };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#twoLineTactic(identityText, inputsText, expectedText, actualText) {
|
|
167
|
+
// Each line, with two spaces between phrases on same line.
|
|
168
|
+
let lineOne = identityText + " " + inputsText;
|
|
169
|
+
let lineTwo = expectedText + " " + actualText;
|
|
170
|
+
|
|
171
|
+
// Left undefined.
|
|
172
|
+
let lineThree;
|
|
173
|
+
|
|
174
|
+
return { lineOne, lineTwo, lineThree };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#oneLineTactic(identityText, inputsText, expectedText, actualText) {
|
|
178
|
+
// Single line, with two spaces between all phrases.
|
|
179
|
+
let lineOne
|
|
180
|
+
= identityText
|
|
181
|
+
+ " "
|
|
182
|
+
+ inputsText
|
|
183
|
+
+ " "
|
|
184
|
+
+ expectedText
|
|
185
|
+
+ " "
|
|
186
|
+
+ actualText;
|
|
187
|
+
|
|
188
|
+
// Left undefined.
|
|
189
|
+
let lineTwo, lineThree;
|
|
190
|
+
|
|
191
|
+
return { lineOne, lineTwo, lineThree };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// endregion Private dependencies of #calculateResultDisplay()
|
|
195
|
+
|
|
196
|
+
// endregion Private dependencies of reportPassed() and reportFailed()
|
|
197
|
+
|
|
198
|
+
reportSummary(summary) {
|
|
199
|
+
console.log();
|
|
200
|
+
|
|
201
|
+
if (summary.anyWereRun) {
|
|
202
|
+
if (summary.allDidPass) {
|
|
203
|
+
console.log(this.#passedSummary(this.#toFullWidth(" Test run succeeded.")));
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
console.log(this.#failedSummary(this.#toFullWidth(" Test run failed.")));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(this.#summary(this.#toFullWidth(` ${ summary.summary }`)));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#toFullWidth(text) {
|
|
214
|
+
text = text || "";
|
|
215
|
+
let width = process.stdout.columns;
|
|
216
|
+
|
|
217
|
+
return text.padEnd(width, "\u00A0");
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// endregion ATestReporter
|
|
221
|
+
|
|
222
|
+
}
|