risei 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -125,7 +125,7 @@ You can save a lot of time by putting repeated test properties into an object on
125
125
 
126
126
  - 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 `[ ]`.
127
127
 
128
- - Collapsing forward doesn't reduce test isolation — for instance, with mutable args — because Risei always runs a copy of the test definition, not the original.
128
+ - When the tested code mutates the contents of `.with` or `.in`, provide these args as the output of a function or method, because at present, the mutated values are collapsed forward.
129
129
 
130
130
 
131
131
 
@@ -254,9 +254,8 @@ There's plenty more to learn about Risei and how it makes testing easy...
254
254
  - Changing the tested class in `.on` wipes out all prior test properties, so the new class has a fresh start.
255
255
  - Changing the tested method in `.of` wipes out only test properties related to methods, to preserve class values you usually want.
256
256
 
257
- - Individual tests remain fully isolated:
258
- - Test args, including objects and functions in `.with`, `.plus`, and `.in`, are all copied for running in each test, so nothing is reused across tests.
259
- - Class instances, spoofs (covered later), and so on are all created anew for each test.
257
+ - Individual tests remain isolated, except for when args are mutable.
258
+ - To use the original, un-mutated args for successive tests, just return them from a function or method to set `.with` or `.in`.
260
259
 
261
260
 
262
261
 
@@ -551,27 +550,33 @@ Most problems with using Risei are minor mistakes in syntax, or omissions in tes
551
550
  | `"Test loading failed for... SyntaxError: Unexpected token ':'"` | Missing comma in your tests in the named file |
552
551
  | `"Test loading failed for... SyntaxError: Unexpected token '.'"` | Using `this.tests = []` instead of `tests = []` in the file named |
553
552
  | Other `... Unexpected token ...` errors | Some other syntax error in the file named, most likely a missing or extra delimiter |
554
- | 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>Pre-listed test properties produce extra tests: preventable by restating class in `.on` |
555
-
553
+ | Unexpected actual values, or unexpected passes / fails in test runs | Spoofs, retrievals, or special conditions from previous tests not replaced or reset with `[ ]` |
554
+ | Unexpected actual values, or unexpected passes / fails in test runs | Pre-listed test properties have produced extra tests; changing properties as part of new tests prevents this |
555
+ | Unexpected actual values, or unexpected passes / fails in test runs | `.with` or `.in` values were mutated by a test and passed to subsequent tests; providing contents of these properties with a function prevents this |
556
556
 
557
557
 
558
558
 
559
559
  ## Known issues and workarounds
560
560
 
561
- - No bugs are known to exist at present.
561
+ Two issues are known to exist:
562
562
 
563
- - One unexpected result is known to exist:
564
- - Changing one or more properties in objects by themselves in the middle of tests (perhaps to emphasize them) can create extra tests.
565
- - The extra tests usually fail, because they have an unintended mix of old and new properties.
566
- - In this case, Rs is actually working properly, running prior tests with the new properties swapped in.
567
- - To avoid this problem, include changed properties in full tests to run, or restate `.of` along with the changed properties.
563
+ - If the contents of `.with` or `.in` are mutated by the code being tested, the mutated values are carried from test to test.
564
+ - To use the original, un-mutated args for successive tests, just return them from a function or method, and call it to set `.with` or `.in`.
565
+ - Ways to prevent this are being looked at.
568
566
 
569
- - This situation is rarely an issue, but ways to optionally prevent it are being considered.
567
+
568
+ - Changing one or more properties in objects by themselves in the middle of tests (perhaps to emphasize them) can create extra tests.
569
+ - The extra tests usually fail, because they have an unintended mix of old and new properties.
570
+ - In this case, Rs is actually working properly, running prior tests with the new properties swapped in.
571
+ - To avoid this problem, include changed properties in full tests to run, or restate `.of` along with the changed properties.
572
+ - This situation is rarely an issue, but ways to optionally prevent it are being considered.
570
573
 
571
574
 
572
575
 
573
576
  ## What's new in Risei
574
577
 
578
+ - Release **1.3.1** (February, 2024) reverses some changes in 1.3.0 that were intended to fully isolate tests against mutable args, but which caused incomplete or inaccurate object contents in some cases.&nbsp; Later work may restore full isolation in a different way at some point.
579
+
575
580
  - Release **1.3.0** (February, 2024) moves the display of test-loading error messages to a new gold bar that appears at the bottom of the summary when there are any loading errors.&nbsp; A sorting error that occurs when no test files exist yet has also been fixed.
576
581
 
577
582
  - Release **1.2.0** (January, 2024) changes sorting of test files.&nbsp; Previously they were displayed in the order found.&nbsp; Now the order remains the same, except that the last-edited file is moved to the bottom, making it easy to see the latest test results.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "risei",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "Risei is the framework that allows you to write unit tests as collections of values in JavaScript objects, so it's easy and fast, and tests don't serve as a drag on redesigns.",
5
5
  "keywords": [
6
6
  "unit test",
@@ -48,6 +48,6 @@
48
48
  "fs": "^0.0.1-security",
49
49
  "mocha": "^10.0.0",
50
50
  "morgan": "~1.9.1",
51
- "risei": "1.2.0"
51
+ "risei": "1.3.1"
52
52
  }
53
53
  }
@@ -53,10 +53,10 @@ export class SpoofClassMethodsFixture extends ASpoofingFixture {
53
53
  }
54
54
 
55
55
  // Converting to scope object type.
56
- let spoofDefinitionss = SpoofDefinition.fromNonceTuples(spoofNonces);
56
+ let spoofDefinitions = SpoofDefinition.fromNonceTuples(spoofNonces);
57
57
 
58
58
  // Storing spoofing definitions.
59
- this.#retainSpoofsByClass(type, spoofDefinitionss);
59
+ this.#retainSpoofsByClass(type, spoofDefinitions);
60
60
 
61
61
  // Storing originals for restoring later.
62
62
  this.#reserveOriginalsByClass();
@@ -67,9 +67,9 @@ export class SpoofClassMethodsFixture extends ASpoofingFixture {
67
67
 
68
68
  // region Dependencies of spoof()
69
69
 
70
- #retainSpoofsByClass(type, spoofDefinitionss) {
70
+ #retainSpoofsByClass(type, spoofDefinitions) {
71
71
  // Map of Maps: by type, then by method.
72
- for (let tuple of spoofDefinitionss) {
72
+ for (let tuple of spoofDefinitions) {
73
73
  // If no .target, use `type`.
74
74
  if (!tuple.target) {
75
75
  tuple.target = type;
@@ -299,8 +299,7 @@ export class TestDefinition {
299
299
  // Add (copy of) latest (for isolation),
300
300
  // only if it's ready to run as a test.
301
301
  if (latest.isRunnable) {
302
- let copy = latest.copy();
303
- tuples.push(copy);
302
+ tuples.push(latest);
304
303
  }
305
304
  }
306
305
 
@@ -14,6 +14,7 @@ export class TestFrame {
14
14
 
15
15
  testFrame(test) {
16
16
  console.log(`cruft : a`);
17
+
17
18
  // Outer try for fails of groundwork requested.
18
19
  try {
19
20
  console.log(`cruft : b`);
@@ -1,103 +1,106 @@
1
- /**/
2
-
3
- import { TypeIdentifier } from "./TypeIdentifier.js";
4
- import { Types } from "./Types.js";
5
-
6
- export class TotalCopier {
7
- #identifier = new TypeIdentifier();
8
-
9
- copy(original) /* passed */ {
10
- return this.recursiveCopy(original);
11
- }
12
-
13
- recursiveCopy(original) /* verified */ {
14
- /* &cruft, factor, possibly abstracting similar code,
15
- and/or return copy only once, or similar */
16
-
17
- /* Algorithm: First copied at current level by value if a value, by reference if
18
- not a value; then recursively replaced at next level, and so on. */
19
-
20
- let copy;
21
-
22
- let type = this.#identifier.identify(original);
23
-
24
- if (type === Types.isArray) {
25
- copy = [ ];
26
-
27
- // Traversal construction with recursion.
28
- for (let item of original) {
29
- let next = this.recursiveCopy(item);
30
- copy.push(next);
31
- }
32
-
33
- return copy;
34
- }
35
-
36
- if (type === Types.isMap) {
37
- copy = new Map();
38
-
39
- // Traversal construction with recursion.
40
- for (let entry of original.entries()) {
41
- let next = this.recursiveCopy(entry);
42
- copy.set(next[0], next[1]);
43
- }
44
-
45
- return copy;
46
- }
47
-
48
- if (type === Types.isSet) {
49
- copy = new Set();
50
-
51
- // Traversal construction with recursion.
52
- for (let value of original.values()) {
53
- let next = this.recursiveCopy(value);
54
- copy.add(next);
55
- }
56
-
57
- return copy;
58
- }
59
-
60
- if (type === Types.isDate) {
61
- copy = new Date(original);
62
- return copy;
63
- }
64
-
65
- // Actually copying isn't needed (or desirable) here,
66
- // since should always point to same thing regardless.
67
- if (type === Types.isClass) {
68
- copy = original;
69
- return copy;
70
- }
71
-
72
- // The workaround here makes a true independent copy of the
73
- // original function, regardless of its definition style.
74
- if (type === Types.isFunction) {
75
- let passer = [ original ];
76
- passer = [ ...passer ];
77
- copy = passer[0];
78
-
79
- return copy;
80
- }
81
-
82
- if (original instanceof Object) {
83
- copy = { };
84
-
85
- let keys = Object.keys(original);
86
-
87
- // Traversal construction with recursion.
88
- for (let key of Object.keys(original)) {
89
- let next = this.recursiveCopy(original[key]);
90
- copy[key] = next;
91
- }
92
-
93
- return copy;
94
- }
95
-
96
- /* &cruft, other object types here */
97
-
98
- // All object types exhausted, so this is a value,
99
- // which can be used directly for a copy.
100
- copy = original;
101
- return copy;
102
- }
103
- }
1
+ /**/
2
+
3
+ import { TypeIdentifier } from "./TypeIdentifier.js";
4
+ import { Types } from "./Types.js";
5
+
6
+ export class TotalCopier {
7
+ #identifier = new TypeIdentifier();
8
+
9
+ copy(original) /* passed */ {
10
+ return this.recursiveCopy(original);
11
+ }
12
+
13
+ recursiveCopy(original) /* verified */ {
14
+ /* &cruft, factor, possibly abstracting similar code,
15
+ and/or return copy only once, or similar */
16
+
17
+ /* Algorithm: First copied at current level by value if a value, by reference if
18
+ not a value; then recursively replaced at next level, and so on. */
19
+
20
+ let copy;
21
+
22
+ let type = this.#identifier.identify(original);
23
+
24
+ if (type === Types.isArray) {
25
+ copy = [ ];
26
+
27
+ // Traversal construction with recursion.
28
+ for (let item of original) {
29
+ let next = this.recursiveCopy(item);
30
+ copy.push(next);
31
+ }
32
+
33
+ return copy;
34
+ }
35
+
36
+ if (type === Types.isMap) {
37
+ copy = new Map();
38
+
39
+ // Traversal construction with recursion.
40
+ for (let entry of original.entries()) {
41
+ let next = this.recursiveCopy(entry);
42
+ copy.set(next[0], next[1]);
43
+ }
44
+
45
+ return copy;
46
+ }
47
+
48
+ if (type === Types.isSet) {
49
+ copy = new Set();
50
+
51
+ // Traversal construction with recursion.
52
+ for (let value of original.values()) {
53
+ let next = this.recursiveCopy(value);
54
+ copy.add(next);
55
+ }
56
+
57
+ return copy;
58
+ }
59
+
60
+ if (type === Types.isDate) {
61
+ copy = new Date(original);
62
+ return copy;
63
+ }
64
+
65
+ // Actually copying isn't needed (or desirable) here,
66
+ // since should always point to same thing regardless.
67
+ if (type === Types.isClass) {
68
+ copy = original;
69
+ return copy;
70
+ }
71
+
72
+ // The workaround here makes a true independent copy of the
73
+ // original function, regardless of its definition style.
74
+ if (type === Types.isFunction) {
75
+ let passer = [ original ];
76
+ passer = [ ...passer ];
77
+ copy = passer[0];
78
+
79
+ return copy;
80
+ }
81
+
82
+ if (original instanceof Object) {
83
+ // If original has a prototype, it's probably a class instance,
84
+ // so it's constructed, so it includes all methods and accessors.
85
+ let prototype = Object.getPrototypeOf(original);
86
+ copy = prototype !== null ? new prototype.constructor() : { };
87
+
88
+ let keys = Object.keys(original);
89
+
90
+ // Settable properties are added, with recursion.
91
+ for (let key of Object.keys(original)) {
92
+ let next = this.recursiveCopy(original[key]);
93
+ copy[key] = next;
94
+ }
95
+
96
+ return copy;
97
+ }
98
+
99
+ /* &cruft, other object types here */
100
+
101
+ // All object types exhausted, so this is a value,
102
+ // which can be used directly for a copy.
103
+ copy = original;
104
+ return copy;
105
+ }
106
+ }
@@ -1,103 +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
- }
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
+ }