Comparing version 1.2.1 to 1.2.2
62
index.js
@@ -230,4 +230,8 @@ "use strict" | ||
let data = this.objectData.get(target); | ||
//save a reference to the event-pool because we are about to immediately empty it, so all future changes, even those | ||
//that can occur now because of the listeners, will go to a new event-pool and will be emitted next round (after delay) | ||
let eventPool = data.eventPool; | ||
data.eventPool = []; | ||
for(let change of data.eventPool) { //FIFO - first event in, first event out | ||
for(let change of eventPool) { //FIFO - first event in, first event out | ||
for(let listener of data.listeners) { //listener = [event, function] | ||
@@ -242,7 +246,5 @@ if(listener[0] === change.type) { //will invoke create/update/delete listeners one by one. | ||
if(listener[0] === acceptableEvents[0]) { //change | ||
listener[1].call(data.proxy, data.eventPool); //on(change) is always called with an array of one or more changes | ||
listener[1].call(data.proxy, eventPool); //on(change) is always called with an array of one or more changes | ||
} | ||
} | ||
data.eventPool = []; //empty the event pool | ||
} | ||
@@ -288,2 +290,5 @@ | ||
} | ||
else if(!target.propertyIsEnumerable(property) || typeof property === 'symbol') { | ||
return target[property]; //non-enumerable or non-path'able aren't proxied | ||
} | ||
else { | ||
@@ -297,2 +302,12 @@ let data = this.objectData.get(target[property]); | ||
let data = this.objectData.get(target); | ||
/** | ||
* property can be a regular object because of 3 possible reasons: | ||
* 1. proxy is deleted from tree but user keeps accessing it then it means he saved a reference | ||
* 2. it is a non-enumerable property which means it was intentionally hidden | ||
* 3. property is a symbol and symbols can't be proxied because we can't create a normal path for them. | ||
* these properties are not proxied and should not emit change-event. | ||
* except for: length | ||
* TODO - make a list of all possible properties exceptions (maybe function 'name'?) | ||
*/ | ||
if(data.status === statuses[2]) { //blocked from changing values | ||
@@ -302,7 +317,14 @@ console.error(`can't change value of property '${property}'. object is blocked.`); | ||
} | ||
else if(data.status === statuses[3]) { | ||
//if proxy is deleted from tree but user keeps accessing it then it means he saved a reference and is now using it as a regular object | ||
else if(data.status === statuses[3] || typeof property === 'symbol') { | ||
target[property] = value; | ||
return true; | ||
} | ||
else if(property !== 'length' && !target.propertyIsEnumerable(property)) { | ||
//if setting a whole new property then it is non-enumerable (yet) so a further test is needed | ||
let descriptor = Object.getOwnPropertyDescriptor(target, property); | ||
if(typeof descriptor === 'object' && descriptor.enumerable === false) { | ||
target[property] = value; | ||
return true; | ||
} | ||
} | ||
@@ -331,3 +353,27 @@ let typeofvalue = realtypeof(value); | ||
defineProperty: (target, property, descriptor) => { | ||
//currently handling nothing, but just excluding non-enumerable properties from being proxied. | ||
//also excludes symbol properties as they can't have a path | ||
Object.defineProperty(target, property, descriptor); | ||
let typeofvalue = realtypeof(descriptor.value); | ||
if(acceptableTypes.includes(typeofvalue) && descriptor.enumerable === true && typeof property !== 'symbol') { | ||
this.createProxy(descriptor.value, target, `${path}${currentPathProperty}`, property); //if trying to add a new value which is an object then make it a proxy | ||
let newValue = descriptor.value; | ||
if(!this.emitReference) { //also prevents emitting proxies | ||
newValue = simpleClone(descriptor.value); | ||
} | ||
add2emitQueue.call(this, target, property2path(target, property), undefined, newValue); | ||
} | ||
return true; | ||
}, | ||
deleteProperty: (target, property) => { | ||
if(!target.propertyIsEnumerable(property) || typeof property === 'symbol') { | ||
//non-proxied properties simply get deleted and nothing more | ||
delete target[property]; | ||
return true; | ||
} | ||
let data = this.objectData.get(target); | ||
@@ -513,2 +559,6 @@ if(data.status === statuses[2]) { //blocked from changing values | ||
} | ||
static simpleClone(obj) { | ||
return simpleClone(obj); | ||
} | ||
} | ||
@@ -515,0 +565,0 @@ })(); |
{ | ||
"name": "proxserve", | ||
"version": "1.2.1", | ||
"version": "1.2.2", | ||
"description": "Proxy Observe on objects and properties changes", | ||
@@ -5,0 +5,0 @@ "license": "Apache 2.0", |
@@ -99,3 +99,31 @@ "use strict" | ||
test('3. Proxies should contain built-in functions', () => { | ||
test('3. defineProperty should convert string/number properties to proxy', () => { | ||
let origin = cloneDeep(testObject); | ||
let proxy = new Proxserve(origin); | ||
let sym = Symbol.for('sym'); | ||
let desc = { | ||
enumerable: false, | ||
configurable: true, | ||
writable: true, | ||
value: { this_is: { inner: 'some value' } } | ||
}; | ||
Object.defineProperty(proxy, sym, cloneDeep(desc)); | ||
Object.defineProperty(proxy, 'obj', cloneDeep(desc)); | ||
expect(util.types.isProxy(proxy[sym])).toBe(false); | ||
expect(util.types.isProxy(proxy.obj)).toBe(false); | ||
desc.enumerable = true; | ||
Object.defineProperty(proxy, sym, cloneDeep(desc)); | ||
Object.defineProperty(proxy, 'obj', cloneDeep(desc)); | ||
expect(util.types.isProxy(proxy[sym])).toBe(false); //symbol isn't proxied anyway | ||
expect(util.types.isProxy(proxy[sym].this_is)).toBe(false); | ||
expect(util.types.isProxy(proxy.obj)).toBe(true); | ||
expect(util.types.isProxy(proxy.obj.this_is)).toBe(true); | ||
}); | ||
test('4. Proxies should contain built-in functions', () => { | ||
let proxy = new Proxserve(cloneDeep(testObject)); | ||
@@ -134,3 +162,3 @@ | ||
test('4. Basic events of changes', (done) => { | ||
test('5. Basic events of changes', (done) => { | ||
let proxy = new Proxserve(cloneDeep(testObject)); | ||
@@ -188,3 +216,3 @@ proxy.on('create', function(change) { | ||
test('5. Delay of events', (done) => { | ||
test('6. Delay of events', (done) => { | ||
let proxy = new Proxserve(cloneDeep(testObject), { delay: 0 }); | ||
@@ -236,3 +264,3 @@ let changes = []; | ||
test('6. Stop/Block/Activate proxies', () => { | ||
test('7. Stop/Block/Activate proxies', () => { | ||
let proxy = new Proxserve(cloneDeep(testObject), {delay:0}); | ||
@@ -318,3 +346,48 @@ let numberOfEmits = 0; | ||
test('7. Destroy proxy and sub-proxies', (done) => { | ||
test('8. get/set/delete properties after defineProperty', () => { | ||
let origin = cloneDeep(testObject); | ||
let proxy = new Proxserve(origin); | ||
let sym = Symbol.for('sym'); | ||
let desc = { | ||
enumerable: false, | ||
configurable: true, | ||
writable: true, | ||
value: 5 | ||
}; | ||
let valueObj = { this_is: { inner: 'some value' } }; | ||
Object.defineProperty(proxy, sym, cloneDeep(desc)); | ||
Object.defineProperty(proxy, 'obj', cloneDeep(desc)); | ||
proxy[sym] = cloneDeep(valueObj); //set | ||
proxy.obj = cloneDeep(valueObj); //set | ||
expect(util.types.isProxy(proxy[sym])).toBe(false); | ||
expect(util.types.isProxy(proxy[sym].this_is)).toBe(false); | ||
expect(util.types.isProxy(proxy.obj)).toBe(false); | ||
expect(util.types.isProxy(proxy.obj.this_is)).toBe(false); | ||
delete proxy[sym]; | ||
delete proxy.obj; //deleting a non-proxy | ||
expect(proxy[sym]).toBe(undefined); | ||
expect(proxy.obj).toBe(undefined); | ||
desc.enumerable = true; | ||
Object.defineProperty(proxy, sym, cloneDeep(desc)); | ||
Object.defineProperty(proxy, 'obj', cloneDeep(desc)); | ||
proxy[sym] = cloneDeep(valueObj); //set | ||
proxy.obj = cloneDeep(valueObj); //set | ||
expect(util.types.isProxy(proxy[sym])).toBe(false); //symbol isn't proxied anyway | ||
expect(util.types.isProxy(proxy[sym].this_is)).toBe(false); | ||
expect(util.types.isProxy(proxy.obj)).toBe(true); | ||
expect(util.types.isProxy(proxy.obj.this_is)).toBe(true); | ||
delete proxy[sym]; | ||
delete proxy.obj; //deleting a regular proxy | ||
expect(proxy[sym]).toBe(undefined); | ||
expect(proxy.obj).toBe(undefined); | ||
}); | ||
test('9. Destroy proxy and sub-proxies', (done) => { | ||
let proxy = new Proxserve(cloneDeep(testObject), {delay:-950}); //hack to decrease the 1000ms delay of destroy | ||
@@ -362,3 +435,3 @@ Proxserve.destroy(proxy); | ||
test('8. Keep using proxies after deletion/detachment in non-strict instantiation', (done) => { | ||
test('10. Keep using proxies after deletion/detachment in non-strict instantiation', (done) => { | ||
let proxy = new Proxserve(cloneDeep(testObject), {delay:-1000, strict:false}); | ||
@@ -378,3 +451,3 @@ let level1_1 = proxy.level1_1; | ||
test('9. Observe on referenced changes and cloned changes', (done) => { | ||
test('11. Observe on referenced changes and cloned changes', (done) => { | ||
let proxy = new Proxserve(cloneDeep(testObject), {emitReference: false}); | ||
@@ -412,3 +485,3 @@ proxy.level1_1.on('change', function(changes) { | ||
//benchmark on a CPU with baseclock of 3.6 GHz is around 0.5s | ||
test('10. Proxserve 50,000 objects in less than 1 second', () => { | ||
test('12. Proxserve 50,000 objects in less than 1 second', () => { | ||
let objectsInTest = deepCountObjects(testObject); | ||
@@ -433,3 +506,3 @@ let repeatitions = Math.ceil(50000 / objectsInTest); | ||
//benchmark on a CPU with baseclock of 3.6 GHz is around 0.9s | ||
test('11. Destroy 50,000 proxserves in less than 1.5 seconds', (done) => { | ||
test('13. Destroy 50,000 proxserves in less than 1.5 seconds', (done) => { | ||
let objectsInTest = deepCountObjects(testObject); | ||
@@ -455,3 +528,3 @@ let repeatitions = Math.ceil(50000 / objectsInTest); | ||
test('12. Comprehensive events of changes', (done) => { | ||
test('14. Comprehensive events of changes', (done) => { | ||
let proxy = new Proxserve(cloneDeep(testObject), {emitReference: false}); | ||
@@ -614,3 +687,3 @@ proxy.on('create', function(change) { | ||
test('13. splitPath - split path to segments', () => { | ||
test('15. splitPath - split path to segments', () => { | ||
let path = Proxserve.splitPath('.level2_1.level3_1'); | ||
@@ -642,3 +715,3 @@ let path2 = Proxserve.splitPath('level2_1.level3_1'); | ||
test('14. getPathTarget - get target property of object and path', (done) => { | ||
test('16. getPathTarget - get target property of object and path', (done) => { | ||
let proxy = new Proxserve(cloneDeep(testObject), {delay: 0}); | ||
@@ -680,2 +753,23 @@ proxy.on('change', function(changes) { | ||
proxy.removeAllListeners(); | ||
}); | ||
test('17. On-change listener that makes its own changes', (done) => { | ||
let proxy = new Proxserve(cloneDeep(testObject)); | ||
proxy.level1_1.arr1.on('change', function(changes) { | ||
if(changes.length === 3) { | ||
let propValue = Proxserve.getPathTarget(this, changes[0].path); | ||
proxy.level1_1.arr1[0] = 123; //immediate change should be insterted to next round event emiting | ||
expect(changes.length).toBe(3); //shouldn't have changed yet | ||
expect(changes[0].value).toBe(17); | ||
expect(changes[1].value).toBe(18); | ||
expect(changes[2].value).toBe(19); | ||
} else { | ||
expect(changes.length).toBe(1); | ||
expect(changes[0].value).toBe(123); | ||
done(); | ||
} | ||
}); | ||
proxy.level1_1.arr1[0] = 17; | ||
proxy.level1_1.arr1[1] = 18; | ||
proxy.level1_1.arr1[2] = 19; | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
56032
1181