risei 1.0.4 → 1.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.
Files changed (78) hide show
  1. package/README.md +236 -339
  2. package/index.js +8 -8
  3. package/package.json +23 -9
  4. package/{public/javascript → system}/ASpoofingFixture.js +1 -1
  5. package/{public/javascript → system}/ChosenTestFinder.js +4 -3
  6. package/{public/javascript → system}/SpoofClassMethodsFixture.js +38 -22
  7. package/system/SpoofObjectMethodsFixture.js +58 -0
  8. package/{public/javascript → system}/SpoofTuple.js +14 -1
  9. package/{public/javascript → system}/TerminalReporter.js +249 -222
  10. package/system/TestFrame.js +187 -0
  11. package/system/TestFrameChooser.js +54 -0
  12. package/system/TestFrames.js +232 -0
  13. package/system/TestResult.js +187 -0
  14. package/system/TestRunner.js +280 -0
  15. package/system/TestStages.js +278 -0
  16. package/{public/javascript → system}/TestTuple.js +132 -13
  17. package/{public/javascript → system}/TotalComparer.js +12 -0
  18. package/system/TotalCopier.js +79 -0
  19. package/system/TotalDisplayer.js +143 -0
  20. package/system/TypeAnalyzer.js +103 -0
  21. package/system/TypeIdentifier.js +70 -0
  22. package/system/Types.js +17 -0
  23. package/tests/other-tests/ASpoofingFixture.tests.js +242 -0
  24. package/tests/other-tests/SpoofClassesFixture.tests.js +130 -0
  25. package/tests/other-tests/SpoofObjectsFixture.tests.js +95 -0
  26. package/tests/other-tests/SpoofTuple.tests.js +93 -0
  27. package/tests/other-tests/TotalComparer.tests.js +920 -0
  28. package/tests/other-tests/package.json +7 -0
  29. package/tests/risei-tests/ASpoofingFixtureTests.rt.js +51 -0
  30. package/tests/risei-tests/MomentTests.rt.js +103 -0
  31. package/tests/risei-tests/SpoofTupleTests.rt.js +274 -0
  32. package/tests/risei-tests/TestFrameChooserTests.rt.js +74 -0
  33. package/tests/risei-tests/TestFrameTests.rt.js +84 -0
  34. package/tests/risei-tests/TestStagesTests.rt.js +99 -0
  35. package/tests/risei-tests/TestTupleTests.rt.js +140 -0
  36. package/tests/risei-tests/TotalComparerTests.rt.js +184 -0
  37. package/tests/risei-tests/TotalCopierTests.rt.js +74 -0
  38. package/tests/risei-tests/TotalDisplayerTests.rt.js +186 -0
  39. package/tests/risei-tests/TypeAnalyzerTests.rt.js +29 -0
  40. package/tests/risei-tests/TypeIdentifierTests.rt.js +44 -0
  41. package/tests/self-tests/SelfTests.outward-rt.js +583 -0
  42. package/tests/target-objects/CompositionModel.js +38 -0
  43. package/tests/target-objects/ConditionalThrowModel.js +11 -0
  44. package/tests/target-objects/CountModel.js +46 -0
  45. package/tests/target-objects/DomModel.js +37 -0
  46. package/tests/target-objects/MixedContents.js +33 -0
  47. package/tests/target-objects/MutationModel.js +27 -0
  48. package/tests/target-objects/ObjectCompositionModel.js +34 -0
  49. package/tests/target-objects/PolySpoofableInner.js +30 -0
  50. package/tests/target-objects/PolySpoofableOuter.js +52 -0
  51. package/tests/target-objects/PropertiesModel.js +47 -0
  52. package/tests/target-objects/Returner.js +9 -0
  53. package/tests/target-objects/SearchModel.js +25 -0
  54. package/tests/target-objects/SortModel.js +91 -0
  55. package/tests/target-objects/SpoofCaller.js +24 -0
  56. package/tests/target-objects/Spoofable.js +36 -0
  57. package/tests/target-objects/SpoofableArgsCaller.js +33 -0
  58. package/tests/target-objects/StateModel.js +34 -0
  59. package/tests/target-objects/StaticModel.js +17 -0
  60. package/tests/target-objects/TestableModel.js +47 -0
  61. package/tests/topic-tests/TopicTests.outward-rt.js +354 -0
  62. package/public/javascript/SpoofObjectMethodsFixture.js +0 -52
  63. package/public/javascript/TestResult.js +0 -338
  64. package/public/javascript/TestRunner.js +0 -476
  65. /package/{public/javascript → system}/AComparer.js +0 -0
  66. /package/{public/javascript → system}/ATestCaller.js +0 -0
  67. /package/{public/javascript → system}/ATestFinder.js +0 -0
  68. /package/{public/javascript → system}/ATestFixture.js +0 -0
  69. /package/{public/javascript → system}/ATestReporter.js +0 -0
  70. /package/{public/javascript → system}/ATestSource.js +0 -0
  71. /package/{public/javascript → system}/ClassTestGroup.js +0 -0
  72. /package/{public/javascript → system}/LocalCaller.js +0 -0
  73. /package/{public/javascript → system}/MethodTestGroup.js +0 -0
  74. /package/{public/javascript → system}/Moment.js +0 -0
  75. /package/{public/javascript → system}/Risei.js +0 -0
  76. /package/{public/javascript → system}/TestFinder.js +0 -0
  77. /package/{public/javascript → system}/TestGroup.js +0 -0
  78. /package/{public/javascript → system}/TestSummary.js +0 -0
package/README.md CHANGED
@@ -1,76 +1,62 @@
1
1
 
2
- # RiseiJs
2
+ # Risei
3
3
 
4
- ## What RiseiJs is
4
+ ## What Risei is
5
5
 
6
- **RiseiJs is a new way to write unit tests that allows you to:**
6
+ **Risei is a new way to write unit tests that allows you to:**
7
7
  * Whip up full test coverage of object-oriented or object-hosted JavaScript in no time.
8
8
  * Refactor or replace existing designs without worrying about the cost in past or future test time.
9
9
  * Create tests with immediate confidence, because you can't introduce mistakes in test code you write.
10
10
 
11
- RiseiJs' `npm`-friendly name is **Risei**.  The package is often referred to as **Rjs** here for brevity.
11
+ > Risei is under active development.  To see the latest changes, check out [What's new in Risei](#whats-new-in-risei).
12
+ >
13
+ > For a list of any known bugs and workarounds, see [Known bugs and workarounds](#known-bugs-and-workarounds).  To see which JavaScript features Risei doesn't address yet, check out [Limitations in Risei](#limitations-in-risei).
12
14
 
13
- ### Basics of the RiseiJs approach
15
+ Risei may be referred to as **Rs** here for brevity.  A dotted `.name` here means a property, just like a `name()` with parens is a method or function.
14
16
 
15
- RiseiJs replaces coded tests with simple declarative syntax that's far easier to draft than any other unit-testing approach.
16
17
 
17
- Here are two example tests.  `SortModel.countSort()` is being tested with the inputs found in `.in` and the expected output found in `.out`:
18
18
 
19
- <div style="padding-left: 1.5rem">
19
+
20
+ ## The Risei approach
21
+
22
+ Risei replaces coded tests with simple declarative syntax that's far easier to draft than any other unit-testing approach.
23
+
24
+ Here are two example tests.&nbsp; `SortModel.countSort()` is being tested using the inputs from `.in` and the expected output from `.out`:
20
25
 
21
26
  ![https://deusware.com/Syntax-example.png](https://deusware.com/Syntax-example.png)
22
27
 
23
- </div>
24
28
 
25
29
  Here is the terminal output of these two example tests.&nbsp; Tests are grouped by class and method.&nbsp; Inputs, expected, and actual values are displayed for all tests, to make intended usage obvious at a glance:
26
30
 
27
- <div style="padding-left: 1.5rem;">
28
-
29
31
  ![https://deusware.com/Output-example.png](https://deusware.com/Output-example.png)
30
32
 
31
- </div>
32
33
 
33
34
  An individual test may appear on one line or multiple lines, depending on how wide the terminal window is.&nbsp; Any failing tests appear in light red.&nbsp;
34
35
 
35
36
  Test runs also feature a title bar, as well as a summary bar at the bottom:
36
37
 
37
- <div style="padding-left: 1.5rem;">
38
-
39
38
  ![https://deusware.com/Summary-example.png](https://deusware.com/Summary-example.png)
40
39
 
41
- </div>
42
40
 
43
41
 
44
- ## How to use RiseiJs
45
42
 
46
- 1. Install RiseiJs the usual way for a development-time `npm` package, using its `npm`-friendly name `risei`:
43
+ ## Using Risei
44
+
45
+ ### Installation
47
46
 
48
- <div style="padding-left: 1.5rem;">
47
+ Install Risei for development time only:
49
48
 
50
49
  ```bash
51
50
  npm install --save-dev risei
52
51
  ```
53
52
 
54
- </div>
53
+ Make sure your `package.json` specifies that ESM is in use:
55
54
 
56
- <details>
57
- <summary>
58
- &nbsp; More information
59
- </summary>
60
- <br>
61
-
62
- - RiseiJs is an `npm` package that uses ESM syntax.&nbsp; It only works well with modern versions of Node that automatically support ESM.
63
- - Although ESM is usable with older Node versions using external workarounds, a modern version is highly recommended, and Rjs is not guaranteed to work with older versions.
64
- - You can install Rjs as a regular dependency if you want, using `npm install risei`, but ordinarily this doesn't make sense, and exposing tests in production is generally considered a bad practice.
65
- - RiseiJs doesn't directly add any security risks to your code, but exposing tests that use it might (as it can with other test frameworks).
66
-
67
- </details>
68
-
69
-
70
-
71
- 2. Add a `risei` node to your project's `package.json` to tell Rjs what kind of files to find tests in:
55
+ ```json
56
+ "type": "module"
57
+ ```
72
58
 
73
- <div style="padding-left: 1.5rem;">
59
+ And add Risei's metadata to `package.json`:
74
60
 
75
61
  ```json
76
62
  "risei": {
@@ -78,368 +64,246 @@ npm install --save-dev risei
78
64
  }
79
65
  ```
80
66
 
81
- </div>
82
-
83
- <details>
84
- <summary>
85
- &nbsp; More options
86
- </summary>
87
- <br>
88
-
89
- - The extension `.rt.js` is standard, but you can actually use any extension you want.
90
- - Whatever extension you use, it should be unique, to avoid scanning many extra files for tests at each test run.
91
-
92
- </details>
93
-
94
-
95
67
 
96
- 3. Create test files with a `.rt.js` extension.
97
68
 
98
- <details>
99
- <summary>
100
- &nbsp; More options
101
- </summary>
102
- <br>
103
-
104
- - If you chose another extension for your unit-test files (covered earlier), use that instead here.
105
-
106
- </details>
69
+ ### Siting tests
107
70
 
108
-
109
-
110
- 4. Import `ATestSource` from `risei/ATestSource` in each test file, subclass it, and set `tests` to an array of test definitions.
111
-
112
- <div style="padding-left: 1.5rem;">
71
+ Add tests in files ending in `.rt.js` like this:
113
72
 
114
73
  ```javascript
115
74
  import { ATestSource } from "risei/ATestSource";
116
- import { TargetClass } from "your/path/and/file.js";
75
+ import { ClassToBeTested } from "somewhere";
117
76
 
118
- export TargetClassTests extends ATestSource {
119
- tests = [ ]; // Not `this.tests`. This array is where test definitions are written.
77
+ export class SomeTests extends ATestSource {
78
+ tests = [ ... ]; // This array is where test definitions are written.
120
79
  }
121
80
  ```
122
81
 
123
- </div>
124
-
125
- <details>
126
- <summary>
127
- &nbsp; More information
128
- </summary>
129
- <br>
130
-
131
- - Tests must be placed in subclasses of `ATestSource`.
132
- - RiseiJs looks for this class relationship internally, so duck-typing does not work.
133
- - You have to `import` the class/es being tested in the file/s containing their tests.
134
- - Don't use &cross;`this.tests` by accident &mdash; it just causes an exception when tests are run.
135
-
136
- </details>
137
-
138
82
 
139
83
 
140
- 5. Write each test as a JavaScript object literal in the array, using RiseiJs' simple, light syntax:
84
+ ### Writing tests
141
85
 
142
- <div style="padding-left: 1.5rem;">
86
+ Write tests with Risei's easy syntax:
143
87
 
144
88
  ```javascript
145
- tests = [ ...
146
- { on: ContainerModelClass, with: [ "a", "b", "c" ], // Target class and constructor args.
147
- of: "doesContain", // Target method.
148
- for: "When the input arg is present, returns true.", // Description of test.
149
- in: [ "c" ], out: true } // Inputs and expected output.
150
- ... ];
89
+ tests = [ ...
90
+ { on: ContainerModelClass, with: [ "a", "b", "c" ], // Target class and constructor args.
91
+ of: "doesContain", // Target method.
92
+ for: "When the input arg is present, returns true.", // Description of test.
93
+ in: [ "c" ], // Inputs.
94
+ out: true }, // Expected output.
95
+ ...];
151
96
  ```
152
97
 
153
- </div>
154
-
155
- <details>
156
- <summary>
157
- &nbsp; Basic test properties and options for each
158
- </summary>
159
- <br>
160
-
161
- <div style="padding-left: 1.5rem;">
162
-
163
- | Name | Contents |
164
- |--------|--------------------------------------------------------------------------|
165
- | `on` | The class under test, as a symbol (already imported into the test file) |
166
- | `with` | An array of any arguments to pass to the constructor of the tested class |
167
- | `of` | The name of the method under test, as a string, no `()` needed |
168
- | `for` | A description of what the test proves, for test output |
169
- | `in` | An array of the input arguments to pass to the tested method |
170
- | `out` | The expected return value, not in an array |
171
-
172
- - Properties can be listed in any order within a test definition.
173
- - There are additional properties for extended functionality (covered later).
174
- - When there are no parameters for a class' constructor, use an empty array `[ ]` for `.with`.
175
- - When there are no inputs to a method, use an empty array `[ ]` for `.in`.
176
- - When the output of a method is an array, freely use an array for `.out`.
177
- - When there is no output to a method, put the expected value in `.out`, and use special syntax to get the actual value to compare it with (covered later).
178
- - The property names were chosen to be easy to remember and type, but longer alternatives are available (covered later).
179
-
180
- </div>
181
98
 
182
- </details>
183
99
 
100
+ #### Write more tests with less syntax:
184
101
 
185
-
186
- 6. Write more tests with less syntax, by defining properties that are automatically reused in upcoming tests, and/or setting only the properties in a test that are different from the previous test/s:
187
-
188
- <div style="padding-left: 1.5rem;">
102
+ You can save a lot of time by putting repeated test properties into an object once, just before the related tests:
189
103
 
190
104
  ```javascript
191
- /* All of the following tests are of ContainerModelClass. */
192
- { on: ContainerModelClass, with: [ "a", "b", "c", "a", "b" ] },
193
-
194
- /* Two tests for ContainerModelClass .doesContain(). */
195
- { of: "doesContain" },
196
- { for: "When the input arg is present, returns true.",
197
- in: [ "c" ], out: true },
198
- { for: "When the input arg is not present, returns false.",
199
- in: [ "d" ], out: false },
105
+ /* All of the following tests are of ContainerModelClass. */
106
+ { on: ContainerModelClass, with: [ "a", "b", "c", "a", "b" ] },
107
+
108
+ /* Two tests for ContainerModelClass .doesContain(). */
109
+ { of: "doesContain" },
110
+ { for: "When the input arg is present, returns true.",
111
+ in: [ "c" ], out: true },
112
+ { for: "When the input arg is not present, returns false.",
113
+ in: [ "d" ], out: false },
200
114
 
201
- /* First test for ContainerModelClass .countOf(). */
202
- { of: "countOf", for: "Returns the number present.", in: [ "b" ], out: 2 }
115
+ /* First test for ContainerModelClass .countOf(). */
116
+ { of: "countOf", for: "Returns the number present.", in: [ "b" ], out: 2 }
203
117
  ```
204
118
 
205
- </div>
119
+ 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 `[ ]`.
206
120
 
207
- <details>
208
- <summary>
209
- &nbsp; More information
210
- </summary>
211
- <br>
212
121
 
213
- - This simplification tactic, called _collapsing forward_, saves a lot of typing and makes reading tests much easier.
214
- - Individual tests remain isolated because class instances, spoofed methods (covered later) and so on are all created anew for each test.
215
- - Test contents are partially or fully reset when it makes the most sense:
216
- - Changing the class under test in `.on` wipes out all prior test properties, so the new class has a fresh start.
217
- - Changing the method under test with a new `.of` wipes out only test properties related to methods, to preserve class values you usually want.
218
- - You can also change individual test properties such as `.with` whenever you want just by stating a new value &mdash; but by design, they are collapsed forward to following tests.
219
122
 
220
- </details>
123
+ #### Spoof away code dependencies:
221
124
 
222
-
223
-
224
- 7. To isolate any tests with dependencies, you define test-only results for the methods depended on using _spoofing_, which is similar to mocks or fakes, but easier to use.&nbsp; Definitions for spoofs are set in the `.plus` property:
225
-
226
- <div style="padding-left: 1.5rem;">
125
+ When your code has dependencies on other code, just _spoof_ the results of that code to get what your test needs from it, using a `.plus` property on your tests that offers many options:
227
126
 
228
127
  ```javascript
229
128
  { on: CombinerClass, with: [ ],
230
129
  of: "combineResults",
231
130
  plus: [
232
- { on: ArraySource, of: "getAllValues", as: [ 7, 8, 9, 10 ] },
233
- { on: ScalarSource, of: "getValue", as: 12 },
234
- { on: ObjectSource, of: "getObject", as: { color: "green" } }
235
- ]
131
+ { on: ClassA, of: "someMethod", as: 10 }, // Spoof this ClassA method to return 10.
132
+ { on: ClassB, of: "someMethod" }, // Spoof this ClassB method not to return (or do) anything.
133
+ { of: "firstMethod", as: [ 7, 8, 9, 10 ] }, // Spoof a method on the tested class (CombinerClass).
134
+ { of: "secondMethod" }, // Spoof a method on CombinerClass not to do anything.
135
+ { on: ClassC, // Spoof this ClassC method to be this nonce code.
136
+ of: "someMethod",
137
+ as: (arg) => { return { color: arg }; } },
138
+ { on: ClassD, as: [ // Spoof two methods on ClassD at the same time.
139
+ { of: "firstMethod", as: 11 },
140
+ { of: "secondMethod", as: 12 }
141
+ ] },
142
+ ],
236
143
  for: "When called, returns an array of values from sources.",
237
- in: [ ], out: [ 7, 8, 9, 10, 12, { color: "green" } ] }
144
+ in: [ "green" ], out: [ 7, 8, 9, 10, 10, 11, 12, { color: "green" } ] }
238
145
  ```
239
146
 
240
- - Spoofing definitions collapse forward across tests unless they're replaced with new definitions, or are erased by setting `.plus` to an empty array `[ ]`.
241
- - Defining new spoofing wipes out all old definitions.
147
+ These are basically all varieties of spoofing.&nbsp; Spoofing collapses forward across tests, but not across the elements within `.plus`.
242
148
 
243
- </div>
244
149
 
245
- <details>
246
- <summary>
247
- &nbsp; Spoofing syntax and options
248
- </summary>
249
- <br>
250
150
 
251
- - Each object in `.plus` consists of the following:
151
+ ### Running tests
252
152
 
253
- <div style="padding-left: 1.5rem;">
153
+ Run your tests by invoking Risei's `index.js` file:
254
154
 
255
- | Name | Necessary or Optional | Contents |
256
- |------|-----------------------|---------------------------------------------------------------------------------------------------------------------------|
257
- | `on` | Optional | Symbol for a type (class); if omitted, the targeted model class is assumed |
258
- | `of` | Necessary | Name of method to spoof, as a string, `()` not needed |
259
- | `as` | Optional | Value to return, or nonce implementation; if omitted, an empty method with no return value is used |
155
+ ```bash
156
+ node ./node_modules/risei/index.js
157
+ ```
260
158
 
261
- </div>
159
+ Or write a `package.json` script that does the same:
262
160
 
263
- - You can leave out `.as` when you don't need a return value.&nbsp; In that case, an empty method is used (that is, `() => { }`).
264
- - When necessary, you can provide a function to use in place of the real code, in `.as`.
161
+ ```json
162
+ "scripts": {
163
+ ...
164
+ "test": "node ./node_modules/risei/index.js",
165
+ ...
166
+ }
167
+ ```
265
168
 
169
+ And then run that script:
266
170
 
267
- - This example uses all three syntax options:
171
+ ```bash
172
+ npm test
173
+ ```
268
174
 
269
- <div style="padding-left: 1.5rem;">
175
+ Risei can be used alongside other test frameworks.&nbsp; You might run them all from the same test script, or perhaps write unique scripts for each.
270
176
 
271
- ```javascript
272
- { on: CombinerClass, with: [ ],
273
- of: "combineResults",
274
- plus: [
275
- { on: ArraySource, of: "getAllValues", as: [ 7, 8, 9, 10 ] },
276
- { on: ScalarSource, of: "getValue" },
277
- { on: ObjectSource, of: "getObject", as: () => { return { color: "blue" }; } }
278
- ]
279
- for: "When called, returns sources' values, skipping any undefineds.",
280
- in: [ ], out: [ 7, 8, 9, 10, { color: "blue" } ] }
281
- ```
282
177
 
283
- </div>
284
178
 
285
- - You can spoof methods nested inside of other objects using `.` syntax in the `.of` property:
179
+ ### Learning more about Risei
286
180
 
287
- <div style="padding-left: 1.5rem;">
181
+ Be sure to read through the rest of this doc to learn more about Risei's many capabilities, and about where Risei is headed.
288
182
 
289
- ```javascript
290
- plus: [
291
- { on: SimpleObject, of: "getText", as: "Text" },
292
- { on: DeepObject, of: "root.branch.leaf.getOptimum", as: 100 }
293
- ]
294
- ```
183
+ - For instance, learn about using `.and` to test static code or throws.
184
+ - Or learn about using `.from` to test property results or other state.
185
+ - Don't miss out:&nbsp; Once you've tried the basics, read the rest...!
295
186
 
296
- </div>
297
187
 
298
- - Types whose members are being spoofed (like `ArraySource` or `SimpleObject` in the examples) have to be imported normally.
299
- - The properties in the spoof definitions within `.plus` don't collapse forward within the array, but must be repeated for each one.
300
- - You can spoof methods of objects provided in a test's `.with` and `.in` as well, although you usually don't need to.
301
- - Properties of spoof objects also have long names (covered later).
302
188
 
303
- <details>
304
- <summary>
305
- &nbsp; Further spoofing options
306
- </summary>
307
- <br>
308
189
 
309
- - You can spoof many methods on a class at the same time with an array of partial spoof objects for the `.as` property:
190
+ ## Risei in depth
310
191
 
311
- <div style="padding-left: 1.5rem;">
192
+ ### Installation
312
193
 
313
- ```javascript
314
- plus: [
315
- { on: SimpleObject, of: "getText", as: "Text" },
316
- { on: ComplexObject,
317
- as: [ { of: "getIntegers", as: [ 6, 7, 8 ] },
318
- { of: "getDoubles", as: [ 6.0, 7.0, 8.0 ] } ]
319
- }
320
- ]
321
- ```
194
+ - You can use any file extension you want in Risei's metadata in `package.json`, and Risei will scan those files for tests.
322
195
 
323
- </div>
196
+ - You can install Risei outside of development time the usual way, but as with any test system, this is definitely not recommended.
324
197
 
325
- - You can use this syntax, or list multiple full `.on`-`.as`-`.of` objects for a class in `.plus`, with the same effect either way.
326
- - Nested partial spoof definitions in `.as: [ ... ]` never contain `.on`, and must contain at least `.of`.
327
- - As in other spoof definitions, `.as` can be omitted in these nested partial spoof objects, with the same effect.
198
+ - Risei uses ESM syntax, and may not work in older environments where that's not available or is patched in.
328
199
 
329
- </details>
330
200
 
331
- </details>
332
201
 
202
+ ### Siting tests
333
203
 
204
+ - Match your Risei test file's extensions to whatever you choose in the metadata in `package.json`.
334
205
 
335
- 8. Run your tests by invoking RiseiJs' `index.js` file:
206
+ - Test classes must inherit from `ATestSource` in `"risei/ATestSource"`: this type name is looked for specifically, so duck-typing doesn't work.
336
207
 
337
- <div style="padding-left: 1.5rem;">
338
208
 
339
- ```bash
340
- node ./node_modules/risei/index.js
341
- ```
342
209
 
343
- </div>
210
+ ### Writing tests
344
211
 
345
- <details>
346
- <summary>
347
- &nbsp; More information
348
- </summary>
349
- <br>
212
+ - The order and placement of test properties doesn't matter, although the order seen in the examples is probably most readable.
213
+
214
+
215
+ #### Basic test properties and options for each:
216
+
217
+ | Name | Contents |
218
+ |--------|-----------------------------------------------------------------------------------------------------------|
219
+ | `on` | The class under test, as a symbol / name (already imported into the test file) |
220
+ | `with` | An array of any arguments to pass to the constructor of the tested class, or an empty array `[ ]` if none |
221
+ | `of` | The name of the method under test, as a string, no `()` needed |
222
+ | `for` | A description of what the test proves, for test output |
223
+ | `in` | An array of the input arguments to pass to the tested method, or an empty array `[ ]` if none |
224
+ | `out` | The expected return value, not in an array |
225
+
226
+ - There are additional properties for extended functionality, covered later.
227
+ - The property names were chosen to be easy to remember and type, but longer alternatives are available, covered later.
350
228
 
351
- - At present, Rjs isn't set up as a script that runs independently, nor as a compiled executable, so you have to run it via its entry-point script.
352
229
 
353
230
  </details>
354
231
 
355
232
 
233
+ ### Test-property reuse _AKA_ Collapsing forward
356
234
 
357
- 9. Or write the `test` script in `package.json` to invoke `index.js`, to make your life easier:
235
+ - 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 `[ ]`.
236
+ - Property values are gathered across partial test objects until they add up to a runnable test, which is then run.
237
+ - Changes to properties are combined with previous ones to produce intended new tests.
238
+ - For a rare and avoidable side-effect of this system, see [Known bugs and workarounds](#known-bugs-and-workarounds).
358
239
 
359
- <div style="padding-left: 1.5rem;">
240
+ - Test contents, reused or not, are automatically reset when it makes the most sense:
241
+ - Changing the tested class in `.on` wipes out all prior test properties, so the new class has a fresh start.
242
+ - Changing the tested method in `.of` wipes out only test properties related to methods, to preserve class values you usually want.
360
243
 
361
- ```json
362
- "scripts": {
363
- "test": "node ./node_modules/risei/index.js"
364
- }
365
- ```
366
- </div>
367
244
 
368
- <details>
369
- <summary>
370
- &nbsp; More options
371
- </summary>
372
- <br>
245
+ - Individual tests remain isolated because class instances, spoofed methods (covered later) and so on are all created anew for each test.
246
+ - However, input (`.in`) and instantiation (`.with`) arguments that are mutated by tested code are not yet isolated.
247
+ - They will be fully isolated in a future release.
248
+ - In the meantime, calling a function to supply them to `.in` or `.with` does isolate them completely.
373
249
 
374
- - You can instead write a unique script for Rjs, though this takes more syntax to call later:
375
250
 
376
- <div style="padding-left: 1.5rem;">
251
+ ### Choosing test-only dependency inputs _AKA_ Spoofing
377
252
 
378
- ```json
379
- "scripts": {
380
- "risei": "node ./node_modules/risei/index.js"
381
- }
382
- ```
253
+ - Spoofing is Risei's way of providing test-only inputs from dependencies the tested code uses, written in simple declarative syntax.
254
+ - It's therefore the equivalent of test doubles, mocks, fakes, and so on.
383
255
 
384
- </div>
385
256
 
386
- - You can define your test scripting to include the RiseiJs tests alongside other tests if you wish to mix framework usages, which may be appropriate for some scenarios:
257
+ - As the earlier examples and this table show, you can spoof in many ways, both on the dependencies, and on the class being tested.
258
+ - All classes whose members are being spoofed have to be imported.
387
259
 
388
- <div style="padding-left: 1.5rem;">
389
260
 
390
- ```json
391
- "risei": "node ./node_modules/risei/index.js"
392
- "test": "mocha **/* && risei"
393
- ```
261
+ #### Spoof-definition properties:
394
262
 
395
- </div>
263
+ | Name | Necessary or Optional | Contents |
264
+ |------|-----------------------|----------------------------------------------------------------------------|
265
+ | `on` | Optional | Symbol for a type (class); if omitted, the targeted model class is assumed |
266
+ | `of` | Necessary | Name of the method to spoof, as a string, trailing `()` not needed |
267
+ | `as` | Optional | Value to return or nonce implementation; if omitted, an empty method with no return value is used<br> &mdash; or &mdash;<br>A list of partial spoof definitions for the same class |
396
268
 
397
- - Of course, if you are used to Linux / Unix scripting, you may also find other techniques to make running tests easier.
398
269
 
399
- </details>
270
+ - You can spoof multiple methods of a class individually, or list them together with partial definitions as seen in the example, with the same effect either way.
400
271
 
401
272
 
273
+ #### Partial spoof-definition properties:
402
274
 
403
- 7. Run that script whenever you want to run tests:
275
+ | Name | Necessary or Optional | Contents |
276
+ |------|-----------------------|---------------------------------------------------------------------------------------------------|
277
+ | `of` | Necessary | Name of the method to spoof, as a string, trailing `()` not needed |
278
+ | `as` | Optional | Value to return or nonce implementation; if omitted, an empty method with no return value is used |
404
279
 
405
- <div style="padding-left: 1.5rem;">
406
280
 
407
- ```bash
408
- npm test
409
- ```
281
+ - Defining new spoofing wipes out all old definitions.
410
282
 
411
- </div>
412
283
 
413
- <details>
414
- <summary>
415
- &nbsp; More options
416
- </summary>
417
- <br>
284
+ - Spoofing is done at the start of each test and undone at the end of each test, keeping all tests isolated.
418
285
 
419
- - Or run a custom script with longer syntax like this:
420
286
 
421
- <div style="padding-left: 1.5rem;">
422
287
 
423
- ```bash
424
- npm run risei
425
- ```
426
288
 
427
- </div>
289
+ ## Advanced Risei usage
428
290
 
429
- </details>
291
+ ### Testing special test conditions with `.and`
430
292
 
293
+ You can use the special test property `.and`, always a string, to indicate special conditions that apply to your test.
294
+ - At present, the values available are `"static"` and `"throw"` / `"throws"` (either one works).
295
+ - You can list `static` and `throw` / `throws` together if needed, separated by a space.
431
296
 
297
+ The `.and` property is an expansion point for supporting more special conditions in the future.&nbsp; Values will always be listable together (as long as any particular grouping makes sense).
432
298
 
433
- ## Advanced RiseiJs usage
434
299
 
435
- ### Testing static methods
436
300
 
437
- To test a static method, you add an `.and` property to a test, with the value `"static"`:
301
+ #### Testing static methods
438
302
 
439
- <div style="padding-left: 1.5rem">
303
+ To test a static method, use an `.and` of `"static"`:
440
304
 
441
305
  ```javascript
442
- { on: StaticModel, with: [] },
306
+ { on: StaticTextProfileModel, with: [] },
443
307
 
444
308
  { of: "getTextProfile",
445
309
  for: "Returns accurate profile of arg text.",
@@ -449,31 +313,34 @@ To test a static method, you add an `.and` property to a test, with the value `"
449
313
  }
450
314
  ```
451
315
 
452
- </div>
453
316
 
454
- <details>
455
- <summary>
456
- &nbsp; More information
457
- </summary>
458
- <br>
459
317
 
460
- - The `.and` property is an expansion point for supporting more unusual situations in the future.&nbsp; At present, it only supports static operations.
318
+ #### Testing throws
319
+
320
+ To test a throw, use an `.and` of `"throw"` or `"throws"`:
321
+
322
+ ```javascript
323
+ { on: InstanceTextProfileModel, with: [] },
324
+
325
+ { of: "getTextProfile",
326
+ for: "Throws with a helpful message when there is no arg text.",
327
+ and: "throws",
328
+ in: [ ],
329
+ out: "Could not build a profile. Did you forget to provide a text?"
330
+ }
331
+ ```
461
332
 
462
- </details>
463
333
 
464
334
 
465
335
  ### Testing object properties and other non-`return` results
466
336
 
467
337
  To test a value that isn't the exercised code's return value, you use `.out` normally, and you add a `.from` property to your test, which can be one of two things:
468
338
 
469
- <div style="padding-left: 1.5rem">
470
-
471
339
  | Contents of `.from` | Usage |
472
340
  |--------------------------------|-------------------------------------------------------------------------|
473
341
  | Property name as string | Retrieves the actual from the named property on the test's target class |
474
342
  | Specialized function | Retrieves the actual from either its `target` or `test` parameter |
475
343
 
476
- </div>
477
344
 
478
345
  - Getting properties, mutated args, and other non-return values is known as _retrieving_.
479
346
  - Retrieving definitions collapse forward across tests unless replaced with new definitions, or erased by setting `.from` to an empty array `[ ]`.
@@ -487,8 +354,6 @@ To test a value that isn't the exercised code's return value, you use `.out` nor
487
354
 
488
355
  - Use of `.from` with a property name looks like this:
489
356
 
490
- <div style="padding-left: 1.5rem">
491
-
492
357
  ```javascript
493
358
  { on: StatefulModel, with: [] },
494
359
 
@@ -497,20 +362,15 @@ To test a value that isn't the exercised code's return value, you use `.out` nor
497
362
  in: [ 10, 8, 9, 6 ], out: 4320, from: "result" },
498
363
  ```
499
364
 
500
- </div>
501
-
502
365
  - Only instance properties can be retrieved by name.&nbsp; For static properties, use `.and` (covered earlier), plus a function as `.from` (covered next).
503
366
 
504
367
  When the contents of `.from` are a function, these are the two parameters:
505
368
 
506
- <div style="padding-left: 1.5rem">
507
-
508
369
  | Name | Contents |
509
370
  |----------|---------------------------------------------------------------------------------------------------|
510
371
  | `target` | The instance of the class being tested |
511
- | `test` | The test definition itself, whose properties may have been mutated by the tested method |
372
+ | `test` | The test definition itself, whose properties may have been mutated by the tested method |
512
373
 
513
- </div>
514
374
 
515
375
  - The `test` parameter to a `.from` function contains all of the properties of your test definition, including those that collapsed forward.
516
376
  - These properties are available by both short or long names (covered later).
@@ -522,8 +382,6 @@ When the contents of `.from` are a function, these are the two parameters:
522
382
 
523
383
  - Another usage of a `.from` function is to look at an input to see if it was changed as expected:
524
384
 
525
- <div style="padding-left: 1.5rem">
526
-
527
385
  ```javascript
528
386
  { on: SecurityModel, with: [] },
529
387
 
@@ -535,11 +393,10 @@ When the contents of `.from` are a function, these are the two parameters:
535
393
  },
536
394
  ```
537
395
 
538
- </div>
539
-
540
396
  </details>
541
397
 
542
398
 
399
+
543
400
  ### Property long names
544
401
 
545
402
  Property names are short so they're easy to use.&nbsp; Some of them overlap with JavaScript keywords, but this causes no harm.
@@ -552,8 +409,6 @@ All test properties have long names that you can use instead of the short ones i
552
409
  &nbsp; Test properties' short and long names
553
410
  </summary>
554
411
 
555
- <div style="padding-left: 1.5rem;">
556
-
557
412
  | Short Name | Long Name |
558
413
  |------------|-----------|
559
414
  | `on` | `type` |
@@ -566,8 +421,6 @@ All test properties have long names that you can use instead of the short ones i
566
421
  | `from` | `source` |
567
422
  | `and` | `factors` |
568
423
 
569
- </div>
570
-
571
424
  </details>
572
425
 
573
426
  <br>
@@ -577,20 +430,17 @@ All test properties have long names that you can use instead of the short ones i
577
430
  &nbsp; Spoof properties' short and long names
578
431
  </summary>
579
432
 
580
- <div style="padding-left: 1.5rem;">
581
-
582
433
  | Short Name | Long Name |
583
434
  |------------|-----------|
584
435
  | `on` | `target` |
585
436
  | `of` | `method` |
586
437
  | `as` | `output` |
587
438
 
588
- </div>
589
-
590
439
  </details>
591
440
 
592
441
 
593
- ### Further capabilities of RiseiJs
442
+
443
+ ### Further capabilities of Risei
594
444
 
595
445
  Constructors can be tested with no special test properties or keywords.
596
446
  - The method name in `.of` is simply `"constructor"`.
@@ -598,45 +448,92 @@ Constructors can be tested with no special test properties or keywords.
598
448
  - However, a `.with` must still be provided.&nbsp; It can simply be an empty array `[ ]`.
599
449
 
600
450
 
601
- ### Limitations in RiseiJs
451
+
452
+ ### Limitations in Risei
602
453
 
603
454
  The following are not supported at present:
604
455
  - Use of `async` syntax
456
+ - Code written in ESM `export` modules, but not within classes
605
457
  - Standalone functions and other functionality not built into classes, AKA _loose code_
606
458
  - CJS syntax using `require()`
607
459
  - Spoofing mixes of properties and methods, such as `something.method.property.method`
608
- - Debugging model code during tests.
460
+ - Debugging model code during tests
461
+ - Comparing rare JS object types like `Proxy` or `ArrayBuffer` in test assertions
462
+
463
+ Some of these are on the tentative future-development list.&nbsp; In the meantime, Risei can be used to save development time for most of your JavaScript code, while these can be tested using another framework invoked alongside Risei.
464
+
465
+
466
+
609
467
 
610
468
  ## Troubleshooting
611
469
 
612
- Most problems with using RiseiJs are minor mistakes in syntax, or omissions in test definitions:
470
+ Most problems with using Risei are minor mistakes in syntax, or omissions in test definitions:
471
+
472
+ | Error condition or text | Probable cause / fix |
473
+ |-----------------------------------|----------------------------------------------------------------|
474
+ | Sudden drop in the number of tests run | An error occurred in a test file; error output at the top of the test run explains why |
475
+ | `"Test loading failed for... SyntaxError: Unexpected token ':'"` | Missing comma in your tests in the named file |
476
+ | `"Test loading failed for... SyntaxError: Unexpected token '.'"` | Using `this.tests = []` instead of `tests = []` in the file named |
477
+ | Other `... Unexpected token ...` errors | Some other syntax error in the file named, most likely a missing or extra delimiter |
478
+ | Unexpected actual values, or unexpected passes / fails in test runs | Spoofs, retrievals, or special conditions from previous tests not replaced or reset with `[ ]`<br>&mdash; or &mdash;<br>Tested method mutates `.in` or `.with` args: preventable by using a function to supply those args<br>&mdash; or &mdash;<br>Pre-listed test properties produce extra tests: preventable by restating class in `.on`
479
+ |
480
+
481
+
482
+
483
+
484
+ ## Known bugs and workarounds
485
+
486
+ One known bug exists:
487
+ - When the args stated in `.with` or `.in` are mutated by the method under test (or by anything done within a test), the mutated values are carried forward to other tests.
488
+ - A workaround exists for now: just provide those args using a fixture function when they may be mutated.&nbsp; The function is called each time, isolating them completely.
489
+
490
+
491
+ - This problem with test isolation is being fixed in an upcoming release.
492
+
493
+
494
+ One known unexpected result (not quite a bug) exists:
495
+ - Because test properties collapse forward to form whole tests, pre-stating new properties for upcoming tests without changing `.on` is treated as a new test like prior ones, with just those properties changed.
496
+ - Pre-stating properties might be done as a way to make them stand out when reading through the tests.
497
+
498
+ - This means you see an extra unexpected test that most likely fails, due to the mix of old and new properties.
499
+
500
+ - A workaround exists: if you pre-state new properties, also restate the class under test in `.on`, which erases all prior test properties.
501
+ - Restating `.on` doesn't cause the class name to be displayed again in output.
502
+
503
+
504
+ - This output is rarely an issue, and may be what is wanted in some cases, but options for preventing it are being considered.
505
+
506
+
507
+
508
+ ## What's new in Risei
509
+
510
+ Release **1.1.1** of Risei (January, 2024) fixes two problems:
511
+ - Classes were sometimes displayed in output as their entire definition, instead of just the class name.
512
+ - All `Date` instances were considered equal, regardless of their actual value.
513
+
514
+ Development of Risei is ongoing, including internal improvements, problem fixes, and additional options for testing.
515
+
516
+
613
517
 
614
- | Error text | Probable cause / fix |
615
- |---------------------------------------------------------------------|----------------------------------------------------------------------------------------------------|
616
- | `"Test loading failed for... SyntaxError: Unexpected token ':'"` | Missing comma in your tests in the named file |
617
- | `"Test loading failed for... SyntaxError: Unexpected token '.'"` | Using `this.tests = []` instead of `tests = []` in the file named |
618
- | Other `... Unexpected token ...` errors | Some other syntax error in the file named, most likely a missing or extra delimiter |
619
- | Unexpected actual values, or unexpected passes / fails in test runs | Spoofs, retrievals, or special conditions from previous tests not replaced or reset with `[ ]`
620
518
 
621
- When something is wrong in one test file, other test files still usually load and run, so a good sign that there's a problem is a sudden decrease in the number of run tests reported.
519
+ ## Who makes Risei
622
520
 
521
+ Risei is written by myself, Ed Fallin.&nbsp; I'm a longtime software developer who likes to find better ways to do things.
623
522
 
624
- ## Who makes RiseiJs
523
+ If you find Risei useful, consider spreading the word to other devs, making a donation, suggesting enhancements, or proposing sponsorships.
625
524
 
626
- RiseiJs is written by myself, Ed Fallin.&nbsp; I'm a longtime software developer who likes to find better ways to do things.
525
+ You can get in touch about Risei at **riseimaker@gmail.com**.
627
526
 
628
- If you find RiseiJs useful, consider spreading the word to other devs, making a donation, suggesting enhancements, or proposing sponsorships.
629
527
 
630
- You can get in touch about RiseiJs at **riseijsmaker@gmail.com**.
631
528
 
632
529
 
633
- ## What the RiseiJs license is
530
+ ## Risei license
634
531
 
635
- RiseiJs is published for use under the terms of the MIT license:
532
+ Risei is published for use under the terms of the MIT license:
636
533
 
637
534
  <div style="border: solid darkgray 1px; padding: 0.5rem;">
638
535
 
639
- <b>RiseiJs Copyright &copy; 2023 Ed Fallin</b>
536
+ <b>Risei Copyright &copy; 2023 Ed Fallin</b>
640
537
 
641
538
  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:
642
539