risei 3.3.3 → 3.3.4
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 +32 -29
- package/package.json +2 -2
- package/system/ASpoofingFixture.js +23 -15
- package/system/SpoofDef.js +171 -171
package/README.md
CHANGED
|
@@ -13,6 +13,8 @@ Risei does all this by replacing hand-coded tests with simple declarative syntax
|
|
|
13
13
|
|
|
14
14
|
You can find a longer version of this read-me at [https://deusware.com/risei](https://deusware.com/risei/index.html). It expands greatly on the information here.
|
|
15
15
|
|
|
16
|
+
Risei's major features are now complete, but new enhancements or fixes may appear from time to time. For release notes, see the [version history](https://deusware.com/risei/index.html#version-history).
|
|
17
|
+
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
## Examples
|
|
@@ -44,31 +46,26 @@ And they have a summary bar at the bottom:
|
|
|
44
46
|
|
|
45
47
|
|
|
46
48
|
|
|
47
|
-
## Status
|
|
48
|
-
|
|
49
|
-
Risei's major features are now complete, but new enhancements or fixes may appear from time to time.
|
|
50
|
-
|
|
51
|
-
The latest release, **3.3.3**, adds `async` capabilities to poly-testing, the last place they were missing.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
49
|
## Features of Risei
|
|
56
50
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
- ####
|
|
60
|
-
- ####
|
|
61
|
-
- ####
|
|
62
|
-
- ####
|
|
63
|
-
- ####
|
|
64
|
-
- ####
|
|
65
|
-
- ####
|
|
66
|
-
- ####
|
|
67
|
-
- ####
|
|
51
|
+
Click ▼ / ▲ to see sections here, or the ► links to see full explanations on Risei's website.
|
|
52
|
+
|
|
53
|
+
- #### Easy declarative syntax to write tests simply and quickly. [▼](#writing-tests) [► Writing tests](https://deusware.com/risei/index.html#writing-tests)
|
|
54
|
+
- #### Easy-to-read test definitions and outputs. [▲](#examples) [► Examples](https://deusware.com/risei/index.html#examples)
|
|
55
|
+
- #### Simple reuse of test properties. [▼](#property-reuse-with-collapsing-forward) [► Collapsing forward](https://deusware.com/risei/index.html#collapsing-forward)
|
|
56
|
+
- #### Built-in declarative dependency faking. [▼](#test-isolation-with-spoofing) [► Spoofing using `.plus`](https://deusware.com/risei/index.html#spoofing)
|
|
57
|
+
- #### Full support for `async` code without special syntax. [▼](#writing-tests) [► Writing tests](https://deusware.com/risei/index.html#writing-tests)
|
|
58
|
+
- #### Consistent syntax for all callable code. [▼](#writing-tests) [► Properties and statics](https://deusware.com/risei/index.html#testing-properties-and-static-methods)
|
|
59
|
+
- #### Testing exception / error paths effortlessly. [► Using `.and: "throws"`](https://deusware.com/risei/index.html#using-and-throws)
|
|
60
|
+
- #### Deriving values to test indirectly. [► Using `.from`](https://deusware.com/risei/index.html#using-from)
|
|
61
|
+
- #### Setting up and tearing down arbitrary test state. [► Using `.do` and `.undo`](https://deusware.com/risei/index.html#using-do-and-undo)
|
|
62
|
+
- #### Testing for `undefined` as output. [► Using `this.undef`](https://deusware.com/risei/index.html#using-undef)
|
|
63
|
+
- #### Running a method repeatedly in one test. [► Using `.and: "poly"`](https://deusware.com/risei/index.html#using-and-poly)
|
|
68
64
|
|
|
69
65
|
- And more! Check out the full [Risei home page](https://deusware.com/risei).
|
|
70
66
|
|
|
71
67
|
|
|
68
|
+
|
|
72
69
|
## Installation
|
|
73
70
|
|
|
74
71
|
Install Risei for development time only:
|
|
@@ -91,11 +88,13 @@ And add Risei's metadata to `package.json`:
|
|
|
91
88
|
}
|
|
92
89
|
```
|
|
93
90
|
|
|
91
|
+
|
|
94
92
|
## Testing Risei Itself
|
|
95
93
|
|
|
96
94
|
To test Risei itself, you clone it from a parallel [repository](https://gitlab.com/riseimaker/risei-public.git), install its dependencies, and run its self-tests. See the full explanation [here](https://deusware.com/risei/index.html#self-testing).
|
|
97
95
|
|
|
98
96
|
|
|
97
|
+
|
|
99
98
|
## Writing Tests
|
|
100
99
|
|
|
101
100
|
You write tests in `.rt.js` files like this:
|
|
@@ -122,11 +121,13 @@ tests = [ ...
|
|
|
122
121
|
```
|
|
123
122
|
|
|
124
123
|
- **Asynchronous / awaitable code can tested with no changes at all to this syntax.**
|
|
125
|
-
-
|
|
126
|
-
-
|
|
124
|
+
- But you use `async` and `await` if you define functions within tests.
|
|
125
|
+
- **Properties can be tested with no changes at all to this syntax.**
|
|
126
|
+
- Use empty arrays for `.in` or `.with` when there are no args to pass.
|
|
127
127
|
- You can use [long names](https://deusware.com/risei/index.html#long-names) for properties if you want.
|
|
128
128
|
|
|
129
129
|
|
|
130
|
+
|
|
130
131
|
## Running Tests
|
|
131
132
|
|
|
132
133
|
Once you have some tests written, you can run them manually:
|
|
@@ -150,7 +151,8 @@ npm test
|
|
|
150
151
|
```
|
|
151
152
|
|
|
152
153
|
|
|
153
|
-
|
|
154
|
+
|
|
155
|
+
## Property reuse with collapsing forward
|
|
154
156
|
|
|
155
157
|
You can state repeated test properties just once, and let them _collapse forward_ across subsequent tests to save time and make tests easier to read.
|
|
156
158
|
|
|
@@ -169,12 +171,13 @@ Risei collapses values together from partial or full test objects until it has a
|
|
|
169
171
|
{ on: SortingClass, with: [ ] }, // Change of tested class. All existing props are wiped out.
|
|
170
172
|
```
|
|
171
173
|
|
|
174
|
+
- **To change just one property between tests, restate it along with `just: true`.**
|
|
172
175
|
- There are more options available, and an exclusion for mutated args.
|
|
173
176
|
- Learn all the details [here](https://deusware.com/risei/index.html#collapsing-forward).
|
|
174
177
|
|
|
175
178
|
|
|
176
179
|
|
|
177
|
-
##
|
|
180
|
+
## Test isolation with spoofing
|
|
178
181
|
|
|
179
182
|
You can use declarative _spoofing_ syntax to define what dependencies of your targeted code return for it to use.
|
|
180
183
|
|
|
@@ -221,16 +224,16 @@ If errors are thrown while testing, gold bars listing them appear at the bottom,
|
|
|
221
224
|
|
|
222
225
|
There are the known minor issues:
|
|
223
226
|
|
|
224
|
-
-
|
|
225
|
-
-
|
|
227
|
+
- Any mutated test args are used in mutated form by later tests when collapsing forward.
|
|
228
|
+
- To work around this, just restate those args for each test.
|
|
226
229
|
|
|
227
230
|
|
|
228
231
|
- Spoofing accessor properties only works when they have both a getter and a setter.
|
|
229
|
-
-
|
|
232
|
+
- You can use `.do` and `.undo` to set and reverse other properties.
|
|
230
233
|
|
|
231
234
|
|
|
232
|
-
- Custom
|
|
233
|
-
-
|
|
235
|
+
- Custom overrides of `toString()` and other built-in methods aren't recognized automatically. Nor are instance methods with the same names as static methods.
|
|
236
|
+
- You can use an `.and` value of `"instance"` to ensure these are recognized.
|
|
234
237
|
|
|
235
238
|
|
|
236
239
|
|
|
@@ -261,7 +264,7 @@ Risei is published for use under the terms of the MIT license:
|
|
|
261
264
|
|
|
262
265
|
<div style="border: solid darkgray 1px; padding: 0.5rem;">
|
|
263
266
|
|
|
264
|
-
<b>Risei Copyright © 2023–
|
|
267
|
+
<b>Risei Copyright © 2023–2026 Ed Fallin</b>
|
|
265
268
|
|
|
266
269
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
267
270
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "risei",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.4",
|
|
4
4
|
"description": "Risei allows you to write unit tests as simple JavaScript objects, so it's easy and fast, with no drag on redesign.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"unit test",
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
"fs": "^0.0.1-security",
|
|
55
55
|
"mocha": "^10.0.0",
|
|
56
56
|
"morgan": "~1.9.1",
|
|
57
|
-
"risei": "^3.3.
|
|
57
|
+
"risei": "^3.3.4"
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -6,36 +6,44 @@ export default class ASpoofingFixture {
|
|
|
6
6
|
constructor() {
|
|
7
7
|
/* No operations. */
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
/* A spoof might be supplied as a custom function,
|
|
9
|
+
|
|
10
|
+
/* A spoof might be supplied as a custom function,
|
|
11
11
|
but it might just be defined by a return value. */
|
|
12
|
-
spoofMethod(spoofOutput) {
|
|
12
|
+
spoofMethod(spoofOutput) /* passed */ {
|
|
13
13
|
// No spoof supplied, so spoof is an empty function.
|
|
14
14
|
if (spoofOutput === undefined) {
|
|
15
15
|
let empty = () => { };
|
|
16
16
|
return empty;
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
// Spoof is a custom function to use as-is.
|
|
20
20
|
if (spoofOutput instanceof Function) {
|
|
21
21
|
return spoofOutput;
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
// Recursive case: Spoof defines a whole object
|
|
23
|
+
|
|
24
|
+
// Recursive case: Spoof defines a whole object
|
|
25
25
|
// tree to be returned as method output.
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
spoofOutput = this.spoofObjectTree(spoofOutput);
|
|
30
|
-
}
|
|
26
|
+
if (this.isSpoofArray(spoofOutput)) {
|
|
27
|
+
spoofOutput = SpoofDef.fromNonceTuples(spoofOutput);
|
|
28
|
+
spoofOutput = this.spoofObjectTree(spoofOutput);
|
|
31
29
|
}
|
|
32
|
-
|
|
30
|
+
|
|
33
31
|
// Commonest case: Spoof is a value to be returned.
|
|
34
32
|
let spoof = () => { return spoofOutput; }
|
|
35
33
|
return spoof;
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
/*
|
|
36
|
+
isSpoofArray(subject) /* good */ {
|
|
37
|
+
if (!Array.isArray(subject)) { return false; }
|
|
38
|
+
|
|
39
|
+
// Empty array is assumed to be a spoof return value.
|
|
40
|
+
if (subject.length === 0) { return false; }
|
|
41
|
+
|
|
42
|
+
let is = subject.every(x => SpoofDef.isASpoof(x));
|
|
43
|
+
return is;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Returns an object with all spoofed methods,
|
|
39
47
|
possibly with intermediate objects. */
|
|
40
48
|
spoofObjectTree(ofAsPairs) {
|
|
41
49
|
let spoof = {};
|
|
@@ -66,7 +74,7 @@ export default class ASpoofingFixture {
|
|
|
66
74
|
next = next[name];
|
|
67
75
|
}
|
|
68
76
|
|
|
69
|
-
// Final or only name is the leaf method;
|
|
77
|
+
// Final or only name is the leaf method;
|
|
70
78
|
// name may mistakenly end with `()`.
|
|
71
79
|
name = names.shift();
|
|
72
80
|
name = name.replace("()", "");
|
|
@@ -75,7 +83,7 @@ export default class ASpoofingFixture {
|
|
|
75
83
|
let method = this.spoofMethod(output);
|
|
76
84
|
next[name] = method;
|
|
77
85
|
}
|
|
78
|
-
|
|
86
|
+
|
|
79
87
|
// And back to caller.
|
|
80
88
|
return spoof;
|
|
81
89
|
}
|
package/system/SpoofDef.js
CHANGED
|
@@ -4,255 +4,255 @@ import TypeAnalyzer from "./TypeAnalyzer.js";
|
|
|
4
4
|
import NameAnalyzer from "./NameAnalyzer.js";
|
|
5
5
|
import TotalDisplayer from "./TotalDisplayer.js";
|
|
6
6
|
|
|
7
|
-
/* Defines what is found in a spoof definition, typically as
|
|
7
|
+
/* Defines what is found in a spoof definition, typically as
|
|
8
8
|
a nonce object rather than an instance of this class. */
|
|
9
9
|
|
|
10
10
|
export default class SpoofDef {
|
|
11
11
|
// region Static fields
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
/* Properties can have short or long names. */
|
|
14
|
+
static #onNames = [ "on", "target" ];
|
|
15
|
+
static #ofNames = [ "of", "method" ];
|
|
16
|
+
static #asNames = [ "as", "output" ];
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
static #longsByShort = new Map([
|
|
19
|
+
SpoofDef.#onNames,
|
|
20
|
+
SpoofDef.#ofNames,
|
|
21
|
+
SpoofDef.#asNames
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
// endregion Static fields
|
|
25
25
|
|
|
26
26
|
// region Public fields (long names)
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
/* These long names are clearer and match the constructor's parameter names. */
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
/* The test-definition field names use the longer, possibly clearer forms.
|
|
31
|
+
Equivalent short-name and long-name properties use the same fields. */
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
target;
|
|
34
|
+
method;
|
|
35
|
+
output;
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
// endregion Public fields (long names)
|
|
38
38
|
|
|
39
39
|
// region Properties
|
|
40
40
|
|
|
41
41
|
// region Spoof definition, short names
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
/* These short names make JSON-like definitions easier. */
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
get on() { return this.target; }
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
set on(value) { this.target = value; }
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
get of() { return this.method; }
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
set of(value) { this.method = value; }
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
get as() { return this.output; }
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
set as(value) { this.output = value; }
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
// endregion Spoof definition, short names
|
|
58
58
|
|
|
59
59
|
// region Def state
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/* Returns true if arg provides enough to spoof with. */
|
|
67
|
-
static isASpoof(nonce) /* passed */ {
|
|
68
|
-
/* Any nonce that is null or undefined is not a SpoofDef. */
|
|
69
|
-
if (nonce === undefined || nonce === null) {
|
|
70
|
-
return false;
|
|
61
|
+
/* Returns true if arg doesn't provide enough to spoof with. */
|
|
62
|
+
static isNotASpoof(nonce) /* passed */ {
|
|
63
|
+
return !SpoofDef.isASpoof(nonce);
|
|
71
64
|
}
|
|
72
|
-
|
|
73
|
-
let hasAnOf = SpoofDef.#hasEitherName(nonce, SpoofDef.#ofNames);
|
|
74
|
-
let hasAnAs = SpoofDef.#hasEitherName(nonce, SpoofDef.#asNames);
|
|
75
65
|
|
|
76
|
-
/*
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
/* Returns true if arg provides enough to spoof with. */
|
|
67
|
+
static isASpoof(nonce) /* passed */ {
|
|
68
|
+
/* Any nonce that is null or undefined is not a SpoofDef. */
|
|
69
|
+
if (nonce === undefined || nonce === null) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
73
|
+
let hasAnOf = SpoofDef.#hasEitherName(nonce, SpoofDef.#ofNames);
|
|
74
|
+
let hasAnAs = SpoofDef.#hasEitherName(nonce, SpoofDef.#asNames);
|
|
75
|
+
|
|
76
|
+
/* Any nonce with at least an .of or an .as is a SpoofDef. */
|
|
77
|
+
return hasAnOf || hasAnAs;
|
|
83
78
|
}
|
|
84
79
|
|
|
85
|
-
|
|
80
|
+
get isMethodSpoof() /* passed */ {
|
|
81
|
+
if (this.target === undefined) {
|
|
82
|
+
throw new Error("No .on / .target present. It must be set for .isMethodSpoof to work.");
|
|
83
|
+
}
|
|
86
84
|
|
|
87
|
-
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
85
|
+
let plainName = NameAnalyzer.plainNameOf(this.method);
|
|
90
86
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
if (NameAnalyzer.hasPropertySigil(this.method)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
94
90
|
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
let isMethod
|
|
92
|
+
= NameAnalyzer.hasMethodSigil(this.method)
|
|
93
|
+
|| TypeAnalyzer.isMethodMember(this.target, plainName);
|
|
97
94
|
|
|
98
|
-
|
|
99
|
-
if (this.target === undefined) {
|
|
100
|
-
throw new Error("No .on / .target present. It must be set for .isPropertySpoof to work.");
|
|
95
|
+
return isMethod;
|
|
101
96
|
}
|
|
102
97
|
|
|
103
|
-
|
|
98
|
+
get isPropertySpoof() /* passed */ {
|
|
99
|
+
if (this.target === undefined) {
|
|
100
|
+
throw new Error("No .on / .target present. It must be set for .isPropertySpoof to work.");
|
|
101
|
+
}
|
|
104
102
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
let plainName = NameAnalyzer.plainNameOf(this.method);
|
|
104
|
+
|
|
105
|
+
if (NameAnalyzer.hasMethodSigil(this.method)) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
let isProperty
|
|
110
|
+
= NameAnalyzer.hasPropertySigil(this.method)
|
|
111
|
+
|| TypeAnalyzer.isPropertyMember(this.target, plainName);
|
|
112
112
|
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
return isProperty;
|
|
114
|
+
}
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
// endregion Def state
|
|
117
117
|
|
|
118
118
|
// endregion Properties
|
|
119
119
|
|
|
120
120
|
// region Initing, including constructor() and statics
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/* Arg 'type' should be test.type if present. */
|
|
130
|
-
static fromNonceTuples(nonces, type) /* passed */ {
|
|
131
|
-
// Throughput and output.
|
|
132
|
-
let full = new SpoofDef();
|
|
133
|
-
let output = [ ];
|
|
134
|
-
|
|
135
|
-
// Looping over all.
|
|
136
|
-
for (let nonce of nonces) {
|
|
137
|
-
// Get latest spoof definition/s and retain for output.
|
|
138
|
-
let latests = SpoofDef.fromNonceTuple(nonce, type);
|
|
139
|
-
output.push(...latests);
|
|
122
|
+
/* Long names used, just in case. */
|
|
123
|
+
constructor(target, method, output) /* passed */ {
|
|
124
|
+
this.target = target;
|
|
125
|
+
this.method = method;
|
|
126
|
+
this.output = output;
|
|
140
127
|
}
|
|
141
128
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
129
|
+
/* Arg 'type' should be test.type if present. */
|
|
130
|
+
static fromNonceTuples(nonces, type) /* passed */ {
|
|
131
|
+
// Throughput and output.
|
|
132
|
+
let full = new SpoofDef();
|
|
133
|
+
let output = [ ];
|
|
134
|
+
|
|
135
|
+
// Looping over all.
|
|
136
|
+
for (let nonce of nonces) {
|
|
137
|
+
// Get latest spoof definition/s and retain for output.
|
|
138
|
+
let latests = SpoofDef.fromNonceTuple(nonce, type);
|
|
139
|
+
output.push(...latests);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Back to caller.
|
|
143
|
+
return output;
|
|
151
144
|
}
|
|
152
145
|
|
|
153
|
-
|
|
146
|
+
/* Arg 'type' should be test.type if present. */
|
|
147
|
+
static fromNonceTuple(nonce, type) /* passed */ {
|
|
148
|
+
// No spoof-def to output.
|
|
149
|
+
if (SpoofDef.isNotASpoof(nonce)) {
|
|
150
|
+
return [ ];
|
|
151
|
+
}
|
|
154
152
|
|
|
155
|
-
|
|
156
|
-
if (SpoofDef.#isMonoSpoof(nonce)) {
|
|
157
|
-
let def = SpoofDef.#fromMonoNonce(nonce, type);
|
|
158
|
-
defs.push(def);
|
|
159
|
-
}
|
|
153
|
+
let defs = [ ];
|
|
160
154
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
155
|
+
// Nonce defines one spoof.
|
|
156
|
+
if (SpoofDef.#isMonoSpoof(nonce)) {
|
|
157
|
+
let def = SpoofDef.#fromMonoNonce(nonce, type);
|
|
158
|
+
defs.push(def);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Nonce defines more than one spoof: Recursion.
|
|
162
|
+
if (SpoofDef.#isPolySpoof(nonce)) {
|
|
163
|
+
let locals = SpoofDef.#fromPolyNonce(nonce, type);
|
|
164
|
+
defs.push(...locals);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return defs;
|
|
165
168
|
}
|
|
166
169
|
|
|
167
|
-
|
|
168
|
-
}
|
|
170
|
+
// region Dependencies of nonce-tuple initing methods
|
|
169
171
|
|
|
170
|
-
|
|
172
|
+
static #isMonoSpoof(nonce) /* verified */ {
|
|
173
|
+
return SpoofDef.#hasEitherName(nonce, SpoofDef.#ofNames);
|
|
174
|
+
}
|
|
171
175
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
static #isPolySpoof(nonce) /* verified */ {
|
|
177
|
+
return !SpoofDef.#isMonoSpoof(nonce);
|
|
178
|
+
}
|
|
175
179
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
/* Arg 'type' should be test.type if present. */
|
|
181
|
+
static #fromMonoNonce(nonce, type) /* verified */ {
|
|
182
|
+
// Empty, since each prop can
|
|
183
|
+
// have either of two names.
|
|
184
|
+
let def = new SpoofDef();
|
|
179
185
|
|
|
180
|
-
|
|
181
|
-
static #fromMonoNonce(nonce, type) /* verified */ {
|
|
182
|
-
// Empty, since each prop can
|
|
183
|
-
// have either of two names.
|
|
184
|
-
let def = new SpoofDef();
|
|
186
|
+
let shortNames = SpoofDef.#longsByShort.keys();
|
|
185
187
|
|
|
186
|
-
|
|
188
|
+
// Traversing name pairs to use whichever one is present;
|
|
189
|
+
// if neither is present, the property is undefined.
|
|
190
|
+
for (let shortName of shortNames) {
|
|
191
|
+
let longName = SpoofDef.#longsByShort.get(shortName);
|
|
187
192
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
193
|
+
def[shortName]
|
|
194
|
+
= nonce[shortName] !== undefined
|
|
195
|
+
? nonce[shortName]
|
|
196
|
+
: nonce[longName];
|
|
197
|
+
}
|
|
192
198
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
199
|
+
// Adding test target's type, if no type provided.
|
|
200
|
+
if (def.target === undefined) {
|
|
201
|
+
def.target = type;
|
|
202
|
+
}
|
|
198
203
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
def.target = type;
|
|
202
|
-
}
|
|
204
|
+
return def;
|
|
205
|
+
}
|
|
203
206
|
|
|
204
|
-
|
|
205
|
-
|
|
207
|
+
/* Arg 'type' should be test.type if present. */
|
|
208
|
+
static #fromPolyNonce(nonce, type) /* verified */ {
|
|
209
|
+
let defs = [ ];
|
|
206
210
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
let defs = [ ];
|
|
211
|
+
// Poly-nonce's .on applies to all its partial spoofs.
|
|
212
|
+
let on = nonce.on !== undefined ? nonce.on : nonce.target;
|
|
210
213
|
|
|
211
|
-
|
|
212
|
-
|
|
214
|
+
// Poly-nonce's .as is an array of partial-spoof nonces.
|
|
215
|
+
let pairs = nonce.as !== undefined ? nonce.as : nonce.output;
|
|
213
216
|
|
|
214
|
-
|
|
215
|
-
|
|
217
|
+
// Traversing partial-spoof nonces (.of and .as only).
|
|
218
|
+
for (let pair of pairs) {
|
|
219
|
+
// Either name (or neither) may exist on nonce partial.
|
|
220
|
+
let of = pair.of !== undefined ? pair.of : pair.method;
|
|
221
|
+
let as = pair.as !== undefined ? pair.as : pair.output;
|
|
216
222
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// Either name (or neither) may exist on nonce partial.
|
|
220
|
-
let of = pair.of !== undefined ? pair.of : pair.method;
|
|
221
|
-
let as = pair.as !== undefined ? pair.as : pair.output;
|
|
223
|
+
// Only one level of recursion.
|
|
224
|
+
let passer = SpoofDef.#fromMonoNonce({ on, of, as }, type);
|
|
222
225
|
|
|
223
|
-
|
|
224
|
-
|
|
226
|
+
defs.push(passer);
|
|
227
|
+
}
|
|
225
228
|
|
|
226
|
-
|
|
227
|
-
|
|
229
|
+
return defs;
|
|
230
|
+
}
|
|
228
231
|
|
|
229
|
-
|
|
230
|
-
|
|
232
|
+
static #hasEitherName(nonce, names) /* verified */ {
|
|
233
|
+
let hasEither = false;
|
|
231
234
|
|
|
232
|
-
|
|
233
|
-
|
|
235
|
+
for (let name of names) {
|
|
236
|
+
hasEither ||= nonce[name] !== undefined;
|
|
237
|
+
}
|
|
234
238
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return hasEither;
|
|
240
|
-
}
|
|
239
|
+
return hasEither;
|
|
240
|
+
}
|
|
241
241
|
|
|
242
|
-
|
|
242
|
+
// endregion Dependencies of nonce-tuple initing methods
|
|
243
243
|
|
|
244
|
-
|
|
244
|
+
// endregion Initing, including constructor() and statics
|
|
245
245
|
|
|
246
246
|
// region Overrides and dependencies
|
|
247
247
|
|
|
248
|
-
|
|
249
|
-
|
|
248
|
+
toString() /* passed */ {
|
|
249
|
+
let displayer = new TotalDisplayer();
|
|
250
250
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
251
|
+
let text = `SpoofDef{ on:${ displayer.display(this.target) }, `
|
|
252
|
+
+ `of:${ displayer.display(this.method) }, `
|
|
253
|
+
+ `as:${ displayer.display(this.output) } }`;
|
|
254
|
+
return text;
|
|
255
|
+
}
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
// endregion Overrides and dependencies
|
|
258
258
|
}
|