risei 3.0.0 → 3.1.1

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, **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.
49
+ Risei's major features are now complete, but new enhancements or fixes may appear from time to time.  The latest release, **3.1.1**, includes significant improvements and bug fixes, on top of the major improvements in releases **3.0.0** and **2.0.0**.
50
50
 
51
51
  Check out the [full list of changes](#version-history).
52
52
 
@@ -91,6 +91,9 @@ And add Risei's metadata to `package.json`:
91
91
  }
92
92
  ```
93
93
 
94
+ ## Testing Risei
95
+
96
+ To test Risei, you clone it from a parallel [repository](https://gitlab.com/riseimaker/risei-public.git), install its dependencies, and run its self-tests.  See the full explanation [here](https://deusware.com/risei/index.html#self-testing).
94
97
 
95
98
 
96
99
  ## Running Tests
@@ -205,21 +208,30 @@ To test TypeScript code with Risei, you make sure the code is transpiled before
205
208
 
206
209
  ## Troubleshooting
207
210
 
208
- If problems cause test files not to load, a gold bar appears and tests in those files disappear from output:
211
+ If errors are thrown while testing, gold bars listing them appear at the bottom, and full stack traces appear amid the test output.  If problems cause test files not to load, a gold bar with the error message also appears:
209
212
 
210
213
  ![https://deusware.com/risei/images/Gold-bar-example.png](https://deusware.com/risei/images/Gold-bar-example.png)
211
214
 
215
+ - If files don't load, tests in those files disappear from the output and the totals.
212
216
  - Those and other problems can be solved with the help of this [troubleshooting guide](https://deusware.com/risei/index.html#troubleshooting).
213
217
 
214
218
 
215
219
 
216
220
  ## Version history
217
221
 
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_).
222
+ - Release **3.1.1** (January, 2025) contains these changes:
223
+ - Extra commas and non-objects in arrays of test objects are disregarded, and no longer cause a throw.
224
+ - Throws in wider scopes are now listed at the end, like others already found there.
225
+
226
+ - Release **3.1.0** (January, 2025) contains these changes:
227
+ - Accessor properties (formally _accessor descriptors_) are now compared for test results and displayed in test outputs.
228
+ - Any accessor properties that throw errors during display are displayed as `(threw)`.
229
+ - Throws in tested code and in test framing code are now listed at the end, and also displayed amid the tests.
230
+ - `File` objects now have a succinct custom display in outputs.
231
+ - **(Breaking change)**  In `constructor` tests, constructed instances are available in `.from` functions only as `actual` / `test.actual`.
232
+ - **(Breaking change)**  In `constructor` tests, `test.target` is now the `prototype` of the tested class.
233
+ - Widespread internal reengineering of other kinds.
234
+
223
235
 
224
236
 
225
237
  <details>
@@ -227,7 +239,13 @@ If problems cause test files not to load, a gold bar appears and tests in those
227
239
  Older releases
228
240
  </summary>
229
241
 
230
- - Release **2.0.1** (August, 2024) contained all of these changes:
242
+ - Release **3.0.0** (August, 2024) contained all of these changes:
243
+ - Asynchronous code with `async` syntax is now supported, with no special test syntax.
244
+ - **(Breaking change)**&nbsp; `.from` functions now take `test` and `actual` as parameters, rather than `target` and `test`.
245
+ - 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`.
246
+ - You can now call target code more than once in one test using an `.and` of `"poly"` (AKA _poly-calling_).
247
+
248
+ - Release **2.0.0** / **2.0.1** (August, 2024) contained all of these changes:
231
249
  - Risei now can determine automatically if tested methods are static.
232
250
  - You can now directly test properties just like methods.
233
251
  - Risei can also determine automatically if these are static.
@@ -242,17 +260,7 @@ Older releases
242
260
  - **(Breaking change)**&nbsp; `ATestSource` is now a default export, changing its imports from `import { ATestSource } from` to `import ATestSource from`.
243
261
  - Major internal re-engineering.
244
262
 
245
- - Release **2.0.0** (August, 2024) was identical to 2.0.1 except for some unneeded extra files.
246
-
247
- - Release **1.3.4** (March, 2024) fixed a bug that caused class display problems in some cases.
248
- - Release **1.3.3** (March, 2024) fixed a major bug that prevented tests from running in Windows.
249
- - Release **1.3.2** (February, 2024) only improved this read-me's contents.
250
- - Release **1.3.1** (February, 2024) reversed some changes in 1.3.0 that did not work as hoped.
251
- - Release **1.3.0** (February, 2024) added the loading-error gold bar and fixed a test-sorting bug.
252
- - Release **1.2.0** (January, 2024) changed test sorting to move last-edited tests to the end.
253
- - Release **1.1.2** of Risei (January, 2024) fixed class displays and inaccurate `Date` comparisons.
254
-
255
- > The oldest releases are no longer listed here, and old releases are dropped progressively over time.&nbsp; Using the latest release is recommended.
263
+ > Older releases are dropped from this list progressively over time.&nbsp; Using the latest release is recommended.
256
264
 
257
265
  </details>
258
266
 
@@ -260,9 +268,14 @@ Older releases
260
268
 
261
269
  ## Known issues and workarounds
262
270
 
263
- The only issue at present is reuse of method args mutated by tested code when collapsing forward.
271
+ There are two minor issues at present:
264
272
 
265
- - The workaround is just to restate those args for each test.
273
+ - If args for a test are mutated by tested code, the mutated args are used when collapsing forward.
274
+ - The workaround is just to restate those args for each test.
275
+
276
+
277
+ - Spoofing accessor properties only works when they have both a getter and a setter.
278
+ - The workaround is to find another way to produce the property values you need.
266
279
 
267
280
 
268
281
 
@@ -271,7 +284,9 @@ The only issue at present is reuse of method args mutated by tested code when co
271
284
  At present, there are a few things Risei doesn't support.&nbsp; Some of these may be supported in the future.
272
285
 
273
286
  - You can see the whole list [here](https://deusware.com/risei/index.html#exclusions-from-risei).
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.
287
+
288
+
289
+ Risei can be run alongside other test frameworks, so if you can't test all of your code with Risei, you can still save a lot of time by using Risei to test the bulk of it.
275
290
 
276
291
 
277
292
 
@@ -291,7 +306,7 @@ Risei is published for use under the terms of the MIT license:
291
306
 
292
307
  <div style="border: solid darkgray 1px; padding: 0.5rem;">
293
308
 
294
- <b>Risei Copyright &copy; 2023&ndash;2024 Ed Fallin</b>
309
+ <b>Risei Copyright &copy; 2023&ndash;2025 Ed Fallin</b>
295
310
 
296
311
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
297
312
 
package/index.js CHANGED
@@ -8,60 +8,21 @@
8
8
 
9
9
  // region Imports
10
10
 
11
- // region Test-running dependencies
12
-
11
+ import TerminalReporter from "./system/TerminalReporter.js";
13
12
  import TestRunner from "./system/TestRunner.js";
14
13
  import LocalCaller from "./system/LocalCaller.js";
15
- import TerminalReporter from "./system/TerminalReporter.js";
16
14
  import TestFinder from "./system/TestFinder.js";
17
15
 
18
- // endregion Test-running dependencies
19
-
20
- // region Display dependencies
21
-
22
- import chalk from "chalk";
23
- import Moment from "./system/Moment.js";
24
-
25
- // endregion Display dependencies
26
-
27
16
  // endregion Imports
28
17
 
29
18
  // region Exports
30
19
 
31
- // region Classes that users typically subclass
20
+ /* Class that users subclass for test classes. */
32
21
 
33
22
  export * from "./system/ATestSource.js";
34
23
 
35
- // endregion Classes that users typically subclass
36
-
37
- // region Classes that users might subclass for uncommon cases
38
-
39
- export * from "./system/ATestFinder.js";
40
- export * from "./system/ATestReporter.js";
41
-
42
- // endregion Classes that users might subclass for uncommon cases
43
-
44
24
  // endregion Exports
45
25
 
46
- // region Named styles
47
-
48
- /* Display styles for the start title and other general needs. */
49
- const title = chalk.hex("FFFFFF").bgHex("191970").bold;
50
- const fails = chalk.hex("000000").bgHex("FFD700").bold;
51
-
52
- // endregion Named styles
53
-
54
- // region Styling for color stripes
55
-
56
- let wide = (text) => {
57
- let width = process.stdout.columns;
58
- text = text || "";
59
-
60
- return text.padEnd(width, "\u00A0");
61
- };
62
-
63
- // endregion Styling for color stripes
64
-
65
26
  // region Key callable, its scripted call, and its export
66
27
 
67
28
  async function runRiseiTests(testFinderPath) {
@@ -76,30 +37,21 @@ async function runRiseiTests(testFinderPath) {
76
37
 
77
38
  // endregion Converting test-finder from path to class
78
39
 
40
+ let reporter = new TerminalReporter();
41
+
79
42
  // region Intro / title
80
43
 
81
- console.clear();
82
- console.log();
83
- console.log(title(wide()));
84
-
85
- let now = new Date();
86
- now = new Moment(now);
87
-
88
- console.log(title(wide(` Risei tests run on ${ now.asReadable() } local time.`)));
89
- console.log(title(wide()));
90
-
91
- console.log();
92
-
93
- // endregion Intro / title
44
+ reporter.reportTitle();
45
+
46
+ // // endregion Intro / title
94
47
 
95
48
  // region Running tests
96
49
 
97
50
  // Test-system objects and their relationships.
98
- const finder = new finderClass.prototype.constructor();
99
- const runner = new TestRunner();
100
- const reporter = new TerminalReporter();
101
-
102
- const caller = new LocalCaller(finder, runner, reporter);
51
+ let finder = new finderClass.prototype.constructor();
52
+ let runner = new TestRunner();
53
+
54
+ let caller = new LocalCaller(finder, runner, reporter);
103
55
 
104
56
  // Actually running the tests using this system.
105
57
  await caller.runAllTests();
@@ -109,20 +61,20 @@ async function runRiseiTests(testFinderPath) {
109
61
  // region Trailing display of test-loading issues
110
62
 
111
63
  if (finder.thrown.length !== 0) {
64
+ reporter.blankLine();
65
+
112
66
  let loadFails = finder.thrown;
113
67
 
114
68
  for (let fail of loadFails) {
115
- console.log(fails(wide(` ${ fail }`)));
69
+ reporter.reportLoadFail(fail);
116
70
  }
117
71
  }
118
72
 
119
- // endregion Trailing display of test-loading issues
120
-
121
73
  // region Trailing formatting
122
-
123
- console.log();
124
- console.log();
125
-
74
+
75
+ reporter.blankLine();
76
+ reporter.blankLine();
77
+
126
78
  // endregion Trailing formatting
127
79
  }
128
80
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "risei",
3
- "version": "3.0.0",
3
+ "version": "3.1.1",
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",
@@ -24,11 +24,11 @@
24
24
  "private": false,
25
25
  "scripts": {
26
26
  "start": "node ./bin/www",
27
- "rself": "clear && node system/Risei.js",
27
+ "rself": "clear && node ./TrialTests.js",
28
28
  "rtest": "clear && node ./node_modules/risei/index.js",
29
29
  "xtest": "clear && mocha **/*.tests.js",
30
30
  "mixedtest": "clear && mocha **/*.tests.js; node ./node_modules/risei/index.js",
31
- "alltest": "clear && mocha **/*.tests.js; node ./node_modules/risei/index.js; node system/Risei.js",
31
+ "alltest": "clear && mocha **/*.tests.js; node ./node_modules/risei/index.js; node ./TrialTests.js",
32
32
  "test": "clear && node ./node_modules/risei/index.js"
33
33
  },
34
34
  "risei": {
@@ -41,7 +41,9 @@
41
41
  "dependencies": {
42
42
  "chalk": "^5.0.0",
43
43
  "fs": "^0.0.1-security",
44
- "minimatch": "^9.0.1"
44
+ "minimatch": "^9.0.1",
45
+ "npm": "^11.0.0",
46
+ "risei": "^3.1.0"
45
47
  },
46
48
  "devDependencies": {
47
49
  "chai": "^4.3.6",
@@ -49,7 +51,6 @@
49
51
  "debug": "~2.6.9",
50
52
  "fs": "^0.0.1-security",
51
53
  "mocha": "^10.0.0",
52
- "morgan": "~1.9.1",
53
- "risei": "^3.0.0"
54
+ "morgan": "~1.9.1"
54
55
  }
55
56
  }
@@ -0,0 +1,10 @@
1
+ /**/
2
+
3
+ export default class CallTypes {
4
+ static methodMono = "methodMono";
5
+ static propMono = "propMono";
6
+ static conMono = "conMono";
7
+ static methodPoly = "methodPoly";
8
+ static propPoly = "propPoly";
9
+ static conPoly = "conPoly";
10
+ }
@@ -0,0 +1,92 @@
1
+ /**/
2
+
3
+ import path from "node:path";
4
+ import fs from "node:fs";
5
+
6
+ export default class Choices {
7
+ // region Definitions
8
+
9
+ static METADATA_FILE = "package.json";
10
+ static TESTS_PROP = "tests";
11
+
12
+ // endregion Definitions
13
+
14
+ // region Static fields
15
+
16
+ // Local copy of package.json's .risei node.
17
+ static #node;
18
+
19
+ // endregion Static fields
20
+
21
+ // region Static choice properties
22
+
23
+ static get testTargeting() /* verified */ {
24
+ let tests = Choices.#supplyOrThrow(Choices.TESTS_PROP);
25
+ return tests;
26
+ }
27
+
28
+ // endregion Static choice properties
29
+
30
+ // region Dependencies of static properties
31
+
32
+ /* Used for properties that are always needed. */
33
+ static #supplyOrThrow(prop) /* verified */ {
34
+ Choices.#maybeSetNode();
35
+ Choices.#throwWhenMissing(prop);
36
+ return Choices.#node[prop];
37
+ }
38
+
39
+ /* Used for properties that aren't always needed. */
40
+ static #supplyOrUndef(prop) /* verified */ {
41
+ Choices.#maybeSetNode();
42
+
43
+ if (!Choices.#nodeContains(prop)) {
44
+ return undefined;
45
+ }
46
+
47
+ return Choices.#node[prop];
48
+ }
49
+
50
+ static #maybeSetNode() /* verified */ {
51
+ if (Choices.#node === undefined) {
52
+ Choices.#setNode();
53
+ }
54
+ }
55
+
56
+ // To init hidden Singleton instance.
57
+ static #setNode() /* verified */ {
58
+ // App's package.json always found at process.cwd().
59
+ let packageSite = process.cwd();
60
+ let pathAndName = path.join(packageSite, Choices.METADATA_FILE);
61
+
62
+ // Contents of package.json are just a single JSON object.
63
+ let packageJson = fs.readFileSync(pathAndName, { encoding: "utf8" });
64
+ packageJson = JSON.parse(packageJson);
65
+
66
+ // Can't run or reasonably recover if this is not defined.
67
+ if (packageJson.risei === undefined) {
68
+ throw new Error(
69
+ `For Risei tests to run, this app's package.json must contain `
70
+ + `a node like this: "risei": { tests: "path-or-glob-here" }. `
71
+ + `One glob or path may be used, or an array of them.`);
72
+ }
73
+
74
+ // Only Risei's own metadata is needed.
75
+ Choices.#node = packageJson.risei;
76
+ }
77
+
78
+ static #throwWhenMissing(prop) /* verified */ {
79
+ if (!Choices.#nodeContains(prop)) {
80
+ throw new Error(
81
+ `The property .${ prop } is not defined `
82
+ + `for .risei in package.json.`
83
+ );
84
+ }
85
+ }
86
+
87
+ static #nodeContains(prop) /* verified */ {
88
+ return prop in Choices.#node;
89
+ }
90
+
91
+ // endregion Dependencies of static properties
92
+ }
@@ -1,14 +1,54 @@
1
1
  /**/
2
2
 
3
- /* LocalCaller is an ATestCaller that invokes each available test in turn locally
4
- on the server, without HTTP calls from a browser or other external source. */
3
+ /* LocalCaller has TestRunner run each test in turn on the server,
4
+ without HTTP calls from a browser or other external source. */
5
5
 
6
- import ATestFinder from "./ATestFinder.js";
7
- import ATestCaller from "./ATestCaller.js";
8
6
  import TerminalReporter from "./TerminalReporter.js";
9
7
  import TestRunner from "./TestRunner.js";
10
8
 
11
- export default class LocalCaller extends ATestCaller {
9
+ export default class LocalCaller {
10
+ // region Fields
11
+
12
+ #finder;
13
+ #runner;
14
+ #reporter;
15
+
16
+ // endregion Fields
17
+
18
+ // region Properties
19
+
20
+ get finder() {
21
+ return this.#finder;
22
+ }
23
+
24
+ set finder(value) {
25
+ this.#finder = value;
26
+ }
27
+
28
+ get runner() {
29
+ return this.#runner;
30
+ }
31
+
32
+ set runner(value) {
33
+ this.#runner = value;
34
+ }
35
+
36
+ get reporter() {
37
+ return this.#reporter;
38
+ }
39
+
40
+ set reporter(value) {
41
+ this.#reporter = value;
42
+ }
43
+
44
+ // endregion Properties
45
+
46
+ constructor(finder, runner, reporter) {
47
+ this.#finder = finder;
48
+ this.#runner = runner;
49
+ this.#reporter = reporter;
50
+ }
51
+
12
52
  async runAllTests() {
13
53
  let tests = await this.finder.findAllTests();
14
54
  this.runner.useTests(tests);
@@ -0,0 +1,36 @@
1
+ /**/
2
+
3
+ import TypeAnalyzer from "./TypeAnalyzer.js";
4
+
5
+ export default class ObjectAnalyzer {
6
+ /* Retrieves names of all properties, both values
7
+ and accessors, including all in superclasses. */
8
+ static allProperties(topic) /* passed */ {
9
+ let proto = Object.getPrototypeOf(topic);
10
+
11
+ let getters = [];
12
+
13
+ // Accessors, including in superclasses.
14
+ // Nonce object has no prototype / type.
15
+ if (proto !== null) {
16
+ let type = proto.constructor;
17
+ getters = TypeAnalyzer.allGetters(type);
18
+ }
19
+
20
+ let names = [];
21
+
22
+ // All value names, including inherited ones.
23
+ for (let name in topic) {
24
+ names.push(name);
25
+ }
26
+
27
+ // Concat good enough since sorting needed after anyway.
28
+ let output = names.concat(getters);
29
+
30
+ // Instead of mixed two sets in order coded,
31
+ // accessors backwards up prototype chain.
32
+ output.sort();
33
+
34
+ return output;
35
+ }
36
+ }