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.
@@ -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
+ }