risei 3.3.2 → 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 +34 -82
- package/package.json +2 -2
- package/system/ASpoofingFixture.js +23 -15
- package/system/SpoofDef.js +171 -171
- package/system/TestTargets.js +2 -2
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
## Overview
|
|
5
5
|
|
|
6
|
-
Risei is a new way to write unit tests that's easier
|
|
6
|
+
Risei is a new way to write unit tests that's easier, faster, and more readable. Tests made with Risei aren't a source of errors or a drag on redesigns.
|
|
7
7
|
|
|
8
8
|
Risei does all this by replacing hand-coded tests with simple declarative syntax.
|
|
9
9
|
|
|
@@ -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
|
|
@@ -40,36 +42,30 @@ And they have a summary bar at the bottom:
|
|
|
40
42
|
|
|
41
43
|

|
|
42
44
|
|
|
43
|
-
- This bar is red when any tests fail,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
## Status
|
|
48
|
-
|
|
49
|
-
Risei's major features are now complete, but new enhancements or fixes may appear from time to time. \
|
|
50
|
-
The latest release, **3.3.2**, adds `async` capabilities formerly missing from the `.from` feature.
|
|
51
|
-
|
|
52
|
-
Check out the [full list of changes](#version-history).
|
|
45
|
+
- This bar is red when any tests fail, just as you'd expect.
|
|
53
46
|
|
|
54
47
|
|
|
55
48
|
|
|
56
49
|
## Features of Risei
|
|
57
50
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
- ####
|
|
61
|
-
- ####
|
|
62
|
-
- ####
|
|
63
|
-
- ####
|
|
64
|
-
- ####
|
|
65
|
-
- ####
|
|
66
|
-
- ####
|
|
67
|
-
- ####
|
|
68
|
-
- ####
|
|
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)
|
|
69
64
|
|
|
70
65
|
- And more! Check out the full [Risei home page](https://deusware.com/risei).
|
|
71
66
|
|
|
72
67
|
|
|
68
|
+
|
|
73
69
|
## Installation
|
|
74
70
|
|
|
75
71
|
Install Risei for development time only:
|
|
@@ -92,11 +88,13 @@ And add Risei's metadata to `package.json`:
|
|
|
92
88
|
}
|
|
93
89
|
```
|
|
94
90
|
|
|
91
|
+
|
|
95
92
|
## Testing Risei Itself
|
|
96
93
|
|
|
97
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).
|
|
98
95
|
|
|
99
96
|
|
|
97
|
+
|
|
100
98
|
## Writing Tests
|
|
101
99
|
|
|
102
100
|
You write tests in `.rt.js` files like this:
|
|
@@ -123,11 +121,13 @@ tests = [ ...
|
|
|
123
121
|
```
|
|
124
122
|
|
|
125
123
|
- **Asynchronous / awaitable code can tested with no changes at all to this syntax.**
|
|
126
|
-
-
|
|
127
|
-
-
|
|
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.
|
|
128
127
|
- You can use [long names](https://deusware.com/risei/index.html#long-names) for properties if you want.
|
|
129
128
|
|
|
130
129
|
|
|
130
|
+
|
|
131
131
|
## Running Tests
|
|
132
132
|
|
|
133
133
|
Once you have some tests written, you can run them manually:
|
|
@@ -151,7 +151,8 @@ npm test
|
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
|
|
155
|
+
## Property reuse with collapsing forward
|
|
155
156
|
|
|
156
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.
|
|
157
158
|
|
|
@@ -170,12 +171,13 @@ Risei collapses values together from partial or full test objects until it has a
|
|
|
170
171
|
{ on: SortingClass, with: [ ] }, // Change of tested class. All existing props are wiped out.
|
|
171
172
|
```
|
|
172
173
|
|
|
174
|
+
- **To change just one property between tests, restate it along with `just: true`.**
|
|
173
175
|
- There are more options available, and an exclusion for mutated args.
|
|
174
176
|
- Learn all the details [here](https://deusware.com/risei/index.html#collapsing-forward).
|
|
175
177
|
|
|
176
178
|
|
|
177
179
|
|
|
178
|
-
##
|
|
180
|
+
## Test isolation with spoofing
|
|
179
181
|
|
|
180
182
|
You can use declarative _spoofing_ syntax to define what dependencies of your targeted code return for it to use.
|
|
181
183
|
|
|
@@ -218,70 +220,20 @@ If errors are thrown while testing, gold bars listing them appear at the bottom,
|
|
|
218
220
|
|
|
219
221
|
|
|
220
222
|
|
|
221
|
-
## Version history
|
|
222
|
-
|
|
223
|
-
- Release **3.3.2** (September, 2025) contains this change:
|
|
224
|
-
- You can now use asynchronous / awaitable code in `.from` using `async` and `await` normally.
|
|
225
|
-
- You already could use awaitable code with the normal syntax in `.plus`, `.do`, and `.undo`.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
- Release **3.3.1** (February, 2025) contains this change:
|
|
229
|
-
- You can now spoof and otherwise address value properties (formally _data descriptors_) that don't have an initial value, whether they are static or instance class members.
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
- Release **3.3.0** (January, 2025) adds this change to those of other recent releases:
|
|
233
|
-
- You can now test instance members with the same names as static members using a new `.and` option of `"instance"`.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
<details>
|
|
238
|
-
<summary>
|
|
239
|
-
Older releases
|
|
240
|
-
</summary>
|
|
241
|
-
|
|
242
|
-
- Release **3.2.1** (January, 2025) contains all the changes from **3.2.0**, **3.1.1**, and **3.1.0**, plus this change:
|
|
243
|
-
- Risei's mistaken nominal dependency on **npm** has been removed.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
- Release **3.2.0** (January, 2025) contains all the changes from **3.1.1** and **3.1.0**, plus this change:
|
|
247
|
-
- Risei's self-dependency for its own self-testing has been moved back to the development-only scope.
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
- Release **3.1.1** (January, 2025) contains these changes:
|
|
251
|
-
- Extra commas and non-objects in arrays of test objects are disregarded, and no longer cause a throw.
|
|
252
|
-
- Throws in wider scopes are now listed at the end, like others already found there.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
- Release **3.1.0** (January, 2025) contains these changes:
|
|
256
|
-
- Accessor properties (formally _accessor descriptors_) are now compared for test results and displayed in test outputs.
|
|
257
|
-
- Any accessor properties that throw errors during display are displayed as `(threw)`.
|
|
258
|
-
- Throws in tested code and in test framing code are now listed at the end, and also displayed amid the tests.
|
|
259
|
-
- `File` objects now have a succinct custom display in outputs.
|
|
260
|
-
- **(Breaking change)** In `constructor` tests, constructed instances are available in `.from` functions only as `actual` / `test.actual`.
|
|
261
|
-
- **(Breaking change)** In `constructor` tests, `test.target` is now the `prototype` of the tested class.
|
|
262
|
-
- Widespread internal reengineering of other kinds.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
> Older releases are dropped from this list progressively over time. Using the latest release is recommended.
|
|
266
|
-
|
|
267
|
-
</details>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
223
|
## Known issues and workarounds
|
|
272
224
|
|
|
273
225
|
There are the known minor issues:
|
|
274
226
|
|
|
275
|
-
-
|
|
276
|
-
-
|
|
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.
|
|
277
229
|
|
|
278
230
|
|
|
279
231
|
- Spoofing accessor properties only works when they have both a getter and a setter.
|
|
280
|
-
-
|
|
232
|
+
- You can use `.do` and `.undo` to set and reverse other properties.
|
|
281
233
|
|
|
282
234
|
|
|
283
|
-
-
|
|
284
|
-
-
|
|
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.
|
|
285
237
|
|
|
286
238
|
|
|
287
239
|
|
|
@@ -312,7 +264,7 @@ Risei is published for use under the terms of the MIT license:
|
|
|
312
264
|
|
|
313
265
|
<div style="border: solid darkgray 1px; padding: 0.5rem;">
|
|
314
266
|
|
|
315
|
-
<b>Risei Copyright © 2023–
|
|
267
|
+
<b>Risei Copyright © 2023–2026 Ed Fallin</b>
|
|
316
268
|
|
|
317
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:
|
|
318
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
|
}
|
package/system/TestTargets.js
CHANGED
|
@@ -66,11 +66,11 @@ export default class TestTargets {
|
|
|
66
66
|
// region Dependencies of targeting / naming methods
|
|
67
67
|
|
|
68
68
|
supplyPolyMethodNonce(test, callable) /* verified */ {
|
|
69
|
-
let nonce = (...inputs) => {
|
|
69
|
+
let nonce = async (...inputs) => {
|
|
70
70
|
let actuals = [];
|
|
71
71
|
|
|
72
72
|
for (let args of inputs) {
|
|
73
|
-
let local = test.target[callable](...args);
|
|
73
|
+
let local = await test.target[callable](...args);
|
|
74
74
|
actuals.push(local);
|
|
75
75
|
}
|
|
76
76
|
|