risei 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -220
- package/package.json +2 -2
- package/system/ATestReporter.js +26 -26
- package/system/ClassTestGroup.js +8 -8
- package/system/LocalCaller.js +2 -2
- package/system/MethodSpoofer.js +2 -2
- package/system/MethodTestGroup.js +8 -8
- package/system/Moment.js +29 -29
- package/system/NameAnalyzer.js +26 -26
- package/system/PropertySpoofer.js +1 -1
- package/system/SpoofDef.js +0 -5
- package/system/TestDef.js +14 -13
- package/system/TestFrame.js +8 -10
- package/system/TestGroup.js +25 -25
- package/system/TestRunner.js +8 -6
- package/system/TestStages.js +66 -10
- package/system/TestSummary.js +37 -37
- package/system/TypeAnalyzer.js +79 -26
- package/Read-me reduced.md +0 -294
- package/Read-me redux.md +0 -581
- package/system/ChosenTestFinder.js +0 -31
- package/system/Risei.js +0 -118
- package/test-target-objects/ConditionalThrowTarget.js +0 -11
- package/test-target-objects/Counter.js +0 -46
- package/test-target-objects/DomTarget.js +0 -37
- package/test-target-objects/InterSpoofer.js +0 -230
- package/test-target-objects/MixedContents.js +0 -33
- package/test-target-objects/MutationTarget.js +0 -37
- package/test-target-objects/ObjectComposer.js +0 -34
- package/test-target-objects/PolySpoofableInner.js +0 -29
- package/test-target-objects/PolySpoofableOuter.js +0 -52
- package/test-target-objects/PropertiesTarget.js +0 -98
- package/test-target-objects/Returner.js +0 -7
- package/test-target-objects/Searcher.js +0 -25
- package/test-target-objects/Sorter.js +0 -91
- package/test-target-objects/Spoofable.js +0 -36
- package/test-target-objects/StateTarget.js +0 -34
- package/test-target-objects/StaticTarget.js +0 -17
- package/test-target-objects/TestableTarget.js +0 -57
- package/trial-tests/SelfTests.outward-rt.js +0 -511
- package/trial-tests/TopicTests.outward-rt.js +0 -313
- package/usage-examples/Gold-bar-example.png +0 -0
- package/usage-examples/Output-example.png +0 -0
- package/usage-examples/Summary-example.png +0 -0
- package/usage-examples/Syntax-example.png +0 -0
- package/usage-examples/Title-example.png +0 -0
- package/xternal-tests/ASpoofingFixture.tests.js +0 -242
- package/xternal-tests/MethodSpoofer.tests.js +0 -130
- package/xternal-tests/SpoofDef.tests.js +0 -91
- package/xternal-tests/TotalComparer.tests.js +0 -1055
- package/xternal-tests/package.json +0 -7
package/system/TypeAnalyzer.js
CHANGED
|
@@ -45,40 +45,93 @@ export default class TypeAnalyzer {
|
|
|
45
45
|
/* Returns true if instance or static member is a method.
|
|
46
46
|
Returns false if member is any kind of property. */
|
|
47
47
|
static isMethodMember(type, name) /* passed */ {
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
return !TypeAnalyzer.isPropertyMember(type, name);
|
|
49
|
+
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
/* Returns true if instance or static member is a property
|
|
52
|
+
(value or accessor). Returns false if member is a method. */
|
|
53
|
+
static isPropertyMember(type, name) /* passed */ {
|
|
54
|
+
/* Type and supertypes are tested directly for static props,
|
|
55
|
+
and their definition texts are read for instance props. */
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
let isInstanceMethod = anyInstanceType === Types.isFunction;
|
|
57
|
-
let isStaticMethod = anyStaticType === Types.isFunction;
|
|
57
|
+
let isProperty = this.#typeChainIncludesAsProperty(type, name);
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
return isProperty;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static #typeChainIncludesAsProperty(type, name) /* verified */ {
|
|
63
|
+
let prototype = type.prototype;
|
|
64
|
+
|
|
65
|
+
while (prototype !== null) {
|
|
66
|
+
// Type is needed, rather than prototype.
|
|
67
|
+
type = prototype.constructor;
|
|
68
|
+
|
|
69
|
+
// First line finds any static props; second, any instance props.
|
|
70
|
+
let isProperty
|
|
71
|
+
= TypeAnalyzer.#typeDefinitionIncludesAsProperty(type, name)
|
|
72
|
+
|| TypeAnalyzer.#typeTextContainsNameAsProperty(type, name);
|
|
73
|
+
|
|
74
|
+
// Found as static or instance.
|
|
75
|
+
if (isProperty) {
|
|
76
|
+
return true;
|
|
69
77
|
}
|
|
70
78
|
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
// Parent class.
|
|
80
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
81
|
+
|
|
82
|
+
if (prototype === null) {
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
73
85
|
}
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
// Never found.
|
|
88
|
+
return false;
|
|
77
89
|
}
|
|
78
90
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
91
|
+
static #typeDefinitionIncludesAsProperty(type, name) /* verified */ {
|
|
92
|
+
let named = Object.getOwnPropertyDescriptor(type, name);
|
|
93
|
+
|
|
94
|
+
// If not found, try parent.
|
|
95
|
+
if (named === undefined) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Members with getters are properties.
|
|
100
|
+
if (named.get !== undefined) {
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Members with function values are methods.
|
|
105
|
+
if (named.value instanceof Function) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Members with any other values are properties.
|
|
110
|
+
if (named.value !== undefined) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
static #typeTextContainsNameAsProperty(type, name) /* verified */ {
|
|
116
|
+
let text = type.toString();
|
|
117
|
+
|
|
118
|
+
// Instance properties are always defined as 'name =',
|
|
119
|
+
// 'name;', or 'get name()' at the start of a line.
|
|
120
|
+
let valueRegex = new RegExp(`^\\s*${ name }\\s*[=;]`, "m");
|
|
121
|
+
let accessorRegex = new RegExp(`^\\s*get ${ name }\\(\\)`, "m");
|
|
122
|
+
|
|
123
|
+
let anyIndexOf = text.search(valueRegex);
|
|
124
|
+
|
|
125
|
+
if (anyIndexOf >= 0) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
anyIndexOf = text.search(accessorRegex);
|
|
130
|
+
|
|
131
|
+
if (anyIndexOf >= 0) {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return false;
|
|
83
136
|
}
|
|
84
137
|
}
|
package/Read-me reduced.md
DELETED
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
# Risei Read-Me
|
|
3
|
-
|
|
4
|
-
## Overview
|
|
5
|
-
|
|
6
|
-
Risei is a new way to write unit tests that's easier and faster, more dependable, and keeps your tests from standing in the way of redesigns.
|
|
7
|
-
|
|
8
|
-
Risei does all this by replacing hand-coded tests with simple declarative syntax.
|
|
9
|
-
|
|
10
|
-
- Risei has many convenient and time-saving [features](#features-of-risei).
|
|
11
|
-
- Risei works with object-oriented JavaScript in modules.
|
|
12
|
-
- Risei works with TypeScript after just a few [tweaks](https://deusware.com/risei/index.html#typescript-with-risei).
|
|
13
|
-
|
|
14
|
-
You can find a longer version of this read-me at [https://deusware.com/risei](https://deusware.com/risei/index.html). It expands greatly on the information here.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
## Examples
|
|
19
|
-
|
|
20
|
-
Here are two example tests, in which `SortModel.countSort()` is being tested using the inputs from `.in` and the expected output from `.out`:
|
|
21
|
-
|
|
22
|
-

|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Here are the example test results for those two tests:
|
|
26
|
-
|
|
27
|
-

|
|
28
|
-
|
|
29
|
-
- Outputs are always listed, even on passing tests, which makes code usage much clearer.
|
|
30
|
-
- Any failing tests appear in light red.
|
|
31
|
-
- Your latest tests always sort to the bottom so it's easy to find their results.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Test runs have a title bar so the starting point is easy to find:
|
|
35
|
-
|
|
36
|
-

|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
And they have a summary bar at the bottom:
|
|
40
|
-
|
|
41
|
-

|
|
42
|
-
|
|
43
|
-
- This bar is red when any tests fail, much as you'd expect.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
## Status
|
|
48
|
-
|
|
49
|
-
Risei is under active development and new enhancements appear often. The latest release, **2.0.0**, brings many major improvements, including broader capabilities and simpler syntax, and a few minor breaking changes.
|
|
50
|
-
|
|
51
|
-
Check out the [full list of changes](#version-history).
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
## Features of Risei
|
|
56
|
-
|
|
57
|
-
- #### Declarative syntax to write tests simply and quickly. ► [Writing tests](https://deusware.com/risei/index.html#writing-tests) (basics below)
|
|
58
|
-
- #### Easy-to-read test definitions and test outputs. ► [Test and output examples](https://deusware.com/risei/index.html#examples) (also above)
|
|
59
|
-
- #### Even less to write by stating reused test properties only once. ► [Collapsing forward](https://deusware.com/risei/index.html#collapsing-forward) (basics below)
|
|
60
|
-
- #### Built-in declarative syntax to fake test-time values from dependencies. ► [Spoofing using `.plus`](https://deusware.com/risei/index.html#spoofing) (basics below)
|
|
61
|
-
- #### Testing properties, methods, static and instance members all the same way. ► [Properties and statics](https://deusware.com/risei/index.html#testing-properties-and-static-methods)
|
|
62
|
-
- #### Testing `throw` paths effortlessly. ► [Using `.and: "throws"`](https://deusware.com/risei/index.html#using-and-throws)
|
|
63
|
-
- #### Deriving actual values to test from raw outputs or property retrieval. ► [Using `.from`](https://deusware.com/risei/index.html#using-from)
|
|
64
|
-
- #### Setting up and tearing down arbitrary test state. ► [Using `.do` and `.undo`](https://deusware.com/risei/index.html#using-do-and-undo)
|
|
65
|
-
- #### Testing for `undefined` as output. ► [Using `this.undef`](https://deusware.com/risei/index.html#using-undef)
|
|
66
|
-
|
|
67
|
-
- And more! Check out the full [Risei home page](https://deusware.com/risei).
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
## Installation
|
|
71
|
-
|
|
72
|
-
Install Risei for development time only:
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
npm install --save-dev risei
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Ensure that `package.json` specifies ECMAScript modules:
|
|
79
|
-
|
|
80
|
-
```json
|
|
81
|
-
"type": "module"
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
And add Risei's metadata to `package.json`:
|
|
85
|
-
|
|
86
|
-
```json
|
|
87
|
-
"risei": {
|
|
88
|
-
"tests": "**.rt.js"
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
## Running Tests
|
|
95
|
-
|
|
96
|
-
Once you have some tests written, you can run them manually:
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
node ./node_modules/risei/index.js
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
Or write a script in `package.json` that does the same:
|
|
103
|
-
|
|
104
|
-
```json
|
|
105
|
-
"scripts": {
|
|
106
|
-
"test": "node ./node_modules/risei/index.js"
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
And then run that script:
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
npm test
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
## Writing Tests
|
|
119
|
-
|
|
120
|
-
You write tests in `.rt.js` files like this:
|
|
121
|
-
|
|
122
|
-
```javascript
|
|
123
|
-
import ATestSource from "risei/ATestSource";
|
|
124
|
-
import ClassToTest from "ClassToTest.js";
|
|
125
|
-
|
|
126
|
-
export class SomeTests extends ATestSource {
|
|
127
|
-
tests = [ ... ];
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
Write individual tests as plain JavaScript objects with Risei's simple syntax:
|
|
132
|
-
|
|
133
|
-
```javascript
|
|
134
|
-
tests = [ ...
|
|
135
|
-
{ on: ContainerClass, with: [ "a", "b", "c" ], // Target class and constructor args.
|
|
136
|
-
of: "doesContain", // Target method name.
|
|
137
|
-
for: "When the arg is present, returns true.", // Description of test.
|
|
138
|
-
in: [ "c" ], // Inputs to method.
|
|
139
|
-
out: true }, // Expected output.
|
|
140
|
-
... ];
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
- Use empty arrays for `.in` or `.with` when there are no args to pass.
|
|
144
|
-
- You can use [long names](https://deusware.com/risei/index.html#long-names) for properties if you want.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
## Basic collapsing forward example
|
|
149
|
-
|
|
150
|
-
You can state test properties once and let them _collapse forward_ across subsequent tests to save time and make tests easier to read.
|
|
151
|
-
|
|
152
|
-
Risei collapses values together from partial or full test objects until it has a full test to run. Every property collapses forward until it is changed or wiped out:
|
|
153
|
-
|
|
154
|
-
```javascript
|
|
155
|
-
{ on: ContainerClass, with: [ "a", "b", "c" ] }, // Following tests are of this class.
|
|
156
|
-
|
|
157
|
-
{ of: "doesContain" }, // Following tests are of this method.
|
|
158
|
-
{ for: "Returns true when arg present.", in: [ "c" ], out: true }, // First test: now Risei has enough to run on.
|
|
159
|
-
{ for: "Returns false when arg absent.", in: [ "d" ], out: false }, // Next test: same method, different test case.
|
|
160
|
-
|
|
161
|
-
{ of: "countOf" }, // Change of tested method. Method-related props are wiped out.
|
|
162
|
-
...
|
|
163
|
-
|
|
164
|
-
{ on: SortingClass, with: [ ] }, // Change of tested class. All existing props are wiped out.
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
- There are more options available, and an exclusion for mutated args.
|
|
168
|
-
- Learn all the details [here](https://deusware.com/risei/index.html#collapsing-forward).
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
## Basic spoofing example
|
|
173
|
-
|
|
174
|
-
You can use declarative _spoofing_ syntax to define what dependencies of your targeted code return for it to use.
|
|
175
|
-
|
|
176
|
-
The most basic spoofing looks like this:
|
|
177
|
-
|
|
178
|
-
```javascript
|
|
179
|
-
{
|
|
180
|
-
on: TestedClass,
|
|
181
|
-
...
|
|
182
|
-
plus: [
|
|
183
|
-
{ on: Dependency, of: "someMethod", as: 10 }, // Dependency.someMethod() returns 10 in this test.
|
|
184
|
-
{ of: "testedClassMethod", as: 11 } // TestedClass.testedClassMethod() returns 11 in this test.
|
|
185
|
-
],
|
|
186
|
-
...
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
- Numerous additional capabilities, as well as compressed syntax, can be mixed freely in many ways.
|
|
191
|
-
- Learn more [here](https://deusware.com/risei/index.html#spoofing).
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
## TypeScript with Risei
|
|
196
|
-
|
|
197
|
-
To test TypeScript code with Risei, you make sure the code is transpiled before the tests are run, and you point Risei to the transpiled `.js` files.
|
|
198
|
-
|
|
199
|
-
- Learn all about it [here](https://deusware.com/risei/index.html#typescript-with-risei).
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
## Troubleshooting
|
|
204
|
-
|
|
205
|
-
If problems cause test files not to load, a gold bar appears and tests in those files disappear from output:
|
|
206
|
-
|
|
207
|
-

|
|
208
|
-
|
|
209
|
-
- Those and other problems can be solved with the help of this [troubleshooting guide](https://deusware.com/risei/index.html#troubleshooting).
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
## Version history
|
|
213
|
-
|
|
214
|
-
- Release **2.0.0** (August, 2024) contains all of these changes:
|
|
215
|
-
- Risei now can determine automatically if tested methods are static.
|
|
216
|
-
- You can now directly test properties just like methods.
|
|
217
|
-
- Risei can also determine automatically if these are static.
|
|
218
|
-
- Directly tested properties appear in the output in the form `.name`.
|
|
219
|
-
- You can now spoof properties on the targeted class instance, and static properties on the target class itself.
|
|
220
|
-
- You can now perform arbitrary operations, if needed, with the two-part `.do` property and one-part `.undo` property.
|
|
221
|
-
- You can now change test properties without accidentally creating a new test using `.just`.
|
|
222
|
-
- You can now directly test for `undefined` in `.out` using `this.undef` / `ATestSource.undefSymbol`.
|
|
223
|
-
- `Error` objects are now compared accurately.
|
|
224
|
-
- You can now identify member types with `.`, `:`, and `()` in `.of`, `.plus`, and `.from`, though they aren't necessary.
|
|
225
|
-
- **(Breaking change)** The actual for throw tests is now the entire thrown object (usually an `Error`), rather than the `Error`'s message text.
|
|
226
|
-
- **(Breaking change)** `ATestSource` is now a default export, changing its imports from `import { ATestSource } from` to `import ATestSource from`.
|
|
227
|
-
|
|
228
|
-
> Risei has been massively re-engineered internally along with these improvements.
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
<details>
|
|
233
|
-
<summary>
|
|
234
|
-
Older releases
|
|
235
|
-
</summary>
|
|
236
|
-
|
|
237
|
-
- Release **1.3.4** (March, 2024) fixed a bug that caused class display problems in some cases.
|
|
238
|
-
- Release **1.3.3** (March, 2024) fixed a major bug that prevented tests from running in Windows.
|
|
239
|
-
- Release **1.3.2** (February, 2024) only improved this read-me's contents.
|
|
240
|
-
- Release **1.3.1** (February, 2024) reversed some changes in 1.3.0 that did not work as hoped.
|
|
241
|
-
- Release **1.3.0** (February, 2024) added the loading-error gold bar and fixed a test-sorting bug.
|
|
242
|
-
- Release **1.2.0** (January, 2024) changed test sorting to move last-edited tests to the end.
|
|
243
|
-
- Release **1.1.2** of Risei (January, 2024) fixed class displays and inaccurate `Date` comparisons.
|
|
244
|
-
|
|
245
|
-
> The oldest releases are no longer listed here, and old releases are dropped progressively over time. Using the latest release is recommended.
|
|
246
|
-
|
|
247
|
-
</details>
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
## Known issues and workarounds
|
|
251
|
-
|
|
252
|
-
The only issue at present is reuse of method args mutated by tested code when collapsing forward.
|
|
253
|
-
|
|
254
|
-
- The workaround is just to restate those args for each test.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
## Exclusions from Risei
|
|
258
|
-
|
|
259
|
-
At present, there are a few things Risei doesn't support, such as `async` syntax. Some of these may be supported in the future.
|
|
260
|
-
|
|
261
|
-
- You can see the whole list [here](https://deusware.com/risei/index.html#exclusions-from-risei).
|
|
262
|
-
- You can save a lot of time by using Risei for most of your code, and another framework for whatever it doesn't support.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
## Maker
|
|
267
|
-
|
|
268
|
-
Risei is written by myself, Ed Fallin. I'm a longtime software developer who likes to find better ways to do things.
|
|
269
|
-
|
|
270
|
-
If you find Risei useful, consider spreading the word to other devs, making a donation, suggesting enhancements, or proposing sponsorships.
|
|
271
|
-
|
|
272
|
-
You can get in touch about Risei at **riseimaker@gmail.com**.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
## License
|
|
277
|
-
|
|
278
|
-
Risei is published for use under the terms of the MIT license:
|
|
279
|
-
|
|
280
|
-
<div style="border: solid darkgray 1px; padding: 0.5rem;">
|
|
281
|
-
|
|
282
|
-
<b>Risei Copyright © 2023–2024 Ed Fallin</b>
|
|
283
|
-
|
|
284
|
-
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:
|
|
285
|
-
|
|
286
|
-
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
|
|
287
|
-
|
|
288
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
289
|
-
</div>
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|