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.
- package/README.md +236 -339
- package/index.js +8 -8
- package/package.json +23 -9
- package/{public/javascript → system}/ASpoofingFixture.js +1 -1
- package/{public/javascript → system}/ChosenTestFinder.js +4 -3
- package/{public/javascript → system}/SpoofClassMethodsFixture.js +38 -22
- package/system/SpoofObjectMethodsFixture.js +58 -0
- package/{public/javascript → system}/SpoofTuple.js +14 -1
- package/{public/javascript → system}/TerminalReporter.js +249 -222
- package/system/TestFrame.js +187 -0
- package/system/TestFrameChooser.js +54 -0
- package/system/TestFrames.js +232 -0
- package/system/TestResult.js +187 -0
- package/system/TestRunner.js +280 -0
- package/system/TestStages.js +278 -0
- package/{public/javascript → system}/TestTuple.js +132 -13
- package/{public/javascript → system}/TotalComparer.js +12 -0
- package/system/TotalCopier.js +79 -0
- package/system/TotalDisplayer.js +143 -0
- package/system/TypeAnalyzer.js +103 -0
- package/system/TypeIdentifier.js +70 -0
- package/system/Types.js +17 -0
- package/tests/other-tests/ASpoofingFixture.tests.js +242 -0
- package/tests/other-tests/SpoofClassesFixture.tests.js +130 -0
- package/tests/other-tests/SpoofObjectsFixture.tests.js +95 -0
- package/tests/other-tests/SpoofTuple.tests.js +93 -0
- package/tests/other-tests/TotalComparer.tests.js +920 -0
- package/tests/other-tests/package.json +7 -0
- package/tests/risei-tests/ASpoofingFixtureTests.rt.js +51 -0
- package/tests/risei-tests/MomentTests.rt.js +103 -0
- package/tests/risei-tests/SpoofTupleTests.rt.js +274 -0
- package/tests/risei-tests/TestFrameChooserTests.rt.js +74 -0
- package/tests/risei-tests/TestFrameTests.rt.js +84 -0
- package/tests/risei-tests/TestStagesTests.rt.js +99 -0
- package/tests/risei-tests/TestTupleTests.rt.js +140 -0
- package/tests/risei-tests/TotalComparerTests.rt.js +184 -0
- package/tests/risei-tests/TotalCopierTests.rt.js +74 -0
- package/tests/risei-tests/TotalDisplayerTests.rt.js +186 -0
- package/tests/risei-tests/TypeAnalyzerTests.rt.js +29 -0
- package/tests/risei-tests/TypeIdentifierTests.rt.js +44 -0
- package/tests/self-tests/SelfTests.outward-rt.js +583 -0
- package/tests/target-objects/CompositionModel.js +38 -0
- package/tests/target-objects/ConditionalThrowModel.js +11 -0
- package/tests/target-objects/CountModel.js +46 -0
- package/tests/target-objects/DomModel.js +37 -0
- package/tests/target-objects/MixedContents.js +33 -0
- package/tests/target-objects/MutationModel.js +27 -0
- package/tests/target-objects/ObjectCompositionModel.js +34 -0
- package/tests/target-objects/PolySpoofableInner.js +30 -0
- package/tests/target-objects/PolySpoofableOuter.js +52 -0
- package/tests/target-objects/PropertiesModel.js +47 -0
- package/tests/target-objects/Returner.js +9 -0
- package/tests/target-objects/SearchModel.js +25 -0
- package/tests/target-objects/SortModel.js +91 -0
- package/tests/target-objects/SpoofCaller.js +24 -0
- package/tests/target-objects/Spoofable.js +36 -0
- package/tests/target-objects/SpoofableArgsCaller.js +33 -0
- package/tests/target-objects/StateModel.js +34 -0
- package/tests/target-objects/StaticModel.js +17 -0
- package/tests/target-objects/TestableModel.js +47 -0
- package/tests/topic-tests/TopicTests.outward-rt.js +354 -0
- package/public/javascript/SpoofObjectMethodsFixture.js +0 -52
- package/public/javascript/TestResult.js +0 -338
- package/public/javascript/TestRunner.js +0 -476
- /package/{public/javascript → system}/AComparer.js +0 -0
- /package/{public/javascript → system}/ATestCaller.js +0 -0
- /package/{public/javascript → system}/ATestFinder.js +0 -0
- /package/{public/javascript → system}/ATestFixture.js +0 -0
- /package/{public/javascript → system}/ATestReporter.js +0 -0
- /package/{public/javascript → system}/ATestSource.js +0 -0
- /package/{public/javascript → system}/ClassTestGroup.js +0 -0
- /package/{public/javascript → system}/LocalCaller.js +0 -0
- /package/{public/javascript → system}/MethodTestGroup.js +0 -0
- /package/{public/javascript → system}/Moment.js +0 -0
- /package/{public/javascript → system}/Risei.js +0 -0
- /package/{public/javascript → system}/TestFinder.js +0 -0
- /package/{public/javascript → system}/TestGroup.js +0 -0
- /package/{public/javascript → system}/TestSummary.js +0 -0
package/README.md
CHANGED
|
@@ -1,76 +1,62 @@
|
|
|
1
1
|
|
|
2
|
-
#
|
|
2
|
+
# Risei
|
|
3
3
|
|
|
4
|
-
## What
|
|
4
|
+
## What Risei is
|
|
5
5
|
|
|
6
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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. `SortModel.countSort()` is being tested using the inputs from `.in` and the expected output from `.out`:
|
|
20
25
|
|
|
21
26
|

|
|
22
27
|
|
|
23
|
-
</div>
|
|
24
28
|
|
|
25
29
|
Here is the terminal output of these two example tests. Tests are grouped by class and method. 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
|

|
|
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. Any failing tests appear in light red.
|
|
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
|

|
|
40
39
|
|
|
41
|
-
</div>
|
|
42
40
|
|
|
43
41
|
|
|
44
|
-
## How to use RiseiJs
|
|
45
42
|
|
|
46
|
-
|
|
43
|
+
## Using Risei
|
|
44
|
+
|
|
45
|
+
### Installation
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
Install Risei for development time only:
|
|
49
48
|
|
|
50
49
|
```bash
|
|
51
50
|
npm install --save-dev risei
|
|
52
51
|
```
|
|
53
52
|
|
|
54
|
-
|
|
53
|
+
Make sure your `package.json` specifies that ESM is in use:
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
</summary>
|
|
60
|
-
<br>
|
|
61
|
-
|
|
62
|
-
- RiseiJs is an `npm` package that uses ESM syntax. 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
|
-
|
|
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
|
-
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
|
-
|
|
99
|
-
<summary>
|
|
100
|
-
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 {
|
|
75
|
+
import { ClassToBeTested } from "somewhere";
|
|
117
76
|
|
|
118
|
-
export
|
|
119
|
-
tests = [ ];
|
|
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
|
-
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 ✗`this.tests` by accident — it just causes an exception when tests are run.
|
|
135
|
-
|
|
136
|
-
</details>
|
|
137
|
-
|
|
138
82
|
|
|
139
83
|
|
|
140
|
-
|
|
84
|
+
### Writing tests
|
|
141
85
|
|
|
142
|
-
|
|
86
|
+
Write tests with Risei's easy syntax:
|
|
143
87
|
|
|
144
88
|
```javascript
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
-
|
|
202
|
-
|
|
115
|
+
/* First test for ContainerModelClass .countOf(). */
|
|
116
|
+
{ of: "countOf", for: "Returns the number present.", in: [ "b" ], out: 2 }
|
|
203
117
|
```
|
|
204
118
|
|
|
205
|
-
|
|
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
|
-
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 — but by design, they are collapsed forward to following tests.
|
|
219
122
|
|
|
220
|
-
|
|
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. 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:
|
|
233
|
-
{ on:
|
|
234
|
-
{
|
|
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
|
-
|
|
241
|
-
- Defining new spoofing wipes out all old definitions.
|
|
147
|
+
These are basically all varieties of spoofing. Spoofing collapses forward across tests, but not across the elements within `.plus`.
|
|
242
148
|
|
|
243
|
-
</div>
|
|
244
149
|
|
|
245
|
-
<details>
|
|
246
|
-
<summary>
|
|
247
|
-
Spoofing syntax and options
|
|
248
|
-
</summary>
|
|
249
|
-
<br>
|
|
250
150
|
|
|
251
|
-
|
|
151
|
+
### Running tests
|
|
252
152
|
|
|
253
|
-
|
|
153
|
+
Run your tests by invoking Risei's `index.js` file:
|
|
254
154
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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
|
-
|
|
159
|
+
Or write a `package.json` script that does the same:
|
|
262
160
|
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
171
|
+
```bash
|
|
172
|
+
npm test
|
|
173
|
+
```
|
|
268
174
|
|
|
269
|
-
|
|
175
|
+
Risei can be used alongside other test frameworks. 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
|
-
|
|
179
|
+
### Learning more about Risei
|
|
286
180
|
|
|
287
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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: 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
|
-
Further spoofing options
|
|
306
|
-
</summary>
|
|
307
|
-
<br>
|
|
308
189
|
|
|
309
|
-
|
|
190
|
+
## Risei in depth
|
|
310
191
|
|
|
311
|
-
|
|
192
|
+
### Installation
|
|
312
193
|
|
|
313
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
210
|
+
### Writing tests
|
|
344
211
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
|
|
251
|
+
### Choosing test-only dependency inputs _AKA_ Spoofing
|
|
377
252
|
|
|
378
|
-
|
|
379
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
391
|
-
"risei": "node ./node_modules/risei/index.js"
|
|
392
|
-
"test": "mocha **/* && risei"
|
|
393
|
-
```
|
|
261
|
+
#### Spoof-definition properties:
|
|
394
262
|
|
|
395
|
-
|
|
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> — or —<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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
408
|
-
npm test
|
|
409
|
-
```
|
|
281
|
+
- Defining new spoofing wipes out all old definitions.
|
|
410
282
|
|
|
411
|
-
</div>
|
|
412
283
|
|
|
413
|
-
|
|
414
|
-
<summary>
|
|
415
|
-
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
|
-
|
|
289
|
+
## Advanced Risei usage
|
|
428
290
|
|
|
429
|
-
|
|
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. 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
|
-
|
|
301
|
+
#### Testing static methods
|
|
438
302
|
|
|
439
|
-
|
|
303
|
+
To test a static method, use an `.and` of `"static"`:
|
|
440
304
|
|
|
441
305
|
```javascript
|
|
442
|
-
{ on:
|
|
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
|
-
More information
|
|
457
|
-
</summary>
|
|
458
|
-
<br>
|
|
459
317
|
|
|
460
|
-
|
|
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. 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. 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
|
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
|
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
|
-
|
|
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. It can simply be an empty array `[ ]`.
|
|
599
449
|
|
|
600
450
|
|
|
601
|
-
|
|
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. 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
|
|
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>— or —<br>Tested method mutates `.in` or `.with` args: preventable by using a function to supply those args<br>— or —<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. 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
|
-
|
|
519
|
+
## Who makes Risei
|
|
622
520
|
|
|
521
|
+
Risei is written by myself, Ed Fallin. I'm a longtime software developer who likes to find better ways to do things.
|
|
623
522
|
|
|
624
|
-
|
|
523
|
+
If you find Risei useful, consider spreading the word to other devs, making a donation, suggesting enhancements, or proposing sponsorships.
|
|
625
524
|
|
|
626
|
-
|
|
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
|
-
##
|
|
530
|
+
## Risei license
|
|
634
531
|
|
|
635
|
-
|
|
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>
|
|
536
|
+
<b>Risei Copyright © 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
|
|