object-manage
Advanced tools
Comparing version 0.4.0 to 0.5.0
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
module.exports = function(grunt){ | ||
@@ -17,3 +18,3 @@ | ||
}, | ||
src: ['test/init.js','test/*.test.js'] | ||
src: ['test/*.test.js'] | ||
} | ||
@@ -20,0 +21,0 @@ }, |
@@ -0,1 +1,2 @@ | ||
'use strict'; | ||
module.exports = require('./lib/ObjectManage') |
@@ -1,3 +0,6 @@ | ||
var merge = require('merge-recursive') | ||
, util = require('util') | ||
'use strict'; | ||
var util = require('util') | ||
, events = require('events') | ||
, mergeRecursive = require('merge-recursive') | ||
, objectMerge = require('object-merge') | ||
@@ -64,2 +67,3 @@ /** | ||
* @param path | ||
* @returns {boolean} | ||
*/ | ||
@@ -69,11 +73,167 @@ var remove = function(target,path){ | ||
for(var a,p=path.split('.'),i=0; o&&(a=p[i++]); o=o[a]){ | ||
if(i !== p.length && undefined === o[a]) break | ||
else if(i === p.length){ | ||
if(i !== p.length && undefined === o[a]){ | ||
return false | ||
} else if(i === p.length){ | ||
o[a] = undefined | ||
delete o[a] | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
/** | ||
* Object Manage Validate Instance | ||
* @param verb | ||
* @param message | ||
* @param path | ||
* @param value | ||
* @constructor | ||
*/ | ||
var Validate = function(verb,message,path,value){ | ||
Error.call(this) | ||
this.message = message | ||
this.verb = verb | ||
this.path = path | ||
this.value = value | ||
} | ||
//inherit Error prototype | ||
util.inherits(Validate,Error) | ||
/** | ||
* Verb to handle the validation call | ||
* @type {null} | ||
*/ | ||
Validate.prototype.verb = null | ||
/** | ||
* Path being operated on | ||
* @type {null} | ||
*/ | ||
Validate.prototype.path = null | ||
/** | ||
* Value of the call be validated | ||
* @type {undefined} | ||
*/ | ||
Validate.prototype.value = undefined | ||
/** | ||
* Validate Helpers construction | ||
* @param path | ||
* @param value | ||
* @constructor | ||
*/ | ||
var ValidateHelpers = function(path,value){ | ||
this.path = path | ||
this.value = value | ||
} | ||
/** | ||
* Path being operated on | ||
* @type {null} | ||
*/ | ||
ValidateHelpers.prototype.path = null | ||
/** | ||
* Value being operated on | ||
* @type {null} | ||
*/ | ||
ValidateHelpers.prototype.value = null | ||
/** | ||
* Convenience method to drop a request | ||
* @param message | ||
*/ | ||
ValidateHelpers.prototype.drop = function(message){ | ||
throw new Validate('drop',message,this.path,this.value) | ||
} | ||
/** | ||
* Convenience method to reject a request | ||
* @param message | ||
*/ | ||
ValidateHelpers.prototype.reject = function(message){ | ||
throw new Validate('reject',message,this.path,this.value) | ||
} | ||
/** | ||
* Convenience method to warn about a request | ||
* @param message | ||
* @param value | ||
*/ | ||
ValidateHelpers.prototype.warn = function(message,value){ | ||
throw new Validate('warn',message,this.path,value) | ||
} | ||
/** | ||
* Convenience method to error about a request | ||
* @param message | ||
*/ | ||
ValidateHelpers.prototype.error = function(message){ | ||
throw new Validate('error',message,this.path,this.value) | ||
} | ||
/** | ||
* Convenience method to succeed a request | ||
* @param value | ||
*/ | ||
ValidateHelpers.prototype.ok = function(value){ | ||
throw new Validate('ok',null,this.path,value) | ||
} | ||
/** | ||
* Run validation of an action | ||
* | ||
* @param validate Validation function | ||
* @param path Path being evaluated | ||
* @param value Value being evaluated | ||
* @param emit Callback to emit events | ||
* @param pre Callback before emit | ||
* @param post Callback after emit for return | ||
* @returns {*} | ||
*/ | ||
var runValidation = function(validate,path,value,emit,pre,post){ | ||
var err = null | ||
, self = this | ||
if('string' === typeof emit){ | ||
var event = emit | ||
emit = function(verb,message,path,value){ | ||
if('ok' === verb || 'warn' === verb){ | ||
self.emit(event,path,value) | ||
} | ||
if('ok' !== verb){ | ||
self.emit(verb,event,message,path,value) | ||
} | ||
} | ||
} | ||
if(!pre) pre = function(verb,value){return value} | ||
if(!post) post = function(verb,value){return value} | ||
if('function' !== typeof validate){ | ||
value = pre('ok',value) | ||
emit('ok','',path,value) | ||
return post('ok',value) | ||
} else { | ||
try { | ||
value = validate.call(new ValidateHelpers(path,value),path,value) | ||
} catch(e){ | ||
if(!(e instanceof Validate)){ | ||
throw e | ||
} else { | ||
err = e | ||
} | ||
} | ||
if(null === err || 'ok' === err.verb){ | ||
if(null !== err && 'ok' === err.verb) value = err.value | ||
value = pre('ok',value) | ||
emit('ok','',path,value) | ||
return post('ok',value) | ||
} else if('warn' === err.verb){ | ||
value = pre('warn',value) | ||
emit('warn',err.message,path,value) | ||
return post('warn',value) | ||
} else if('drop' === err.verb){ | ||
emit('drop',err.message,path,value) | ||
return post('drop',value) | ||
} else if('reject' === err.verb){ | ||
emit('reject',err.message,path,value) | ||
return post('reject',value) | ||
} else { | ||
emit('error',err.message,path,value) | ||
throw new Error(err.message) | ||
} | ||
} | ||
} | ||
/** | ||
* Manage constructor passes the initial argument to ObjectManage.load() | ||
@@ -85,5 +245,7 @@ * and accepts the same set of parameters | ||
var ObjectManage = function(data){ | ||
events.EventEmitter.call(this) | ||
this.data = {} | ||
this.load(data) | ||
} | ||
util.inherits(ObjectManage,events.EventEmitter) | ||
@@ -103,2 +265,8 @@ /** | ||
/** | ||
* Reference to the Validate object | ||
* @type {Function} | ||
*/ | ||
ObjectManage.prototype.Validate = Validate | ||
/** | ||
* Set respective path to value | ||
@@ -109,6 +277,28 @@ * @param path | ||
ObjectManage.prototype.set = function(path,value){ | ||
set(this.data,path,value) | ||
var self = this | ||
return runValidation.call( | ||
self, | ||
self.validateSet, | ||
path, | ||
value, | ||
'set', | ||
function(verb,value){ | ||
if('ok' === verb || 'warn' === verb || 'drop' === verb){ | ||
set(self.data,path,value) | ||
} | ||
return value | ||
}, | ||
function(verb){ | ||
return ('ok' === verb || 'warn' === verb || 'drop' === verb) | ||
} | ||
) | ||
} | ||
/** | ||
* Validation for set directives | ||
* @type {null} | ||
*/ | ||
ObjectManage.prototype.validateSet = null | ||
/** | ||
* Get value of path returns undefined if path does not exist | ||
@@ -119,6 +309,20 @@ * @param path | ||
ObjectManage.prototype.get = function(path){ | ||
return get(this.data,path) | ||
var self = this | ||
return runValidation.call( | ||
self, | ||
self.validateGet, | ||
path, | ||
get(self.data,path), | ||
'get' | ||
) | ||
} | ||
/** | ||
* Validation for get directives | ||
* directly | ||
* @type {null} | ||
*/ | ||
ObjectManage.prototype.validateGet = null | ||
/** | ||
* Check if path exists (uses hasOwnProperty and is safe to having undefined set as a value) | ||
@@ -129,15 +333,55 @@ * @param path | ||
ObjectManage.prototype.exists = function(path){ | ||
return exists(this.data,path) | ||
var self = this | ||
return runValidation.call( | ||
self, | ||
self.validateExists, | ||
path, | ||
null, | ||
'exists', | ||
function(verb){ | ||
if('ok' === verb || 'warn' === verb){ | ||
return exists(self.data,path) | ||
} else { | ||
return false | ||
} | ||
} | ||
) | ||
} | ||
/** | ||
* Validation of exists directives | ||
* @type {null} | ||
*/ | ||
ObjectManage.prototype.validateExists = null | ||
/** | ||
* Remove value and children at desired path (does this by deleting the value) | ||
* @param path | ||
* @returns {} | ||
* @returns {boolean} | ||
*/ | ||
ObjectManage.prototype.remove = function(path){ | ||
return remove(this.data,path) | ||
var self = this | ||
return runValidation.call( | ||
self, | ||
self.validateRemove, | ||
path, | ||
null, | ||
'remove', | ||
function(verb,value){ | ||
if('ok' === verb || 'warning' === verb){ | ||
return remove(self.data,path) | ||
} else { | ||
return value | ||
} | ||
} | ||
) | ||
} | ||
/** | ||
* Validation of removal directives | ||
* @type {null} | ||
*/ | ||
ObjectManage.prototype.validateRemove = null | ||
/** | ||
* Merge arguments into data object | ||
@@ -149,15 +393,39 @@ * Can be an Array or recursive Array of objects that get merged | ||
ObjectManage.prototype.load = function(data){ | ||
var that = this | ||
var self = this | ||
if(util.isArray(data)){ | ||
data.forEach(function(v){ | ||
that.load(v) | ||
self.load(v) | ||
}) | ||
} else { | ||
if('object' === typeof data){ | ||
var depth = this.countDepth(data) | ||
if(depth >= this.maxDepth){ | ||
var depth = self.countDepth(data) | ||
if(depth >= self.maxDepth){ | ||
self.emit('warning',self.data,'Object being merged is too deep (' + depth +')') | ||
console.log('WARN [object-manage]: Object being merged is too deep (' + depth +')') | ||
console.trace() | ||
} | ||
that.data = merge.recursive(that.data,data) || {} | ||
runValidation.call( | ||
self, | ||
self.validateLoad, | ||
null, | ||
data, | ||
function(verb,message,path,value){ | ||
if('ok' === verb){ | ||
self.emit('load',value) | ||
} else { | ||
self.emit(verb,'load',message,path,value) | ||
} | ||
}, | ||
function(verb,value){ | ||
if('ok' === verb || 'warn' === verb){ | ||
self.data = self.merge(self.data,value) || {} | ||
return self.data | ||
} else { | ||
return false | ||
} | ||
}, | ||
function(verb){ | ||
return ('ok' === verb || 'warn' === verb) | ||
} | ||
) | ||
} | ||
@@ -168,2 +436,40 @@ } | ||
/** | ||
* Validation for load directives | ||
* @type {null} | ||
*/ | ||
ObjectManage.prototype.validateLoad = null | ||
/** | ||
* Merge implementation using object-merge | ||
* @param obj1 | ||
* @param obj2 | ||
* @returns {*|exports} | ||
*/ | ||
ObjectManage.prototype.objectMerge = function(obj1,obj2){ | ||
var opts = objectMerge.createOptions({ | ||
depth: this.maxDepth, | ||
throwOnCircularRef: true | ||
}) | ||
return objectMerge(opts,obj1,obj2) | ||
} | ||
/** | ||
* Merge implementation using merge-recursive | ||
* @param obj1 | ||
* @param obj2 | ||
* @returns {*} | ||
*/ | ||
ObjectManage.prototype.mergeRecursive = function(obj1,obj2){ | ||
return mergeRecursive.recursive(obj1,obj2) | ||
} | ||
/** | ||
* Merge prototype allowed to be overirden | ||
* @param obj1 | ||
* @param obj2 | ||
* @returns {*|exports} | ||
*/ | ||
Object.prototype.merge = ObjectManage.prototype.objectMerge | ||
/** | ||
* Count the depth of an object and return | ||
@@ -170,0 +476,0 @@ * @param object Target object |
@@ -6,3 +6,3 @@ { | ||
"bugs": "https://github.com/snailjs/object-manage/issues", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"author": "Bryan Tong <contact@nullivex.com>", | ||
@@ -14,2 +14,4 @@ "license": "MIT", | ||
"setter", | ||
"exists", | ||
"remove", | ||
"merge", | ||
@@ -28,3 +30,4 @@ "path", | ||
"dependencies": { | ||
"merge-recursive": "~0.0.3" | ||
"object-merge": "~2.5.1", | ||
"merge-recursive": "0.0.3" | ||
}, | ||
@@ -31,0 +34,0 @@ "devDependencies": { |
289
README.md
@@ -19,22 +19,131 @@ object-manage [![Build Status](https://travis-ci.org/snailjs/object-manage.png?branch=master)](https://travis-ci.org/snailjs/object-manage) | ||
ObjectManage is also an event emitter that allows the internal data object to be watched | ||
and augmented manually. | ||
```js | ||
var ObjectManage = require('object-manage') | ||
, obj = new ObjectManage({box: 'square'}) | ||
//construct | ||
var obj = new ObjectManage({box: 'square'}) | ||
//watch data | ||
var mydata = {} | ||
obj.on('load',function(data){ | ||
mydata = data | ||
}) | ||
//load in data | ||
obj.load({foo: 'bar'}) | ||
//set a path | ||
obj.set('bas.boo','foo1') | ||
//get a path | ||
obj.get('bas') //{boo: 'foo1'} | ||
obj.get('bas.boo') //'foo1' | ||
//access data directly | ||
console.log(obj.data.bas.boo) //'foo1' | ||
//check if a path exists | ||
obj.exists('bas') //true | ||
obj.exists('badkey') //false | ||
//remove a path | ||
obj.remove('bas') | ||
obj.exists('bas') //false | ||
``` | ||
## Inheritance | ||
It is also useful to use ObjectManage as a superconstructor for libraries with options. | ||
Here is a quick example | ||
```js | ||
var ObjectManage = require('object-manage') | ||
, util = require('util') | ||
var myObj = function(data){ | ||
ObjectManage.call(this,data) | ||
} | ||
util.inherits(myObj,ObjectManage) | ||
myObj.prototype.foo = function(){ | ||
console.log(this.data) //this.data managed by ObjectManage | ||
} | ||
``` | ||
## Switching Merge Package | ||
In order to make object-manage more performance friendly in smaller environments | ||
the merger can easily be switched between **object-merge** for **merge-recursive**. | ||
**merge-recursive** will only merge pointers and thus when the object-manage instance | ||
is modified the original objects will be as well. We choose **object-merge** as the | ||
default because it will decouple from the objects being merged in. This comes with a | ||
performance and memory cost. | ||
To use **merge-recursive** | ||
```js | ||
var ObjectManage = require('object-manage') | ||
ObjectManage.prototype.merge = ObjectManage.prototype.mergeRecursive | ||
``` | ||
It is also possible to implement one's own merging function. | ||
```js | ||
var ObjectManage = require('object-manage') | ||
ObjectManage.prototype.merge = function(obj1,obj2){ | ||
var mergedObject = obj2 | ||
return mergedObject | ||
} | ||
``` | ||
## Validation | ||
In order for object-manage to be useful in more hostile environments. | ||
It allows for validation functions to be defined per instance. | ||
Quick example of a validation function for setting values | ||
```js | ||
var obj = new ObjectManage() | ||
obj.validateSet = function(path,value){ | ||
//your validation code here that calls one of the below functions | ||
//erroneous method that still processes the action | ||
this.warn('should have passed a boolean',value) | ||
//erroneous methods that will halt processing of the action | ||
this.drop('value must be boolean') | ||
this.reject('value must be boolean') | ||
//will throw an exception that must be caught in user space | ||
this.error('something bad happened') | ||
//non erroneous return methods | ||
this.ok(value) | ||
//or | ||
return value | ||
} | ||
``` | ||
### Callbacks | ||
The following callbacks are available. | ||
* validateGet `ObjectManage.validateGet` -- Validate get directives | ||
* validateSet `ObjectManage.validateSet` -- Validate set directives | ||
* validateExists `ObjectManage.validateExists` -- Validate exists directives | ||
* validateRemove `ObjectManage.validateRemove` -- Validate remove directives | ||
* validateLoad `ObjectManage.validateLoad` -- Validate load directives | ||
### Verbs | ||
There are 5 verbs used to handle exceptions | ||
* **drop** -- Silently drop the set/get operation (returns undefined for get, set returns true) | ||
* **reject** -- Actively reject the set/get operation and issue an error back to the setter/getter | ||
* **warn** -- Accept the get/set request but still issue an error to the user. | ||
* **error** -- Treated like a regular exception and will be thrown upwards. | ||
* **ok** -- Only accepts the value to returned for processing | ||
## API Reference | ||
@@ -47,2 +156,11 @@ | ||
```js | ||
var data = {foo: 'foo'} | ||
var inst = new ObjectManage(data) | ||
``` | ||
**NOTE** If watching data via the `load` event is desired data | ||
should not be passed to the construct as it will be impossible to | ||
listen for the `load` event. | ||
### Load Object(s) | ||
@@ -65,3 +183,3 @@ | ||
, data3 = {test5: {test6: 'val6'}} | ||
var inst = ObjectManage() | ||
var inst = new ObjectManage() | ||
inst.load([data1,data2,data3]) | ||
@@ -119,3 +237,3 @@ ``` | ||
inst.exists('mykey.mykey2') //true | ||
inst.remove('mykey') | ||
inst.remove('mykey') //true | ||
inst.exists('mykey.mykey') //false | ||
@@ -134,4 +252,169 @@ ``` | ||
## Events | ||
### Set | ||
Fired when a set is processed on the managed object | ||
* path -- Path to be set | ||
* value -- Value to be set to the path | ||
* valid -- If a validation function was used this is the validity of that result (boolean) | ||
```js | ||
var obj = new require('object-manage')() | ||
obj.on('set',function(path,value,valid){ | ||
valid = valid ? 'valid' : 'invalid' | ||
console.log('a ' + valid + ' value of (' + value + ') set to (' + path + ')') | ||
}) | ||
obj.set('foo','bar') | ||
``` | ||
### Get | ||
Fired when a get is processed on the managed object | ||
* path -- Path to be retrieved | ||
* value -- Value of the path retrieved | ||
** valid -- If a validation function was used this is the validity of that result (boolean) | ||
```js | ||
var obj = new require('object-manage')() | ||
obj.on('get',function(path,value){ | ||
console.log(value + ' was retrieved from ' + path) | ||
}) | ||
obj.get('foo') | ||
``` | ||
### Exists | ||
Fired when an exists operation is performed | ||
* path -- Path that is being checked for existance | ||
* exists -- Result of exists check (boolean) | ||
```js | ||
var obj = new require('object-manage')() | ||
obj.on('exists',function(path,exists){ | ||
var does = exists ? 'does' : 'does not' | ||
console.log('checked if ' + path + ' exists and it ' + does) | ||
}) | ||
obj.exists('foo') | ||
``` | ||
### Remove | ||
Fired when an remove operation is performed | ||
* path -- Path that is being checked for existance | ||
* removed -- Result of removal operation (boolean) | ||
```js | ||
var obj = new require('object-manage')() | ||
obj.on('exists',function(path,removed){ | ||
var successfully = removed ? 'successfully' : 'unsuccessfully' | ||
console.log(successfully + ' removed path (' + path + ')') | ||
}) | ||
obj.remove('foo') | ||
``` | ||
### Load | ||
Fired when a load and merge is performed on the managed object | ||
* data -- Result of the load and merge | ||
```js | ||
var obj = new require('object-manage')() | ||
obj.on('load',function(data){ | ||
console.log('a merge was performed and the resulting data: ' + data) | ||
}) | ||
obj.load({foo: 'bar'}) | ||
``` | ||
### Drop | ||
Fired when there is a validation `drop` | ||
* verb -- The current action type (get,set) | ||
* message -- The warning message | ||
* path -- Path of the property being operated on | ||
* value -- The value being operated on | ||
```js | ||
var obj = new require('object-manage')() | ||
obj.on('drop',function(verb,message,path,value){ | ||
console.log('object-manage drop [' + verb + ':' + path + ']: ' + message) | ||
}) | ||
obj.validateSet = function(path,value){ | ||
this.drop('not accepting anything') | ||
} | ||
obj.set('foo','will drop') //returns true | ||
``` | ||
### Reject | ||
Fired when there is a validation `reject` | ||
* verb -- The current action type (get,set) | ||
* message -- The warning message | ||
* path -- Path of the property being operated on | ||
* value -- The value being operated on | ||
```js | ||
var obj = new require('object-manage')() | ||
obj.on('reject',function(verb,message,path,value){ | ||
console.log('object-manage reject [' + verb + ':' + path + ']: ' + message) | ||
}) | ||
obj.validateSet = function(path,value){ | ||
this.reject('not accepting anything') | ||
} | ||
obj.set('foo','will drop') //returns false | ||
``` | ||
### Warning | ||
Fired when there is a set/get/merge warning. | ||
* verb -- The current action type (get,set,merge) | ||
* message -- The warning message | ||
* path -- Path of the property being operated on (blank during merge warnings) | ||
* value -- The value being operated on (the value being merged in during a merge warning) | ||
```js | ||
var obj = new require('object-manage')() | ||
obj.on('warn',function(verb,message,path,value){ | ||
console.log('object-manage warning [' + verb + ']: ' + message) | ||
}) | ||
obj.load(overlyDeepObject) | ||
``` | ||
### Error | ||
Fired when there is a set/get/merge error. | ||
* verb -- The current action type (get,set,merge) | ||
* message -- The warning message | ||
* path -- Path of the property being operated on (blank during merge warnings) | ||
* value -- The value being operated on (the value being merged in during a merge warning) | ||
```js | ||
var obj = new require('object-manage')() | ||
obj.on('error',function(verb,message,path,value){ | ||
console.log('object-manage error [' + verb + ']: ' + message) | ||
}) | ||
obj.load(overlyDeepObject) | ||
``` | ||
## Changelog | ||
### 0.5.0 | ||
* ObjectManage is now an event emitter that fires events for watching data see README for event types | ||
* **object-merge** selected as the default merge package. | ||
* Added switchable merger type based on desired environment. | ||
* Added testing against circular referenced objects | ||
* ObjectManage will not modify objects passed into and are decoupled when using **object-merge** | ||
* ObjectManage.merge prototype function added so the merger can be overridden to allow customised usage. | ||
* Validation now supported on get, set, exists, load, and remove. | ||
* Organized tests into groups for easier future additions. | ||
### 0.4.0 | ||
@@ -138,0 +421,0 @@ * Added max depth warning for recursive objects that would normally throw `Maximum call stack exceeded` |
@@ -0,116 +1,394 @@ | ||
'use strict'; | ||
var ObjectManage = require('../lib/ObjectManage') | ||
describe.only('ObjectManage',function(){ | ||
, expect = require('chai').expect | ||
describe('ObjectManage',function(){ | ||
var data1 = {test1: 'val1', test2: 'val2'} | ||
, data2 = {test3: 'val3', test4: 'val4'} | ||
, data3 = {test5: {test6: 'val6'}} | ||
it('should accept data to the constructor',function(){ | ||
var obj = new ObjectManage(data1) | ||
expect(obj.data.test1).to.equal('val1') | ||
expect(obj.data.test2).to.equal('val2') | ||
describe('Construction',function(){ | ||
it('should accept data to the constructor',function(){ | ||
var obj = new ObjectManage(data1) | ||
expect(obj.data.test1).to.equal('val1') | ||
expect(obj.data.test2).to.equal('val2') | ||
}) | ||
it('should be able to merge in data after constructing',function(){ | ||
var obj = new ObjectManage([data1,data2]) | ||
expect(obj.data.test3).to.equal('val3') | ||
expect(obj.data.test4).to.equal('val4') | ||
}) | ||
}) | ||
it('should be able to merge in data after constructing',function(){ | ||
var obj = new ObjectManage([data1,data2]) | ||
expect(obj.data.test3).to.equal('val3') | ||
expect(obj.data.test4).to.equal('val4') | ||
describe('Setters and Getters',function(){ | ||
it('should be able to get a nested key',function(){ | ||
var obj = new ObjectManage([data1,data3]) | ||
expect(obj.get('test5.test6')).to.equal('val6') | ||
}) | ||
it('should be able to set a nested key',function(){ | ||
var obj = new ObjectManage([data1,data3]) | ||
obj.set('test5.test7','val7') | ||
expect(obj.data.test5.test7).to.equal('val7') | ||
}) | ||
it('should overwrite a key to object if nested below',function(){ | ||
var obj = new ObjectManage([data1,data2,data3]) | ||
obj.set('test5.test6.new','val8') | ||
expect(obj.data.test5.test6.new).to.equal('val8') | ||
}) | ||
it('should returned undefined on a missing key',function(){ | ||
var obj = new ObjectManage(data1) | ||
expect(obj.get('test5.test6')).to.equal(undefined) | ||
}) | ||
}) | ||
it('should be able to get a nested key',function(){ | ||
var obj = new ObjectManage([data1,data3]) | ||
expect(obj.get('test5.test6')).to.equal('val6') | ||
describe('Removal and Existence',function(){ | ||
it('should return true if the property exists',function(){ | ||
var obj = new ObjectManage(data1) | ||
expect(obj.exists('test1')).to.equal(true) | ||
}) | ||
it('should return false if the property does not exist',function(){ | ||
var obj = new ObjectManage() | ||
expect(obj.exists('test1')).to.equal(false) | ||
}) | ||
it('should remove a property and all its children',function(){ | ||
var obj = new ObjectManage([data1,data2,data3]) | ||
obj.remove('test5') | ||
expect(obj.data.hasOwnProperty('test5')).to.equal(false) | ||
}) | ||
}) | ||
it('should be able to set a nested key',function(){ | ||
var obj = new ObjectManage([data1,data3]) | ||
obj.set('test5.test7','val7') | ||
expect(obj.data.test5.test7).to.equal('val7') | ||
describe('Merging',function(){ | ||
it('should always have an object at .data',function(){ | ||
var obj = new ObjectManage() | ||
obj.load(undefined) | ||
expect(obj.data).to.be.an('object') | ||
}) | ||
it('should return the whole object if no argument if passed to get',function(){ | ||
var obj = new ObjectManage([data1,data2,data3]) | ||
expect(Object.keys(obj.get()).length).to.equal(5) | ||
}) | ||
it('should merge recursively',function(){ | ||
var obj = new ObjectManage(data3) | ||
obj.load({test5: {test7: 'val7'}}) | ||
expect(obj.get('test5.test6')).to.equal('val6') | ||
expect(obj.get('test5.test7')).to.equal('val7') | ||
}) | ||
}) | ||
it('should overwrite a key to object if nested below',function(){ | ||
var obj = new ObjectManage([data1,data2,data3]) | ||
obj.set('test5.test6.new','val8') | ||
expect(obj.data.test5.test6.new).to.equal('val8') | ||
describe('Events',function(){ | ||
it('should be able to watch the data object',function(done){ | ||
var obj = new ObjectManage() | ||
obj.once('load',function(data){ | ||
expect(data.test5.test6).to.equal('val6') | ||
done() | ||
}) | ||
obj.load(data3) | ||
}) | ||
it('should emit a set event',function(done){ | ||
var obj = new ObjectManage() | ||
obj.once('set',function(path,value){ | ||
expect(path).to.equal('foo') | ||
expect(value).to.equal('baz') | ||
done() | ||
}) | ||
obj.set('foo','baz') | ||
}) | ||
it('should emit a get event',function(done){ | ||
var obj = new ObjectManage(data1) | ||
obj.once('get',function(path,value){ | ||
expect(path).to.equal('test1') | ||
expect(value).to.equal('val1') | ||
done() | ||
}) | ||
obj.get('test1') | ||
}) | ||
it('should emit an exists event',function(done){ | ||
var obj = new ObjectManage(data1) | ||
obj.once('exists',function(path,exists){ | ||
expect(path).to.equal('foo') | ||
expect(exists).to.equal(false) | ||
done() | ||
}) | ||
obj.exists('foo') | ||
}) | ||
it('should emit a remove event',function(done){ | ||
var obj = new ObjectManage(data1) | ||
obj.once('remove',function(path,removed){ | ||
expect(path).to.equal('test1') | ||
expect(removed).to.equal(true) | ||
done() | ||
}) | ||
obj.remove('test1') | ||
}) | ||
it('should emit a load event',function(done){ | ||
var obj = new ObjectManage() | ||
obj.once('load',function(data){ | ||
expect(data.test1).to.equal('val1') | ||
done() | ||
}) | ||
obj.load(data1) | ||
}) | ||
}) | ||
it('should returned undefined on a missing key',function(){ | ||
var obj = new ObjectManage(data1) | ||
expect(obj.get('test5.test6')).to.equal(undefined) | ||
describe('Set with Validation',function(){ | ||
it('should validate using drop',function(done){ | ||
var obj = new ObjectManage() | ||
obj.validateSet = function(path){ | ||
expect(path).to.equal('test1') | ||
this.drop(path + ' must be set to val2') | ||
} | ||
obj.once('drop',function(verb,message,path,value){ | ||
expect(verb).to.equal('set') | ||
expect(message).to.equal('test1 must be set to val2') | ||
expect(path).to.equal('test1') | ||
expect(value).to.equal('val3') | ||
done() | ||
}) | ||
obj.set('test1','val3') | ||
}) | ||
it('should validate using reject',function(done){ | ||
var obj = new ObjectManage() | ||
obj.validateSet = function(path){ | ||
expect(path).to.equal('test1') | ||
this.reject(path + ' must be set to val2') | ||
} | ||
obj.once('reject',function(verb,message,path,value){ | ||
expect(verb).to.equal('set') | ||
expect(message).to.equal('test1 must be set to val2') | ||
expect(path).to.equal('test1') | ||
expect(value).to.equal('val3') | ||
done() | ||
}) | ||
obj.set('test1','val3') | ||
}) | ||
it('should validate using warn',function(done){ | ||
var obj = new ObjectManage() | ||
obj.validateSet = function(path,value){ | ||
expect(path).to.equal('test1') | ||
this.warn(path + ' must be set to val2',value) | ||
} | ||
obj.once('warn',function(verb,message,path,value){ | ||
expect(verb).to.equal('set') | ||
expect(message).to.equal('test1 must be set to val2') | ||
expect(path).to.equal('test1') | ||
expect(value).to.equal('val3') | ||
expect(obj.get('test1')).to.equal('val3') | ||
done() | ||
}) | ||
obj.set('test1','val3') | ||
}) | ||
it('should validate using error',function(done){ | ||
var obj = new ObjectManage() | ||
obj.validateSet = function(path){ | ||
expect(path).to.equal('test1') | ||
this.error(path + ' must be set to val2') | ||
} | ||
obj.once('error',function(verb,message,path,value){ | ||
expect(verb).to.equal('set') | ||
expect(message).to.equal('test1 must be set to val2') | ||
expect(path).to.equal('test1') | ||
expect(value).to.equal('val3') | ||
done() | ||
}) | ||
expect(function(){obj.set('test1','val3')}).to.throw('test1 must be set to val2') | ||
}) | ||
it('should validate using ok',function(done){ | ||
var obj = new ObjectManage() | ||
obj.validateSet = function(path,value){ | ||
expect(path).to.equal('test1') | ||
this.ok(value) | ||
} | ||
obj.once('set',function(path,value){ | ||
expect(path).to.equal('test1') | ||
expect(value).to.equal('val3') | ||
expect(obj.get('test1')).to.equal('val3') | ||
done() | ||
}) | ||
obj.set('test1','val3') | ||
}) | ||
it('should validate using return',function(done){ | ||
var obj = new ObjectManage() | ||
obj.validateSet = function(path,value){ | ||
expect(path).to.equal('test1') | ||
return value | ||
} | ||
obj.once('set',function(path,value){ | ||
expect(path).to.equal('test1') | ||
expect(value).to.equal('val3') | ||
expect(obj.get('test1')).to.equal('val3') | ||
done() | ||
}) | ||
obj.set('test1','val3') | ||
}) | ||
}) | ||
it('should return true if the property exists',function(){ | ||
var obj = new ObjectManage(data1) | ||
expect(obj.exists('test1')).to.equal(true) | ||
describe('Get with Validation',function(){ | ||
it('should validate using drop',function(done){ | ||
var obj = new ObjectManage() | ||
obj.validateGet = function(path){ | ||
expect(path).to.equal('test1') | ||
this.drop(path + ' must be set to val2') | ||
} | ||
obj.once('drop',function(verb,message,path,value){ | ||
expect(verb).to.equal('get') | ||
expect(message).to.equal('test1 must be set to val2') | ||
expect(path).to.equal('test1') | ||
expect(value).to.equal('val3') | ||
done() | ||
}) | ||
obj.set('test1','val3') | ||
obj.get('test1') | ||
}) | ||
it('should validate using reject',function(done){ | ||
var obj = new ObjectManage() | ||
obj.validateGet = function(path){ | ||
expect(path).to.equal('test1') | ||
this.reject(path + ' must be set to val2') | ||
} | ||
obj.once('reject',function(verb,message,path,value){ | ||
expect(verb).to.equal('get') | ||
expect(message).to.equal('test1 must be set to val2') | ||
expect(path).to.equal('test1') | ||
expect(value).to.equal('val3') | ||
done() | ||
}) | ||
obj.set('test1','val3') | ||
obj.get('test1') | ||
}) | ||
it('should validate using warn',function(done){ | ||
var obj = new ObjectManage() | ||
obj.validateGet = function(path,value){ | ||
expect(path).to.equal('test1') | ||
this.warn(path + ' must be set to val2',value) | ||
} | ||
obj.once('warn',function(verb,message,path,value){ | ||
expect(verb).to.equal('get') | ||
expect(message).to.equal('test1 must be set to val2') | ||
expect(path).to.equal('test1') | ||
expect(value).to.equal('val3') | ||
done() | ||
}) | ||
obj.set('test1','val3') | ||
obj.get('test1') | ||
}) | ||
it('should validate using error',function(done){ | ||
var obj = new ObjectManage() | ||
obj.validateGet = function(path){ | ||
expect(path).to.equal('test1') | ||
this.error(path + ' must be set to val2') | ||
} | ||
obj.once('error',function(verb,message,path,value){ | ||
expect(verb).to.equal('get') | ||
expect(message).to.equal('test1 must be set to val2') | ||
expect(path).to.equal('test1') | ||
expect(value).to.equal('val3') | ||
done() | ||
}) | ||
obj.set('test1','val3') | ||
expect(function(){obj.get('test1')}).to.throw('test1 must be set to val2') | ||
}) | ||
it('should validate using ok',function(done){ | ||
var obj = new ObjectManage() | ||
obj.validateGet = function(path){ | ||
expect(path).to.equal('test1') | ||
this.ok('val4') | ||
} | ||
obj.once('get',function(path,value){ | ||
expect(path).to.equal('test1') | ||
expect(value).to.equal('val4') | ||
done() | ||
}) | ||
obj.set('test1','val3') | ||
obj.get('test1') | ||
}) | ||
it('should validate using return',function(done){ | ||
var obj = new ObjectManage() | ||
obj.validateGet = function(path){ | ||
expect(path).to.equal('test1') | ||
return 'val4' | ||
} | ||
obj.once('get',function(path,value){ | ||
expect(path).to.equal('test1') | ||
expect(value).to.equal('val4') | ||
done() | ||
}) | ||
obj.set('test1','val3') | ||
obj.get('test1') | ||
}) | ||
}) | ||
it('should return false if the property does not exist',function(){ | ||
var obj = new ObjectManage() | ||
expect(obj.exists('test1')).to.equal(false) | ||
describe('Circular Reference Handling',function(){ | ||
it('should fail on circular referenced objects',function(){ | ||
var x = { | ||
'a' : function () {return null} | ||
} | ||
x.b = x.a | ||
var obj = new ObjectManage() | ||
function load(){ | ||
obj.load(x) | ||
} | ||
expect(load).to.throw(Error) | ||
}) | ||
it('should work using merge-recursive',function(){ | ||
var obj = new ObjectManage() | ||
obj.merge = ObjectManage.prototype.mergeRecursive | ||
obj.load(data1) | ||
obj.load(data3) | ||
expect(obj.get('test5.test6')).to.equal('val6') | ||
}) | ||
it('should count object depth accurately',function(){ | ||
var testObject = {foo: {foo: {foo: 'baz'}}} | ||
, obj = new ObjectManage() | ||
expect(obj.countDepth(testObject)).to.equal(3) | ||
}) | ||
it('should warn on objects more than 50 levels deep',function(){ | ||
//setup console mocking | ||
var oldLog = console.log | ||
, oldTrace = console.trace | ||
, log = [] | ||
, traced = false | ||
console.log = function(msg){ | ||
log.push(msg) | ||
} | ||
console.trace = function(){ | ||
traced = true | ||
} | ||
var buildObject = function(object,depth,count){ | ||
if(undefined === object) object = {} | ||
if(undefined === count) count = 1 | ||
if(undefined === depth) depth = 100 | ||
if(count > depth) return object | ||
object[count] = buildObject(object[count],depth,(count + 1)) | ||
return object | ||
} | ||
//var obj = new ObjectManage() | ||
var badObject = buildObject() | ||
, obj = new ObjectManage() | ||
//verify the limiter on depth is working | ||
expect(obj.countDepth(badObject)).to.equal(obj.maxDepth) | ||
//verify that we can adjust the maxDepth | ||
var oldMaxDepth = obj.maxDepth | ||
obj.maxDepth = 20 | ||
expect(obj.countDepth(badObject)).to.equal(20) | ||
//restore original | ||
obj.maxDepth = oldMaxDepth | ||
//load the bad object which should puke | ||
obj.load(badObject) | ||
//verify the warning was thrown | ||
expect(log[0]).to.equal('WARN [object-manage]: Object being merged is too deep (50)') | ||
expect(traced).to.equal(true) | ||
//cleanup | ||
log = [] | ||
traced = false | ||
//set the max depth to a lower value and try again | ||
obj.maxDepth = 10 | ||
//load the bad object which should puke | ||
obj.load(badObject) | ||
expect(log[0]).to.equal('WARN [object-manage]: Object being merged is too deep (10)') | ||
expect(traced).to.equal(true) | ||
//restore native console functions | ||
console.log = oldLog | ||
console.trace = oldTrace | ||
}) | ||
}) | ||
it('should remove a property and all its children',function(){ | ||
var obj = new ObjectManage([data1,data2,data3]) | ||
obj.remove('test5') | ||
expect(obj.data.hasOwnProperty('test5')).to.equal(false) | ||
}) | ||
it('should always have an object at .data',function(){ | ||
var obj = new ObjectManage() | ||
obj.load(undefined) | ||
expect(obj.data).to.be.an('object') | ||
}) | ||
it('should return the whole object if no argument if passed to get',function(){ | ||
var obj = new ObjectManage([data1,data2,data3]) | ||
expect(Object.keys(obj.get()).length).to.equal(5) | ||
}) | ||
it('should merge recursively',function(){ | ||
var obj = new ObjectManage(data3) | ||
obj.load({test5: {test7: 'val7'}}) | ||
expect(obj.get('test5.test6')).to.equal('val6') | ||
expect(obj.get('test5.test7')).to.equal('val7') | ||
}) | ||
it('should count object depth accurately',function(){ | ||
var testObject = {foo: {foo: {foo: 'baz'}}} | ||
, obj = new ObjectManage() | ||
expect(obj.countDepth(testObject)).to.equal(3) | ||
}) | ||
it('should warn on objects more than 50 levels deep',function(){ | ||
//setup console mocking | ||
var oldLog = console.log | ||
, oldTrace = console.trace | ||
, log = [] | ||
, traced = false | ||
console.log = function(msg){ | ||
log.push(msg) | ||
} | ||
console.trace = function(){ | ||
traced = true | ||
} | ||
var buildObject = function(object,depth,count){ | ||
if(undefined === object) object = {} | ||
if(undefined === count) count = 1 | ||
if(undefined === depth) depth = 100 | ||
if(count > depth) return object | ||
object[count] = buildObject(object[count],depth,(count + 1)) | ||
return object | ||
} | ||
//var obj = new ObjectManage() | ||
var badObject = buildObject() | ||
, obj = new ObjectManage() | ||
//verify the limiter on depth is working | ||
expect(obj.countDepth(badObject)).to.equal(obj.maxDepth) | ||
//verify that we can adjust the maxDepth | ||
var oldMaxDepth = obj.maxDepth | ||
obj.maxDepth = 20 | ||
expect(obj.countDepth(badObject)).to.equal(20) | ||
//restore original | ||
obj.maxDepth = oldMaxDepth | ||
//load the bad object which should puke | ||
obj.load(badObject) | ||
//verify the warning was thrown | ||
expect(log[0]).to.equal('WARN [object-manage]: Object being merged is too deep (50)') | ||
expect(traced).to.equal(true) | ||
//cleanup | ||
log = [] | ||
traced = false | ||
//set the max depth to a lower value and try again | ||
obj.maxDepth = 10 | ||
//load the bad object which should puke | ||
obj.load(badObject) | ||
expect(log[0]).to.equal('WARN [object-manage]: Object being merged is too deep (10)') | ||
expect(traced).to.equal(true) | ||
//restore native console functions | ||
console.log = oldLog | ||
console.trace = oldTrace | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
40203
881
450
2
11
1
+ Addedobject-merge@~2.5.1
+ Addedclone-function@1.0.6(transitive)
+ Addedobject-foreach@0.1.2(transitive)
+ Addedobject-merge@2.5.1(transitive)
Updatedmerge-recursive@0.0.3