risei 1.3.2 → 1.3.3
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 +11 -9
- package/package.json +2 -2
- package/system/{SpoofClassMethodsFixture.js → ClassMethodSpoofer.js} +5 -4
- package/system/ClassPropertySpoofer.js +182 -0
- package/system/{SpoofObjectMethodsFixture.js → ObjectMethodSpoofer.js} +3 -3
- package/system/ObjectPropertySpoofer.js +136 -0
- package/system/SpoofDefinition.js +1 -9
- package/system/TestDefinition.js +12 -16
- package/system/TestFinder.js +3 -1
- package/system/TestFrame.js +28 -29
- package/system/TestFrames.js +3 -3
- package/system/TestRunner.js +4 -4
- package/system/TestStages.js +12 -89
- package/system/TypeAnalyzer.js +40 -21
package/README.md
CHANGED
|
@@ -123,7 +123,7 @@ You can save a lot of time by putting repeated test properties into an object on
|
|
|
123
123
|
{ of: "countOf", for: "Returns the number present.", in: [ "b" ], out: 2 }
|
|
124
124
|
```
|
|
125
125
|
|
|
126
|
-
- This _collapsing forward_ is reset when you change the class in `.on`, when you give the property a new value, or you erase it with an empty array `[
|
|
126
|
+
- This _collapsing forward_ is reset when you change the class in `.on`, when you give the property a new value, or you erase it with an empty array `[]`.
|
|
127
127
|
|
|
128
128
|
- Tests are isolated in general, but if the code you're testing mutates its arguments, you have to state them in each test so the mutations aren't carried forward.
|
|
129
129
|
|
|
@@ -134,7 +134,7 @@ You can save a lot of time by putting repeated test properties into an object on
|
|
|
134
134
|
When your tests need certain results from code dependencies, just _spoof_ what you want using a `.plus` test property:
|
|
135
135
|
|
|
136
136
|
```javascript
|
|
137
|
-
{ on: CombinerClass, with: [
|
|
137
|
+
{ on: CombinerClass, with: [],
|
|
138
138
|
of: "combineResults",
|
|
139
139
|
plus: [
|
|
140
140
|
{ on: ClassA, of: "someMethod", as: 10 }, // Spoof this ClassA method to return 10.
|
|
@@ -232,10 +232,10 @@ There's plenty more to learn about Risei and how it makes testing easy...
|
|
|
232
232
|
| Name | Contents |
|
|
233
233
|
|--------|-----------------------------------------------------------------------------------------------------------|
|
|
234
234
|
| `on` | The class under test, as a symbol / name (already imported into the test file) |
|
|
235
|
-
| `with` | An array of any arguments to pass to the constructor of the tested class, or an empty array `[
|
|
235
|
+
| `with` | An array of any arguments to pass to the constructor of the tested class, or an empty array `[]` if none |
|
|
236
236
|
| `of` | The name of the method under test, as a string, no `()` needed |
|
|
237
237
|
| `for` | A description of what the test proves, for test output |
|
|
238
|
-
| `in` | An array of the input arguments to pass to the tested method, or an empty array `[
|
|
238
|
+
| `in` | An array of the input arguments to pass to the tested method, or an empty array `[]` if none |
|
|
239
239
|
| `out` | The expected return value, not in an array |
|
|
240
240
|
|
|
241
241
|
- There are additional properties for extended functionality, covered later.
|
|
@@ -245,7 +245,7 @@ There's plenty more to learn about Risei and how it makes testing easy...
|
|
|
245
245
|
|
|
246
246
|
### Test-property reuse _AKA_ Collapsing forward
|
|
247
247
|
|
|
248
|
-
- To save time and effort, any property you write is collapsed forward to subsequent tests unless you replace it or erase it with an empty array `[
|
|
248
|
+
- To save time and effort, any property you write is collapsed forward to subsequent tests unless you replace it or erase it with an empty array `[]`.
|
|
249
249
|
- Property values are gathered across partial test objects until they add up to a runnable test, which is then run.
|
|
250
250
|
- Changes to properties are combined with previous ones to produce intended new tests.
|
|
251
251
|
- For a rare and avoidable side-effect of this system, see [known issues and workarounds](#known-issues-and-workarounds).
|
|
@@ -337,7 +337,7 @@ To test a throw, use an `.and` of `"throw"` or `"throws"`:
|
|
|
337
337
|
{ of: "getTextProfile",
|
|
338
338
|
for: "Throws with a helpful message when there is no arg text.",
|
|
339
339
|
and: "throws",
|
|
340
|
-
in: [
|
|
340
|
+
in: [],
|
|
341
341
|
out: "Could not build a profile. Did you forget to provide a text?"
|
|
342
342
|
}
|
|
343
343
|
```
|
|
@@ -355,7 +355,7 @@ To test a value that isn't the exercised code's return value, you use `.out` nor
|
|
|
355
355
|
|
|
356
356
|
|
|
357
357
|
- Getting properties, mutated args, and other non-return values is known as _retrieving_.
|
|
358
|
-
- Retrieving definitions collapse forward across tests unless replaced with new definitions, or erased by setting `.from` to an empty array `[
|
|
358
|
+
- Retrieving definitions collapse forward across tests unless replaced with new definitions, or erased by setting `.from` to an empty array `[]`.
|
|
359
359
|
|
|
360
360
|
|
|
361
361
|
<details>
|
|
@@ -457,7 +457,7 @@ All test properties have long names that you can use instead of the short ones i
|
|
|
457
457
|
Constructors can be tested with no special test properties or keywords.
|
|
458
458
|
- The method name in `.of` is simply `"constructor"`.
|
|
459
459
|
- Any `.with` args are completely ignored for a constructor test. Only those in the test's `.in` property are used.
|
|
460
|
-
- However, a `.with` must still be provided. It can simply be an empty array `[
|
|
460
|
+
- However, a `.with` must still be provided. It can simply be an empty array `[]`.
|
|
461
461
|
|
|
462
462
|
</details>
|
|
463
463
|
|
|
@@ -552,7 +552,7 @@ Most problems with using Risei are minor mistakes in syntax, or omissions in tes
|
|
|
552
552
|
| `"Test loading failed for... SyntaxError: Unexpected token '.'"` | Using `this.tests = []` instead of `tests = []` in the file named |
|
|
553
553
|
| Other `... Unexpected token ...` errors | Some other syntax error in the file named, most likely a missing or extra delimiter |
|
|
554
554
|
| Unexpected extra, failing test/s | Partial test properties in a mid-list object have produced extra tests |
|
|
555
|
-
| Unexpected actual values, or unexpected passes / fails in test runs | Test properties from previous tests not replaced or reset with `[
|
|
555
|
+
| Unexpected actual values, or unexpected passes / fails in test runs | Test properties from previous tests not replaced or reset with `[]` <br>— or —<br> Args mutated by tested code were not restated in following tests to isolate them |
|
|
556
556
|
|
|
557
557
|
|
|
558
558
|
|
|
@@ -574,6 +574,8 @@ Two issues are known to exist:
|
|
|
574
574
|
|
|
575
575
|
## What's new in Risei
|
|
576
576
|
|
|
577
|
+
- Release **1.3.3** (March, 2024) fixes a major bug related to file paths that prevented any tests from loading or running in Windows environments.
|
|
578
|
+
|
|
577
579
|
- Release **1.3.2** (February, 2024) does not change the code at all, but improves this read-me, notably explaining the easiest way to deal with mutable args (which is just restating the args), and correcting a few mistakes.
|
|
578
580
|
|
|
579
581
|
- Release **1.3.1** (February, 2024) reverses some changes in 1.3.0 that were intended to fully isolate tests against mutable args, but which caused incomplete or inaccurate object contents in some cases. This sort of built-in total isolation is not feasible, and in fact is rarely needed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "risei",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"description": "Risei is the framework that allows you to write unit tests as collections of values in JavaScript objects, so it's easy and fast, and tests don't serve as a drag on redesigns.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"unit test",
|
|
@@ -48,6 +48,6 @@
|
|
|
48
48
|
"fs": "^0.0.1-security",
|
|
49
49
|
"mocha": "^10.0.0",
|
|
50
50
|
"morgan": "~1.9.1",
|
|
51
|
-
"risei": "1.3.
|
|
51
|
+
"risei": "1.3.3"
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { ASpoofingFixture } from "./ASpoofingFixture.js";
|
|
4
4
|
import { SpoofDefinition } from "./SpoofDefinition.js";
|
|
5
|
+
import { TypeAnalyzer } from "./TypeAnalyzer.js";
|
|
5
6
|
|
|
6
|
-
export class
|
|
7
|
+
export class ClassMethodSpoofer extends ASpoofingFixture {
|
|
7
8
|
/* Algorithm: Forward method spoof() looks at spoof definitions in .plus and applies them
|
|
8
9
|
to each class' prototypes after first saving the original member definitions.
|
|
9
10
|
Reverse method unspoof() restores each original member definition.
|
|
10
|
-
Fundamentally different from what
|
|
11
|
+
Fundamentally different from what ObjectMethodSpoofer does. */
|
|
11
12
|
|
|
12
13
|
// region Private fields
|
|
13
14
|
|
|
@@ -37,7 +38,7 @@ export class SpoofClassMethodsFixture extends ASpoofingFixture {
|
|
|
37
38
|
// endregion Properties
|
|
38
39
|
|
|
39
40
|
constructor() {
|
|
40
|
-
super("
|
|
41
|
+
super("ClassMethodSpoofer", "Construction");
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/* Spoofs methods of classes as defined in nonce
|
|
@@ -140,7 +141,7 @@ export class SpoofClassMethodsFixture extends ASpoofingFixture {
|
|
|
140
141
|
for (let type of types) {
|
|
141
142
|
let spoofsByTarget = this.#spoofsByClass.get(type);
|
|
142
143
|
let names = spoofsByTarget.keys();
|
|
143
|
-
|
|
144
|
+
|
|
144
145
|
// Actually spoofing, one by one.
|
|
145
146
|
for (let name of names) {
|
|
146
147
|
let spoofSource = spoofsByTarget.get(name);
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
import { ASpoofingFixture } from "./ASpoofingFixture.js";
|
|
4
|
+
import { SpoofDefinition } from "./SpoofDefinition.js";
|
|
5
|
+
import { TypeAnalyzer } from "./TypeAnalyzer.js";
|
|
6
|
+
|
|
7
|
+
export class ClassPropertySpoofer extends ASpoofingFixture {
|
|
8
|
+
/* Algorithm: Forward method spoof() looks at spoof definitions in .plus and applies them
|
|
9
|
+
to each class' prototypes after first saving the original member definitions.
|
|
10
|
+
Reverse method unspoof() restores each original member definition.
|
|
11
|
+
Fundamentally different from what ObjectMethodSpoofer does. */
|
|
12
|
+
|
|
13
|
+
// region Private fields
|
|
14
|
+
|
|
15
|
+
#spoofsByClass = new Map();
|
|
16
|
+
#originalsByClass = new Map();
|
|
17
|
+
|
|
18
|
+
// endregion Private fields
|
|
19
|
+
|
|
20
|
+
// region Properties
|
|
21
|
+
|
|
22
|
+
get spoofsByClass() {
|
|
23
|
+
return this.#spoofsByClass;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
set spoofsByClass(value) {
|
|
27
|
+
this.#spoofsByClass = value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get originalsByClass() {
|
|
31
|
+
return this.#originalsByClass;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
set originalsByClass(value) {
|
|
35
|
+
this.#originalsByClass = value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// endregion Properties
|
|
39
|
+
|
|
40
|
+
constructor() {
|
|
41
|
+
super("ClassPropertySpoofer", "Construction");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Spoofs methods of classes as defined in nonce
|
|
45
|
+
spoof-tuple definitions in any test.plus. */
|
|
46
|
+
spoof(test) {
|
|
47
|
+
// Localizing.
|
|
48
|
+
let type = test.on;
|
|
49
|
+
let spoofNonces = test.plus;
|
|
50
|
+
|
|
51
|
+
// Spoofing definitions are always in an array.
|
|
52
|
+
if (!Array.isArray(spoofNonces)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Converting to scope object type.
|
|
57
|
+
let spoofDefinitions = SpoofDefinition.fromNonceTuples(spoofNonces);
|
|
58
|
+
|
|
59
|
+
// Storing spoofing definitions.
|
|
60
|
+
this.#retainSpoofsByClass(type, spoofDefinitions);
|
|
61
|
+
|
|
62
|
+
// Storing originals for restoring later.
|
|
63
|
+
this.#reserveOriginalsByClass();
|
|
64
|
+
|
|
65
|
+
// Actually spoofing.
|
|
66
|
+
this.#spoofMembersByClass();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// region Dependencies of spoof()
|
|
70
|
+
|
|
71
|
+
#retainSpoofsByClass(type, spoofDefinitions) {
|
|
72
|
+
// Map of Maps: by type, then by method.
|
|
73
|
+
for (let tuple of spoofDefinitions) {
|
|
74
|
+
// If no .target, use `type`.
|
|
75
|
+
if (!tuple.target) {
|
|
76
|
+
tuple.target = type;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let spoofsByTarget = this.#supplyMemberMap(this.#spoofsByClass, tuple.target);
|
|
80
|
+
|
|
81
|
+
// A single-method spoof is set for later use.
|
|
82
|
+
if (tuple.method) {
|
|
83
|
+
spoofsByTarget.set(tuple.method, tuple.output);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Two or more method spoofs are set for later use.
|
|
88
|
+
if (tuple.output) {
|
|
89
|
+
let ofAsPairs = tuple.output;
|
|
90
|
+
|
|
91
|
+
for (let pair of ofAsPairs) {
|
|
92
|
+
spoofsByTarget.set(pair.of, pair.as);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#reserveOriginalsByClass() {
|
|
101
|
+
/* Any spoofing of multiple methods on each class has already been
|
|
102
|
+
split up into Map elements, so this code can use those naively. */
|
|
103
|
+
|
|
104
|
+
let types = this.#spoofsByClass.keys();
|
|
105
|
+
|
|
106
|
+
// For each class.
|
|
107
|
+
for (let type of types) {
|
|
108
|
+
let originalsByTarget = this.#supplyMemberMap(this.#originalsByClass, type);
|
|
109
|
+
|
|
110
|
+
let spoofsByTarget = this.#spoofsByClass.get(type);
|
|
111
|
+
let names = spoofsByTarget.keys();
|
|
112
|
+
|
|
113
|
+
// Saving originals in the Map for later.
|
|
114
|
+
for (let name of names) {
|
|
115
|
+
let original = type.prototype[name];
|
|
116
|
+
originalsByTarget.set(name, original);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Dependency of #retainSpoofsByClass()
|
|
122
|
+
and #reserveOriginalsByClass(). */
|
|
123
|
+
#supplyMemberMap(source, target) {
|
|
124
|
+
let mapForType = source.get(target);
|
|
125
|
+
|
|
126
|
+
if (!mapForType) {
|
|
127
|
+
mapForType = new Map();
|
|
128
|
+
source.set(target, mapForType);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return mapForType;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#spoofMembersByClass() {
|
|
135
|
+
/* Any spoofing of multiple methods on each class has already been
|
|
136
|
+
split up into Map elements, so this code can use those naively. */
|
|
137
|
+
|
|
138
|
+
let types = this.#spoofsByClass.keys();
|
|
139
|
+
|
|
140
|
+
// For each class.
|
|
141
|
+
for (let type of types) {
|
|
142
|
+
let spoofsByTarget = this.#spoofsByClass.get(type);
|
|
143
|
+
let names = spoofsByTarget.keys();
|
|
144
|
+
|
|
145
|
+
// Actually spoofing, one by one.
|
|
146
|
+
for (let name of names) {
|
|
147
|
+
let spoofSource = spoofsByTarget.get(name);
|
|
148
|
+
let spoof = super.spoofMethod(spoofSource);
|
|
149
|
+
type.prototype[name] = spoof;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// endregion Dependencies of spoof()
|
|
155
|
+
|
|
156
|
+
// Spoofed methods on any targeted classes
|
|
157
|
+
// are restored to their original forms.
|
|
158
|
+
unspoof() {
|
|
159
|
+
let types = this.#originalsByClass.keys();
|
|
160
|
+
|
|
161
|
+
// For each class.
|
|
162
|
+
for (let target of types) {
|
|
163
|
+
let prototype = target.prototype;
|
|
164
|
+
|
|
165
|
+
let originalsByTarget = this.#originalsByClass.get(target);
|
|
166
|
+
let names = originalsByTarget.keys();
|
|
167
|
+
|
|
168
|
+
// Actually restoring, one by one.
|
|
169
|
+
for (let name of names) {
|
|
170
|
+
let original = originalsByTarget.get(name);
|
|
171
|
+
target.prototype[name] = original;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Removes definitions, useful if this instance is reused,
|
|
177
|
+
// or else they might be applied when they shouldn't be.
|
|
178
|
+
removeDefinitions() {
|
|
179
|
+
this.#spoofsByClass.clear();
|
|
180
|
+
this.#originalsByClass.clear();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
import { ASpoofingFixture } from "./ASpoofingFixture.js";
|
|
4
4
|
import { SpoofDefinition } from "./SpoofDefinition.js";
|
|
5
5
|
|
|
6
|
-
export class
|
|
6
|
+
export class ObjectMethodSpoofer extends ASpoofingFixture {
|
|
7
7
|
/* Algorithm: Forward method spoof() looks at a test's .with and .in, and replaces entire objects
|
|
8
8
|
in each with spoofed object trees, if the originals are spoof definitions.
|
|
9
9
|
No reverse unspoofing method is needed, since objects are nonces tossed after tests.
|
|
10
|
-
Fundamentally different from much of what
|
|
10
|
+
Fundamentally different from much of what ClassMethodSpoofer does. */
|
|
11
11
|
|
|
12
12
|
constructor() {
|
|
13
|
-
super("
|
|
13
|
+
super("ObjectMethodSpoofer", "Construction");
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
/* Spoofs methods of any objects in test.with and test.in
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
import { ASpoofingFixture } from "./ASpoofingFixture.js";
|
|
4
|
+
import { SpoofDefinition } from "./SpoofDefinition.js";
|
|
5
|
+
import { TypeAnalyzer } from "./TypeAnalyzer.js";
|
|
6
|
+
|
|
7
|
+
export class ObjectPropertySpoofer extends ASpoofingFixture {
|
|
8
|
+
/* Algorithm: Forward method spoof() looks at property spoof definitions in .plus
|
|
9
|
+
and first saves the original values, then applies the spoofed ones.
|
|
10
|
+
Reverse method unspoof() restores each original property value.
|
|
11
|
+
Fundamentally different from what ObjectMethodSpoofer does. */
|
|
12
|
+
|
|
13
|
+
// region Private fields
|
|
14
|
+
|
|
15
|
+
// Spoofed and original property values.
|
|
16
|
+
#settables = [ ];
|
|
17
|
+
#unsettables = [ ];
|
|
18
|
+
|
|
19
|
+
// endregion Private fields
|
|
20
|
+
|
|
21
|
+
// region Properties
|
|
22
|
+
|
|
23
|
+
get settables() {
|
|
24
|
+
return this.#settables;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
set settables(value) {
|
|
28
|
+
this.#settables = value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get unsettables() {
|
|
32
|
+
return this.#unsettables;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
set unsettables(value) {
|
|
36
|
+
this.#unsettables = value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// endregion Properties
|
|
40
|
+
|
|
41
|
+
constructor() {
|
|
42
|
+
super("ObjectPropertySpoofer", "Construction");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Spoofs properties of test.target as defined in any test.plus. */
|
|
46
|
+
spoof(test) {
|
|
47
|
+
// Localizing.
|
|
48
|
+
let target = test.target;
|
|
49
|
+
let spoofNonces = test.plus;
|
|
50
|
+
|
|
51
|
+
// Spoofing definitions are always in an array.
|
|
52
|
+
if (!Array.isArray(spoofNonces)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Converting to scope object type.
|
|
57
|
+
let spoofDefinitions = SpoofDefinition.fromNonceTuples(spoofNonces);
|
|
58
|
+
|
|
59
|
+
// Working only with property spoofs.
|
|
60
|
+
let propertySpoofs = Array.from(spoofDefinitions)
|
|
61
|
+
.filter(x => TypeAnalyzer.memberIsProperty(test.on, x.on));
|
|
62
|
+
|
|
63
|
+
// Storing new values for setting.
|
|
64
|
+
this.#settables = propertySpoofs;
|
|
65
|
+
|
|
66
|
+
// Storing original values for restoring later.
|
|
67
|
+
this.#unsettables = this.#anyOriginalPropertyGetting(target, this.#settables);
|
|
68
|
+
|
|
69
|
+
// Actually spoofing with new values.
|
|
70
|
+
this.#anyPropertySetting(target, this.#settables);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Spoofed methods on any targeted classes
|
|
74
|
+
// are restored to their original forms.
|
|
75
|
+
unspoof() {
|
|
76
|
+
this.#anyPropertyUnsetting(this.target, this.#unsettables);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// region Dependencies of spoof()
|
|
80
|
+
|
|
81
|
+
#anyOriginalPropertyGetting(target, settables) {
|
|
82
|
+
if (!Array.isArray(settables)) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let unsettables = [ ];
|
|
87
|
+
|
|
88
|
+
for (let settable of settables) {
|
|
89
|
+
// Not a setter tuple if no settable value.
|
|
90
|
+
if (settable.of === undefined) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Retaining original.
|
|
95
|
+
let original = target[settable.of];
|
|
96
|
+
let unsettable = { of: settable.of, as: original };
|
|
97
|
+
|
|
98
|
+
unsettables.push(unsettable);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return unsettables;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// endregion Dependencies of spoof()
|
|
105
|
+
|
|
106
|
+
// region Dependencies of unspoof()
|
|
107
|
+
|
|
108
|
+
#anyPropertyUnsetting(target, unsettables) {
|
|
109
|
+
// Unsetting is the same as setting,
|
|
110
|
+
// but with tuples of original values.
|
|
111
|
+
return this.#anyPropertySetting(target, unsettables);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// endregion Dependencies of unspoof()
|
|
115
|
+
|
|
116
|
+
// region Dependencies of both spoof() and unspoof()
|
|
117
|
+
|
|
118
|
+
#anyPropertySetting(target, settables) {
|
|
119
|
+
// No properties to set.
|
|
120
|
+
if (!Array.isArray(settables)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
for (let settable of settables) {
|
|
125
|
+
// Not a setter tuple if no settable value.
|
|
126
|
+
if (settable.of === undefined) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Actually setting property.
|
|
131
|
+
target[settable.of] = settable.as;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// endregion Dependencies of both spoof() and unspoof()
|
|
136
|
+
}
|
|
@@ -20,12 +20,6 @@ export class SpoofDefinition {
|
|
|
20
20
|
|
|
21
21
|
// endregion Static fields
|
|
22
22
|
|
|
23
|
-
// region Private fields
|
|
24
|
-
|
|
25
|
-
#typeAnalyzer;
|
|
26
|
-
|
|
27
|
-
// endregion Private fields
|
|
28
|
-
|
|
29
23
|
// region Public fields (long names)
|
|
30
24
|
|
|
31
25
|
/* These long names are clearer and match the constructor's parameter names. */
|
|
@@ -86,7 +80,7 @@ export class SpoofDefinition {
|
|
|
86
80
|
}
|
|
87
81
|
|
|
88
82
|
isPropertySpoof() /* passed */ {
|
|
89
|
-
return
|
|
83
|
+
return TypeAnalyzer.memberIsProperty(this.target, this.method);
|
|
90
84
|
}
|
|
91
85
|
|
|
92
86
|
// endregion Tuple state
|
|
@@ -101,8 +95,6 @@ export class SpoofDefinition {
|
|
|
101
95
|
this.target = target;
|
|
102
96
|
this.method = method;
|
|
103
97
|
this.output = output;
|
|
104
|
-
|
|
105
|
-
this.#typeAnalyzer = new TypeAnalyzer(target);
|
|
106
98
|
}
|
|
107
99
|
|
|
108
100
|
static fromNonceTuples(nonces) {
|
package/system/TestDefinition.js
CHANGED
|
@@ -42,12 +42,6 @@ export class TestDefinition {
|
|
|
42
42
|
|
|
43
43
|
// endregion Static fields
|
|
44
44
|
|
|
45
|
-
// region Private fields
|
|
46
|
-
|
|
47
|
-
#typeAnalyzer;
|
|
48
|
-
|
|
49
|
-
// endregion Private fields
|
|
50
|
-
|
|
51
45
|
// region Fields
|
|
52
46
|
|
|
53
47
|
/* These longer names match the constructor-arg names.
|
|
@@ -64,8 +58,8 @@ export class TestDefinition {
|
|
|
64
58
|
source;
|
|
65
59
|
factors;
|
|
66
60
|
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
target;
|
|
62
|
+
localCallable;
|
|
69
63
|
|
|
70
64
|
actual;
|
|
71
65
|
thrown;
|
|
@@ -118,7 +112,7 @@ export class TestDefinition {
|
|
|
118
112
|
this.spoofed = value;
|
|
119
113
|
}
|
|
120
114
|
|
|
121
|
-
/* &cruft, rename this .amid property pair */
|
|
115
|
+
/* &cruft, drop or rename this .amid property pair */
|
|
122
116
|
get amid() {
|
|
123
117
|
return this.settables;
|
|
124
118
|
}
|
|
@@ -180,8 +174,12 @@ export class TestDefinition {
|
|
|
180
174
|
}
|
|
181
175
|
|
|
182
176
|
get isStaticTest() /* passed */ {
|
|
183
|
-
let
|
|
184
|
-
|
|
177
|
+
let plainName = TestDefinition.plainNameOf(this.of);
|
|
178
|
+
|
|
179
|
+
let is = TypeAnalyzer.memberIsStatic(this.on, plainName);
|
|
180
|
+
let stated = this.andStringContains(TestDefinition.staticName);
|
|
181
|
+
|
|
182
|
+
return is || stated;
|
|
185
183
|
}
|
|
186
184
|
|
|
187
185
|
get isThrowTest() /* passed */ {
|
|
@@ -214,7 +212,7 @@ export class TestDefinition {
|
|
|
214
212
|
let isPropertyTest
|
|
215
213
|
= this.of.startsWith(TestDefinition.dotOperator)
|
|
216
214
|
|| this.of.endsWith(TestDefinition.colonOperator)
|
|
217
|
-
||
|
|
215
|
+
|| TypeAnalyzer.memberIsProperty(this.on, plainName);
|
|
218
216
|
|
|
219
217
|
return isPropertyTest;
|
|
220
218
|
}
|
|
@@ -258,8 +256,6 @@ export class TestDefinition {
|
|
|
258
256
|
this.output = output; // .out
|
|
259
257
|
this.source = source; // .from
|
|
260
258
|
this.factors = factors; // .and
|
|
261
|
-
|
|
262
|
-
this.#typeAnalyzer = new TypeAnalyzer(this.type);
|
|
263
259
|
}
|
|
264
260
|
|
|
265
261
|
copy() {
|
|
@@ -296,8 +292,7 @@ export class TestDefinition {
|
|
|
296
292
|
// combining with next time.
|
|
297
293
|
full = latest;
|
|
298
294
|
|
|
299
|
-
// Add
|
|
300
|
-
// only if it's ready to run as a test.
|
|
295
|
+
// Add latest, if it's ready to run as a test.
|
|
301
296
|
if (latest.isRunnable) {
|
|
302
297
|
tuples.push(latest);
|
|
303
298
|
}
|
|
@@ -350,6 +345,7 @@ export class TestDefinition {
|
|
|
350
345
|
combineWith(other) {
|
|
351
346
|
let shortNames = TestDefinition.longsByShort.keys();
|
|
352
347
|
|
|
348
|
+
// Collapse properties forward.
|
|
353
349
|
for (let shortName of shortNames) {
|
|
354
350
|
TestDefinition.combineValues(this, other, shortName);
|
|
355
351
|
}
|
package/system/TestFinder.js
CHANGED
|
@@ -137,7 +137,9 @@ export class TestFinder extends ATestFinder {
|
|
|
137
137
|
|
|
138
138
|
// Loading modules should load their dependencies.
|
|
139
139
|
try {
|
|
140
|
-
|
|
140
|
+
// Pseudo-protocol needed for non-POSIX OSes.
|
|
141
|
+
let asFileUrl = `file:///${ classPath }`;
|
|
142
|
+
let module = await import(asFileUrl);
|
|
141
143
|
|
|
142
144
|
// A module may have many contents.
|
|
143
145
|
for (let moduleKey in module) {
|
package/system/TestFrame.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { TestStages } from "./TestStages.js";
|
|
4
4
|
|
|
5
5
|
export class TestFrame {
|
|
6
|
-
/* &cruft
|
|
6
|
+
/* &cruft, when TestStages is ready, use it and its methods here */
|
|
7
7
|
|
|
8
8
|
#stages;
|
|
9
9
|
|
|
@@ -13,84 +13,83 @@ export class TestFrame {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
testFrame(test) {
|
|
16
|
-
|
|
16
|
+
/* &cruft, removing logging after it's no longer needed */
|
|
17
|
+
// console.log(`cruft : a`);
|
|
17
18
|
|
|
18
19
|
// Outer try for fails of groundwork requested.
|
|
19
20
|
try {
|
|
20
|
-
console.log(`cruft : b`);
|
|
21
|
+
// console.log(`cruft : b`);
|
|
21
22
|
|
|
22
23
|
// Spoofing, setting static properties, and so on.
|
|
23
24
|
this.#stages.anyPreTargetingGroundwork(test);
|
|
24
25
|
|
|
25
|
-
console.log(`cruft : c`);
|
|
26
|
+
// console.log(`cruft : c`);
|
|
26
27
|
|
|
27
28
|
// Target may be instance, prototype for constructors, the class
|
|
28
29
|
// itself for statics, or nonce for properties. Sets test.target.
|
|
29
|
-
this.#stages.
|
|
30
|
+
this.#stages.setTarget(test);
|
|
30
31
|
|
|
31
|
-
console.log(`cruft : d`);
|
|
32
|
+
// console.log(`cruft : d`);
|
|
32
33
|
|
|
33
34
|
// Method under test may be on instance, class,
|
|
34
35
|
// or nonce for properties. Sets test.callable.
|
|
35
36
|
this.#stages.setLocalCallable(test);
|
|
36
37
|
|
|
37
|
-
console.log(`cruft : e`);
|
|
38
|
+
// console.log(`cruft : e`);
|
|
38
39
|
|
|
39
40
|
// Setting instance properties and so on.
|
|
40
41
|
this.#stages.anyPostTargetingGroundwork(test);
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
console.log(`cruft : f`);
|
|
43
|
+
// console.log(`cruft : f`);
|
|
44
44
|
|
|
45
45
|
// Inner try for fails of targeted code only.
|
|
46
46
|
try {
|
|
47
|
-
console.log(`cruft : g`);
|
|
47
|
+
// console.log(`cruft : g`);
|
|
48
48
|
|
|
49
|
-
console.log(`cruft : test.actual:`, test.actual);
|
|
50
|
-
console.log(`cruft : test.
|
|
51
|
-
console.log(`cruft : test.localCallable:`, test.localCallable);
|
|
52
|
-
console.log(`cruft : test.in:`, test.in);
|
|
49
|
+
// console.log(`cruft : test.actual:`, test.actual);
|
|
50
|
+
// console.log(`cruft : test.target:`, test.target);
|
|
51
|
+
// console.log(`cruft : test.localCallable:`, test.localCallable);
|
|
52
|
+
// console.log(`cruft : test.in:`, test.in);
|
|
53
53
|
|
|
54
54
|
// Tests usually look for this.
|
|
55
|
-
test.actual = test.
|
|
55
|
+
test.actual = test.target[test.localCallable](...test.in);
|
|
56
56
|
|
|
57
|
-
console.log(`cruft : test.actual:`, test.actual);
|
|
58
|
-
console.log(`cruft : h`);
|
|
57
|
+
// console.log(`cruft : test.actual:`, test.actual);
|
|
58
|
+
// console.log(`cruft : h`);
|
|
59
59
|
}
|
|
60
60
|
catch (thrown) {
|
|
61
|
-
console.log(`cruft : i`);
|
|
61
|
+
// console.log(`cruft : i`);
|
|
62
62
|
|
|
63
63
|
// Sometimes tests look for this.
|
|
64
64
|
test.thrown = thrown;
|
|
65
65
|
|
|
66
|
-
console.log(`cruft : j`);
|
|
66
|
+
// console.log(`cruft : j`);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
console.log(`cruft : k`);
|
|
69
|
+
// console.log(`cruft : k`);
|
|
70
70
|
|
|
71
71
|
// Sometimes tests look for results elsewhere.
|
|
72
72
|
this.#stages.anyModifyActual(test);
|
|
73
73
|
|
|
74
|
-
console.log(`cruft : l`);
|
|
75
|
-
|
|
76
|
-
/* &cruft, remove test-checking line when all tests pass */
|
|
77
|
-
// test.didPass = "fails";
|
|
74
|
+
// console.log(`cruft : l`);
|
|
78
75
|
|
|
79
76
|
// Actually testing the result.
|
|
80
77
|
test.didPass = this.#stages.compareResults(test.output, test.actual);
|
|
81
78
|
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
/* &cruft, remove this test-checking line when all tests pass */
|
|
80
|
+
// test.didPass = !test.didPass;
|
|
81
|
+
|
|
82
|
+
// console.log(`cruft : m`);
|
|
84
83
|
}
|
|
85
84
|
finally {
|
|
86
|
-
console.log(`cruft : n`);
|
|
85
|
+
// console.log(`cruft : n`);
|
|
87
86
|
|
|
88
87
|
// Undoing any groundwork changes made.
|
|
89
88
|
this.#stages.anyGroundworkReversion(test);
|
|
90
89
|
|
|
91
|
-
console.log(`cruft : o`);
|
|
90
|
+
// console.log(`cruft : o`);
|
|
92
91
|
}
|
|
93
92
|
|
|
94
|
-
console.log(`cruft : p`);
|
|
93
|
+
// console.log(`cruft : p`);
|
|
95
94
|
}
|
|
96
95
|
}
|
package/system/TestFrames.js
CHANGED
|
@@ -4,12 +4,12 @@ import { AComparer } from "./AComparer.js";
|
|
|
4
4
|
import { TotalComparer } from "./TotalComparer.js";
|
|
5
5
|
|
|
6
6
|
export class TestFrames {
|
|
7
|
-
/* &cruft
|
|
7
|
+
/* &cruft, refactor to merge with same on TestFrameChooser */
|
|
8
8
|
// Used for special test cases.
|
|
9
9
|
constructorName = "constructor";
|
|
10
10
|
staticName = "static";
|
|
11
11
|
|
|
12
|
-
/* &cruft
|
|
12
|
+
/* &cruft, probably refactor comparing usage */
|
|
13
13
|
/* Each test frame is bound to the calling instance of TestRunner,
|
|
14
14
|
for now at least. TestRunner includes #compare() for now. */
|
|
15
15
|
|
|
@@ -215,7 +215,7 @@ export class TestFrames {
|
|
|
215
215
|
return test.from;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
/* &cruft
|
|
218
|
+
/* &cruft, refactor to merge with same on TestFrameChooser */
|
|
219
219
|
#doesAddressStatics(test) {
|
|
220
220
|
if (typeof test.and === "string") {
|
|
221
221
|
if (test.and.includes(this.staticName)) {
|
package/system/TestRunner.js
CHANGED
|
@@ -12,8 +12,8 @@ import { TestSummary } from "./TestSummary.js";
|
|
|
12
12
|
import { TestFrameChooser } from "./TestFrameChooser.js";
|
|
13
13
|
import { TestFrames } from "./TestFrames.js";
|
|
14
14
|
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { ClassMethodSpoofer } from "./ClassMethodSpoofer.js";
|
|
16
|
+
import { ObjectMethodSpoofer } from "./ObjectMethodSpoofer.js";
|
|
17
17
|
import { SpoofDefinition } from "./SpoofDefinition.js";
|
|
18
18
|
|
|
19
19
|
export class TestRunner {
|
|
@@ -112,8 +112,8 @@ export class TestRunner {
|
|
|
112
112
|
|
|
113
113
|
constructor() {
|
|
114
114
|
this.#tests = [];
|
|
115
|
-
this.#classSpoofer = new
|
|
116
|
-
this.#instanceSpoofer = new
|
|
115
|
+
this.#classSpoofer = new ClassMethodSpoofer();
|
|
116
|
+
this.#instanceSpoofer = new ObjectMethodSpoofer();
|
|
117
117
|
|
|
118
118
|
this.#frameSource = new TestFrames();
|
|
119
119
|
this.#frameChooser = new TestFrameChooser(this.#frameSource);
|
package/system/TestStages.js
CHANGED
|
@@ -19,28 +19,28 @@ export class TestStages {
|
|
|
19
19
|
// Setting any static properties here.
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
setTarget(test) /* passed */ {
|
|
23
23
|
/* Target may be instance, prototype for constructors,
|
|
24
24
|
the class itself for statics, or a nonce for either
|
|
25
25
|
static or instance properties. */
|
|
26
26
|
|
|
27
27
|
if (test.isConstructorTest) {
|
|
28
|
-
test.
|
|
28
|
+
test.target = test.on.prototype;
|
|
29
29
|
return;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
let callable = TestDefinition.plainNameOf(test.of);
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
let target = test.isInstanceTest
|
|
35
|
-
? this.#
|
|
36
|
-
: this.#
|
|
35
|
+
? this.#supplyInstanceTarget(test, callable)
|
|
36
|
+
: this.#supplyStaticTarget(test, callable);
|
|
37
37
|
|
|
38
|
-
test.
|
|
38
|
+
test.target = target;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
// region Dependencies of
|
|
41
|
+
// region Dependencies of setTarget()
|
|
42
42
|
|
|
43
|
-
#
|
|
43
|
+
#supplyStaticTarget(test, callable) /* verified */ {
|
|
44
44
|
let type = test.on;
|
|
45
45
|
|
|
46
46
|
// Methods are called on class directly.
|
|
@@ -52,7 +52,7 @@ export class TestStages {
|
|
|
52
52
|
return target;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
#
|
|
55
|
+
#supplyInstanceTarget(test, callable) /* verified */ {
|
|
56
56
|
let instance = new test.on.prototype.constructor(...test.with);
|
|
57
57
|
|
|
58
58
|
// Methods are called on instance directly.
|
|
@@ -64,7 +64,7 @@ export class TestStages {
|
|
|
64
64
|
return target;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// endregion Dependencies of
|
|
67
|
+
// endregion Dependencies of setTarget()
|
|
68
68
|
|
|
69
69
|
setLocalCallable(test) /* passed */ {
|
|
70
70
|
/* Method under test is a method named in test,
|
|
@@ -103,7 +103,6 @@ export class TestStages {
|
|
|
103
103
|
// Undoing any other groundwork changes made.
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
107
106
|
// region Dependencies of test stages
|
|
108
107
|
|
|
109
108
|
#anyOriginalPropertyGetting(target, settables) {
|
|
@@ -159,7 +158,7 @@ export class TestStages {
|
|
|
159
158
|
// When there is a .from that's a string,
|
|
160
159
|
// the actual is the named target member.
|
|
161
160
|
if (typeof test.from === "string") {
|
|
162
|
-
/* &cruft, maybe explain how this differs from setting .
|
|
161
|
+
/* &cruft, maybe explain how this differs from setting .target,
|
|
163
162
|
which can be a nonce, or else maybe make them the same */
|
|
164
163
|
|
|
165
164
|
// Common member host;
|
|
@@ -180,7 +179,7 @@ export class TestStages {
|
|
|
180
179
|
// the actual is the result of calling it,
|
|
181
180
|
// given everything that might be needed.
|
|
182
181
|
if (test.from instanceof Function) {
|
|
183
|
-
return test.from(test.actual, test.
|
|
182
|
+
return test.from(test.actual, test.target);
|
|
184
183
|
}
|
|
185
184
|
|
|
186
185
|
/* &cruft, probably drop this, change maybe to a throw */
|
|
@@ -191,80 +190,4 @@ export class TestStages {
|
|
|
191
190
|
|
|
192
191
|
// endregion Dependencies of test stages
|
|
193
192
|
|
|
194
|
-
|
|
195
|
-
/* &cruft, remove or use this code... */
|
|
196
|
-
|
|
197
|
-
// anyOriginalPropertyGetting(target, settables) {
|
|
198
|
-
// if (!Array.isArray(settables)) {
|
|
199
|
-
// return;
|
|
200
|
-
// }
|
|
201
|
-
//
|
|
202
|
-
// let unsettables = [ ];
|
|
203
|
-
//
|
|
204
|
-
// for (let settable of settables) {
|
|
205
|
-
// // Not a setter tuple.
|
|
206
|
-
// if (!settable.of) {
|
|
207
|
-
// continue;
|
|
208
|
-
// }
|
|
209
|
-
//
|
|
210
|
-
// // Retaining original.
|
|
211
|
-
// let original = target[settable.of];
|
|
212
|
-
// let unsettable = { of: settable.of, as: original };
|
|
213
|
-
// unsettables.push(unsettable);
|
|
214
|
-
// }
|
|
215
|
-
//
|
|
216
|
-
// return unsettables;
|
|
217
|
-
// }
|
|
218
|
-
//
|
|
219
|
-
// anyPropertySetting(target, settables) {
|
|
220
|
-
// // No properties to set.
|
|
221
|
-
// if (!Array.isArray(settables)) {
|
|
222
|
-
// return;
|
|
223
|
-
// }
|
|
224
|
-
//
|
|
225
|
-
// for (let settable of settables) {
|
|
226
|
-
// // Not a setter tuple.
|
|
227
|
-
// if (!settable.of) {
|
|
228
|
-
// continue;
|
|
229
|
-
// }
|
|
230
|
-
//
|
|
231
|
-
// // Actually setting property.
|
|
232
|
-
// target[settable.of] = settable.as;
|
|
233
|
-
// }
|
|
234
|
-
// }
|
|
235
|
-
//
|
|
236
|
-
// anyPropertyUnsetting(target, unsettables) {
|
|
237
|
-
// // Unsetting is the same as setting,
|
|
238
|
-
// // but with tuples of original values.
|
|
239
|
-
// return this.anyPropertySetting(target, unsettables);
|
|
240
|
-
// }
|
|
241
|
-
//
|
|
242
|
-
// supplyNonReturnActual(test, target) {
|
|
243
|
-
// // When there is a .from that's a string,
|
|
244
|
-
// // the actual is the named target member.
|
|
245
|
-
// if (typeof test.from === "string") {
|
|
246
|
-
// // Common member host;
|
|
247
|
-
// // undefined if static.
|
|
248
|
-
// let host = target;
|
|
249
|
-
//
|
|
250
|
-
// // When .and defines static.
|
|
251
|
-
// if (test.isStaticTest) {
|
|
252
|
-
// host = test.on;
|
|
253
|
-
// }
|
|
254
|
-
//
|
|
255
|
-
// return host[test.from];
|
|
256
|
-
// }
|
|
257
|
-
//
|
|
258
|
-
// // When there is a .from that's a function,
|
|
259
|
-
// // the actual is the result of calling it,
|
|
260
|
-
// // given everything that might be needed.
|
|
261
|
-
// if (test.from instanceof Function) {
|
|
262
|
-
// return test.from(target, test);
|
|
263
|
-
// }
|
|
264
|
-
//
|
|
265
|
-
// // When there is any other .from, the actual is the
|
|
266
|
-
// // value of the code element provided as .from.
|
|
267
|
-
// return test.from;
|
|
268
|
-
// }
|
|
269
|
-
|
|
270
193
|
}
|
package/system/TypeAnalyzer.js
CHANGED
|
@@ -11,32 +11,51 @@ export class TypeAnalyzer {
|
|
|
11
11
|
|
|
12
12
|
// endregion Definitions
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
/* Returns true if member is static (value or accessor
|
|
15
|
+
property, or method). Returns false otherwise. */
|
|
16
|
+
static memberIsStatic(type, name) /* passed */ {
|
|
17
|
+
// Constructor of class type, available with `in`,
|
|
18
|
+
// is not the constructor normally being sought,
|
|
19
|
+
// which instead is treated as instance-hosted.
|
|
20
|
+
if (name === TypeAnalyzer.constructorName) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
15
23
|
|
|
16
|
-
|
|
24
|
+
// All static methods and properties
|
|
25
|
+
// (value or accessor, in this class
|
|
26
|
+
// or a superclass) are `in` type.
|
|
27
|
+
if (name in type) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
17
30
|
|
|
18
|
-
|
|
31
|
+
// Anything else is an instance member,
|
|
32
|
+
// present in this class or inherited.
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
/* Returns true if instance or static member is a method.
|
|
37
|
+
Returns false if member is any kind of property. */
|
|
38
|
+
static memberIsMethod(type, name) /* passed */ {
|
|
39
|
+
return !TypeAnalyzer.memberIsProperty(type, name);
|
|
22
40
|
}
|
|
23
41
|
|
|
24
|
-
/* Returns true if instance or static member is a property
|
|
25
|
-
|
|
42
|
+
/* Returns true if instance or static member is a property
|
|
43
|
+
(value or accessor). Returns false if member is a method. */
|
|
44
|
+
static memberIsProperty(type, name) /* passed */ {
|
|
26
45
|
// Constructor is never a property and is irregular,
|
|
27
46
|
// so other type-analysis code can't handle it.
|
|
28
47
|
if (name === TypeAnalyzer.constructorName) {
|
|
29
48
|
return false;
|
|
30
49
|
}
|
|
31
|
-
|
|
50
|
+
|
|
32
51
|
// Static methods and properties (value or accessor) are on type.
|
|
33
|
-
if (name in
|
|
34
|
-
return
|
|
52
|
+
if (name in type) {
|
|
53
|
+
return TypeAnalyzer.#staticMemberIsProperty(type, name);
|
|
35
54
|
}
|
|
36
55
|
|
|
37
56
|
// Methods and accessor properties are on prototype.
|
|
38
|
-
if (name in
|
|
39
|
-
return
|
|
57
|
+
if (name in type.prototype) {
|
|
58
|
+
return TypeAnalyzer.#instanceMemberIsProperty(type.prototype, name);
|
|
40
59
|
}
|
|
41
60
|
|
|
42
61
|
// Instance value properties are not on type or prototype.
|
|
@@ -46,16 +65,16 @@ export class TypeAnalyzer {
|
|
|
46
65
|
// region Dependencies of memberIsProperty()
|
|
47
66
|
|
|
48
67
|
/* Algorithm specialized with function check for static members. */
|
|
49
|
-
#staticMemberIsProperty(type, name) {
|
|
68
|
+
static #staticMemberIsProperty(type, name) {
|
|
50
69
|
let descriptor = Object.getOwnPropertyDescriptor(type, name);
|
|
51
70
|
|
|
52
71
|
// If on type and has accessors, it's a static accessor property.
|
|
53
|
-
if (
|
|
72
|
+
if (TypeAnalyzer.#doesHaveAccessorProps(descriptor)) {
|
|
54
73
|
return true;
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
// If on type and has non-Function .value, it's a static value property.
|
|
58
|
-
if (
|
|
77
|
+
if (TypeAnalyzer.#doesNotHaveFunctionValue(descriptor)) {
|
|
59
78
|
return true;
|
|
60
79
|
}
|
|
61
80
|
|
|
@@ -64,11 +83,11 @@ export class TypeAnalyzer {
|
|
|
64
83
|
}
|
|
65
84
|
|
|
66
85
|
/* Algorithm specialized for instance members, no function check. */
|
|
67
|
-
#instanceMemberIsProperty(prototype, name) {
|
|
86
|
+
static #instanceMemberIsProperty(prototype, name) {
|
|
68
87
|
let descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
|
69
88
|
|
|
70
89
|
// If on prototype and has accessors, it's an accessor property.
|
|
71
|
-
if (
|
|
90
|
+
if (TypeAnalyzer.#doesHaveAccessorProps(descriptor)) {
|
|
72
91
|
return true;
|
|
73
92
|
}
|
|
74
93
|
|
|
@@ -78,7 +97,7 @@ export class TypeAnalyzer {
|
|
|
78
97
|
|
|
79
98
|
// region Internally reused dependencies of other memberIsProperty() dependencies
|
|
80
99
|
|
|
81
|
-
#doesHaveAccessorProps(descriptor) /* verified */ {
|
|
100
|
+
static #doesHaveAccessorProps(descriptor) /* verified */ {
|
|
82
101
|
// Accessor properties have one or both of these in descriptor.
|
|
83
102
|
if (TypeAnalyzer.getName in descriptor || TypeAnalyzer.setName in descriptor) {
|
|
84
103
|
return true;
|
|
@@ -87,12 +106,12 @@ export class TypeAnalyzer {
|
|
|
87
106
|
return false;
|
|
88
107
|
}
|
|
89
108
|
|
|
90
|
-
#doesNotHaveFunctionValue(descriptor) /* verified */ {
|
|
109
|
+
static #doesNotHaveFunctionValue(descriptor) /* verified */ {
|
|
91
110
|
// Simple inverter of other method for readability.
|
|
92
|
-
return !
|
|
111
|
+
return !TypeAnalyzer.#doesHaveFunctionValue(descriptor);
|
|
93
112
|
}
|
|
94
113
|
|
|
95
|
-
#doesHaveFunctionValue(descriptor) /* verified */ {
|
|
114
|
+
static #doesHaveFunctionValue(descriptor) /* verified */ {
|
|
96
115
|
// If no member, .value of undefined.
|
|
97
116
|
return descriptor.value instanceof Function;
|
|
98
117
|
}
|