risei 1.3.1 → 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 CHANGED
@@ -123,9 +123,9 @@ 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_ works for just about every test property, but 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
- - When the tested code mutates the contents of `.with` or `.in`, provide these args as the output of a function or method, because at present, the mutated values are collapsed forward.
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
 
130
130
 
131
131
 
@@ -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.
@@ -153,9 +153,9 @@ When your tests need certain results from code dependencies, just _spoof_ what y
153
153
  in: [ "green" ], out: [ 7, 8, 9, 10, 10, 11, 12, { color: "green" } ] }
154
154
  ```
155
155
 
156
- - Spoofing collapses forward across tests, but not across the elements within `.plus`.
156
+ - It's just like _fakes_, _mocks_, or _test doubles_ in other test systems, but much easier to write and read.
157
157
 
158
- - Spoofing is also fully isolated within tests.
158
+ - Spoofed code is fully isolated within tests, even though spoof definitions collapse forward across tests (and within `.plus`).
159
159
 
160
160
 
161
161
 
@@ -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 `[ ]` if none |
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 `[ ]` if none |
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).
@@ -254,9 +254,9 @@ There's plenty more to learn about Risei and how it makes testing easy...
254
254
  - Changing the tested class in `.on` wipes out all prior test properties, so the new class has a fresh start.
255
255
  - Changing the tested method in `.of` wipes out only test properties related to methods, to preserve class values you usually want.
256
256
 
257
- - Individual tests remain isolated, except for when args are mutable.
258
- - To use the original, un-mutated args for successive tests, just return them from a function or method to set `.with` or `.in`.
259
-
257
+ - Individual tests remain isolated, except for when args are mutated by the code under test.
258
+ - Restate args for each test when the code mutates them.
259
+ - This limitation is the result of limits on what can be copied reliably in JavaScript.
260
260
 
261
261
 
262
262
  ### Choosing test-only dependency inputs _AKA_ Spoofing
@@ -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.&nbsp; Only those in the test's `.in` property are used.
460
- - However, a `.with` must still be provided.&nbsp; It can simply be an empty array `[ ]`.
460
+ - However, a `.with` must still be provided.&nbsp; It can simply be an empty array `[]`.
461
461
 
462
462
  </details>
463
463
 
@@ -514,7 +514,7 @@ You set up the transpiler to output files to a directory with these settings:
514
514
 
515
515
  ### In test files:
516
516
 
517
- All `import` statements have to point to the `outDir` path or subfolders there, using relative-path syntax:
517
+ All `import` statements have to point to the Javascript (`.js`) files in the `outDir` path or subfolders there, using relative-path syntax:
518
518
 
519
519
  ```javascript
520
520
  import { TestedClass } from "../../dist/out-tsc/SubPath/TestedClass.js";
@@ -524,6 +524,7 @@ import { TestedClass } from "../../dist/out-tsc/SubPath/TestedClass.js";
524
524
 
525
525
 
526
526
 
527
+
527
528
  ## Limitations in Risei
528
529
 
529
530
  The following are not supported at present:
@@ -544,15 +545,15 @@ Some of these are on the tentative future-development list.&nbsp; In the meanti
544
545
 
545
546
  Most problems with using Risei are minor mistakes in syntax, or omissions in test definitions:
546
547
 
547
- | Error condition or text | Probable cause / fix |
548
+ | Error condition or text | Probable cause |
548
549
  |---------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
549
550
  | Gold bar appears below the summary, and the number of tests run drops | A syntax error caused a test file not to load; error output in the gold bar explains why |
550
551
  | `"Test loading failed for... SyntaxError: Unexpected token ':'"` | Missing comma in your tests in the named file |
551
552
  | `"Test loading failed for... SyntaxError: Unexpected token '.'"` | Using `this.tests = []` instead of `tests = []` in the file named |
552
553
  | Other `... Unexpected token ...` errors | Some other syntax error in the file named, most likely a missing or extra delimiter |
553
- | Unexpected actual values, or unexpected passes / fails in test runs | Spoofs, retrievals, or special conditions from previous tests not replaced or reset with `[ ]` |
554
- | Unexpected actual values, or unexpected passes / fails in test runs | Pre-listed test properties have produced extra tests; changing properties as part of new tests prevents this |
555
- | Unexpected actual values, or unexpected passes / fails in test runs | `.with` or `.in` values were mutated by a test and passed to subsequent tests; providing contents of these properties with a function prevents this |
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 `[]` <br>&mdash; or &mdash;<br> Args mutated by tested code were not restated in following tests to isolate them |
556
+
556
557
 
557
558
 
558
559
 
@@ -560,22 +561,24 @@ Most problems with using Risei are minor mistakes in syntax, or omissions in tes
560
561
 
561
562
  Two issues are known to exist:
562
563
 
563
- - If the contents of `.with` or `.in` are mutated by the code being tested, the mutated values are carried from test to test.
564
- - To use the original, un-mutated args for successive tests, just return them from a function or method, and call it to set `.with` or `.in`.
565
- - Ways to prevent this are being looked at.
564
+ - When code mutates its args in test definitions, the mutated forms are used in subsequent tests unless those args are restated.
565
+ - To avoid this problem, just restate the args in each test of this code.
566
+ - The workaround for this &mdash; copying and initing objects to bypass mutation &mdash; requires too many assumptions to be reliable.
566
567
 
567
568
 
568
- - Changing one or more properties in objects by themselves in the middle of tests (perhaps to emphasize them) can create extra tests.
569
- - The extra tests usually fail, because they have an unintended mix of old and new properties.
570
- - In this case, Rs is actually working properly, running prior tests with the new properties swapped in.
571
- - To avoid this problem, include changed properties in full tests to run, or restate `.of` along with the changed properties.
572
- - This situation is rarely an issue, but ways to optionally prevent it are being considered.
569
+ - 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
+ - To avoid this problem, just change properties as part of whole new tests, or else restate `.of` along with the changed properties.
571
+ - Rs is actually working as intended when this happens, but ways to optionally prevent it are being considered.
573
572
 
574
573
 
575
574
 
576
575
  ## What's new in Risei
577
576
 
578
- - 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.&nbsp; Later work may restore full isolation in a different way at some point.
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
+
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.
580
+
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.&nbsp; This sort of built-in total isolation is not feasible, and in fact is rarely needed.
579
582
 
580
583
  - Release **1.3.0** (February, 2024) moves the display of test-loading error messages to a new gold bar that appears at the bottom of the summary when there are any loading errors.&nbsp; A sorting error that occurs when no test files exist yet has also been fixed.
581
584
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "risei",
3
- "version": "1.3.1",
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.1"
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 SpoofClassMethodsFixture extends ASpoofingFixture {
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 SpoofObjectMethodsFixture does. */
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("SpoofClassMethodsFixture", "Construction");
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 SpoofObjectMethodsFixture extends ASpoofingFixture {
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 SpoofClassMethodsFixture does. */
10
+ Fundamentally different from much of what ClassMethodSpoofer does. */
11
11
 
12
12
  constructor() {
13
- super("SpoofObjectMethodsFixture", "Construction");
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 this.#typeAnalyzer.memberIsProperty(this.method);
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) {
@@ -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
- localTarget;
68
- localMethod;
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 does = this.andStringContains(TestDefinition.staticName);
184
- return does;
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
- || this.#typeAnalyzer.memberIsProperty(plainName);
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 (copy of) latest (for isolation),
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
  }
@@ -137,7 +137,9 @@ export class TestFinder extends ATestFinder {
137
137
 
138
138
  // Loading modules should load their dependencies.
139
139
  try {
140
- let module = await import(classPath);
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) {
@@ -3,7 +3,7 @@
3
3
  import { TestStages } from "./TestStages.js";
4
4
 
5
5
  export class TestFrame {
6
- /* &cruft : when TestStages is ready, use it and its methods here */
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
- console.log(`cruft : a`);
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.setLocalTarget(test);
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.localTarget:`, test.localTarget);
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.localTarget[test.localCallable](...test.in);
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
- console.log(`cruft : m`);
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
  }
@@ -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 : refactor to merge with same on TestFrameChooser */
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 : probably refactor comparing usage */
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 : refactor to merge with same on TestFrameChooser */
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)) {
@@ -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 { SpoofClassMethodsFixture } from "./SpoofClassMethodsFixture.js";
16
- import { SpoofObjectMethodsFixture } from "./SpoofObjectMethodsFixture.js";
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 SpoofClassMethodsFixture();
116
- this.#instanceSpoofer = new SpoofObjectMethodsFixture();
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);
@@ -19,28 +19,28 @@ export class TestStages {
19
19
  // Setting any static properties here.
20
20
  }
21
21
 
22
- setLocalTarget(test) /* passed */ {
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.localTarget = test.on.prototype;
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.#supplyInstanceLocalTarget(test, callable)
36
- : this.#supplyStaticLocalTarget(test, callable);
35
+ ? this.#supplyInstanceTarget(test, callable)
36
+ : this.#supplyStaticTarget(test, callable);
37
37
 
38
- test.localTarget = target;
38
+ test.target = target;
39
39
  }
40
40
 
41
- // region Dependencies of setLocalTarget()
41
+ // region Dependencies of setTarget()
42
42
 
43
- #supplyStaticLocalTarget(test, callable) /* verified */ {
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
- #supplyInstanceLocalTarget(test, callable) /* verified */ {
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 setLocalTarget()
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 .localTarget,
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.localTarget);
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
  }
@@ -11,32 +11,51 @@ export class TypeAnalyzer {
11
11
 
12
12
  // endregion Definitions
13
13
 
14
- // region Fields
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
- #type;
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
- // endregion Fields
31
+ // Anything else is an instance member,
32
+ // present in this class or inherited.
33
+ return false;
34
+ }
19
35
 
20
- constructor(type) {
21
- this.#type = type;
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 (value or accessor). Returns false if member is a method. */
25
- memberIsProperty(name) /* passed */ {
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 this.#type) {
34
- return this.#staticMemberIsProperty(this.#type, name);
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 this.#type.prototype) {
39
- return this.#instanceMemberIsProperty(this.#type.prototype, name);
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 (this.#doesHaveAccessorProps(descriptor)) {
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 (this.#doesNotHaveFunctionValue(descriptor)) {
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 (this.#doesHaveAccessorProps(descriptor)) {
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 !this.#doesHaveFunctionValue(descriptor);
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
  }