cfprotected 0.1.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/.vscode/launch.json +19 -0
- package/.vscode/settings.json +3 -0
- package/README.md +152 -0
- package/index.js +183 -0
- package/package.json +24 -0
- package/test/test.js +138 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
|
3
|
+
// Hover to view descriptions of existing attributes.
|
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"configurations": [
|
|
7
|
+
{
|
|
8
|
+
"type": "node",
|
|
9
|
+
"request": "launch",
|
|
10
|
+
"name": "Jest Tests",
|
|
11
|
+
"program": "${workspaceRoot}/node_modules/jest/bin/jest.js",
|
|
12
|
+
"internalConsoleOptions": "openOnSessionStart",
|
|
13
|
+
"outFiles": [
|
|
14
|
+
"${workspaceRoot}/dist/**/*"
|
|
15
|
+
],
|
|
16
|
+
"envFile": "${workspaceRoot}/.env"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# CFProtected
|
|
2
|
+
This project provides a simple and straight forward means of sharing private
|
|
3
|
+
fields between classes, providing a facility similar to protected members in
|
|
4
|
+
other languages. This project also provides a few other convenience functions
|
|
5
|
+
to work around some the issues inherent in using private fields.
|
|
6
|
+
|
|
7
|
+
## How to use
|
|
8
|
+
Let's just start with a simple example:
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
const { share, accessor } = require("cfprotected");
|
|
12
|
+
|
|
13
|
+
class Example {
|
|
14
|
+
//shared static private fields
|
|
15
|
+
static #shared = share(this, {
|
|
16
|
+
//Data fields
|
|
17
|
+
theAnswer: 42,
|
|
18
|
+
//Methods
|
|
19
|
+
callMe: () => {
|
|
20
|
+
console.log("Arrow notation simplifies things.");
|
|
21
|
+
},
|
|
22
|
+
//Accessor properties
|
|
23
|
+
prop: accessor({
|
|
24
|
+
get: () => { return "An ordinary getter."; },
|
|
25
|
+
set: (v) => { /* set something here */ }
|
|
26
|
+
})
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
//shared instance private fields
|
|
30
|
+
#shared = share(this, Example, {
|
|
31
|
+
data: "HHGTTG",
|
|
32
|
+
method: () => { /* do whatever */ },
|
|
33
|
+
readOnly: accessor({
|
|
34
|
+
get: () => { return "Nothing to see here!" }
|
|
35
|
+
})
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
whatIsTheAnswer() {
|
|
39
|
+
return this.#shared.theAnswer;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class Derived extends Example {
|
|
44
|
+
static #shared = share(this, {});
|
|
45
|
+
|
|
46
|
+
#shared = share(this, Derived, {});
|
|
47
|
+
|
|
48
|
+
test() {
|
|
49
|
+
console.log(`the Answer is ${this.cla$$.#shared.theAnswer}`);
|
|
50
|
+
console.log(`Why use arrow notation?`);
|
|
51
|
+
this.#shared.callMe();
|
|
52
|
+
this.#shared.theAnswer /= 2;
|
|
53
|
+
console.log(`What is the answer? ${this.whatIsTheAnswer()}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
There are 5 API functions:
|
|
59
|
+
|
|
60
|
+
* share
|
|
61
|
+
* accessor
|
|
62
|
+
* saveSelf
|
|
63
|
+
* abstract
|
|
64
|
+
* final
|
|
65
|
+
|
|
66
|
+
The first provides the actual sharing feature. The next 2 provide for work arounds to issues related to this sharing feature as well as Proxy support. The last 2 are just helper functions providing the corresponding limitation to the class.
|
|
67
|
+
|
|
68
|
+
## **share(instance, class?, memberObject)**
|
|
69
|
+
This function does all the leg work in setting up sharing between a given class and those derived from it. It follows the following general steps.
|
|
70
|
+
|
|
71
|
+
1. Verify the parameters.
|
|
72
|
+
2. Find any shared member left for the instance.
|
|
73
|
+
3. Create/modify the protected data structure.
|
|
74
|
+
4. Construct and store the shared member record.
|
|
75
|
+
5. Create a class specific accessor object and return it.
|
|
76
|
+
|
|
77
|
+
The object returned by this function contains an accessor for each of the properties described in the `memberObject` as well as accessors for each of the members that were listed in the shared member record. Any members re-defined on the derived class shadow the same member from the base class **<sup>+</sup>**. As a result, functions of the base class that access this member when used from an instance of the derived class will access the derived class version of that member.
|
|
78
|
+
|
|
79
|
+
### Notes:
|
|
80
|
+
**+** There is 1 caveat when it comes to shadowing base members. Accessors need to be handled specially. If an accessor is defined directly in `memberObject`, it cannot be properly shadowed. The methods of each class will only be able to access the version of the member defined in that class. To ensure accessors can properly be shadowed across the entire inheritance chain, use the following API function.
|
|
81
|
+
|
|
82
|
+
## **accessor(descriptor)**
|
|
83
|
+
The property descriptor passed to this function is a limited version of the standard ES property descriptor object, only allowing `get` and `set` members. All other members of this object are ignored. A new property descriptor is created and tagged so that the `share` function will use it as the accessor descriptor for the corresponding property in the accessor object. It is this relocation that allows for proper shadowing of shared accessors.
|
|
84
|
+
|
|
85
|
+
### Notes:
|
|
86
|
+
There is a secondary purpose for this `accessor` method. The biggest "gotcha" related to this approach to sharing members is that the members are all owned by an object other than `this`. At the same time, if the member is a function or accessor, it needs to be bound to `this`. Only runtime assignment can guarantee this, so creating such methods as field initializers is the only simple way to do it without resorting to the constructor.
|
|
87
|
+
|
|
88
|
+
## **saveSelf(self, name)**
|
|
89
|
+
This method provides a means to work around the issue that comes along with using private fields together with Proxy. Since Proxy does not pass through access to private fields without a full membrane setup, the most straight forward solution is to provide a "self" property on the instance. This function is a convenience function that allows you to create and name that property. Use this function in the static block and/or constructor.
|
|
90
|
+
```js
|
|
91
|
+
class Example {
|
|
92
|
+
static {
|
|
93
|
+
saveSelf(this, "shared");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
constructor() {
|
|
97
|
+
saveSelf(this, "shared");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
...
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
As a bonus, when used on the constructor, it creates an additional property named "cla\$\$" on the prototype. This "cla\$\$" property gives instances a means to reference the class constructor even when the class itself is anonymous. This is a fill-in feature for one of the TC39 proposals offering syntax for the same.
|
|
105
|
+
|
|
106
|
+
## **abstract(klass)**
|
|
107
|
+
This method is a class wrapper that prevents instances of the class from being constructed directly. To construct an instance of the class you must extend it. This should be nearly identical to the same functionality that exists in some compiled languages.
|
|
108
|
+
```js
|
|
109
|
+
const Example = abstract(class {
|
|
110
|
+
...
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Note:
|
|
115
|
+
This method uses a proxy internally to keep things consistent. However, the Proxy is not a membrane. So it's best if any use of static private fields is accessed through the property created by using `saveSelf(...)`.
|
|
116
|
+
|
|
117
|
+
## **final(klass)**
|
|
118
|
+
This method is a class wrapper that prevents instances of the class from being created using descendant classes.
|
|
119
|
+
```js
|
|
120
|
+
const Example = final(class {
|
|
121
|
+
...
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Note:
|
|
126
|
+
Just as with `abstract(...)`, this method uses a proxy internally to keep things consistent. However, the Proxy is not a membrane. So it's best if any use of static private fields is accessed through the property created by using `saveSelf(...)`. Unfortunately, there's also a limitation with final. Normally in compiled languages, it is a syntax error to even create a class that extends from a final class. However, without taking over the means of building the class (as can be done with Babel or my ClassicJS library) it's just not possible to restrict that event. As a result, the only thing left to check for is construction of instances of such an error.
|
|
127
|
+
|
|
128
|
+
## Other features
|
|
129
|
+
There will be occasions when a shared function that shadows an ancestor function needs to call the ancestor's function. Unfortunately, `super` cannot give you access to these. There is a similar problem when accessing accessors and data properties. To satisfy this need, the class-specific accessor option is given an additional property: `$uper`. Using this property, it is possible to reach the ancestor version of any shared member.
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
class A {
|
|
133
|
+
#shared = share(this, A, {
|
|
134
|
+
doSomething: () => { console.log(`Called A::doSomething`); }
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
...
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
class B extends A {
|
|
141
|
+
#shared = share(this, B, {
|
|
142
|
+
doSomething: () => {
|
|
143
|
+
console.log(`Called B::doSomething`);
|
|
144
|
+
this.#shared.$uper.doSomething();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test() {
|
|
149
|
+
this.#shared.doSomething();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
package/index.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
const memos = new WeakMap();
|
|
2
|
+
const ACCESSOR = Symbol();
|
|
3
|
+
|
|
4
|
+
function getAllOwnKeys(o) {
|
|
5
|
+
return Object.getOwnPropertyNames(o)
|
|
6
|
+
.concat(Object.getOwnPropertySymbols(o));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Used to both store inherited property information as well as retrieve it.
|
|
11
|
+
* @param {Object} inst The instance object that will own the shared members.
|
|
12
|
+
* @param {Function?} klass The constructor function of the class sharing
|
|
13
|
+
* members. Optional. If omitted, defaults to inst.
|
|
14
|
+
* @param {Object} members The object containing the properties being shared.
|
|
15
|
+
* @returns {Object} The fully constructed inheritance object.
|
|
16
|
+
*/
|
|
17
|
+
module.exports.share = function share(inst, klass, members) {
|
|
18
|
+
let retval = {};
|
|
19
|
+
|
|
20
|
+
if ((typeof(inst) == "function")
|
|
21
|
+
&& klass && (typeof(klass) == "object")
|
|
22
|
+
&& (members === void 0)) {
|
|
23
|
+
members = klass;
|
|
24
|
+
klass = inst;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!inst || !["function", "object"].includes(typeof(inst))) {
|
|
28
|
+
throw new TypeError(`Expected inst to be a function or an object.`);
|
|
29
|
+
}
|
|
30
|
+
if (!klass || (typeof(klass) != "function")) {
|
|
31
|
+
throw new TypeError(`Expected klass to be a function.`);
|
|
32
|
+
}
|
|
33
|
+
if (!members || (typeof(members) != "object")) {
|
|
34
|
+
throw new TypeError(`Expected members to be an object.`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
* Each class' memo entry has the following structure:
|
|
40
|
+
*
|
|
41
|
+
* inst: {
|
|
42
|
+
* data: <Object> - the actual protected data object
|
|
43
|
+
* inheritance: <Object> - the object of accessor properties to share
|
|
44
|
+
* with descendant classes.
|
|
45
|
+
* }
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
//Find the nearest known registered ancestor
|
|
49
|
+
let ancestor = Object.getPrototypeOf(klass);
|
|
50
|
+
while (ancestor && !memos.has(ancestor)) {
|
|
51
|
+
ancestor = Object.getPrototypeOf(ancestor);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//Get the memo from that ancestor
|
|
55
|
+
let ancestorMemo = memos.get(ancestor) || new WeakMap();
|
|
56
|
+
|
|
57
|
+
//Create a memo for the current class
|
|
58
|
+
if (!memos.has(klass)) {
|
|
59
|
+
memos.set(klass, new WeakMap());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//Get the protected data object.
|
|
63
|
+
let memo = ancestorMemo.get(inst) || {data: {}, $uper: {}, inheritance: null};
|
|
64
|
+
let protData = memo.data;
|
|
65
|
+
|
|
66
|
+
//Get the details of the protected properties.
|
|
67
|
+
let mDesc = Object.getOwnPropertyDescriptors(members);
|
|
68
|
+
let mKeys = getAllOwnKeys(members);
|
|
69
|
+
|
|
70
|
+
//Change the prototype of protoData using the new members.
|
|
71
|
+
let prototype = Object.getPrototypeOf(protData);
|
|
72
|
+
let proto = Object.create(prototype,
|
|
73
|
+
Object.fromEntries(mKeys
|
|
74
|
+
.filter(k => !mDesc[k].value?.hasOwnProperty(ACCESSOR))
|
|
75
|
+
.map(k => [k, mDesc[k]])));
|
|
76
|
+
Object.setPrototypeOf(protData, proto);
|
|
77
|
+
|
|
78
|
+
//Build the accessors for this class.
|
|
79
|
+
mKeys.forEach(m => {
|
|
80
|
+
let desc = (mDesc[m].value?.hasOwnProperty(ACCESSOR))
|
|
81
|
+
? {
|
|
82
|
+
enumerable: true,
|
|
83
|
+
get: mDesc[m].value.get,
|
|
84
|
+
set: mDesc[m].value.set
|
|
85
|
+
}
|
|
86
|
+
: mDesc[m];
|
|
87
|
+
|
|
88
|
+
Object.defineProperty(retval, m, desc);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
//Define the "super" accessors
|
|
92
|
+
Object.defineProperty(retval, "$uper", { value: {} });
|
|
93
|
+
|
|
94
|
+
for (let key of mKeys) {
|
|
95
|
+
if (key in prototype) {
|
|
96
|
+
let obj = prototype;
|
|
97
|
+
while (!obj.hasOwnProperty(key)) {
|
|
98
|
+
obj = Object.getPrototypeOf(obj);
|
|
99
|
+
}
|
|
100
|
+
Object.defineProperty(retval.$uper, key, Object.getOwnPropertyDescriptor(obj, key));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
//Attach the super inheritance
|
|
105
|
+
Object.setPrototypeOf(retval.$uper, memo.$uper);
|
|
106
|
+
|
|
107
|
+
//Inherit the inheritance
|
|
108
|
+
Object.setPrototypeOf(retval, memo.inheritance);
|
|
109
|
+
|
|
110
|
+
//Save the inheritance & protected data
|
|
111
|
+
memos.get(klass).set(inst, {
|
|
112
|
+
data: protData,
|
|
113
|
+
inheritance: retval,
|
|
114
|
+
$uper: retval.$uper
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return retval;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Binds the class instance to itself to allow code to selectively avoid Proxy
|
|
122
|
+
* issues, especially ones involving private fields. Also binds the class
|
|
123
|
+
* constructor to the instance for static referencing as "cla$$".
|
|
124
|
+
* @param {Object} self The current class instance as seen from the constructor.
|
|
125
|
+
* @param {String} name The name of the field on which to bind the instance.
|
|
126
|
+
*/
|
|
127
|
+
module.exports.saveSelf = function(self, name) {
|
|
128
|
+
Object.defineProperty(self, name, {value: self});
|
|
129
|
+
if (typeof(self) == "function") {
|
|
130
|
+
Object.defineProperty(self.prototype, "cla$$", {value: self});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Marks the property as a shared, overrideable accessor and defines the
|
|
136
|
+
* getter and setter methods for it.
|
|
137
|
+
* @param {Object} desc Object containing get and/or set definitions for the
|
|
138
|
+
* accessor.
|
|
139
|
+
* @returns {Object} A tagged object that will be used to create the access
|
|
140
|
+
* bindings for the property.
|
|
141
|
+
*/
|
|
142
|
+
module.exports.accessor = function(desc) {
|
|
143
|
+
if ((typeof(desc) == "object") &&
|
|
144
|
+
(("get" in desc) || ("set" in desc))) {
|
|
145
|
+
return {
|
|
146
|
+
[ACCESSOR]: undefined,
|
|
147
|
+
get: desc.get,
|
|
148
|
+
set: desc.set
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* A class wrapper that blocks construction of an instance if the class being
|
|
155
|
+
* instantiated is not a descendant of the current class.
|
|
156
|
+
* @param {Function} klass The constructor of the current class.
|
|
157
|
+
*/
|
|
158
|
+
module.exports.abstract = function(klass) {
|
|
159
|
+
return new Proxy(klass, {
|
|
160
|
+
construct(target, args, newTarget) {
|
|
161
|
+
if (newTarget.prototype === klass.prototype)
|
|
162
|
+
throw new TypeError(`Cannot construct instance of abstract class.`);
|
|
163
|
+
|
|
164
|
+
return Reflect.construct(target, args, newTarget);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* A class wrapper that blocks construction of an instance if the class being
|
|
171
|
+
* constructed is a descendant of the current class.
|
|
172
|
+
* @param {Function} klass The constructor of the current class.
|
|
173
|
+
*/
|
|
174
|
+
module.exports.final = function(klass) {
|
|
175
|
+
return new Proxy(klass, {
|
|
176
|
+
construct(target, args, newTarget) {
|
|
177
|
+
if (newTarget.prototype !== klass.prototype)
|
|
178
|
+
throw new TypeError(`Cannot extend final class.`);
|
|
179
|
+
|
|
180
|
+
return Reflect.construct(target, args, newTarget);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cfprotected",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "An implementation of protected fields on top of class fields.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "jest"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"class",
|
|
11
|
+
"private",
|
|
12
|
+
"protected",
|
|
13
|
+
"inheritance"
|
|
14
|
+
],
|
|
15
|
+
"author": "Ranando D. King",
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/rdking/CFProtected.git"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"jest": "^27.4.3"
|
|
23
|
+
}
|
|
24
|
+
}
|
package/test/test.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const { TestWatcher } = require("@jest/core");
|
|
2
|
+
const { share, saveSelf, accessor } = require("../index"); //require("cfprotected");
|
|
3
|
+
|
|
4
|
+
class Base {
|
|
5
|
+
#prot = share(this, Base, {
|
|
6
|
+
num: 42,
|
|
7
|
+
name: "John Jacob Jingleheimerschmidt",
|
|
8
|
+
method: () => {
|
|
9
|
+
return "It works.";
|
|
10
|
+
},
|
|
11
|
+
prop: accessor({
|
|
12
|
+
get: () => this.propTestVal
|
|
13
|
+
}),
|
|
14
|
+
superTest: () => {
|
|
15
|
+
console.log(`Called Base::superTest() ...`);
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
constructor() {
|
|
21
|
+
saveSelf(this, "pvt");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
testName = "John Jacob Jingleheimerschmidt";
|
|
25
|
+
propTestVal = "I can make this return anything!";
|
|
26
|
+
|
|
27
|
+
checkProp(proxied) {
|
|
28
|
+
expect((proxied?this.pvt:this).#prot.prop).toBe(this.propTestVal);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
run() {
|
|
32
|
+
test(`Access to shared members should just work on the instance`, () => {
|
|
33
|
+
expect(this.#prot.num).toBe(42);
|
|
34
|
+
expect(this.#prot.name).toBe(this.testName);
|
|
35
|
+
expect(this.#prot.method()).toBe("It works.");
|
|
36
|
+
this.checkProp();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test(`Access to shared members should work even through a Proxy`, () => {
|
|
40
|
+
let that = new Proxy(this, {});
|
|
41
|
+
expect(that.pvt.#prot.num).toBe(42);
|
|
42
|
+
expect(that.pvt.#prot.name).toBe(this.testName);
|
|
43
|
+
expect(that.pvt.#prot.method()).toBe("It works.");
|
|
44
|
+
this.checkProp(true);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class Derived extends Base {
|
|
50
|
+
#prot = share(this, Derived, {
|
|
51
|
+
otherMethod: () => {
|
|
52
|
+
this.#prot.name = this.testName;
|
|
53
|
+
},
|
|
54
|
+
prop: accessor({
|
|
55
|
+
get: () => this.propTestVal2
|
|
56
|
+
})
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
constructor() {
|
|
60
|
+
super();
|
|
61
|
+
saveSelf(this, "pvt");
|
|
62
|
+
this.propTestVal2 = this.propTestVal;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
run() {
|
|
66
|
+
super.run();
|
|
67
|
+
|
|
68
|
+
test(`Should be able to change a shared property value`, () => {
|
|
69
|
+
this.testName = "A. Nony Mouse";
|
|
70
|
+
expect(() => { this.#prot.otherMethod(); }).not.toThrow();
|
|
71
|
+
this.checkProp();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
class NonParticipant extends Base {}
|
|
77
|
+
|
|
78
|
+
class GrandChild extends NonParticipant {
|
|
79
|
+
#prot = share(this, GrandChild, {
|
|
80
|
+
otherMethod: () => {
|
|
81
|
+
this.#prot.name = this.testName;
|
|
82
|
+
},
|
|
83
|
+
superTest: () => {
|
|
84
|
+
console.log(`Called GrandChild::superTest() ...`);
|
|
85
|
+
return 1 + this.pvt.#prot.$uper.superTest();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
constructor() {
|
|
90
|
+
super();
|
|
91
|
+
saveSelf(this, "pvt");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
run() {
|
|
95
|
+
super.run();
|
|
96
|
+
|
|
97
|
+
test(`Should be able to change a shared property value`, () => {
|
|
98
|
+
this.testName = "A. Nony Mouse 1";
|
|
99
|
+
expect(() => { this.#prot.otherMethod(); }).not.toThrow();
|
|
100
|
+
this.checkProp();
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
class SuperTest extends GrandChild {
|
|
106
|
+
#prot = share(this, SuperTest, {
|
|
107
|
+
superTest: () => {
|
|
108
|
+
console.log(`Called SuperTest::superTest() ...`);
|
|
109
|
+
return 1 + this.pvt.#prot.$uper.superTest();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
constructor() {
|
|
114
|
+
super();
|
|
115
|
+
saveSelf(this, "pvt");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
run() {
|
|
119
|
+
test(`Should be able to call super through the entire inheritance chain`, () => {
|
|
120
|
+
expect(this.pvt.#prot.superTest()).toBe(3);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
describe(`Testing shared elements in the base class`, () => {
|
|
126
|
+
(new Base).run();
|
|
127
|
+
});
|
|
128
|
+
describe(`Testing shared elements in a direct descendant`, () => {
|
|
129
|
+
(new Derived).run();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe(`Testing shared elements inherited from a non-participant`, () => {
|
|
133
|
+
(new GrandChild).run();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe(`Testing that $uper works in all cases`, () => {
|
|
137
|
+
(new SuperTest).run();
|
|
138
|
+
})
|