risei 2.0.1 → 3.0.0

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
@@ -46,7 +46,7 @@ And they have a summary bar at the bottom:
46
46
 
47
47
  ## Status
48
48
 
49
- Risei is under active development and new enhancements appear often.  The latest release, **2.0.1**, brings many major improvements, including broader capabilities and simpler syntax, and a few minor breaking changes.
49
+ Risei is under active development and new enhancements appear often.  The latest release, **3.0.0**, brings major improvements, starting with the ability to test `async` code, and builds on many major improvements in **2.0.0**, including broader capabilities and simpler syntax, and a few minor breaking changes.
50
50
 
51
51
  Check out the [full list of changes](#version-history).
52
52
 
@@ -58,11 +58,13 @@ Check out the [full list of changes](#version-history).
58
58
  - #### Easy-to-read test definitions and test outputs.    ►  [Test and output examples](https://deusware.com/risei/index.html#examples)  (also above)
59
59
  - #### Even less to write by stating reused test properties only once.    ►  [Collapsing forward](https://deusware.com/risei/index.html#collapsing-forward)  (basics below)
60
60
  - #### Built-in declarative syntax to fake test-time values from dependencies.    ►  [Spoofing using `.plus`](https://deusware.com/risei/index.html#spoofing)  (basics below)
61
- - #### Testing properties, methods, static and instance members all the same way.    ►  [Properties and statics](https://deusware.com/risei/index.html#testing-properties-and-static-methods)
61
+ - #### Full support for `async` code with no special syntax needed.    ►  [Writing tests](https://deusware.com/risei/index.html#writing-tests)  (basics below)
62
+ - #### Testing properties and methods, static and instance members all the same way.    ►  [Properties and statics](https://deusware.com/risei/index.html#testing-properties-and-static-methods)
62
63
  - #### Testing `throw` paths effortlessly.    ►  [Using `.and: "throws"`](https://deusware.com/risei/index.html#using-and-throws)
63
64
  - #### Deriving actual values to test from raw outputs or property retrieval.    ►  [Using `.from`](https://deusware.com/risei/index.html#using-from)
64
65
  - #### Setting up and tearing down arbitrary test state.    ►  [Using `.do` and `.undo`](https://deusware.com/risei/index.html#using-do-and-undo)
65
66
  - #### Testing for `undefined` as output.    ►  [Using `this.undef`](https://deusware.com/risei/index.html#using-undef)
67
+ - #### Running a method repeatedly in one test.    ►  [Using `.and: "poly"`](https://deusware.com/risei/index.html#using-and-poly)
66
68
 
67
69
  - And more!  Check out the full [Risei home page](https://deusware.com/risei).
68
70
 
@@ -140,11 +142,11 @@ tests = [ ...
140
142
  ... ];
141
143
  ```
142
144
 
145
+ - Asynchronous code with `async` and `await` keywords can tested with no changes at all to this syntax.
143
146
  - Use empty arrays for `.in` or `.with` when there are no args to pass.
144
147
  - You can use [long names](https://deusware.com/risei/index.html#long-names) for properties if you want.
145
148
 
146
149
 
147
-
148
150
  ## Basic collapsing forward example
149
151
 
150
152
  You can state test properties once and let them _collapse forward_ across subsequent tests to save time and make tests easier to read.
@@ -187,6 +189,7 @@ The most basic spoofing looks like this:
187
189
  }
188
190
  ```
189
191
 
192
+ - It's just like _fakes_, _mocks_, or _test doubles_ in other test systems, but easier to write and read.
190
193
  - Numerous additional capabilities, as well as compressed syntax, can be mixed freely in many ways.
191
194
  - Learn more [here](https://deusware.com/risei/index.html#spoofing).
192
195
 
@@ -209,9 +212,22 @@ If problems cause test files not to load, a gold bar appears and tests in those
209
212
  - Those and other problems can be solved with the help of this [troubleshooting guide](https://deusware.com/risei/index.html#troubleshooting).
210
213
 
211
214
 
215
+
212
216
  ## Version history
213
217
 
214
- - Release **2.0.1** (August, 2024) contains all of these changes:
218
+ - Release **3.0.0** (August, 2024) contains all of these changes:
219
+ - Asynchronous code with `async` syntax is now fully supported, with no special test syntax required.
220
+ - **(Breaking change)**  `.from` functions now take `test` and `actual` as parameters, rather than `target` and `test`.
221
+ - If you need to work with the target of the test (usually an instance of the class being tested), it's available as `test.target`.
222
+ - You can now call target code more than once in one test using an `.and` of `"poly"` (AKA _poly-calling_).
223
+
224
+
225
+ <details>
226
+ <summary>
227
+ Older releases
228
+ </summary>
229
+
230
+ - Release **2.0.1** (August, 2024) contained all of these changes:
215
231
  - Risei now can determine automatically if tested methods are static.
216
232
  - You can now directly test properties just like methods.
217
233
  - Risei can also determine automatically if these are static.
@@ -221,20 +237,13 @@ If problems cause test files not to load, a gold bar appears and tests in those
221
237
  - You can now change test properties without accidentally creating a new test using `.just`.
222
238
  - You can now directly test for `undefined` in `.out` using `this.undef` / `ATestSource.undefSymbol`.
223
239
  - `Error` objects are now compared accurately.
224
- - You can now identify member types with `.`, `:`, and `()` in `.of`, `.plus`, and `.from`, though they aren't necessary.
240
+ - You can now identify member types with `.`, `:`, and `()` in any of `.of`, `.plus`, and `.from`, though it isn't necessary.
225
241
  - **(Breaking change)**&nbsp; The actual for throw tests is now the entire thrown object (usually an `Error`), rather than the `Error`'s message text.
226
242
  - **(Breaking change)**&nbsp; `ATestSource` is now a default export, changing its imports from `import { ATestSource } from` to `import ATestSource from`.
243
+ - Major internal re-engineering.
227
244
 
228
- > Risei has been massively re-engineered internally along with these improvements.
245
+ - Release **2.0.0** (August, 2024) was identical to 2.0.1 except for some unneeded extra files.
229
246
 
230
-
231
-
232
- <details>
233
- <summary>
234
- Older releases
235
- </summary>
236
-
237
- - Release **2.0.0** (August, 2024) is identical to 2.0.1 except for some unneeded extra files.
238
247
  - Release **1.3.4** (March, 2024) fixed a bug that caused class display problems in some cases.
239
248
  - Release **1.3.3** (March, 2024) fixed a major bug that prevented tests from running in Windows.
240
249
  - Release **1.3.2** (February, 2024) only improved this read-me's contents.
@@ -248,6 +257,7 @@ Older releases
248
257
  </details>
249
258
 
250
259
 
260
+
251
261
  ## Known issues and workarounds
252
262
 
253
263
  The only issue at present is reuse of method args mutated by tested code when collapsing forward.
@@ -255,9 +265,10 @@ The only issue at present is reuse of method args mutated by tested code when co
255
265
  - The workaround is just to restate those args for each test.
256
266
 
257
267
 
268
+
258
269
  ## Exclusions from Risei
259
270
 
260
- At present, there are a few things Risei doesn't support, such as `async` syntax.&nbsp; Some of these may be supported in the future.
271
+ At present, there are a few things Risei doesn't support.&nbsp; Some of these may be supported in the future.
261
272
 
262
273
  - You can see the whole list [here](https://deusware.com/risei/index.html#exclusions-from-risei).
263
274
  - You can save a lot of time by using Risei for most of your code, and another framework for whatever it doesn't support.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "risei",
3
- "version": "2.0.1",
3
+ "version": "3.0.0",
4
4
  "description": "Risei allows you to write unit tests as simple JavaScript objects, so it's easy and fast, with no drag on redesign.",
5
5
  "keywords": [
6
6
  "unit test",
@@ -50,6 +50,6 @@
50
50
  "fs": "^0.0.1-security",
51
51
  "mocha": "^10.0.0",
52
52
  "morgan": "~1.9.1",
53
- "risei": "^1.3.4"
53
+ "risei": "^3.0.0"
54
54
  }
55
55
  }
@@ -1,26 +1,26 @@
1
- /**/
2
-
3
- /* Defines operations that a test reporter must implement. A = abstract. */
4
-
5
- /* &cruft, ? replace with my own code eventually */
6
- import chalk from "chalk";
7
-
8
- export default class ATestReporter {
9
- // Reports whatever was just provided by the TestRunner.
10
- // Can delegate to the other report-x methods.
11
- reportNext(result) {
12
- }
13
-
14
- reportGroup() {
15
- }
16
-
17
- reportPassed(result) {
18
- }
19
-
20
- reportFailed(result) {
21
- }
22
-
23
- reportSummary(summary) {
24
- }
25
-
26
- }
1
+ /**/
2
+
3
+ /* Defines operations that a test reporter must implement. A = abstract. */
4
+
5
+ /* &cruft, ? replace with my own code eventually */
6
+ import chalk from "chalk";
7
+
8
+ export default class ATestReporter {
9
+ // Reports whatever was just provided by the TestRunner.
10
+ // Can delegate to the other report-x methods.
11
+ reportNext(result) {
12
+ }
13
+
14
+ reportGroup() {
15
+ }
16
+
17
+ reportPassed(result) {
18
+ }
19
+
20
+ reportFailed(result) {
21
+ }
22
+
23
+ reportSummary(summary) {
24
+ }
25
+
26
+ }
@@ -1,8 +1,8 @@
1
- /**/
2
-
3
- import TestGroup from "./TestGroup.js";
4
-
5
- /* Identifies a grouping of tests by class. No specialized workings. */
6
-
7
- export default class ClassTestGroup extends TestGroup {
8
- }
1
+ /**/
2
+
3
+ import TestGroup from "./TestGroup.js";
4
+
5
+ /* Identifies a grouping of tests by class. No specialized workings. */
6
+
7
+ export default class ClassTestGroup extends TestGroup {
8
+ }
@@ -11,11 +11,11 @@ import TestRunner from "./TestRunner.js";
11
11
  export default class LocalCaller extends ATestCaller {
12
12
  async runAllTests() {
13
13
  let tests = await this.finder.findAllTests();
14
- this.runner.useTests(tests);
14
+ this.runner.useTests(tests);
15
15
 
16
16
  // Getting each run test / summary result
17
17
  // and having it reported in the terminal.
18
- for (let result of this.runner) {
18
+ for await (let result of this.runner.runTests()) {
19
19
  this.reporter.reportNext(result);
20
20
  }
21
21
  }
@@ -39,7 +39,7 @@ export default class MethodSpoofer extends ASpoofingFixture {
39
39
  // endregion Properties
40
40
 
41
41
  constructor() {
42
- super("MethodSpoofer", "Construction");
42
+ super();
43
43
  }
44
44
 
45
45
  // Spoofs methods of classes as defined in nonce
@@ -53,7 +53,7 @@ export default class MethodSpoofer extends ASpoofingFixture {
53
53
  if (!Array.isArray(spoofNonces)) {
54
54
  return;
55
55
  }
56
-
56
+
57
57
  // Converting to scope object type.
58
58
  let spoofDefs = SpoofDef.fromNonceTuples(spoofNonces, type);
59
59
 
@@ -1,8 +1,8 @@
1
- /**/
2
-
3
- import TestGroup from "./TestGroup.js";
4
-
5
- /* Identifies a grouping of tests by method. No specialized workings. */
6
-
7
- export default class MethodTestGroup extends TestGroup {
8
- }
1
+ /**/
2
+
3
+ import TestGroup from "./TestGroup.js";
4
+
5
+ /* Identifies a grouping of tests by method. No specialized workings. */
6
+
7
+ export default class MethodTestGroup extends TestGroup {
8
+ }
package/system/Moment.js CHANGED
@@ -1,29 +1,29 @@
1
- /**/
2
-
3
- export default class Moment {
4
- #now;
5
-
6
- constructor(now) {
7
- this.#now = now;
8
- }
9
-
10
- asReadable() /* passed */ {
11
- let now = this.#now;
12
- let asTwo = this.#asTwo;
13
-
14
- let day = `${ now.getMonth() + 1 }/${ asTwo(now.getDate()) }/${ now.getFullYear() }`;
15
-
16
- let time = `${ now.getHours() % 12 || 12 }:${ asTwo(now.getMinutes()) }:${ asTwo(now.getSeconds()) } `
17
- + `${ now.getHours() < 12 ? "AM" : "PM" }`;
18
-
19
- return `${ day } at ${ time }`;
20
- }
21
-
22
- #asTwo(number) /* verified */ {
23
- let text = `${ number }`;
24
- text = text.padStart(2, "0");
25
-
26
- return text;
27
- }
28
-
29
- }
1
+ /**/
2
+
3
+ export default class Moment {
4
+ #now;
5
+
6
+ constructor(now) {
7
+ this.#now = now;
8
+ }
9
+
10
+ asReadable() /* passed */ {
11
+ let now = this.#now;
12
+ let asTwo = this.#asTwo;
13
+
14
+ let day = `${ now.getMonth() + 1 }/${ asTwo(now.getDate()) }/${ now.getFullYear() }`;
15
+
16
+ let time = `${ now.getHours() % 12 || 12 }:${ asTwo(now.getMinutes()) }:${ asTwo(now.getSeconds()) } `
17
+ + `${ now.getHours() < 12 ? "AM" : "PM" }`;
18
+
19
+ return `${ day } at ${ time }`;
20
+ }
21
+
22
+ #asTwo(number) /* verified */ {
23
+ let text = `${ number }`;
24
+ text = text.padStart(2, "0");
25
+
26
+ return text;
27
+ }
28
+
29
+ }
@@ -1,26 +1,26 @@
1
- /**/
2
-
3
- export default class NameAnalyzer {
4
- // region Definitions
5
-
6
- static dotOperator = ".";
7
- static colonOperator = ":";
8
- static parensOperator = "()";
9
- static operatorsRegExp = /\.|:|\(\)/g;
10
-
11
- // endregion Definitions
12
-
13
- static hasPropertySigil(name) /* passed */ {
14
- return name.startsWith(NameAnalyzer.dotOperator) || name.endsWith(NameAnalyzer.colonOperator);
15
- }
16
-
17
- static hasMethodSigil(name) /* passed */ {
18
- return name.endsWith(NameAnalyzer.parensOperator);
19
- }
20
-
21
- static plainNameOf(name) /* passed */ {
22
- let plain = name.replaceAll(NameAnalyzer.operatorsRegExp, "");
23
- return plain;
24
- }
25
- }
26
-
1
+ /**/
2
+
3
+ export default class NameAnalyzer {
4
+ // region Definitions
5
+
6
+ static dotOperator = ".";
7
+ static colonOperator = ":";
8
+ static parensOperator = "()";
9
+ static operatorsRegExp = /\.|:|\(\)/g;
10
+
11
+ // endregion Definitions
12
+
13
+ static hasPropertySigil(name) /* passed */ {
14
+ return name.startsWith(NameAnalyzer.dotOperator) || name.endsWith(NameAnalyzer.colonOperator);
15
+ }
16
+
17
+ static hasMethodSigil(name) /* passed */ {
18
+ return name.endsWith(NameAnalyzer.parensOperator);
19
+ }
20
+
21
+ static plainNameOf(name) /* passed */ {
22
+ let plain = name.replaceAll(NameAnalyzer.operatorsRegExp, "");
23
+ return plain;
24
+ }
25
+ }
26
+
@@ -52,7 +52,7 @@ export default class PropertySpoofer extends ASpoofingFixture {
52
52
  // endregion Properties
53
53
 
54
54
  constructor() {
55
- super("PropertySpoofer", "Construction");
55
+ super();
56
56
  }
57
57
 
58
58
  /* Spoofs properties of test.target as defined in any test.plus. */
@@ -20,11 +20,6 @@ export default class SpoofDef {
20
20
  SpoofDef.#asNames
21
21
  ]);
22
22
 
23
- /* &cruft, remove these after spoofing in .with and .in is removed */
24
- /* Objects with either of these property names aren't
25
- converted to spoofDefs by from-nonce methods. */
26
- static skipNames = [ "not", "skip" ];
27
-
28
23
  // endregion Static fields
29
24
 
30
25
  // region Public fields (long names)
package/system/TestDef.js CHANGED
@@ -13,6 +13,7 @@ export default class TestDef {
13
13
 
14
14
  static staticName = "static";
15
15
  static throwName = "throw";
16
+ static polyName = "poly";
16
17
  static constructorName = "constructor";
17
18
  static nonceLocalCallableName = "nonce";
18
19
 
@@ -60,9 +61,7 @@ export default class TestDef {
60
61
 
61
62
  // endregion Fields
62
63
 
63
- // region Properties
64
-
65
- // region Test definition, short names
64
+ // region Definition properties
66
65
 
67
66
  /* These short names make JSON-like definitions easier. */
68
67
 
@@ -154,9 +153,9 @@ export default class TestDef {
154
153
  this.counteract = value;
155
154
  }
156
155
 
157
- // endregion Test definition, short names
156
+ // endregion Definition properties
158
157
 
159
- // region Def state
158
+ // region State properties
160
159
 
161
160
  get isRunnable() /* passed */ {
162
161
  let isRunnable
@@ -182,12 +181,17 @@ export default class TestDef {
182
181
 
183
182
  return is || stated;
184
183
  }
185
-
184
+
186
185
  get isThrowTest() /* passed */ {
187
186
  let is = this.andStringContains(TestDef.throwName);
188
187
  return is;
189
188
  }
190
189
 
190
+ get isPolyCallTest() /* passed */ {
191
+ let is = this.andStringContains(TestDef.polyName);
192
+ return is;
193
+ }
194
+
191
195
  get isConstructorTest() /* passed */ {
192
196
  let plainTarget = NameAnalyzer.plainNameOf(this.of);
193
197
  return plainTarget === TestDef.constructorName;
@@ -255,17 +259,14 @@ export default class TestDef {
255
259
  if (this.isMethodTest) {
256
260
  return `${ plain }()`;
257
261
  }
258
-
259
- if (this.isPropertyTest) {
262
+ else {
260
263
  return `.${ plain }`;
261
264
  }
262
265
  }
263
266
 
264
- // endregion Def state
265
-
266
- // endregion Properties
267
+ // endregion State properties
267
268
 
268
- // region Def state methods
269
+ // region State methods
269
270
 
270
271
  /* Needed externally, and dependency
271
272
  of all .and-based properties. */
@@ -277,7 +278,7 @@ export default class TestDef {
277
278
  return doesContain;
278
279
  }
279
280
 
280
- // endregion Def state methods
281
+ // endregion State methods
281
282
 
282
283
  // region Initing, including constructor()
283
284
 
@@ -9,12 +9,12 @@ export default class TestFrame {
9
9
  // A TestStages is inited for each test.
10
10
  this.#stages = new TestStages();
11
11
  }
12
-
13
- run(test) /* passed */ {
12
+
13
+ async run(test) /* passed */ {
14
14
  // Outer try for fails of groundwork requested.
15
15
  try {
16
16
  // Spoofing and so on.
17
- this.#stages.anyPreTargetingGroundwork(test);
17
+ await this.#stages.anyPreTargetingGroundwork(test);
18
18
 
19
19
  // Target may be instance, prototype for constructors, the class
20
20
  // itself for statics, or nonce for properties. Sets test.target.
@@ -25,14 +25,14 @@ export default class TestFrame {
25
25
  // Method under test may be on instance, class,
26
26
  // or nonce for properties. Sets test.callable.
27
27
  let callable = this.#stages.supplyCallableName(test);
28
-
28
+
29
29
  // Setting (spoofing) properties and so on.
30
- this.#stages.anyPostTargetingGroundwork(test);
31
-
30
+ await this.#stages.anyPostTargetingGroundwork(test);
31
+
32
32
  // Inner try for fails of targeted code only.
33
33
  try {
34
34
  // Tests usually look for this.
35
- test.actual = target[callable](...test.in);
35
+ test.actual = await target[callable](...test.in);
36
36
  }
37
37
  catch (thrown) {
38
38
  // Sometimes tests look for this.
@@ -50,9 +50,7 @@ export default class TestFrame {
50
50
  }
51
51
  finally {
52
52
  // Undoing any groundwork changes made.
53
- this.#stages.anyGroundworkReversal(test);
53
+ await this.#stages.anyGroundworkReversal(test);
54
54
  }
55
-
56
- // console.log(`cruft : q, outer try-catch : after`);
57
55
  }
58
56
  }
@@ -1,25 +1,25 @@
1
- /**/
2
-
3
- export default class TestGroup {
4
- // region Private fields
5
-
6
- #group;
7
-
8
- // endregion Private fields
9
-
10
- // region Properties
11
-
12
- get group() {
13
- return this.#group;
14
- }
15
-
16
- set group(value) {
17
- this.#group = value;
18
- }
19
-
20
- // endregion Properties
21
-
22
- constructor(group) {
23
- this.#group = group;
24
- }
25
- }
1
+ /**/
2
+
3
+ export default class TestGroup {
4
+ // region Private fields
5
+
6
+ #group;
7
+
8
+ // endregion Private fields
9
+
10
+ // region Properties
11
+
12
+ get group() {
13
+ return this.#group;
14
+ }
15
+
16
+ set group(value) {
17
+ this.#group = value;
18
+ }
19
+
20
+ // endregion Properties
21
+
22
+ constructor(group) {
23
+ this.#group = group;
24
+ }
25
+ }
@@ -101,7 +101,7 @@ export default class TestRunner {
101
101
 
102
102
  // Generator for all test results,
103
103
  // including groups and summary.
104
- * [Symbol.iterator]() {
104
+ async * runTests() {
105
105
  if (!this.#tests) {
106
106
  throw new Error("No tests available to run. TestRunner's .tests must be set before an attempt to run is made.");
107
107
  }
@@ -126,13 +126,15 @@ export default class TestRunner {
126
126
 
127
127
  // Each new method / prop name should be a group,
128
128
  // and so should the first method for a class.
129
- if (test.runName !== methodGroup.group || atFirstClassMethod) {
130
- methodGroup.group = test.runName;
129
+ let runName = test.runName;
130
+
131
+ if (runName !== methodGroup.group || atFirstClassMethod) {
132
+ methodGroup.group = runName;
131
133
  atFirstClassMethod = false;
132
134
  yield methodGroup;
133
135
  }
134
136
 
135
- let result = this.runOneTest(test);
137
+ let result = await this.runOneTest(test);
136
138
  yield result;
137
139
  }
138
140
 
@@ -140,7 +142,7 @@ export default class TestRunner {
140
142
  yield this.summarize();
141
143
  }
142
144
 
143
- runOneTest(test) {
145
+ async runOneTest(test) {
144
146
  // Default outputs. Never left undefined.
145
147
  test.didPass = false;
146
148
  test.anyThrow = null;
@@ -151,7 +153,7 @@ export default class TestRunner {
151
153
  result.setNature();
152
154
 
153
155
  // Actually running the test.
154
- this.#frame.run(test);
156
+ await this.#frame.run(test);
155
157
 
156
158
  // Gathering facts based on the test run.
157
159
  result.setResults();
@@ -14,18 +14,22 @@ export default class TestStages {
14
14
  or before / after all test frames, in the test runner.
15
15
  Not necessarily all test steps are found here. */
16
16
 
17
+ // region Components
18
+
17
19
  static #methodSpoofer = new MethodSpoofer();
18
20
  static #propertySpoofer = new PropertySpoofer();
19
21
  static #comparer = new TotalComparer();
20
22
 
21
- anyPreTargetingGroundwork(test) /* passed */ {
23
+ // endregion Components
24
+
25
+ async anyPreTargetingGroundwork(test) /* passed */ {
22
26
  // Spoofing methods; properties spoofed later.
23
27
  TestStages.#methodSpoofer.spoof(test);
24
28
 
25
29
  // Arbitrary user-defined actions.
26
30
  // Only spoofed methods available.
27
31
  if (test.doesHaveDoEarly) {
28
- test.do.early(test);
32
+ await test.do.early(test);
29
33
  }
30
34
  }
31
35
 
@@ -48,36 +52,58 @@ export default class TestStages {
48
52
 
49
53
  supplyLocalTarget(test) /* passed */ {
50
54
  /* Methods are called on instance / type / prototype directly.
51
- Properties are called on instance / type in a nonce. */
55
+ Properties are called on instance / type in a nonce.
56
+ Methods or properties are poly-called in a nonce. */
52
57
 
53
58
  let callable = NameAnalyzer.plainNameOf(test.method);
54
59
 
60
+ // Two most common cases: mono-call
61
+ // tests of methods and properties.
55
62
  let localTarget = test.isMethodTest
56
63
  ? test.target
57
64
  : { nonce: () => { return test.target[callable]; } };
58
65
 
66
+ // Rare cases: poly-call tests
67
+ // of methods and properties.
68
+ if (test.isPolyCallTest) {
69
+ let nonce = test.isMethodTest
70
+ ? this.supplyPolyMethodNonce(test, callable)
71
+ : this.supplyPolyPropNonce(test, callable);
72
+
73
+ localTarget = { nonce };
74
+ }
75
+
59
76
  return localTarget;
60
77
  }
61
78
 
62
79
  supplyCallableName(test) /* passed */ {
63
- /* Method under test is a method named in test,
64
- or a nonce method for a property test. */
80
+ /* Name is of the method named in test,
81
+ or a nonce method for a property or
82
+ any poly-call test (method or prop). */
65
83
 
84
+ // Two most common cases: mono-call
85
+ // test of a method or property.
66
86
  let name = test.isMethodTest
67
87
  ? NameAnalyzer.plainNameOf(test.method)
68
88
  : TestDef.nonceLocalCallableName;
69
89
 
90
+ // Rare cases: poly-call tests
91
+ // of a method or property.
92
+ if (test.isPolyCallTest) {
93
+ name = TestDef.nonceLocalCallableName;
94
+ }
95
+
70
96
  return name;
71
97
  }
72
98
 
73
- anyPostTargetingGroundwork(test) /* passed */ {
99
+ async anyPostTargetingGroundwork(test) /* passed */ {
74
100
  // Spoofing properties; methods spoofed earlier.
75
101
  TestStages.#propertySpoofer.spoof(test);
76
102
 
77
103
  // Arbitrary user-defined actions.
78
104
  // All spoofs normally available.
79
105
  if (test.doesHaveDoLate) {
80
- test.do.late(test);
106
+ await test.do.late(test);
81
107
  }
82
108
  }
83
109
 
@@ -99,7 +125,7 @@ export default class TestStages {
99
125
  return TestStages.#comparer.compare(expected, actual);
100
126
  }
101
127
 
102
- anyGroundworkReversal(test) /* passed */ {
128
+ async anyGroundworkReversal(test) /* passed */ {
103
129
  // Unspoofing methods and properties.
104
130
  TestStages.#methodSpoofer.unspoof(test);
105
131
  TestStages.#propertySpoofer.unspoof(test);
@@ -107,12 +133,42 @@ export default class TestStages {
107
133
  // Arbitrary user-defined operations.
108
134
  // No spoofs are available here.
109
135
  if (test.doesHaveUndo) {
110
- test.undo(test);
136
+ await test.undo(test);
111
137
  }
112
138
  }
113
139
 
114
140
  // region Dependencies of test stages
115
141
 
142
+ supplyPolyMethodNonce(test, callable) /* verified */ {
143
+ let nonce = (...inputs) => {
144
+ let actuals = [];
145
+
146
+ for (let args of inputs) {
147
+ let local = test.target[callable](...args);
148
+ actuals.push(local);
149
+ }
150
+
151
+ return actuals;
152
+ };
153
+
154
+ return nonce;
155
+ }
156
+
157
+ supplyPolyPropNonce(test, callable) /* verified */ {
158
+ let nonce = (...inputs) => {
159
+ let actuals = [];
160
+
161
+ for (let args of inputs) {
162
+ let local = test.target[callable];
163
+ actuals.push(local);
164
+ }
165
+
166
+ return actuals;
167
+ };
168
+
169
+ return nonce;
170
+ }
171
+
116
172
  supplyNonReturnActual(test) /* passed */ {
117
173
  // When .from is a string, the actual
118
174
  // is the named target or type member.
@@ -136,7 +192,7 @@ export default class TestStages {
136
192
  // given everything that might be needed.
137
193
  if (test.from instanceof Function) {
138
194
  // Actually supplying value.
139
- return test.from(test.target, test);
195
+ return test.from(test, test.actual);
140
196
  }
141
197
 
142
198
  // Only property names and functions make sense to support.
@@ -1,37 +1,37 @@
1
- /**/
2
-
3
- export default class TestSummary {
4
- #summary;
5
- #allDidPass;
6
- #anyWereRun;
7
-
8
- get summary() {
9
- return this.#summary;
10
- }
11
-
12
- set summary(value) {
13
- this.#summary = value;
14
- }
15
-
16
- get allDidPass() {
17
- return this.#allDidPass;
18
- }
19
-
20
- set allDidPass(value) {
21
- this.#allDidPass = value;
22
- }
23
-
24
- get anyWereRun() {
25
- return this.#anyWereRun;
26
- }
27
-
28
- set anyWereRun(value) {
29
- this.#anyWereRun = value;
30
- }
31
-
32
- constructor(summary, allDidPass, anyWereRun) {
33
- this.summary = summary;
34
- this.allDidPass = allDidPass;
35
- this.anyWereRun = anyWereRun;
36
- }
37
- }
1
+ /**/
2
+
3
+ export default class TestSummary {
4
+ #summary;
5
+ #allDidPass;
6
+ #anyWereRun;
7
+
8
+ get summary() {
9
+ return this.#summary;
10
+ }
11
+
12
+ set summary(value) {
13
+ this.#summary = value;
14
+ }
15
+
16
+ get allDidPass() {
17
+ return this.#allDidPass;
18
+ }
19
+
20
+ set allDidPass(value) {
21
+ this.#allDidPass = value;
22
+ }
23
+
24
+ get anyWereRun() {
25
+ return this.#anyWereRun;
26
+ }
27
+
28
+ set anyWereRun(value) {
29
+ this.#anyWereRun = value;
30
+ }
31
+
32
+ constructor(summary, allDidPass, anyWereRun) {
33
+ this.summary = summary;
34
+ this.allDidPass = allDidPass;
35
+ this.anyWereRun = anyWereRun;
36
+ }
37
+ }
@@ -45,40 +45,93 @@ export default class TypeAnalyzer {
45
45
  /* Returns true if instance or static member is a method.
46
46
  Returns false if member is any kind of property. */
47
47
  static isMethodMember(type, name) /* passed */ {
48
- /* A method is always defined either on a type or
49
- its prototype, even if declared on a superclass. */
48
+ return !TypeAnalyzer.isPropertyMember(type, name);
49
+ }
50
50
 
51
- try {
52
- let anyInstanceType = TypeIdentifier.identify(type.prototype[name]);
53
- let anyStaticType = TypeIdentifier.identify(type[name]);
51
+ /* Returns true if instance or static member is a property
52
+ (value or accessor). Returns false if member is a method. */
53
+ static isPropertyMember(type, name) /* passed */ {
54
+ /* Type and supertypes are tested directly for static props,
55
+ and their definition texts are read for instance props. */
54
56
 
55
- /* Excludes class "functions", which are not methods. */
56
- let isInstanceMethod = anyInstanceType === Types.isFunction;
57
- let isStaticMethod = anyStaticType === Types.isFunction;
57
+ let isProperty = this.#typeChainIncludesAsProperty(type, name);
58
58
 
59
- /* Returns net method-ness; if not found at all, a property
60
- is assumed (true as long as the member exists at all.) */
61
- return isInstanceMethod || isStaticMethod;
62
- }
63
- catch (thrown) {
64
- /* If private fields are used behind public accessor properties,
65
- there's a characteristic message which shows it's not a method.
66
- It may be a property returning a function, but not a method. */
67
- if (thrown.message.startsWith(TypeAnalyzer.accessorOfPrivate)) {
68
- return false;
59
+ return isProperty;
60
+ }
61
+
62
+ static #typeChainIncludesAsProperty(type, name) /* verified */ {
63
+ let prototype = type.prototype;
64
+
65
+ while (prototype !== null) {
66
+ // Type is needed, rather than prototype.
67
+ type = prototype.constructor;
68
+
69
+ // First line finds any static props; second, any instance props.
70
+ let isProperty
71
+ = TypeAnalyzer.#typeDefinitionIncludesAsProperty(type, name)
72
+ || TypeAnalyzer.#typeTextContainsNameAsProperty(type, name);
73
+
74
+ // Found as static or instance.
75
+ if (isProperty) {
76
+ return true;
69
77
  }
70
78
 
71
- /* Other throws are unpredictable and mean there's a problem. */
72
- throw thrown;
79
+ // Parent class.
80
+ prototype = Object.getPrototypeOf(prototype);
81
+
82
+ if (prototype === null) {
83
+ break;
84
+ }
73
85
  }
74
86
 
75
- /* Probably never reached. No previous throw, but some unknown problem. */
76
- throw new Error("Unable to determine method vs. property state.");
87
+ // Never found.
88
+ return false;
77
89
  }
78
90
 
79
- /* Returns true if instance or static member is a property
80
- (value or accessor). Returns false if member is a method. */
81
- static isPropertyMember(type, name) /* passed */ {
82
- return !TypeAnalyzer.isMethodMember(type, name);
91
+ static #typeDefinitionIncludesAsProperty(type, name) /* verified */ {
92
+ let named = Object.getOwnPropertyDescriptor(type, name);
93
+
94
+ // If not found, try parent.
95
+ if (named === undefined) {
96
+ return false;
97
+ }
98
+
99
+ // Members with getters are properties.
100
+ if (named.get !== undefined) {
101
+ return true;
102
+ }
103
+
104
+ // Members with function values are methods.
105
+ if (named.value instanceof Function) {
106
+ return false;
107
+ }
108
+
109
+ // Members with any other values are properties.
110
+ if (named.value !== undefined) {
111
+ return true;
112
+ }
113
+ }
114
+
115
+ static #typeTextContainsNameAsProperty(type, name) /* verified */ {
116
+ let text = type.toString();
117
+
118
+ // Instance properties are always defined as 'name =',
119
+ // 'name;', or 'get name()' at the start of a line.
120
+ let valueRegex = new RegExp(`^\\s*${ name }\\s*[=;]`, "m");
121
+ let accessorRegex = new RegExp(`^\\s*get ${ name }\\(\\)`, "m");
122
+
123
+ let anyIndexOf = text.search(valueRegex);
124
+
125
+ if (anyIndexOf >= 0) {
126
+ return true;
127
+ }
128
+
129
+ anyIndexOf = text.search(accessorRegex);
130
+
131
+ if (anyIndexOf >= 0) {
132
+ return true;
133
+ }
134
+
135
+ return false;
83
136
  }
84
137
  }
@@ -1,31 +0,0 @@
1
- /**/
2
-
3
- /* ChosenTestFinder is an ATestFinder that finds tests in the places coded in its constructor. */
4
-
5
- import ATestFinder from "./ATestFinder.js";
6
- import TopicTests from "../trial-tests/TopicTests.outward-rt.js";
7
- import SelfTests from "../trial-tests/SelfTests.outward-rt.js";
8
-
9
- export default class ChosenTestFinder extends ATestFinder {
10
- constructor() {
11
- super();
12
-
13
- let testSource = new TopicTests();
14
- let selfTestSource = new SelfTests();
15
-
16
- this.testSources = [ testSource, selfTestSource ];
17
- }
18
-
19
- async findAllTests() {
20
- let tests = [];
21
-
22
- for (let source of this.testSources) {
23
- let local = source.tests;
24
- tests.push(...local);
25
- }
26
-
27
- // Returning an awaitable value
28
- // to match async signature.
29
- return Promise.resolve(tests);
30
- }
31
- }
package/system/Risei.js DELETED
@@ -1,118 +0,0 @@
1
- /**/
2
-
3
- /* Copyright (c) 2023-2024 Ed Fallin. Published under the terms of the MIT license. */
4
-
5
- /* This index.js is the entry point for running Risei tests from
6
- other code. It imports other modules to perform its work.
7
- It also exports modules that should / can be subclassed. */
8
-
9
- // region Imports
10
-
11
- // region Test-running dependencies
12
-
13
- import TestRunner from "./TestRunner.js";
14
- import LocalCaller from "./LocalCaller.js";
15
- import TerminalReporter from "./TerminalReporter.js";
16
- import TestFinder from "./TestFinder.js";
17
-
18
- // endregion Test-running dependencies
19
-
20
- // region Display dependencies
21
-
22
- import chalk from "chalk";
23
- import Moment from "./Moment.js";
24
-
25
- // endregion Display dependencies
26
-
27
- // endregion Imports
28
-
29
- // region Named styles
30
-
31
- /* Display styles for the start title and other general needs. */
32
- const title = chalk.hex("FFFFFF").bgHex("191970").bold;
33
- const fails = chalk.hex("000000").bgHex("FFD700").bold;
34
-
35
- // endregion Named styles
36
-
37
- // region Styling for color stripes
38
-
39
- let wide = (text) => {
40
- let width = process.stdout.columns;
41
- text = text || "";
42
-
43
- return text.padEnd(width, "\u00A0");
44
- };
45
-
46
- // endregion Styling for color stripes
47
-
48
- // region Key callable and its scripted call
49
-
50
- async function runRiseiMetaTests(testFinderPath) {
51
- // region Converting test-finder from path to class
52
-
53
- testFinderPath = testFinderPath || "./TestFinder.js";
54
- let finderModule = await import(testFinderPath);
55
-
56
- let moduleKeys = Object.keys(finderModule);
57
- let classKey = moduleKeys[0];
58
- let finderClass = finderModule[classKey];
59
-
60
- // endregion Converting test-finder from path to class
61
-
62
- // region Intro / title
63
-
64
- console.clear();
65
- console.log();
66
- console.log(title(wide()));
67
-
68
- let now = new Date();
69
- now = new Moment(now);
70
-
71
- console.log(title(wide(` Risei tests run on ${ now.asReadable() } local time.`)));
72
- console.log(title(wide()));
73
-
74
- console.log();
75
-
76
- // endregion Intro / title
77
-
78
- // region Running tests
79
-
80
- // Test-system objects and their relationships.
81
- const finder = new finderClass.prototype.constructor();
82
- finder.sourceBySearch = () => { return { tests: "**.outward-rt.js" }; };
83
-
84
- const runner = new TestRunner();
85
- const reporter = new TerminalReporter();
86
-
87
- const caller = new LocalCaller(finder, runner, reporter);
88
-
89
- // Actually running the tests using this system.
90
- await caller.runAllTests();
91
-
92
- // endregion Running tests
93
-
94
- // region Trailing display of test-loading issues
95
-
96
- if (finder.thrown.length !== 0) {
97
- let loadFails = finder.thrown;
98
-
99
- for (let fail of loadFails) {
100
- console.log(fails(wide(` ${ fail }`)));
101
- }
102
- }
103
-
104
- // endregion Trailing display of test-loading issues
105
-
106
- // region Trailing formatting
107
-
108
- console.log();
109
- console.log();
110
-
111
- // endregion Trailing formatting
112
- }
113
-
114
- /* Runs tests now. */
115
- await runRiseiMetaTests();
116
-
117
- // endregion Key callable and its scripted call
118
-