risei 2.0.1 → 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.
@@ -1,26 +1,26 @@
1
- /**/
2
-
3
- export default class NameAnalyzer {
4
- // region Definitions
5
-
6
- static dotOperator = ".";
7
- static colonOperator = ":";
8
- static parensOperator = "()";
9
- static operatorsRegExp = /\.|:|\(\)/g;
10
-
11
- // endregion Definitions
12
-
13
- static hasPropertySigil(name) /* passed */ {
14
- return name.startsWith(NameAnalyzer.dotOperator) || name.endsWith(NameAnalyzer.colonOperator);
15
- }
16
-
17
- static hasMethodSigil(name) /* passed */ {
18
- return name.endsWith(NameAnalyzer.parensOperator);
19
- }
20
-
21
- static plainNameOf(name) /* passed */ {
22
- let plain = name.replaceAll(NameAnalyzer.operatorsRegExp, "");
23
- return plain;
24
- }
25
- }
26
-
1
+ /**/
2
+
3
+ export default class NameAnalyzer {
4
+ // region Definitions
5
+
6
+ static dotOperator = ".";
7
+ static colonOperator = ":";
8
+ static parensOperator = "()";
9
+ static operatorsRegExp = /\.|:|\(\)/g;
10
+
11
+ // endregion Definitions
12
+
13
+ static hasPropertySigil(name) /* passed */ {
14
+ return name.startsWith(NameAnalyzer.dotOperator) || name.endsWith(NameAnalyzer.colonOperator);
15
+ }
16
+
17
+ static hasMethodSigil(name) /* passed */ {
18
+ return name.endsWith(NameAnalyzer.parensOperator);
19
+ }
20
+
21
+ static plainNameOf(name) /* passed */ {
22
+ let plain = name.replaceAll(NameAnalyzer.operatorsRegExp, "");
23
+ return plain;
24
+ }
25
+ }
26
+
@@ -0,0 +1,36 @@
1
+ /**/
2
+
3
+ import TypeAnalyzer from "./TypeAnalyzer.js";
4
+
5
+ export default class ObjectAnalyzer {
6
+ /* Retrieves names of all properties, both values
7
+ and accessors, including all in superclasses. */
8
+ static allProperties(topic) /* passed */ {
9
+ let proto = Object.getPrototypeOf(topic);
10
+
11
+ let getters = [];
12
+
13
+ // Accessors, including in superclasses.
14
+ // Nonce object has no prototype / type.
15
+ if (proto !== null) {
16
+ let type = proto.constructor;
17
+ getters = TypeAnalyzer.allGetters(type);
18
+ }
19
+
20
+ let names = [];
21
+
22
+ // All value names, including inherited ones.
23
+ for (let name in topic) {
24
+ names.push(name);
25
+ }
26
+
27
+ // Concat good enough since sorting needed after anyway.
28
+ let output = names.concat(getters);
29
+
30
+ // Instead of mixed two sets in order coded,
31
+ // accessors backwards up prototype chain.
32
+ output.sort();
33
+
34
+ return output;
35
+ }
36
+ }
@@ -52,7 +52,7 @@ export default class PropertySpoofer extends ASpoofingFixture {
52
52
  // endregion Properties
53
53
 
54
54
  constructor() {
55
- super("PropertySpoofer", "Construction");
55
+ super();
56
56
  }
57
57
 
58
58
  /* Spoofs properties of test.target as defined in any test.plus. */
@@ -2,6 +2,7 @@
2
2
 
3
3
  import TypeAnalyzer from "./TypeAnalyzer.js";
4
4
  import NameAnalyzer from "./NameAnalyzer.js";
5
+ import TotalDisplayer from "./TotalDisplayer.js";
5
6
 
6
7
  /* Defines what is found in a spoof definition, typically as
7
8
  a nonce object rather than an instance of this class. */
@@ -20,11 +21,6 @@ export default class SpoofDef {
20
21
  SpoofDef.#asNames
21
22
  ]);
22
23
 
23
- /* &cruft, remove these after spoofing in .with and .in is removed */
24
- /* Objects with either of these property names aren't
25
- converted to spoofDefs by from-nonce methods. */
26
- static skipNames = [ "not", "skip" ];
27
-
28
24
  // endregion Static fields
29
25
 
30
26
  // region Public fields (long names)
@@ -135,14 +131,14 @@ export default class SpoofDef {
135
131
 
136
132
  // region Initing, including constructor() and statics
137
133
 
138
- /* Constructor can't use param names like `of`, because when not .-prefixed, they are keywords. */
139
-
134
+ /* Long names used, just in case. */
140
135
  constructor(target, method, output) /* passed */ {
141
136
  this.target = target;
142
137
  this.method = method;
143
138
  this.output = output;
144
139
  }
145
140
 
141
+ /* Arg 'type' should be test.type if present. */
146
142
  static fromNonceTuples(nonces, type) /* passed */ {
147
143
  // Throughput and output.
148
144
  let full = new SpoofDef();
@@ -159,82 +155,102 @@ export default class SpoofDef {
159
155
  return output;
160
156
  }
161
157
 
158
+ /* Arg 'type' should be test.type if present. */
162
159
  static fromNonceTuple(nonce, type) /* passed */ {
163
- /* No spoof-def to output. */
160
+ // No spoof-def to output.
164
161
  if (SpoofDef.isNotASpoof(nonce)) {
165
162
  return [ ];
166
163
  }
167
164
 
168
- // Empty, since properties can be set
169
- // by either of two nonce naming styles.
165
+ let defs = [ ];
166
+
167
+ // Nonce defines one spoof.
168
+ if (SpoofDef.#isMonoSpoof(nonce)) {
169
+ let def = SpoofDef.#fromMonoNonce(nonce, type);
170
+ defs.push(def);
171
+ }
172
+
173
+ // Nonce defines more than one spoof: Recursion.
174
+ if (SpoofDef.#isPolySpoof(nonce)) {
175
+ let locals = SpoofDef.#fromPolyNonce(nonce, type);
176
+ defs.push(...locals);
177
+ }
178
+
179
+ return defs;
180
+ }
181
+
182
+ // region Dependencies of nonce-tuple initing methods
183
+
184
+ static #isMonoSpoof(nonce) /* verified */ {
185
+ return SpoofDef.#hasEitherName(nonce, SpoofDef.#ofNames);
186
+ }
187
+
188
+ static #isPolySpoof(nonce) /* verified */ {
189
+ return !SpoofDef.#isMonoSpoof(nonce);
190
+ }
191
+
192
+ /* Arg 'type' should be test.type if present. */
193
+ static #fromMonoNonce(nonce, type) /* verified */ {
194
+ // Empty, since each prop can
195
+ // have either of two names.
170
196
  let def = new SpoofDef();
171
197
 
172
198
  let shortNames = SpoofDef.#longsByShort.keys();
173
199
 
174
- /* &cruft, maybe refactor to put property-setting loop in its own method, and possibly
175
- recurse internally using it without inner arrays, for a cleaner algorithm */
176
-
177
- // Traversing matching pairs of names and applying
178
- // whichever one is present as the tuple property;
200
+ // Traversing name pairs to use whichever one is present;
179
201
  // if neither is present, the property is undefined.
180
202
  for (let shortName of shortNames) {
181
203
  let longName = SpoofDef.#longsByShort.get(shortName);
182
- def[shortName] = shortName in nonce ? nonce[shortName] : nonce[longName];
204
+
205
+ def[shortName]
206
+ = nonce[shortName] !== undefined
207
+ ? nonce[shortName]
208
+ : nonce[longName];
183
209
  }
184
210
 
185
- // No nested partial spoof-defs
186
- // (mono-spoof) is most likely.
187
- let defs = [ def ];
211
+ // Adding test target's type, if no type provided.
212
+ if (def.target === undefined) {
213
+ def.target = type;
214
+ }
188
215
 
189
- // Recursion to handle nested partial spoof-defs (poly-spoofs).
190
- if (def.method === undefined && Array.isArray(def.output)) {
191
- let locals = [ ];
216
+ return def;
217
+ }
192
218
 
193
- for (let ofAsPair of def.output) {
194
- let localOf = ofAsPair.of !== undefined ? ofAsPair.of : ofAsPair.method;
195
- let localAs = ofAsPair.as !== undefined ? ofAsPair.as : ofAsPair.output;
219
+ /* Arg 'type' should be test.type if present. */
220
+ static #fromPolyNonce(nonce, type) /* verified */ {
221
+ let defs = [ ];
196
222
 
197
- let passer = SpoofDef.fromNonceTuple({ on: def.target, of: localOf, as: localAs });
198
- locals.push(passer[0]);
199
- }
223
+ // Poly-nonce's .on applies to all its partial spoofs.
224
+ let on = nonce.on !== undefined ? nonce.on : nonce.target;
200
225
 
201
- // Replace default spoof
202
- // with real poly-spoofs.
203
- defs = locals;
204
- }
226
+ // Poly-nonce's .as is an array of partial-spoof nonces.
227
+ let pairs = nonce.as !== undefined ? nonce.as : nonce.output;
205
228
 
206
- // All defs need a type / target class.
207
- SpoofDef.#setAnyMissingTypes(defs, type);
229
+ // Traversing partial-spoof nonces (.of and .as only).
230
+ for (let pair of pairs) {
231
+ // Either name (or neither) may exist on nonce partial.
232
+ let of = pair.of !== undefined ? pair.of : pair.method;
233
+ let as = pair.as !== undefined ? pair.as : pair.output;
234
+
235
+ // Only one level of recursion.
236
+ let passer = SpoofDef.#fromMonoNonce({ on, of, as }, type);
237
+
238
+ defs.push(passer);
239
+ }
208
240
 
209
- // Back to caller.
210
241
  return defs;
211
242
  }
212
243
 
213
- // region Dependencies of nonce-tuple initing methods
214
-
215
244
  static #hasEitherName(nonce, names) /* verified */ {
216
245
  let hasEither = false;
217
246
 
218
247
  for (let name of names) {
219
- hasEither ||= SpoofDef.#hasName(nonce, name);
248
+ hasEither ||= nonce[name] !== undefined;
220
249
  }
221
250
 
222
251
  return hasEither;
223
252
  }
224
253
 
225
- static #hasName(nonce, name) /* verified */ {
226
- return nonce[name] !== undefined;
227
- }
228
-
229
- static #setAnyMissingTypes(defs, type) /* verified */ {
230
- // Adding test target's type, if no type provided.
231
- for (let def of defs) {
232
- if (def.target === undefined) {
233
- def.target = type;
234
- }
235
- }
236
- }
237
-
238
254
  // endregion Dependencies of nonce-tuple initing methods
239
255
 
240
256
  // endregion Initing, including constructor() and statics
@@ -242,19 +258,13 @@ export default class SpoofDef {
242
258
  // region Overrides and dependencies
243
259
 
244
260
  toString() /* passed */ {
245
- let text = `SpoofDefinition:{ target:${ this.#asRawOrString(this.target) }, `
246
- + `method:${ this.#asRawOrString(this.method) }, `
247
- + `output:${ this.#asRawOrString(this.output) } }`;
261
+ let displayer = new TotalDisplayer();
262
+
263
+ let text = `SpoofDef{ on:${ displayer.display(this.target) }, `
264
+ + `of:${ displayer.display(this.method) }, `
265
+ + `as:${ displayer.display(this.output) } }`;
248
266
  return text;
249
267
  }
250
-
251
- #asRawOrString(value) /* verified */ {
252
- if (typeof value === "string") {
253
- return `"${ value }"`;
254
- }
255
-
256
- return value;
257
- }
258
-
268
+
259
269
  // endregion Overrides and dependencies
260
270
  }
@@ -1,8 +1,8 @@
1
1
  /**/
2
2
 
3
- /* TerminalReporter is an ATestReporter that sends each result to the terminal / console for display. */
3
+ /* TerminalReporter displays each test result in the terminal / console window. */
4
4
 
5
- import ATestReporter from "./ATestReporter.js";
5
+ import Moment from "./Moment.js";
6
6
  import TestGroup from "./TestGroup.js";
7
7
  import ClassTestGroup from "./ClassTestGroup.js";
8
8
  import MethodTestGroup from "./MethodTestGroup.js";
@@ -11,8 +11,12 @@ import TestSummary from "./TestSummary.js";
11
11
 
12
12
  import chalk from "chalk";
13
13
 
14
- export default class TerminalReporter extends ATestReporter {
15
- // region Private fields
14
+ export default class TerminalReporter {
15
+ // region Fields
16
+
17
+ /* Display styles for the start title and other general needs. */
18
+ #title = chalk.hex("FFFFFF").bgHex("191970").bold;
19
+ #fails = chalk.hex("000000").bgHex("FFD700").bold;
16
20
 
17
21
  /* Foreground-colors using dependency calls and hex. */
18
22
  #trueWhite = chalk.hex("FFFFFF");
@@ -31,22 +35,53 @@ export default class TerminalReporter extends ATestReporter {
31
35
  #methodGroup = this.#trueWhite.bgHex(this.#aqua).bold;
32
36
  #passed = this.#trueBlack.bgHex(this.#paleGreen);
33
37
  #failed = this.#trueBlack.bgHex(this.#paleRed);
38
+ #problems = chalk.hex("000000").bgHex("FFD700").bold;
39
+
34
40
  #passedSummary = this.#trueBlack.bgHex(this.#paleGreen).bold;
35
41
  #failedSummary = this.#trueBlack.bgHex(this.#paleRed).bold;
36
42
  #summary = chalk.hex("000080").bgHex("FAFAD2").bold;
37
43
 
44
+
38
45
  // Count of characters used for indent
39
46
  // and text to the left of each test.
40
47
  #preUsedWidth = 20;
41
48
 
42
- // endregion PrivateFields
49
+ // endregion Fields
50
+
51
+ reportTitle() {
52
+ // This moment, ready for use in title.
53
+ let now = new Date();
54
+ now = new Moment(now);
55
+
56
+ // Top of screen and leading color stripe.
57
+ console.clear();
58
+ console.log();
59
+ console.log(this.#title(this.#wide()));
60
+
61
+ // Actually reporting title.
62
+ console.log(this.#title(this.#wide(` Risei tests run on ${ now.asReadable() } local time.`)));
63
+
64
+ // Trailing color stripe and blank line.
65
+ console.log(this.#title(this.#wide()));
66
+ console.log();
67
+ }
68
+
69
+ // region Dependencies of reportTitle()
70
+
71
+ /* Styling for color stripes. */
72
+ #wide(text) {
73
+ let width = process.stdout.columns;
74
+ text = text || "";
75
+
76
+ return text.padEnd(width, "\u00A0");
77
+ };
43
78
 
44
- // region ATestReporter
79
+ // endregion Dependencies of reportTitle()
45
80
 
46
81
  reportNext(result) {
47
82
  // The next result introduces a group of tests.
48
83
  if (result instanceof TestGroup) {
49
- this.reportGroup(result);
84
+ this.#reportGroup(result);
50
85
  return;
51
86
  }
52
87
 
@@ -58,12 +93,14 @@ export default class TerminalReporter extends ATestReporter {
58
93
 
59
94
  // The next result is the summary of all tests.
60
95
  if (result instanceof TestSummary) {
61
- this.reportSummary(result);
96
+ this.#reportSummary(result);
62
97
  return;
63
98
  }
64
99
  }
65
100
 
66
- reportGroup(result) {
101
+ // region Direct dependencies of reportNext(), except summary
102
+
103
+ #reportGroup(result) {
67
104
  if (result instanceof ClassTestGroup) {
68
105
  console.log(this.#classGroup(` ${ result.group }: `));
69
106
  }
@@ -73,32 +110,44 @@ export default class TerminalReporter extends ATestReporter {
73
110
  }
74
111
  }
75
112
 
76
- // region Private dependencies of reportNext()
77
-
78
113
  #reportTestResult(result) {
114
+ if (result.doesHaveThrow) {
115
+ this.#reportThrew(result.anyThrow.stack);
116
+ }
117
+
118
+ if (result.doesHaveTrouble) {
119
+ this.#reportThrew(result.anyTrouble.stack);
120
+ }
121
+
79
122
  if (result.didPass) {
80
- this.reportPassed(result);
123
+ this.#reportPassed(result);
81
124
  }
82
125
 
83
126
  if (!result.didPass) {
84
- this.reportFailed(result);
127
+ this.#reportFailed(result);
85
128
  }
86
129
  }
87
130
 
88
- // endregion Private dependencies of reportNext()
131
+ // endregion Direct dependencies of reportNext(), except summary
132
+
133
+ // region Direct dependencies of #reportTestResult()
134
+
135
+ #reportThrew(threw) {
136
+ console.log(this.#problems(threw));
137
+ }
89
138
 
90
- reportPassed(result) {
139
+ #reportPassed(result) {
91
140
  this.#reportOneTestResult(result, "Passed", this.#passed);
92
141
  }
93
142
 
94
- reportFailed(result) {
143
+ #reportFailed(result) {
95
144
  this.#reportOneTestResult(result, "Failed", this.#failed);
96
145
  }
97
146
 
98
- // region Private dependencies of reportPassed() and reportFailed()
147
+ // endregion Direct dependencies of #reportTestResult()
148
+
149
+ // region Dependencies of #reportPassed() and #reportFailed()
99
150
 
100
- /* &cruft, changes here / in dependencies
101
- for better splitting to multiple lines */
102
151
  #reportOneTestResult(result, net, styler) {
103
152
  let { lineOne, lineTwo, lineThree, lineFour } = this.#calculateResultDisplay(result);
104
153
 
@@ -166,7 +215,7 @@ export default class TerminalReporter extends ATestReporter {
166
215
  return this.#oneLineTactic(identityText, inputsText, expectedText, actualText);
167
216
  }
168
217
 
169
- // region Private dependencies of #calculateResultDisplay()
218
+ // region Dependencies of #calculateResultDisplay()
170
219
 
171
220
  #fourLineTactic(identityText, inputsText, expectedText, actualText) {
172
221
  // Each line on its own.
@@ -218,13 +267,17 @@ export default class TerminalReporter extends ATestReporter {
218
267
  return { lineOne, lineTwo, lineThree, lineFour };
219
268
  }
220
269
 
221
- // endregion Private dependencies of #calculateResultDisplay()
270
+ // endregion Dependencies of #calculateResultDisplay()
271
+
272
+ // endregion Dependencies of #reportPassed() and #reportFailed()
222
273
 
223
- // endregion Private dependencies of reportPassed() and reportFailed()
274
+ // region Summary dependency of reportNext()
224
275
 
225
- reportSummary(summary) {
276
+ #reportSummary(summary) {
226
277
  console.log();
227
278
 
279
+ /* Actual summary. */
280
+
228
281
  if (summary.anyWereRun) {
229
282
  if (summary.allDidPass) {
230
283
  console.log(this.#passedSummary(this.#toFullWidth(" Test run succeeded.")));
@@ -235,7 +288,24 @@ export default class TerminalReporter extends ATestReporter {
235
288
  }
236
289
 
237
290
  console.log(this.#summary(this.#toFullWidth(` ${ summary.summary }`)));
238
- }
291
+
292
+ /* Post-summary reporting of unsought throws. */
293
+
294
+ if (summary.anyThrows.length !== 0) {
295
+ console.log();
296
+ console.log(this.#problems(summary.anyThrows.join("\n")));
297
+ }
298
+
299
+ // And/or reporting of outer-scope throws.
300
+ if (summary.anyTroubles.length !== 0) {
301
+ console.log();
302
+ console.log(this.#problems(summary.anyTroubles.join("\n")));
303
+ }
304
+ }
305
+
306
+ // endregion Summary dependency of reportNext()
307
+
308
+ // region Dependencies of #reportSummary()
239
309
 
240
310
  #toFullWidth(text) {
241
311
  text = text || "";
@@ -244,6 +314,13 @@ export default class TerminalReporter extends ATestReporter {
244
314
  return text.padEnd(width, "\u00A0");
245
315
  }
246
316
 
247
- // endregion ATestReporter
317
+ // endregion Dependencies of #reportSummary()
248
318
 
319
+ blankLine() {
320
+ console.log();
321
+ }
322
+
323
+ reportLoadFail(fail) {
324
+ console.log(this.#fails(this.#wide(` ${ fail }`)));
325
+ }
249
326
  }