risei 1.3.2 → 1.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 +37 -17
- package/package.json +7 -4
- package/system/{SpoofClassMethodsFixture.js → ClassMethodSpoofer.js} +5 -4
- package/system/ClassPropertySpoofer.js +185 -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 -134
- package/system/TotalDisplayer.js +1 -9
- package/system/TypeAnalyzer.js +40 -21
package/README.md
CHANGED
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
* Refactor or replace existing designs without worrying about the cost in past or future test time.
|
|
9
9
|
* Create tests with immediate confidence, because you can't introduce mistakes in test code you write.
|
|
10
10
|
|
|
11
|
-
Risei may be referred to as **Rs** here for brevity.
|
|
12
|
-
|
|
13
11
|
|
|
14
12
|
|
|
15
13
|
|
|
@@ -123,7 +121,7 @@ You can save a lot of time by putting repeated test properties into an object on
|
|
|
123
121
|
{ of: "countOf", for: "Returns the number present.", in: [ "b" ], out: 2 }
|
|
124
122
|
```
|
|
125
123
|
|
|
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 `[
|
|
124
|
+
- 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
125
|
|
|
128
126
|
- 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
127
|
|
|
@@ -134,7 +132,7 @@ You can save a lot of time by putting repeated test properties into an object on
|
|
|
134
132
|
When your tests need certain results from code dependencies, just _spoof_ what you want using a `.plus` test property:
|
|
135
133
|
|
|
136
134
|
```javascript
|
|
137
|
-
{ on: CombinerClass, with: [
|
|
135
|
+
{ on: CombinerClass, with: [],
|
|
138
136
|
of: "combineResults",
|
|
139
137
|
plus: [
|
|
140
138
|
{ on: ClassA, of: "someMethod", as: 10 }, // Spoof this ClassA method to return 10.
|
|
@@ -232,10 +230,10 @@ There's plenty more to learn about Risei and how it makes testing easy...
|
|
|
232
230
|
| Name | Contents |
|
|
233
231
|
|--------|-----------------------------------------------------------------------------------------------------------|
|
|
234
232
|
| `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 `[
|
|
233
|
+
| `with` | An array of any arguments to pass to the constructor of the tested class, or an empty array `[]` if none |
|
|
236
234
|
| `of` | The name of the method under test, as a string, no `()` needed |
|
|
237
235
|
| `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 `[
|
|
236
|
+
| `in` | An array of the input arguments to pass to the tested method, or an empty array `[]` if none |
|
|
239
237
|
| `out` | The expected return value, not in an array |
|
|
240
238
|
|
|
241
239
|
- There are additional properties for extended functionality, covered later.
|
|
@@ -245,7 +243,7 @@ There's plenty more to learn about Risei and how it makes testing easy...
|
|
|
245
243
|
|
|
246
244
|
### Test-property reuse _AKA_ Collapsing forward
|
|
247
245
|
|
|
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 `[
|
|
246
|
+
- 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
247
|
- Property values are gathered across partial test objects until they add up to a runnable test, which is then run.
|
|
250
248
|
- Changes to properties are combined with previous ones to produce intended new tests.
|
|
251
249
|
- For a rare and avoidable side-effect of this system, see [known issues and workarounds](#known-issues-and-workarounds).
|
|
@@ -337,7 +335,7 @@ To test a throw, use an `.and` of `"throw"` or `"throws"`:
|
|
|
337
335
|
{ of: "getTextProfile",
|
|
338
336
|
for: "Throws with a helpful message when there is no arg text.",
|
|
339
337
|
and: "throws",
|
|
340
|
-
in: [
|
|
338
|
+
in: [],
|
|
341
339
|
out: "Could not build a profile. Did you forget to provide a text?"
|
|
342
340
|
}
|
|
343
341
|
```
|
|
@@ -355,7 +353,7 @@ To test a value that isn't the exercised code's return value, you use `.out` nor
|
|
|
355
353
|
|
|
356
354
|
|
|
357
355
|
- 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 `[
|
|
356
|
+
- Retrieving definitions collapse forward across tests unless replaced with new definitions, or erased by setting `.from` to an empty array `[]`.
|
|
359
357
|
|
|
360
358
|
|
|
361
359
|
<details>
|
|
@@ -457,7 +455,7 @@ All test properties have long names that you can use instead of the short ones i
|
|
|
457
455
|
Constructors can be tested with no special test properties or keywords.
|
|
458
456
|
- The method name in `.of` is simply `"constructor"`.
|
|
459
457
|
- 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 `[
|
|
458
|
+
- However, a `.with` must still be provided. It can simply be an empty array `[]`.
|
|
461
459
|
|
|
462
460
|
</details>
|
|
463
461
|
|
|
@@ -472,7 +470,7 @@ Testing TypeScript code with Risei basically just means transpiling before runni
|
|
|
472
470
|
|
|
473
471
|
If you follow the general Risei set-up, you can just make the following few modifications for TypeScript applications.
|
|
474
472
|
- These steps have worked with both Angular and React.
|
|
475
|
-
-
|
|
473
|
+
- Varying other approaches are also sure to work, depending on the other technical choices in play.
|
|
476
474
|
|
|
477
475
|
|
|
478
476
|
|
|
@@ -552,7 +550,7 @@ Most problems with using Risei are minor mistakes in syntax, or omissions in tes
|
|
|
552
550
|
| `"Test loading failed for... SyntaxError: Unexpected token '.'"` | Using `this.tests = []` instead of `tests = []` in the file named |
|
|
553
551
|
| Other `... Unexpected token ...` errors | Some other syntax error in the file named, most likely a missing or extra delimiter |
|
|
554
552
|
| 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 `[
|
|
553
|
+
| 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
554
|
|
|
557
555
|
|
|
558
556
|
|
|
@@ -568,24 +566,46 @@ Two issues are known to exist:
|
|
|
568
566
|
|
|
569
567
|
- When a few properties are changed in separate objects, they are treated as whole new tests, which usually fail because of their unintended mix of old and new properties.
|
|
570
568
|
- To avoid this problem, just change properties as part of whole new tests, or else restate `.of` along with the changed properties.
|
|
571
|
-
-
|
|
569
|
+
- Risei is actually working as intended when this happens, but ways to optionally prevent it are being considered.
|
|
572
570
|
|
|
573
571
|
|
|
574
572
|
|
|
575
573
|
## What's new in Risei
|
|
576
574
|
|
|
577
|
-
- Release **1.3.
|
|
575
|
+
- Release **1.3.4** (March, 2024) fixes a bug that caused class definitions still to appear instead of class names when classes were used as object properties.
|
|
576
|
+
- As a side effect of this change, objects' own `toString()` is never used in onscreen test output any longer, but all of Risei's output display is now handled the same.
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
- 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.
|
|
580
|
+
- Risei is now fully usable in Windows environments again.
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
- Release **1.3.2** (February, 2024) does not change the code at all, but improves this read-me.
|
|
584
|
+
- It now explains the easiest way to deal with mutable args (which is just restating the args).
|
|
585
|
+
- A few mistakes in the text were also fixed.
|
|
578
586
|
|
|
579
|
-
- 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.
|
|
580
587
|
|
|
581
|
-
- Release **1.3.
|
|
588
|
+
- 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.
|
|
589
|
+
- Supporting built-in total test isolation is not feasible with collapsing forward, nor is it usually necessary given easy usage alternatives.
|
|
590
|
+
- In contrast, collapsing forward is both highly beneficial and frequently used.
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
- Release **1.3.0** (February, 2024) makes two changes:
|
|
594
|
+
- If there are any test-loading errors, their messages now appear in a new gold bar that appears below the summary of the test run.
|
|
595
|
+
- A test-sorting error has also been fixed that occurred when no test files existed yet.
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
- Release **1.2.0** (January, 2024) changes sorting of test files to make tests you're working on now appear at the bottom.
|
|
599
|
+
- Previously tests were displayed in the order found, which is always alphabetical (though is not guaranteed to be so).
|
|
600
|
+
- The order for all files except one remains the same.
|
|
601
|
+
- The last-edited file is moved to the bottom, making it easy to see the latest test results.
|
|
582
602
|
|
|
583
|
-
- Release **1.2.0** (January, 2024) changes sorting of test files. Previously they were displayed in the order found. Now the order remains the same, except that the last-edited file is moved to the bottom, making it easy to see the latest test results.
|
|
584
603
|
|
|
585
604
|
- Release **1.1.2** of Risei (January, 2024) fixes two problems:
|
|
586
605
|
- Classes were sometimes displayed in output as their entire definition. Now just the class name is displayed.
|
|
587
606
|
- All `Date` instances were considered equal, regardless of their actual value. Now they only are considered equal when they actually are.
|
|
588
607
|
|
|
608
|
+
|
|
589
609
|
- Don't use release **1.1.1**, which contains an internal path error.
|
|
590
610
|
|
|
591
611
|
|
package/package.json
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "risei",
|
|
3
|
-
"version": "1.3.
|
|
4
|
-
"description": "Risei is the framework that allows you to write unit tests as
|
|
3
|
+
"version": "1.3.4",
|
|
4
|
+
"description": "Risei is the framework that allows you to write unit tests as simple 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",
|
|
7
7
|
"test",
|
|
8
8
|
"unit testing",
|
|
9
9
|
"testing",
|
|
10
|
+
"test framework",
|
|
10
11
|
"easy",
|
|
11
12
|
"fast",
|
|
12
13
|
"simple",
|
|
13
14
|
"values",
|
|
14
15
|
"objects",
|
|
15
|
-
"declarative"
|
|
16
|
+
"declarative",
|
|
17
|
+
"javascript",
|
|
18
|
+
"typescript"
|
|
16
19
|
],
|
|
17
20
|
"author": "Ed Fallin <riseimaker@gmail.com>",
|
|
18
21
|
"license": "MIT",
|
|
@@ -48,6 +51,6 @@
|
|
|
48
51
|
"fs": "^0.0.1-security",
|
|
49
52
|
"mocha": "^10.0.0",
|
|
50
53
|
"morgan": "~1.9.1",
|
|
51
|
-
"risei": "1.3.
|
|
54
|
+
"risei": "1.3.3"
|
|
52
55
|
}
|
|
53
56
|
}
|
|
@@ -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,185 @@
|
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
import { ASpoofingFixture } from "./ASpoofingFixture.js";
|
|
4
|
+
import { SpoofDefinition } from "./SpoofDefinition.js";
|
|
5
|
+
import { TypeAnalyzer } from "./TypeAnalyzer.js";
|
|
6
|
+
|
|
7
|
+
/* &cruft, implement this under test so that anything that can be spoofed
|
|
8
|
+
from the class level is spoofed, but not expecting it to get
|
|
9
|
+
absolutely everything, and supporting poly-spoofing syntax */
|
|
10
|
+
export class ClassPropertySpoofer extends ASpoofingFixture {
|
|
11
|
+
/* Algorithm: Forward method spoof() looks at spoof definitions in .plus and applies them
|
|
12
|
+
to each class' prototypes after first saving the original member definitions.
|
|
13
|
+
Reverse method unspoof() restores each original member definition.
|
|
14
|
+
Fundamentally different from what ObjectMethodSpoofer does. */
|
|
15
|
+
|
|
16
|
+
// region Private fields
|
|
17
|
+
|
|
18
|
+
#spoofsByClass = new Map();
|
|
19
|
+
#originalsByClass = new Map();
|
|
20
|
+
|
|
21
|
+
// endregion Private fields
|
|
22
|
+
|
|
23
|
+
// region Properties
|
|
24
|
+
|
|
25
|
+
get spoofsByClass() {
|
|
26
|
+
return this.#spoofsByClass;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
set spoofsByClass(value) {
|
|
30
|
+
this.#spoofsByClass = value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get originalsByClass() {
|
|
34
|
+
return this.#originalsByClass;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
set originalsByClass(value) {
|
|
38
|
+
this.#originalsByClass = value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// endregion Properties
|
|
42
|
+
|
|
43
|
+
constructor() {
|
|
44
|
+
super("ClassPropertySpoofer", "Construction");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Spoofs methods of classes as defined in nonce
|
|
48
|
+
spoof-tuple definitions in any test.plus. */
|
|
49
|
+
spoof(test) {
|
|
50
|
+
// Localizing.
|
|
51
|
+
let type = test.on;
|
|
52
|
+
let spoofNonces = test.plus;
|
|
53
|
+
|
|
54
|
+
// Spoofing definitions are always in an array.
|
|
55
|
+
if (!Array.isArray(spoofNonces)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Converting to scope object type.
|
|
60
|
+
let spoofDefinitions = SpoofDefinition.fromNonceTuples(spoofNonces);
|
|
61
|
+
|
|
62
|
+
// Storing spoofing definitions.
|
|
63
|
+
this.#retainSpoofsByClass(type, spoofDefinitions);
|
|
64
|
+
|
|
65
|
+
// Storing originals for restoring later.
|
|
66
|
+
this.#reserveOriginalsByClass();
|
|
67
|
+
|
|
68
|
+
// Actually spoofing.
|
|
69
|
+
this.#spoofMembersByClass();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// region Dependencies of spoof()
|
|
73
|
+
|
|
74
|
+
#retainSpoofsByClass(type, spoofDefinitions) {
|
|
75
|
+
// Map of Maps: by type, then by method.
|
|
76
|
+
for (let tuple of spoofDefinitions) {
|
|
77
|
+
// If no .target, use `type`.
|
|
78
|
+
if (!tuple.target) {
|
|
79
|
+
tuple.target = type;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let spoofsByTarget = this.#supplyMemberMap(this.#spoofsByClass, tuple.target);
|
|
83
|
+
|
|
84
|
+
// A single-method spoof is set for later use.
|
|
85
|
+
if (tuple.method) {
|
|
86
|
+
spoofsByTarget.set(tuple.method, tuple.output);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Two or more method spoofs are set for later use.
|
|
91
|
+
if (tuple.output) {
|
|
92
|
+
let ofAsPairs = tuple.output;
|
|
93
|
+
|
|
94
|
+
for (let pair of ofAsPairs) {
|
|
95
|
+
spoofsByTarget.set(pair.of, pair.as);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#reserveOriginalsByClass() {
|
|
104
|
+
/* Any spoofing of multiple methods on each class has already been
|
|
105
|
+
split up into Map elements, so this code can use those naively. */
|
|
106
|
+
|
|
107
|
+
let types = this.#spoofsByClass.keys();
|
|
108
|
+
|
|
109
|
+
// For each class.
|
|
110
|
+
for (let type of types) {
|
|
111
|
+
let originalsByTarget = this.#supplyMemberMap(this.#originalsByClass, type);
|
|
112
|
+
|
|
113
|
+
let spoofsByTarget = this.#spoofsByClass.get(type);
|
|
114
|
+
let names = spoofsByTarget.keys();
|
|
115
|
+
|
|
116
|
+
// Saving originals in the Map for later.
|
|
117
|
+
for (let name of names) {
|
|
118
|
+
let original = type.prototype[name];
|
|
119
|
+
originalsByTarget.set(name, original);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Dependency of #retainSpoofsByClass()
|
|
125
|
+
and #reserveOriginalsByClass(). */
|
|
126
|
+
#supplyMemberMap(source, target) {
|
|
127
|
+
let mapForType = source.get(target);
|
|
128
|
+
|
|
129
|
+
if (!mapForType) {
|
|
130
|
+
mapForType = new Map();
|
|
131
|
+
source.set(target, mapForType);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return mapForType;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#spoofMembersByClass() {
|
|
138
|
+
/* Any spoofing of multiple methods on each class has already been
|
|
139
|
+
split up into Map elements, so this code can use those naively. */
|
|
140
|
+
|
|
141
|
+
let types = this.#spoofsByClass.keys();
|
|
142
|
+
|
|
143
|
+
// For each class.
|
|
144
|
+
for (let type of types) {
|
|
145
|
+
let spoofsByTarget = this.#spoofsByClass.get(type);
|
|
146
|
+
let names = spoofsByTarget.keys();
|
|
147
|
+
|
|
148
|
+
// Actually spoofing, one by one.
|
|
149
|
+
for (let name of names) {
|
|
150
|
+
let spoofSource = spoofsByTarget.get(name);
|
|
151
|
+
let spoof = super.spoofMethod(spoofSource);
|
|
152
|
+
type.prototype[name] = spoof;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// endregion Dependencies of spoof()
|
|
158
|
+
|
|
159
|
+
// Spoofed methods on any targeted classes
|
|
160
|
+
// are restored to their original forms.
|
|
161
|
+
unspoof() {
|
|
162
|
+
let types = this.#originalsByClass.keys();
|
|
163
|
+
|
|
164
|
+
// For each class.
|
|
165
|
+
for (let target of types) {
|
|
166
|
+
let prototype = target.prototype;
|
|
167
|
+
|
|
168
|
+
let originalsByTarget = this.#originalsByClass.get(target);
|
|
169
|
+
let names = originalsByTarget.keys();
|
|
170
|
+
|
|
171
|
+
// Actually restoring, one by one.
|
|
172
|
+
for (let name of names) {
|
|
173
|
+
let original = originalsByTarget.get(name);
|
|
174
|
+
target.prototype[name] = original;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Removes definitions, useful if this instance is reused,
|
|
180
|
+
// or else they might be applied when they shouldn't be.
|
|
181
|
+
removeDefinitions() {
|
|
182
|
+
this.#spoofsByClass.clear();
|
|
183
|
+
this.#originalsByClass.clear();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -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(spoof => TypeAnalyzer.memberIsProperty(test.on, spoof.of));
|
|
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,54 +103,8 @@ 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
|
-
#anyOriginalPropertyGetting(target, settables) {
|
|
110
|
-
if (!Array.isArray(settables)) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
let unsettables = [ ];
|
|
115
|
-
|
|
116
|
-
for (let settable of settables) {
|
|
117
|
-
// Not a setter tuple.
|
|
118
|
-
if (!settable.of) {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Retaining original.
|
|
123
|
-
let original = target[settable.of];
|
|
124
|
-
let unsettable = { of: settable.of, as: original };
|
|
125
|
-
unsettables.push(unsettable);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return unsettables;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
#anyPropertySetting(target, settables) {
|
|
132
|
-
// No properties to set.
|
|
133
|
-
if (!Array.isArray(settables)) {
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
for (let settable of settables) {
|
|
138
|
-
// Not a setter tuple.
|
|
139
|
-
if (!settable.of) {
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Actually setting property.
|
|
144
|
-
target[settable.of] = settable.as;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
#anyPropertyUnsetting(target, unsettables) {
|
|
149
|
-
// Unsetting is the same as setting,
|
|
150
|
-
// but with tuples of original values.
|
|
151
|
-
return this.#anyPropertySetting(target, unsettables);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
108
|
#compare(expected, actual) {
|
|
155
109
|
return this.#comparer.compare(expected, actual);
|
|
156
110
|
}
|
|
@@ -159,7 +113,7 @@ export class TestStages {
|
|
|
159
113
|
// When there is a .from that's a string,
|
|
160
114
|
// the actual is the named target member.
|
|
161
115
|
if (typeof test.from === "string") {
|
|
162
|
-
/* &cruft, maybe explain how this differs from setting .
|
|
116
|
+
/* &cruft, maybe explain how this differs from setting .target,
|
|
163
117
|
which can be a nonce, or else maybe make them the same */
|
|
164
118
|
|
|
165
119
|
// Common member host;
|
|
@@ -180,7 +134,7 @@ export class TestStages {
|
|
|
180
134
|
// the actual is the result of calling it,
|
|
181
135
|
// given everything that might be needed.
|
|
182
136
|
if (test.from instanceof Function) {
|
|
183
|
-
return test.from(test.actual, test.
|
|
137
|
+
return test.from(test.actual, test.target);
|
|
184
138
|
}
|
|
185
139
|
|
|
186
140
|
/* &cruft, probably drop this, change maybe to a throw */
|
|
@@ -191,80 +145,4 @@ export class TestStages {
|
|
|
191
145
|
|
|
192
146
|
// endregion Dependencies of test stages
|
|
193
147
|
|
|
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
148
|
}
|
package/system/TotalDisplayer.js
CHANGED
|
@@ -104,17 +104,9 @@ export class TotalDisplayer {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
displayObject(value) /* passed */ {
|
|
107
|
-
//
|
|
108
|
-
let asString = value.toString();
|
|
109
|
-
|
|
110
|
-
if (asString !== "[object Object]") {
|
|
111
|
-
return asString;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// If toString() not meaningful, list members.
|
|
107
|
+
// Each property of the object is handled individually.
|
|
115
108
|
let items = [ ];
|
|
116
109
|
|
|
117
|
-
// Each property of the object is handled individually.
|
|
118
110
|
for (let p in value) {
|
|
119
111
|
// Cross-recursion.
|
|
120
112
|
let item = this.display(value[p]);
|
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
|
}
|