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
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
/**/
|
|
2
2
|
|
|
3
|
+
import { TypeAnalyzer } from "./TypeAnalyzer.js";
|
|
4
|
+
|
|
3
5
|
/* Defines what is found in a test-definition tuple (TDT). Actual TDTs don't need to be instances of this class. */
|
|
4
6
|
|
|
5
7
|
export class TestTuple {
|
|
8
|
+
// region Definitions
|
|
9
|
+
|
|
10
|
+
static staticName = "static";
|
|
11
|
+
static throwName = "throw";
|
|
12
|
+
static constructorName = "constructor";
|
|
13
|
+
static nonceLocalCallableName = "localCallable";
|
|
14
|
+
static dotOperator = ".";
|
|
15
|
+
static colonOperator = ":";
|
|
16
|
+
static parensOperator = "()";
|
|
17
|
+
static operatorsRegExp = /\.|:|\(\)/g;
|
|
18
|
+
|
|
19
|
+
// endregion Definitions
|
|
20
|
+
|
|
6
21
|
// region Static fields
|
|
7
22
|
|
|
8
23
|
static longsByShort = new Map([
|
|
@@ -11,6 +26,7 @@ export class TestTuple {
|
|
|
11
26
|
[ "with", "initors" ],
|
|
12
27
|
[ "of", "method" ],
|
|
13
28
|
[ "plus", "spoofed" ],
|
|
29
|
+
[ "amid", "settables" ],
|
|
14
30
|
[ "in", "inputs" ],
|
|
15
31
|
[ "out", "output" ],
|
|
16
32
|
[ "from", "source" ],
|
|
@@ -21,6 +37,12 @@ export class TestTuple {
|
|
|
21
37
|
|
|
22
38
|
// region Private fields
|
|
23
39
|
|
|
40
|
+
#typeAnalyzer;
|
|
41
|
+
|
|
42
|
+
// endregion Private fields
|
|
43
|
+
|
|
44
|
+
// region Fields
|
|
45
|
+
|
|
24
46
|
/* These longer names match the constructor-arg names.
|
|
25
47
|
The short-name properties use the same fields. */
|
|
26
48
|
|
|
@@ -29,12 +51,25 @@ export class TestTuple {
|
|
|
29
51
|
initors;
|
|
30
52
|
method;
|
|
31
53
|
spoofed;
|
|
54
|
+
settables;
|
|
32
55
|
inputs;
|
|
33
56
|
output;
|
|
34
57
|
source;
|
|
35
58
|
factors;
|
|
59
|
+
|
|
60
|
+
/* &cruft, possibly drop defaults when properties settable in tests */
|
|
61
|
+
// These fields are not part of collapse-forward system:
|
|
62
|
+
// Local copies of args; local test targets; outcome fields.
|
|
63
|
+
localInitors = [ ];
|
|
64
|
+
localInputs = [ ];
|
|
36
65
|
|
|
37
|
-
|
|
66
|
+
localTarget;
|
|
67
|
+
localMethod;
|
|
68
|
+
|
|
69
|
+
actual;
|
|
70
|
+
thrown;
|
|
71
|
+
|
|
72
|
+
// endregion Fields
|
|
38
73
|
|
|
39
74
|
// region Properties
|
|
40
75
|
|
|
@@ -81,6 +116,15 @@ export class TestTuple {
|
|
|
81
116
|
set plus(value) {
|
|
82
117
|
this.spoofed = value;
|
|
83
118
|
}
|
|
119
|
+
|
|
120
|
+
/* &cruft, rename this .amid property pair */
|
|
121
|
+
get amid() {
|
|
122
|
+
return this.settables;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
set amid(value) {
|
|
126
|
+
this.settables = value;
|
|
127
|
+
}
|
|
84
128
|
|
|
85
129
|
get in() {
|
|
86
130
|
return this.inputs;
|
|
@@ -116,7 +160,7 @@ export class TestTuple {
|
|
|
116
160
|
|
|
117
161
|
// endregion Test definition, short names
|
|
118
162
|
|
|
119
|
-
// region Tuple state
|
|
163
|
+
// region Tuple state and dependencies
|
|
120
164
|
|
|
121
165
|
get isRunnable() {
|
|
122
166
|
let isRunnable
|
|
@@ -130,7 +174,71 @@ export class TestTuple {
|
|
|
130
174
|
return isRunnable;
|
|
131
175
|
}
|
|
132
176
|
|
|
133
|
-
|
|
177
|
+
get isInstanceTest() /* passed */ {
|
|
178
|
+
return !this.isStaticTest || this.isConstructorTest;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
get isStaticTest() /* passed */ {
|
|
182
|
+
let does = this.andStringContains(TestTuple.staticName);
|
|
183
|
+
return does;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
get isThrowTest() /* passed */ {
|
|
187
|
+
let is = this.andStringContains(TestTuple.throwName);
|
|
188
|
+
return is;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* Needed externally, and dependency
|
|
192
|
+
of all .and-based properties. */
|
|
193
|
+
andStringContains(keyword) /* passed */ {
|
|
194
|
+
let doesContain
|
|
195
|
+
= typeof this.and === "string"
|
|
196
|
+
&& this.and.includes(keyword);
|
|
197
|
+
|
|
198
|
+
return doesContain;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
get isConstructorTest() /* passed */ {
|
|
202
|
+
let plainTarget = TestTuple.plainNameOf(this.of);
|
|
203
|
+
return plainTarget === TestTuple.constructorName;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
get isMethodTest() /* passed */ {
|
|
207
|
+
return !this.isPropertyTest;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
get isPropertyTest() /* passed */ {
|
|
211
|
+
let plainName = TestTuple.plainNameOf(this.of);
|
|
212
|
+
|
|
213
|
+
let isPropertyTest
|
|
214
|
+
= this.of.startsWith(TestTuple.dotOperator)
|
|
215
|
+
|| this.of.endsWith(TestTuple.colonOperator)
|
|
216
|
+
|| this.#typeAnalyzer.memberIsProperty(plainName);
|
|
217
|
+
|
|
218
|
+
return isPropertyTest;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
get isRetrievalTest() /* passed */ {
|
|
222
|
+
// Falsy code elements never are a retrieval.
|
|
223
|
+
if (!this.from) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// If a (non-falsy) string, must be a retrieval.
|
|
228
|
+
if (typeof this.from === "string") {
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// If a function, is a retrieval.
|
|
233
|
+
if (this.from instanceof Function) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Anything else is not a retrieval.
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// endregion Tuple state and dependencies
|
|
134
242
|
|
|
135
243
|
// endregion Properties
|
|
136
244
|
|
|
@@ -138,16 +246,19 @@ export class TestTuple {
|
|
|
138
246
|
|
|
139
247
|
/* Constructor can't use param names like `for`, because when not .-prefixed, they are keywords. */
|
|
140
248
|
|
|
141
|
-
constructor(nature, type, spoofed, initors, method, inputs, output, source, factors) {
|
|
142
|
-
this.nature = nature;
|
|
143
|
-
this.type = type;
|
|
144
|
-
this.spoofed = spoofed;
|
|
145
|
-
this.initors = initors;
|
|
146
|
-
this.
|
|
147
|
-
this.
|
|
148
|
-
this.
|
|
149
|
-
this.
|
|
150
|
-
this.
|
|
249
|
+
constructor(nature, type, spoofed, initors, settables, method, inputs, output, source, factors) {
|
|
250
|
+
this.nature = nature; // .for
|
|
251
|
+
this.type = type; // .on
|
|
252
|
+
this.spoofed = spoofed; // .plus
|
|
253
|
+
this.initors = initors; // .with
|
|
254
|
+
this.settables = settables; // .amid
|
|
255
|
+
this.method = method; // .of
|
|
256
|
+
this.inputs = inputs; // .in
|
|
257
|
+
this.output = output; // .out
|
|
258
|
+
this.source = source; // .from
|
|
259
|
+
this.factors = factors; // .and
|
|
260
|
+
|
|
261
|
+
this.#typeAnalyzer = new TypeAnalyzer(this.type);
|
|
151
262
|
}
|
|
152
263
|
|
|
153
264
|
static fromNonceTuples(nonces) {
|
|
@@ -241,4 +352,12 @@ export class TestTuple {
|
|
|
241
352
|
|
|
242
353
|
// endregion Initing, including constructor()
|
|
243
354
|
|
|
355
|
+
// region Other statics
|
|
356
|
+
|
|
357
|
+
static plainNameOf(name) /* passed */ {
|
|
358
|
+
let plain = name.replaceAll(TestTuple.operatorsRegExp, "");
|
|
359
|
+
return plain;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// endregion Other statics
|
|
244
363
|
}
|
|
@@ -36,6 +36,10 @@ export class TotalComparer extends AComparer {
|
|
|
36
36
|
return this.#compareFunctions(expected, actual);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
if (expected instanceof Date) {
|
|
40
|
+
return this.#compareDates(expected, actual);
|
|
41
|
+
}
|
|
42
|
+
|
|
39
43
|
if (expected instanceof Object) {
|
|
40
44
|
/* Traverse / recurse to leaves. */
|
|
41
45
|
return this.#recurseOverObjects(expected, actual);
|
|
@@ -223,6 +227,14 @@ export class TotalComparer extends AComparer {
|
|
|
223
227
|
return expectedCode === actualCode;
|
|
224
228
|
}
|
|
225
229
|
|
|
230
|
+
#compareDates(expected, actual) /* verified */ {
|
|
231
|
+
if (!(actual instanceof Date)) {
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return expected.valueOf() === actual.valueOf();
|
|
236
|
+
}
|
|
237
|
+
|
|
226
238
|
// endregion Dependencies of compare()
|
|
227
239
|
|
|
228
240
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
export class TotalCopier {
|
|
4
|
+
copy(original) /* passed */ {
|
|
5
|
+
return this.recursiveCopy(original);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
recursiveCopy(original) /* verified */ {
|
|
9
|
+
/* &cruft, factor, possibly abstracting similar code,
|
|
10
|
+
and/or return copy only once, or similar */
|
|
11
|
+
|
|
12
|
+
/* Algorithm: First copied at current level by value if a value, by reference if
|
|
13
|
+
not a value; then recursively replaced at next level, and so on. */
|
|
14
|
+
|
|
15
|
+
let copy;
|
|
16
|
+
|
|
17
|
+
if (Array.isArray(original)) {
|
|
18
|
+
copy = [ ];
|
|
19
|
+
|
|
20
|
+
// Traversal construction with recursion.
|
|
21
|
+
for (let item of original) {
|
|
22
|
+
let next = this.recursiveCopy(item);
|
|
23
|
+
copy.push(next);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return copy;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (original instanceof Map) {
|
|
30
|
+
copy = new Map();
|
|
31
|
+
|
|
32
|
+
// Traversal construction with recursion.
|
|
33
|
+
for (let entry of original.entries()) {
|
|
34
|
+
let next = this.recursiveCopy(entry);
|
|
35
|
+
copy.set(next[0], next[1]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return copy;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (original instanceof Set) {
|
|
42
|
+
copy = new Set();
|
|
43
|
+
|
|
44
|
+
// Traversal construction with recursion.
|
|
45
|
+
for (let value of original.values()) {
|
|
46
|
+
let next = this.recursiveCopy(value);
|
|
47
|
+
copy.add(next);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return copy;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (original instanceof Date) {
|
|
54
|
+
copy = new Date(original);
|
|
55
|
+
return copy;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (original instanceof Object) {
|
|
59
|
+
copy = { };
|
|
60
|
+
|
|
61
|
+
let keys = Object.keys(original);
|
|
62
|
+
|
|
63
|
+
// Traversal construction with recursion.
|
|
64
|
+
for (let key of Object.keys(original)) {
|
|
65
|
+
let next = this.recursiveCopy(original[key]);
|
|
66
|
+
copy[key] = next;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return copy;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* &cruft, other object types here */
|
|
73
|
+
|
|
74
|
+
// All object types exhausted, so this is a value,
|
|
75
|
+
// which can be used directly for a copy.
|
|
76
|
+
copy = original;
|
|
77
|
+
return copy;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
import { TypeIdentifier } from "./TypeIdentifier.js";
|
|
4
|
+
import { Types } from "./Types.js";
|
|
5
|
+
|
|
6
|
+
export class TotalDisplayer {
|
|
7
|
+
#identifier;
|
|
8
|
+
|
|
9
|
+
constructor() /* verified */ {
|
|
10
|
+
this.#identifier = new TypeIdentifier();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
display(value) /* passed */ {
|
|
14
|
+
let typeId = this.#identifier.identify(value);
|
|
15
|
+
|
|
16
|
+
switch (typeId) {
|
|
17
|
+
case Types.isValue: return this.displayValue(value);
|
|
18
|
+
case Types.isString: return this.displayString(value);
|
|
19
|
+
case Types.isArray: return this.displayArray(value);
|
|
20
|
+
case Types.isMap: return this.displayMap(value);
|
|
21
|
+
case Types.isSet: return this.displaySet(value);
|
|
22
|
+
case Types.isClass: return this.displayClass(value);
|
|
23
|
+
case Types.isFunction: return this.displayFunction(value);
|
|
24
|
+
case Types.isObject: return this.displayObject(value);
|
|
25
|
+
case Types.isDate: return this.displayValue(value);
|
|
26
|
+
case Types.isUndefined: return this.displayUndefined(value);
|
|
27
|
+
case Types.isNull: return this.displayNull(value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// region Displaying different types
|
|
32
|
+
|
|
33
|
+
/* Plain values (numbers, booleans) and Dates
|
|
34
|
+
are handled together using JS defaults. */
|
|
35
|
+
displayValue(value) /* passed */ {
|
|
36
|
+
return `${ value }`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
displayString(value) /* passed */ {
|
|
40
|
+
return `"${ value }"`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
displayArray(value) /* passed */ {
|
|
44
|
+
// Cross-recursion.
|
|
45
|
+
let outputs = value
|
|
46
|
+
.map(x => this.display(x));
|
|
47
|
+
|
|
48
|
+
// Brackets around, commas between.
|
|
49
|
+
let output = `[${ outputs.join(",") }]`;
|
|
50
|
+
|
|
51
|
+
// And back to caller.
|
|
52
|
+
return output;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
displayMap(value) /* passed */ {
|
|
56
|
+
// Array used for clearer display.
|
|
57
|
+
let items = [ ];
|
|
58
|
+
|
|
59
|
+
// Each key-value pair is handled individually.
|
|
60
|
+
for (let key of value.keys()) {
|
|
61
|
+
let item = value.get(key);
|
|
62
|
+
|
|
63
|
+
// Cross-recursion.
|
|
64
|
+
items.push(`${ this.display(key) }:${ this.display(item) }`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Key-value pairs are all listed together.
|
|
68
|
+
let output = `Map{${ items.join(",") }}`;
|
|
69
|
+
return output;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
displaySet(value) /* passed */ {
|
|
73
|
+
// Array used for clearer display.
|
|
74
|
+
let items = [ ];
|
|
75
|
+
|
|
76
|
+
// Each element is handled individually.
|
|
77
|
+
for (let item of value) {
|
|
78
|
+
// Cross-recursion for styling values.
|
|
79
|
+
let display = `${ this.display(item) }`;
|
|
80
|
+
items.push(display);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Items are all listed together.
|
|
84
|
+
let output = `Set{${ items.join(",") }}`;
|
|
85
|
+
return output;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
displayClass(value) /* passed */ {
|
|
89
|
+
// Class definitions have a .name.
|
|
90
|
+
let output = value.name;
|
|
91
|
+
return output;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
displayFunction(value) /* passed */ {
|
|
95
|
+
// toString() lists the definition, including code.
|
|
96
|
+
let output = value.toString();
|
|
97
|
+
|
|
98
|
+
// Most whitespace is eliminated for readability.
|
|
99
|
+
output = output.replace(/(\r\n?)/g, "");
|
|
100
|
+
output = output.replace(/\s{2,}/g, " ");
|
|
101
|
+
|
|
102
|
+
// Back to caller.
|
|
103
|
+
return output;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
displayObject(value) /* passed */ {
|
|
107
|
+
// Use toString() text if it's meaningful.
|
|
108
|
+
let asString = value.toString();
|
|
109
|
+
|
|
110
|
+
if (asString !== "[object Object]") {
|
|
111
|
+
return asString;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// If toString() not meaningful, list members.
|
|
115
|
+
let items = [ ];
|
|
116
|
+
|
|
117
|
+
// Each property of the object is handled individually.
|
|
118
|
+
for (let p in value) {
|
|
119
|
+
// Cross-recursion.
|
|
120
|
+
let item = this.display(value[p]);
|
|
121
|
+
items.push(`${ p }:${ item }`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Properties are all listed together.
|
|
125
|
+
let output = `{ ${ items.join(", ") } }`;
|
|
126
|
+
|
|
127
|
+
// If empty, internal empty space reduced.
|
|
128
|
+
output = output !== "{ }" ? output : "{ }";
|
|
129
|
+
|
|
130
|
+
// Back to caller.
|
|
131
|
+
return output;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
displayUndefined(value) /* passed */ {
|
|
135
|
+
return "undefined";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
displayNull(value) /* passed */ {
|
|
139
|
+
return "null";
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// endregion Displaying different types
|
|
143
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
/* Used to analyze classes / members of classes being tested. */
|
|
4
|
+
|
|
5
|
+
export class TypeAnalyzer {
|
|
6
|
+
// region Definitions
|
|
7
|
+
|
|
8
|
+
static constructorName = "constructor";
|
|
9
|
+
static getName = "get";
|
|
10
|
+
static setName = "set";
|
|
11
|
+
|
|
12
|
+
// endregion Definitions
|
|
13
|
+
|
|
14
|
+
// region Fields
|
|
15
|
+
|
|
16
|
+
#type;
|
|
17
|
+
|
|
18
|
+
// endregion Fields
|
|
19
|
+
|
|
20
|
+
constructor(type) {
|
|
21
|
+
this.#type = type;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Returns true if instance or static member is a property (value or accessor). Returns false if member is a method. */
|
|
25
|
+
memberIsProperty(name) /* passed */ {
|
|
26
|
+
// Constructor is never a property and is irregular,
|
|
27
|
+
// so other type-analysis code can't handle it.
|
|
28
|
+
if (name === TypeAnalyzer.constructorName) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Static methods and properties (value or accessor) are on type.
|
|
33
|
+
if (name in this.#type) {
|
|
34
|
+
return this.#staticMemberIsProperty(this.#type, name);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Methods and accessor properties are on prototype.
|
|
38
|
+
if (name in this.#type.prototype) {
|
|
39
|
+
return this.#instanceMemberIsProperty(this.#type.prototype, name);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Instance value properties are not on type or prototype.
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// region Dependencies of memberIsProperty()
|
|
47
|
+
|
|
48
|
+
/* Algorithm specialized with function check for static members. */
|
|
49
|
+
#staticMemberIsProperty(type, name) {
|
|
50
|
+
let descriptor = Object.getOwnPropertyDescriptor(type, name);
|
|
51
|
+
|
|
52
|
+
// If on type and has accessors, it's a static accessor property.
|
|
53
|
+
if (this.#doesHaveAccessorProps(descriptor)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// If on type and has non-Function .value, it's a static value property.
|
|
58
|
+
if (this.#doesNotHaveFunctionValue(descriptor)) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// If on type but not a static property, it's a static method.
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Algorithm specialized for instance members, no function check. */
|
|
67
|
+
#instanceMemberIsProperty(prototype, name) {
|
|
68
|
+
let descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
|
69
|
+
|
|
70
|
+
// If on prototype and has accessors, it's an accessor property.
|
|
71
|
+
if (this.#doesHaveAccessorProps(descriptor)) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// If on prototype but not an accessor property, it's a method.
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// region Internally reused dependencies of other memberIsProperty() dependencies
|
|
80
|
+
|
|
81
|
+
#doesHaveAccessorProps(descriptor) /* verified */ {
|
|
82
|
+
// Accessor properties have one or both of these in descriptor.
|
|
83
|
+
if (TypeAnalyzer.getName in descriptor || TypeAnalyzer.setName in descriptor) {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#doesNotHaveFunctionValue(descriptor) /* verified */ {
|
|
91
|
+
// Simple inverter of other method for readability.
|
|
92
|
+
return !this.#doesHaveFunctionValue(descriptor);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#doesHaveFunctionValue(descriptor) /* verified */ {
|
|
96
|
+
// If no member, .value of undefined.
|
|
97
|
+
return descriptor.value instanceof Function;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// endregion Internally reused dependencies of other memberIsProperty() dependencies
|
|
101
|
+
|
|
102
|
+
// endregion Dependencies of memberIsProperty()
|
|
103
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
import { Types } from "./Types.js";
|
|
4
|
+
|
|
5
|
+
export class TypeIdentifier {
|
|
6
|
+
identify(value) /* passed */ {
|
|
7
|
+
// Basis for most top branching.
|
|
8
|
+
let rawType = typeof value;
|
|
9
|
+
|
|
10
|
+
// Array, Map, Set, Date, null, and actual
|
|
11
|
+
// objects are all "object" to JavaScript.
|
|
12
|
+
if (rawType === "object") {
|
|
13
|
+
if (Array.isArray(value)) {
|
|
14
|
+
return Types.isArray;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (value instanceof Map) {
|
|
18
|
+
return Types.isMap;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (value instanceof Set) {
|
|
22
|
+
return Types.isSet;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (value instanceof Date) {
|
|
26
|
+
return Types.isDate;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (value === null) {
|
|
30
|
+
return Types.isNull;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return Types.isObject;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Functions and classes are both "function" to Javascript.
|
|
37
|
+
if (rawType === "function") {
|
|
38
|
+
// Functions can be anonymous.
|
|
39
|
+
if (!value.name) {
|
|
40
|
+
return Types.isFunction;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Classes all start with this syntax
|
|
44
|
+
// in their defs (never with `export`).
|
|
45
|
+
let classSyntax = `class ${ value.name }`;
|
|
46
|
+
|
|
47
|
+
let definition = value.toString();
|
|
48
|
+
|
|
49
|
+
if (definition.startsWith(classSyntax)) {
|
|
50
|
+
return Types.isClass;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Function but not class is a named function.
|
|
54
|
+
return Types.isFunction;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Strings may be handled specially.
|
|
58
|
+
if (typeof value === "string") {
|
|
59
|
+
return Types.isString;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Undefined is different from the rest.
|
|
63
|
+
if (value === undefined) {
|
|
64
|
+
return Types.isUndefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Non-string values all handled the same.
|
|
68
|
+
return Types.isValue;
|
|
69
|
+
}
|
|
70
|
+
}
|
package/system/Types.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**/
|
|
2
|
+
|
|
3
|
+
export class Types {
|
|
4
|
+
/* This class is essentially an enum. */
|
|
5
|
+
|
|
6
|
+
static isValue = "value";
|
|
7
|
+
static isString = "string";
|
|
8
|
+
static isArray = "array";
|
|
9
|
+
static isObject = "object";
|
|
10
|
+
static isMap = "map";
|
|
11
|
+
static isSet = "set";
|
|
12
|
+
static isFunction = "function";
|
|
13
|
+
static isClass = "class";
|
|
14
|
+
static isDate = "date";
|
|
15
|
+
static isUndefined = "undefined";
|
|
16
|
+
static isNull = "null";
|
|
17
|
+
}
|