fast-json-patch
Advanced tools
Comparing version 0.0.2 to 0.3.5
{ | ||
"name": "fast-json-patch", | ||
"version": "0.0.2", | ||
"version": "0.3.5", | ||
"description": "JSON-Patch allows you to update a JSON document by sending the changes rather than the whole document.", | ||
"homepage": "https://github.com/Starcounter-Jack/JSON-Patch", | ||
"keywords": [ | ||
"json", | ||
"patch", | ||
"http", | ||
"rest" | ||
], | ||
"keywords": ["json", "patch", "http", "rest"], | ||
"repository": { | ||
@@ -19,17 +14,13 @@ "type": "git", | ||
}, | ||
"author": "Joachim Wester <joachimwester@me.com> (http://www.starcounter.com/)", | ||
"licenses": [ | ||
{ | ||
"type": "MIT", | ||
"url": "http://www.opensource.org/licenses/MIT" | ||
} | ||
], | ||
"author": { | ||
"name": "Joachim Wester", | ||
"email": "joachimwester@me.com", | ||
"url": "http://www.starcounter.com/" | ||
}, | ||
"licenses": [ { | ||
"type": "MIT", | ||
"url": "http://www.opensource.org/licenses/MIT" | ||
} ], | ||
"main": "./src/json-patch-duplex", | ||
"engines": { | ||
"node": ">= 0.4.0" | ||
}, | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"license": "MIT" | ||
"engines": { "node": ">= 0.4.0" } | ||
} |
JSON-Patch | ||
========== | ||
=============== | ||
@@ -26,2 +26,3 @@ A leaner and meaner implementation of JSON-Patch. Small footprint. High performance. | ||
* ES6/7 Object.observe() is used when available. | ||
* Tested in IE 8-10, Firefox, Chrome and Node.js | ||
@@ -42,4 +43,10 @@ ## Roadmap | ||
Add `fast-json-patch` to dependencies list in your project's `package.json`, then run `npm install`. After installation, you are ready to go with require: | ||
Install the current version (and save it as a dependency in package.json): | ||
``` | ||
$ npm install fast-json-patch --save | ||
``` | ||
Call require to get the instance: | ||
```js | ||
@@ -49,2 +56,5 @@ var jsonpatch = require('fast-json-patch') | ||
:bulb: Node.js supports native `Object.observe` in preview release 0.11.x (and only when started with `--harmony_observation` flag). With stable versions of Node, a shimmed version of `Object.observe` is used. | ||
## Usage | ||
@@ -98,1 +108,63 @@ | ||
- Run command `jasmine-node --matchall --config duplex yes src/test.js src/test-duplex.js` | ||
## API | ||
#### jsonpatch.apply (`obj` Object, `patches` Array) : boolean | ||
Available in *json-patch.js* and *json-patch-duplex.js* | ||
Applies `patches` array on `obj`. | ||
If patch was succesfully applied, returns `true`. Otherwise returns `false`. | ||
If there was a `test` patch in `patches` array, returns the result of the test. | ||
If there was more then one patch in the array, the result of the last patch is returned. | ||
#### jsonpatch.observe (`obj` Object, `callback` Function (optional)) : `observer` Object | ||
Available in *json-patch-duplex.js* | ||
Sets up an deep observer on `obj` that listens for changes in object tree. When changes are detected, the optional | ||
callback is called with the generated patches array as the parameter. | ||
Returns `observer`. | ||
#### jsonpatch.generate (`obj` Object, `observer` Object) : `patches` Array | ||
Available in *json-patch-duplex.js* | ||
If there are pending changes in `obj`, returns them synchronously. If a `callback` was defined in `observe` | ||
method, it will be triggered synchronously as well. | ||
If there are no pending changes in `obj`, returns an empty array. | ||
#### jsonpatch.unobserve (`obj` Object, `observer` Object) : void | ||
Available in *json-patch-duplex.js* | ||
Destroys the observer set up on `obj`. | ||
Any remaining changes are delivered synchronously (as in `jsonpatch.generate`). Note: this is different that ES6/7 `Object.unobserve`, which delivers remaining changes asynchronously. | ||
## Changelog | ||
#### 0.3.5 (Oct 28, 2013) | ||
Bugfix: | ||
- issues with calling observe/unobserve method on an object multiple times in Chrome (native Object.observe) ([#20](https://github.com/Starcounter-Jack/JSON-Patch/issues/20)) | ||
#### 0.3.4 (Oct 16, 2013) | ||
Bugfix: | ||
- generate array item `remove` patches in reverse order, so they can be correctly applied in order they were generated ([#16](https://github.com/Starcounter-Jack/JSON-Patch/issues/16)) | ||
#### 0.3.3 (Oct 11, 2013) | ||
Bugfixes: | ||
- properly escape `~` and `/` characters in poiner paths ([#19](https://github.com/Starcounter-Jack/JSON-Patch/pull/19)) | ||
- generated patch contained array `length` (only in native Object.observe version) ([#14](https://github.com/Starcounter-Jack/JSON-Patch/issues/14)) | ||
- `jsonpatch.unobserve` now delivers pending changes immediately (previously last-minute changes could be lost) | ||
- stability fixes for browsers with native `Object.observe` (Chrome) | ||
- code cleanup | ||
- removed sourcemap reference from output js file |
@@ -0,1 +1,4 @@ | ||
// json-patch-duplex.js 0.3.5 | ||
// (c) 2013 Joachim Wester | ||
// MIT license | ||
var jsonpatch; | ||
@@ -17,39 +20,18 @@ (function (jsonpatch) { | ||
move: function (obj, key, tree) { | ||
var temp = { | ||
op: "_get", | ||
path: this.from | ||
}; | ||
var temp = { op: "_get", path: this.from }; | ||
apply(tree, [temp]); | ||
apply(tree, [ | ||
temp | ||
{ op: "remove", path: this.from } | ||
]); | ||
apply(tree, [ | ||
{ | ||
op: "remove", | ||
path: this.from | ||
} | ||
{ op: "add", path: this.path, value: temp.value } | ||
]); | ||
apply(tree, [ | ||
{ | ||
op: "add", | ||
path: this.path, | ||
value: temp.value | ||
} | ||
]); | ||
return true; | ||
}, | ||
copy: function (obj, key, tree) { | ||
var temp = { | ||
op: "_get", | ||
path: this.from | ||
}; | ||
var temp = { op: "_get", path: this.from }; | ||
apply(tree, [temp]); | ||
apply(tree, [ | ||
temp | ||
{ op: "add", path: this.path, value: temp.value } | ||
]); | ||
apply(tree, [ | ||
{ | ||
op: "add", | ||
path: this.path, | ||
value: temp.value | ||
} | ||
]); | ||
return true; | ||
@@ -64,11 +46,15 @@ }, | ||
}; | ||
var arrOps = { | ||
add: function (arr, i) { | ||
arr.splice(i, 0, this.value); | ||
return true; | ||
}, | ||
remove: function (arr, i) { | ||
arr.splice(i, 1); | ||
return true; | ||
}, | ||
replace: function (arr, i) { | ||
arr[i] = this.value; | ||
return true; | ||
}, | ||
@@ -80,8 +66,8 @@ move: objOps.move, | ||
}; | ||
var observeOps = { | ||
'new': function (patches, path) { | ||
//single quotes needed because 'new' is a keyword in IE8 | ||
var patch = { | ||
op: "add", | ||
path: path + "/" + this.name, | ||
path: path + escapePathComponent(this.name), | ||
value: this.object[this.name] | ||
@@ -94,3 +80,3 @@ }; | ||
op: "remove", | ||
path: path + "/" + this.name | ||
path: path + escapePathComponent(this.name) | ||
}; | ||
@@ -102,3 +88,3 @@ patches.push(patch); | ||
op: "replace", | ||
path: path + "/" + this.name, | ||
path: path + escapePathComponent(this.name), | ||
value: this.object[this.name] | ||
@@ -109,51 +95,130 @@ }; | ||
}; | ||
// ES6 symbols are not here yet. Used to calculate the json pointer to each object | ||
function markPaths(observer, node) { | ||
for(var key in node) { | ||
if(node.hasOwnProperty(key)) { | ||
var kid = node[key]; | ||
if(kid instanceof Object) { | ||
Object.unobserve(kid, observer); | ||
kid.____Path = node.____Path + "/" + key; | ||
markPaths(observer, kid); | ||
function escapePathComponent(str) { | ||
if (str.indexOf('/') === -1 && str.indexOf('~') === -1) | ||
return str; | ||
return str.replace(/~/g, '~0').replace(/\//g, '~1'); | ||
} | ||
function _getPathRecursive(root, obj) { | ||
var found; | ||
for (var key in root) { | ||
if (root.hasOwnProperty(key)) { | ||
if (root[key] === obj) { | ||
return escapePathComponent(key) + '/'; | ||
} else if (typeof root[key] === 'object') { | ||
found = _getPathRecursive(root[key], obj); | ||
if (found != '') { | ||
return escapePathComponent(key) + '/' + found; | ||
} | ||
} | ||
} | ||
} | ||
return ''; | ||
} | ||
// Detach poor mans ES6 symbols | ||
function clearPaths(observer, node) { | ||
delete node.____Path; | ||
Object.observe(node, observer); | ||
for(var key in node) { | ||
if(node.hasOwnProperty(key)) { | ||
var kid = node[key]; | ||
if(kid instanceof Object) { | ||
clearPaths(observer, kid); | ||
} | ||
} | ||
function getPath(root, obj) { | ||
if (root === obj) { | ||
return '/'; | ||
} | ||
var path = _getPathRecursive(root, obj); | ||
if (path === '') { | ||
throw new Error("Object not found in root"); | ||
} | ||
return '/' + path; | ||
} | ||
var beforeDict = []; | ||
//var callbacks = []; this has no purpose | ||
jsonpatch.intervals; | ||
var Mirror = (function () { | ||
function Mirror(obj) { | ||
this.observers = []; | ||
this.obj = obj; | ||
} | ||
return Mirror; | ||
})(); | ||
var ObserverInfo = (function () { | ||
function ObserverInfo(callback, observer) { | ||
this.callback = callback; | ||
this.observer = observer; | ||
} | ||
return ObserverInfo; | ||
})(); | ||
function getMirror(obj) { | ||
for (var i = 0, ilen = beforeDict.length; i < ilen; i++) { | ||
if (beforeDict[i].obj === obj) { | ||
return beforeDict[i]; | ||
} | ||
} | ||
} | ||
function getObserverFromMirror(mirror, callback) { | ||
for (var j = 0, jlen = mirror.observers.length; j < jlen; j++) { | ||
if (mirror.observers[j].callback === callback) { | ||
return mirror.observers[j].observer; | ||
} | ||
} | ||
} | ||
function removeObserverFromMirror(mirror, observer) { | ||
for (var j = 0, jlen = mirror.observers.length; j < jlen; j++) { | ||
if (mirror.observers[j].observer === observer) { | ||
mirror.observers.splice(j, 1); | ||
return; | ||
} | ||
} | ||
} | ||
function unobserve(root, observer) { | ||
generate(observer); | ||
if (Object.observe) { | ||
_unobserve(observer, root); | ||
} else { | ||
clearTimeout(observer.next); | ||
} | ||
var mirror = getMirror(root); | ||
removeObserverFromMirror(mirror, observer); | ||
} | ||
jsonpatch.unobserve = unobserve; | ||
function observe(obj, callback) { | ||
var patches = []; | ||
var root = obj; | ||
if(Object.observe) { | ||
var observer = function (arr) { | ||
if(!root.___Path) { | ||
Object.unobserve(root, observer); | ||
root.____Path = ""; | ||
markPaths(observer, root); | ||
var a = 0, alen = arr.length; | ||
while(a < alen) { | ||
if(arr[a].name != "____Path") { | ||
observeOps[arr[a].type].call(arr[a], patches, arr[a].object.____Path); | ||
} | ||
a++; | ||
var observer; | ||
var mirror = getMirror(obj); | ||
if (!mirror) { | ||
mirror = new Mirror(obj); | ||
beforeDict.push(mirror); | ||
} else { | ||
observer = getObserverFromMirror(mirror, callback); | ||
} | ||
if (observer) { | ||
return observer; | ||
} | ||
if (Object.observe) { | ||
observer = function (arr) { | ||
//This "refresh" is needed to begin observing new object properties | ||
_unobserve(observer, obj); | ||
_observe(observer, obj); | ||
var a = 0, alen = arr.length; | ||
while (a < alen) { | ||
if (!(arr[a].name === 'length' && _isArray(arr[a].object)) && !(arr[a].name === '__Jasmine_been_here_before__')) { | ||
observeOps[arr[a].type].call(arr[a], patches, getPath(root, arr[a].object)); | ||
} | ||
clearPaths(observer, root); | ||
a++; | ||
} | ||
if(callback) { | ||
callback(patches); | ||
if (patches) { | ||
if (callback) { | ||
callback(patches); | ||
} | ||
} | ||
@@ -164,30 +229,13 @@ observer.patches = patches; | ||
} else { | ||
observer = { | ||
}; | ||
var mirror; | ||
for(var i = 0, ilen = beforeDict.length; i < ilen; i++) { | ||
if(beforeDict[i].obj === obj) { | ||
mirror = beforeDict[i]; | ||
break; | ||
} | ||
} | ||
if(!mirror) { | ||
mirror = { | ||
obj: obj | ||
}; | ||
beforeDict.push(mirror); | ||
} | ||
mirror.value = JSON.parse(JSON.stringify(obj))// Faster than ES5 clone - http://jsperf.com/deep-cloning-of-objects/5 | ||
; | ||
if(callback) { | ||
observer = {}; | ||
mirror.value = JSON.parse(JSON.stringify(obj)); | ||
if (callback) { | ||
//callbacks.push(callback); this has no purpose | ||
observer.callback = callback; | ||
var next; | ||
var intervals = this.intervals || [ | ||
100, | ||
1000, | ||
10000, | ||
60000 | ||
]; | ||
observer.next = null; | ||
var intervals = this.intervals || [100, 1000, 10000, 60000]; | ||
var currentInterval = 0; | ||
var dirtyCheck = function () { | ||
@@ -197,7 +245,7 @@ generate(observer); | ||
var fastCheck = function () { | ||
clearTimeout(next); | ||
next = setTimeout(function () { | ||
clearTimeout(observer.next); | ||
observer.next = setTimeout(function () { | ||
dirtyCheck(); | ||
currentInterval = 0; | ||
next = setTimeout(slowCheck, intervals[currentInterval++]); | ||
observer.next = setTimeout(slowCheck, intervals[currentInterval++]); | ||
}, 0); | ||
@@ -207,11 +255,8 @@ }; | ||
dirtyCheck(); | ||
if(currentInterval == intervals.length) { | ||
if (currentInterval == intervals.length) | ||
currentInterval = intervals.length - 1; | ||
} | ||
next = setTimeout(slowCheck, intervals[currentInterval++]); | ||
observer.next = setTimeout(slowCheck, intervals[currentInterval++]); | ||
}; | ||
if(typeof window !== 'undefined') { | ||
//not Node | ||
if(window.addEventListener) { | ||
//standards | ||
if (typeof window !== 'undefined') { | ||
if (window.addEventListener) { | ||
window.addEventListener('mousedown', fastCheck); | ||
@@ -221,3 +266,2 @@ window.addEventListener('mouseup', fastCheck); | ||
} else { | ||
//IE8 | ||
window.attachEvent('onmousedown', fastCheck); | ||
@@ -228,3 +272,3 @@ window.attachEvent('onmouseup', fastCheck); | ||
} | ||
next = setTimeout(slowCheck, intervals[currentInterval++]); | ||
observer.next = setTimeout(slowCheck, intervals[currentInterval++]); | ||
} | ||
@@ -234,15 +278,18 @@ } | ||
observer.object = obj; | ||
return _observe(observer, obj, patches); | ||
mirror.observers.push(new ObserverInfo(callback, observer)); | ||
return _observe(observer, obj); | ||
} | ||
jsonpatch.observe = observe; | ||
/// Listen to changes on an object tree, accumulate patches | ||
function _observe(observer, obj, patches) { | ||
if(Object.observe) { | ||
function _observe(observer, obj) { | ||
if (Object.observe) { | ||
Object.observe(obj, observer); | ||
for(var key in obj) { | ||
if(obj.hasOwnProperty(key)) { | ||
for (var key in obj) { | ||
if (obj.hasOwnProperty(key)) { | ||
var v = obj[key]; | ||
if(v && typeof (v) === "object") { | ||
_observe(observer, v, patches)//path+key); | ||
; | ||
if (v && typeof (v) === "object") { | ||
_observe(observer, v); | ||
} | ||
@@ -254,9 +301,25 @@ } | ||
} | ||
function _unobserve(observer, obj) { | ||
if (Object.observe) { | ||
Object.unobserve(obj, observer); | ||
for (var key in obj) { | ||
if (obj.hasOwnProperty(key)) { | ||
var v = obj[key]; | ||
if (v && typeof (v) === "object") { | ||
_unobserve(observer, v); | ||
} | ||
} | ||
} | ||
} | ||
return observer; | ||
} | ||
function generate(observer) { | ||
if(Object.observe) { | ||
if (Object.observe) { | ||
Object.deliverChangeRecords(observer); | ||
} else { | ||
var mirror; | ||
for(var i = 0, ilen = beforeDict.length; i < ilen; i++) { | ||
if(beforeDict[i].obj === observer.object) { | ||
for (var i = 0, ilen = beforeDict.length; i < ilen; i++) { | ||
if (beforeDict[i].obj === observer.object) { | ||
mirror = beforeDict[i]; | ||
@@ -269,5 +332,5 @@ break; | ||
var temp = observer.patches; | ||
if(temp.length > 0) { | ||
if (temp.length > 0) { | ||
observer.patches = []; | ||
if(observer.callback) { | ||
if (observer.callback) { | ||
observer.callback(temp); | ||
@@ -279,12 +342,11 @@ } | ||
jsonpatch.generate = generate; | ||
var _objectKeys; | ||
if(Object.keys) { | ||
//standards | ||
if (Object.keys) { | ||
_objectKeys = Object.keys; | ||
} else { | ||
//IE8 shim | ||
_objectKeys = function (obj) { | ||
var keys = []; | ||
for(var o in obj) { | ||
if(obj.hasOwnProperty(o)) { | ||
for (var o in obj) { | ||
if (obj.hasOwnProperty(o)) { | ||
keys.push(o); | ||
@@ -296,2 +358,3 @@ } | ||
} | ||
// Dirty check if obj is different from mirror, generate patches and update mirror | ||
@@ -303,17 +366,14 @@ function _generate(mirror, obj, patches, path) { | ||
var deleted = false; | ||
for(var t = 0; t < oldKeys.length; t++) { | ||
for (var t = oldKeys.length - 1; t >= 0; t--) { | ||
var key = oldKeys[t]; | ||
var oldVal = mirror[key]; | ||
if(obj.hasOwnProperty(key)) { | ||
if (obj.hasOwnProperty(key)) { | ||
var newVal = obj[key]; | ||
if(oldVal instanceof Object) { | ||
_generate(oldVal, newVal, patches, path + "/" + key); | ||
if (oldVal instanceof Object) { | ||
_generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key)); | ||
} else { | ||
if(oldVal != newVal) { | ||
if (oldVal != newVal) { | ||
changed = true; | ||
patches.push({ | ||
op: "replace", | ||
path: path + "/" + key, | ||
value: newVal | ||
}); | ||
patches.push({ op: "replace", path: path + "/" + escapePathComponent(key), value: newVal }); | ||
mirror[key] = newVal; | ||
@@ -323,30 +383,25 @@ } | ||
} else { | ||
patches.push({ | ||
op: "remove", | ||
path: path + "/" + key | ||
}); | ||
deleted = true// property has been deleted | ||
; | ||
patches.push({ op: "remove", path: path + "/" + escapePathComponent(key) }); | ||
delete mirror[key]; | ||
deleted = true; | ||
} | ||
} | ||
if(!deleted && newKeys.length == oldKeys.length) { | ||
if (!deleted && newKeys.length == oldKeys.length) { | ||
return; | ||
} | ||
for(var t = 0; t < newKeys.length; t++) { | ||
for (var t = 0; t < newKeys.length; t++) { | ||
var key = newKeys[t]; | ||
if(!mirror.hasOwnProperty(key)) { | ||
patches.push({ | ||
op: "add", | ||
path: path + "/" + key, | ||
value: obj[key] | ||
}); | ||
if (!mirror.hasOwnProperty(key)) { | ||
patches.push({ op: "add", path: path + "/" + escapePathComponent(key), value: obj[key] }); | ||
mirror[key] = JSON.parse(JSON.stringify(obj[key])); | ||
} | ||
} | ||
} | ||
var _isArray; | ||
if(Array.isArray) { | ||
//standards; http://jsperf.com/isarray-shim/4 | ||
if (Array.isArray) { | ||
_isArray = Array.isArray; | ||
} else { | ||
//IE8 shim | ||
_isArray = function (obj) { | ||
@@ -356,20 +411,20 @@ return obj.push && typeof obj.length === 'number'; | ||
} | ||
/// Apply a json-patch operation on an object tree | ||
function apply(tree, patches, listen) { | ||
function apply(tree, patches) { | ||
var result = false, p = 0, plen = patches.length, patch; | ||
while(p < plen) { | ||
while (p < plen) { | ||
patch = patches[p]; | ||
// Find the object | ||
var keys = patch.path.split('/'); | ||
var obj = tree; | ||
var t = 1;//skip empty element - http://jsperf.com/to-shift-or-not-to-shift | ||
var t = 1; | ||
var len = keys.length; | ||
while(true) { | ||
if(_isArray(obj)) { | ||
while (true) { | ||
if (_isArray(obj)) { | ||
var index = parseInt(keys[t], 10); | ||
t++; | ||
if(t >= len) { | ||
result = arrOps[patch.op].call(patch, obj, index, tree)// Apply patch | ||
; | ||
if (t >= len) { | ||
result = arrOps[patch.op].call(patch, obj, index, tree); | ||
break; | ||
@@ -380,10 +435,7 @@ } | ||
var key = keys[t]; | ||
if(key.indexOf('~') != -1) { | ||
key = key.replace('~1', '/').replace('~0', '~'); | ||
}// escape chars | ||
if (key.indexOf('~') != -1) | ||
key = key.replace(/~1/g, '/').replace(/~0/g, '~'); | ||
t++; | ||
if(t >= len) { | ||
result = objOps[patch.op].call(patch, obj, key, tree)// Apply patch | ||
; | ||
if (t >= len) { | ||
result = objOps[patch.op].call(patch, obj, key, tree); | ||
break; | ||
@@ -400,7 +452,8 @@ } | ||
})(jsonpatch || (jsonpatch = {})); | ||
if(typeof exports !== "undefined") { | ||
if (typeof exports !== "undefined") { | ||
exports.apply = jsonpatch.apply; | ||
exports.observe = jsonpatch.observe; | ||
exports.unobserve = jsonpatch.unobserve; | ||
exports.generate = jsonpatch.generate; | ||
} | ||
//@ sourceMappingURL=json-patch-duplex.js.map |
@@ -1,2 +0,2 @@ | ||
// json-patch-duplex.js 0.3 | ||
// json-patch-duplex.js 0.3.5 | ||
// (c) 2013 Joachim Wester | ||
@@ -56,8 +56,11 @@ // MIT license | ||
arr.splice(i, 0, this.value); | ||
return true; | ||
}, | ||
remove: function (arr, i) { | ||
arr.splice(i, 1); | ||
return true; | ||
}, | ||
replace: function (arr, i) { | ||
arr[i] = this.value; | ||
return true; | ||
}, | ||
@@ -74,3 +77,3 @@ move: objOps.move, | ||
op: "add", | ||
path: path + "/" + this.name, | ||
path: path + escapePathComponent(this.name), | ||
value: this.object[this.name]}; | ||
@@ -82,3 +85,3 @@ patches.push(patch); | ||
op: "remove", | ||
path: path + "/" + this.name | ||
path: path + escapePathComponent(this.name) | ||
}; | ||
@@ -90,3 +93,3 @@ patches.push(patch); | ||
op: "replace", | ||
path: path + "/" + this.name, | ||
path: path + escapePathComponent(this.name), | ||
value: this.object[this.name] | ||
@@ -98,81 +101,146 @@ }; | ||
// ES6 symbols are not here yet. Used to calculate the json pointer to each object | ||
function markPaths(observer, node) { | ||
for (var key in node) { | ||
if (node.hasOwnProperty(key)) { | ||
var kid = node[key]; | ||
if (kid instanceof Object) { | ||
Object.unobserve(kid, observer); | ||
kid.____Path = node.____Path + "/" + key; | ||
markPaths(observer, kid); | ||
function escapePathComponent (str) { | ||
if (str.indexOf('/') === -1 && str.indexOf('~') === -1) return str; | ||
return str.replace(/~/g, '~0').replace(/\//g, '~1'); | ||
} | ||
function _getPathRecursive(root:Object, obj:Object):string { | ||
var found; | ||
for (var key in root) { | ||
if (root.hasOwnProperty(key)) { | ||
if (root[key] === obj) { | ||
return escapePathComponent(key) + '/'; | ||
} | ||
else if (typeof root[key] === 'object') { | ||
found = _getPathRecursive(root[key], obj); | ||
if (found != '') { | ||
return escapePathComponent(key) + '/' + found; | ||
} | ||
} | ||
} | ||
} | ||
return ''; | ||
} | ||
// Detach poor mans ES6 symbols | ||
function clearPaths(observer, node) { | ||
delete node.____Path; | ||
Object.observe(node, observer); | ||
for (var key in node) { | ||
if (node.hasOwnProperty(key)) { | ||
var kid = node[key]; | ||
if (kid instanceof Object) { | ||
clearPaths(observer, kid); | ||
} | ||
} | ||
function getPath(root:Object, obj:Object):string { | ||
if (root === obj) { | ||
return '/'; | ||
} | ||
var path = _getPathRecursive(root, obj); | ||
if (path === '') { | ||
throw new Error("Object not found in root"); | ||
} | ||
return '/' + path; | ||
} | ||
var beforeDict = []; | ||
//var callbacks = []; this has no purpose | ||
export var intervals; | ||
class Mirror { | ||
obj: any; | ||
observers = []; | ||
constructor(obj:any){ | ||
this.obj = obj; | ||
} | ||
} | ||
class ObserverInfo { | ||
callback: any; | ||
observer: any; | ||
constructor(callback, observer){ | ||
this.callback = callback; | ||
this.observer = observer; | ||
} | ||
} | ||
function getMirror(obj:any):any { | ||
for (var i = 0, ilen = beforeDict.length; i < ilen; i++) { | ||
if (beforeDict[i].obj === obj) { | ||
return beforeDict[i]; | ||
} | ||
} | ||
} | ||
function getObserverFromMirror(mirror:any, callback):any { | ||
for (var j = 0, jlen = mirror.observers.length; j < jlen; j++) { | ||
if (mirror.observers[j].callback === callback) { | ||
return mirror.observers[j].observer; | ||
} | ||
} | ||
} | ||
function removeObserverFromMirror(mirror:any, observer):any { | ||
for (var j = 0, jlen = mirror.observers.length; j < jlen; j++) { | ||
if (mirror.observers[j].observer === observer) { | ||
mirror.observers.splice(j, 1); | ||
return; | ||
} | ||
} | ||
} | ||
export function unobserve(root, observer) { | ||
generate(observer); | ||
if(Object.observe) { | ||
_unobserve(observer, root); | ||
} | ||
else { | ||
clearTimeout(observer.next); | ||
} | ||
var mirror = getMirror(root); | ||
removeObserverFromMirror(mirror, observer); | ||
} | ||
export function observe(obj:any, callback):any { | ||
var patches = []; | ||
var root = obj; | ||
if (Object.observe) { | ||
var observer = function (arr) { | ||
var observer; | ||
var mirror = getMirror(obj); | ||
if (!root.___Path) { | ||
if (!mirror) { | ||
mirror = new Mirror(obj); | ||
beforeDict.push(mirror); | ||
} else { | ||
observer = getObserverFromMirror(mirror, callback); | ||
} | ||
Object.unobserve(root, observer); | ||
root.____Path = ""; | ||
markPaths(observer, root); | ||
if(observer){ | ||
return observer; | ||
} | ||
var a = 0 | ||
, alen = arr.length; | ||
while (a < alen) { | ||
if (arr[a].name != "____Path") { | ||
observeOps[arr[a].type].call(arr[a], patches, arr[a].object.____Path); | ||
} | ||
a++; | ||
if (Object.observe) { | ||
observer = function (arr) { | ||
//This "refresh" is needed to begin observing new object properties | ||
_unobserve(observer, obj); | ||
_observe(observer, obj); | ||
var a = 0 | ||
, alen = arr.length; | ||
while (a < alen) { | ||
if ( | ||
!(arr[a].name === 'length' && _isArray(arr[a].object)) | ||
&& !(arr[a].name === '__Jasmine_been_here_before__') | ||
) { | ||
observeOps[arr[a].type].call(arr[a], patches, getPath(root, arr[a].object)); | ||
} | ||
a++; | ||
} | ||
clearPaths(observer, root); | ||
if (patches) { | ||
if (callback) { | ||
callback(patches); | ||
} | ||
} | ||
if (callback) { | ||
callback(patches); | ||
} | ||
observer.patches = patches; | ||
patches = []; | ||
}; | ||
} | ||
else { | ||
} else { | ||
observer = {}; | ||
var mirror; | ||
for (var i = 0, ilen = beforeDict.length; i < ilen; i++) { | ||
if (beforeDict[i].obj === obj) { | ||
mirror = beforeDict[i]; | ||
break; | ||
} | ||
} | ||
if (!mirror) { | ||
mirror = {obj: obj}; | ||
beforeDict.push(mirror); | ||
} | ||
mirror.value = JSON.parse(JSON.stringify(obj)); // Faster than ES5 clone - http://jsperf.com/deep-cloning-of-objects/5 | ||
@@ -183,3 +251,3 @@ | ||
observer.callback = callback; | ||
var next; | ||
observer.next = null; | ||
var intervals = this.intervals || [100, 1000, 10000, 60000]; | ||
@@ -192,7 +260,7 @@ var currentInterval = 0; | ||
var fastCheck = function () { | ||
clearTimeout(next); | ||
next = setTimeout(function () { | ||
clearTimeout(observer.next); | ||
observer.next = setTimeout(function () { | ||
dirtyCheck(); | ||
currentInterval = 0; | ||
next = setTimeout(slowCheck, intervals[currentInterval++]); | ||
observer.next = setTimeout(slowCheck, intervals[currentInterval++]); | ||
}, 0); | ||
@@ -204,3 +272,3 @@ }; | ||
currentInterval = intervals.length - 1; | ||
next = setTimeout(slowCheck, intervals[currentInterval++]); | ||
observer.next = setTimeout(slowCheck, intervals[currentInterval++]); | ||
}; | ||
@@ -219,3 +287,3 @@ if (typeof window !== 'undefined') { //not Node | ||
} | ||
next = setTimeout(slowCheck, intervals[currentInterval++]); | ||
observer.next = setTimeout(slowCheck, intervals[currentInterval++]); | ||
} | ||
@@ -225,7 +293,10 @@ } | ||
observer.object = obj; | ||
return _observe(observer, obj, patches); | ||
mirror.observers.push(new ObserverInfo(callback, observer)); | ||
return _observe(observer, obj); | ||
} | ||
/// Listen to changes on an object tree, accumulate patches | ||
function _observe(observer:any, obj:any, patches:any[]):any { | ||
function _observe(observer:any, obj:any):any { | ||
if (Object.observe) { | ||
@@ -237,3 +308,3 @@ Object.observe(obj, observer); | ||
if (v && typeof (v) === "object") { | ||
_observe(observer, v, patches); //path+key); | ||
_observe(observer, v); | ||
} | ||
@@ -246,2 +317,17 @@ } | ||
function _unobserve(observer:any, obj:any):any { | ||
if (Object.observe) { | ||
Object.unobserve(obj, observer); | ||
for (var key in obj) { | ||
if (obj.hasOwnProperty(key)) { | ||
var v:any = obj[key]; | ||
if (v && typeof (v) === "object") { | ||
_unobserve(observer, v); | ||
} | ||
} | ||
} | ||
} | ||
return observer; | ||
} | ||
export function generate(observer) { | ||
@@ -294,3 +380,5 @@ if (Object.observe) { | ||
for (var t = 0; t < oldKeys.length; t++) { | ||
//if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)" | ||
for (var t = oldKeys.length - 1; t >= 0; t--) { | ||
var key = oldKeys[t]; | ||
@@ -301,3 +389,3 @@ var oldVal = mirror[key]; | ||
if (oldVal instanceof Object) { | ||
_generate(oldVal, newVal, patches, path + "/" + key); | ||
_generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key)); | ||
} | ||
@@ -307,3 +395,3 @@ else { | ||
changed = true; | ||
patches.push({op: "replace", path: path + "/" + key, value: newVal}); | ||
patches.push({op: "replace", path: path + "/" + escapePathComponent(key), value: newVal}); | ||
mirror[key] = newVal; | ||
@@ -314,3 +402,4 @@ } | ||
else { | ||
patches.push({op: "remove", path: path + "/" + key}); | ||
patches.push({op: "remove", path: path + "/" + escapePathComponent(key)}); | ||
delete mirror[key]; | ||
deleted = true; // property has been deleted | ||
@@ -327,3 +416,4 @@ } | ||
if (!mirror.hasOwnProperty(key)) { | ||
patches.push({op: "add", path: path + "/" + key, value: obj[key]}); | ||
patches.push({op: "add", path: path + "/" + escapePathComponent(key), value: obj[key]}); | ||
mirror[key] = JSON.parse(JSON.stringify(obj[key])); | ||
} | ||
@@ -344,3 +434,3 @@ } | ||
/// Apply a json-patch operation on an object tree | ||
export function apply(tree:any, patches:any[], listen?:any):bool { | ||
export function apply(tree:any, patches:any[]):boolean { | ||
var result = false | ||
@@ -370,3 +460,3 @@ , p = 0 | ||
if (key.indexOf('~') != -1) | ||
key = key.replace('~1', '/').replace('~0', '~'); // escape chars | ||
key = key.replace(/~1/g, '/').replace(/~0/g, '~'); // escape chars | ||
t++; | ||
@@ -391,3 +481,4 @@ if (t >= len) { | ||
exports.observe = jsonpatch.observe; | ||
exports.unobserve = jsonpatch.unobserve; | ||
exports.generate = jsonpatch.generate; | ||
} |
@@ -1,2 +0,2 @@ | ||
// json-patch.js 0.3 | ||
// json-patch.js 0.3.5 | ||
// (c) 2013 Joachim Wester | ||
@@ -20,39 +20,18 @@ // MIT license | ||
move: function (obj, key, tree) { | ||
var temp = { | ||
op: "_get", | ||
path: this.from | ||
}; | ||
var temp = { op: "_get", path: this.from }; | ||
apply(tree, [temp]); | ||
apply(tree, [ | ||
temp | ||
{ op: "remove", path: this.from } | ||
]); | ||
apply(tree, [ | ||
{ | ||
op: "remove", | ||
path: this.from | ||
} | ||
{ op: "add", path: this.path, value: temp.value } | ||
]); | ||
apply(tree, [ | ||
{ | ||
op: "add", | ||
path: this.path, | ||
value: temp.value | ||
} | ||
]); | ||
return true; | ||
}, | ||
copy: function (obj, key, tree) { | ||
var temp = { | ||
op: "_get", | ||
path: this.from | ||
}; | ||
var temp = { op: "_get", path: this.from }; | ||
apply(tree, [temp]); | ||
apply(tree, [ | ||
temp | ||
{ op: "add", path: this.path, value: temp.value } | ||
]); | ||
apply(tree, [ | ||
{ | ||
op: "add", | ||
path: this.path, | ||
value: temp.value | ||
} | ||
]); | ||
return true; | ||
@@ -67,11 +46,15 @@ }, | ||
}; | ||
var arrOps = { | ||
add: function (arr, i) { | ||
arr.splice(i, 0, this.value); | ||
return true; | ||
}, | ||
remove: function (arr, i) { | ||
arr.splice(i, 1); | ||
return true; | ||
}, | ||
replace: function (arr, i) { | ||
arr[i] = this.value; | ||
return true; | ||
}, | ||
@@ -83,8 +66,7 @@ move: objOps.move, | ||
}; | ||
var _isArray; | ||
if(Array.isArray) { | ||
//standards; http://jsperf.com/isarray-shim/4 | ||
if (Array.isArray) { | ||
_isArray = Array.isArray; | ||
} else { | ||
//IE8 shim | ||
_isArray = function (obj) { | ||
@@ -94,20 +76,20 @@ return obj.push && typeof obj.length === 'number'; | ||
} | ||
/// Apply a json-patch operation on an object tree | ||
function apply(tree, patches, listen) { | ||
function apply(tree, patches) { | ||
var result = false, p = 0, plen = patches.length, patch; | ||
while(p < plen) { | ||
while (p < plen) { | ||
patch = patches[p]; | ||
// Find the object | ||
var keys = patch.path.split('/'); | ||
var obj = tree; | ||
var t = 1;//skip empty element - http://jsperf.com/to-shift-or-not-to-shift | ||
var t = 1; | ||
var len = keys.length; | ||
while(true) { | ||
if(_isArray(obj)) { | ||
while (true) { | ||
if (_isArray(obj)) { | ||
var index = parseInt(keys[t], 10); | ||
t++; | ||
if(t >= len) { | ||
result = arrOps[patch.op].call(patch, obj, index, tree)// Apply patch | ||
; | ||
if (t >= len) { | ||
result = arrOps[patch.op].call(patch, obj, index, tree); | ||
break; | ||
@@ -118,10 +100,7 @@ } | ||
var key = keys[t]; | ||
if(key.indexOf('~') != -1) { | ||
key = key.replace('~1', '/').replace('~0', '~'); | ||
}// escape chars | ||
if (key.indexOf('~') != -1) | ||
key = key.replace(/~1/g, '/').replace(/~0/g, '~'); | ||
t++; | ||
if(t >= len) { | ||
result = objOps[patch.op].call(patch, obj, key, tree)// Apply patch | ||
; | ||
if (t >= len) { | ||
result = objOps[patch.op].call(patch, obj, key, tree); | ||
break; | ||
@@ -138,5 +117,5 @@ } | ||
})(jsonpatch || (jsonpatch = {})); | ||
if(typeof exports !== "undefined") { | ||
if (typeof exports !== "undefined") { | ||
exports.apply = jsonpatch.apply; | ||
} | ||
//@ sourceMappingURL=json-patch.js.map |
@@ -1,2 +0,2 @@ | ||
// json-patch.js 0.3 | ||
// json-patch.js 0.3.5 | ||
// (c) 2013 Joachim Wester | ||
@@ -50,8 +50,11 @@ // MIT license | ||
arr.splice(i, 0, this.value); | ||
return true; | ||
}, | ||
remove: function (arr, i) { | ||
arr.splice(i, 1); | ||
return true; | ||
}, | ||
replace: function (arr, i) { | ||
arr[i] = this.value; | ||
return true; | ||
}, | ||
@@ -65,3 +68,3 @@ move: objOps.move, | ||
var _isArray; | ||
if(Array.isArray) { //standards; http://jsperf.com/isarray-shim/4 | ||
if (Array.isArray) { //standards; http://jsperf.com/isarray-shim/4 | ||
_isArray = Array.isArray; | ||
@@ -76,3 +79,3 @@ } | ||
/// Apply a json-patch operation on an object tree | ||
export function apply(tree:any, patches:any[], listen?:any):bool { | ||
export function apply(tree:any, patches:any[]):boolean { | ||
var result = false | ||
@@ -82,3 +85,3 @@ , p = 0 | ||
, patch; | ||
while(p < plen) { | ||
while (p < plen) { | ||
patch = patches[p]; | ||
@@ -103,3 +106,3 @@ // Find the object | ||
if (key.indexOf('~') != -1) | ||
key = key.replace('~1', '/').replace('~0', '~'); // escape chars | ||
key = key.replace(/~1/g, '/').replace(/~0/g, '~'); // escape chars | ||
t++; | ||
@@ -119,3 +122,3 @@ if (t >= len) { | ||
declare var exports: any; | ||
declare var exports:any; | ||
@@ -122,0 +125,0 @@ if (typeof exports !== "undefined") { |
@@ -12,2 +12,5 @@ var obj, obj2, patches; | ||
describe("JSON-Patch-Duplex", function () { | ||
beforeEach(function () { | ||
this.addMatchers({ | ||
/** | ||
@@ -18,7 +21,8 @@ * This matcher is only needed in Chrome 28 (Chrome 28 cannot successfully compare observed objects immediately after they have been changed. Chrome 30 is unaffected) | ||
*/ | ||
jasmine.Matchers.prototype.toEqualInJSON = function(obj) { | ||
return JSON.stringify(this.actual) == JSON.stringify(obj); | ||
}; | ||
toEqualInJson: function (expected) { | ||
return JSON.stringify(this.actual) == JSON.stringify(expected); | ||
} | ||
}); | ||
}); | ||
describe("JSON-Patch-Duplex", function () { | ||
describe("generate", function () { | ||
@@ -43,2 +47,20 @@ it('should generate replace', function() { | ||
it('should generate replace (escaped chars)', function() { | ||
obj = { "/name/first":"Albert", "/name/last":"Einstein", | ||
"~phone~/numbers":[ {number:"12345"}, {number:"45353"} ]}; | ||
var observer = jsonpatch.observe(obj); | ||
obj['/name/first'] = "Joachim"; | ||
obj['/name/last'] = "Wester"; | ||
obj['~phone~/numbers'][0].number = "123"; | ||
obj['~phone~/numbers'][1].number = "456"; | ||
var patches = jsonpatch.generate(observer); | ||
obj2 = { "/name/first":"Albert", "/name/last":"Einstein", | ||
"~phone~/numbers":[ {number:"12345"}, {number:"45353"} ]}; | ||
jsonpatch.apply(obj2,patches); | ||
expect(obj2).toEqual(obj); | ||
}); | ||
it('should generate replace (2 observers)', function() { | ||
@@ -97,2 +119,50 @@ var person1 = {firstName: "Alexandra", lastName: "Galbreath"}; | ||
it('should generate replace (changes in new array cell, primitive values)', function() { | ||
arr = [1]; | ||
var observer = jsonpatch.observe(arr); | ||
arr.push(2); | ||
var patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([{op: 'add', path: '/1', value: 2}]); | ||
arr[0] = 3; | ||
var patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([{op: 'replace', path: '/0', value: 3}]); | ||
arr[1] = 4; | ||
var patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([{op: 'replace', path: '/1', value: 4}]); | ||
}); | ||
it('should generate replace (changes in new array cell, complex values)', function() { | ||
arr = [ | ||
{ | ||
id: 1, | ||
name: 'Ted' | ||
} | ||
]; | ||
var observer = jsonpatch.observe(arr); | ||
arr.push({id: 2, name: 'Jerry'}); | ||
var patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([{op: 'add', path: '/1', value: {id: 2, name: 'Jerry'}}]); | ||
arr[0].id = 3; | ||
var patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([{op: 'replace', path: '/0/id', value: 3}]); | ||
arr[1].id = 4; | ||
var patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([{op: 'replace', path: '/1/id', value: 4}]); | ||
}); | ||
it('should generate add', function() { | ||
@@ -113,6 +183,6 @@ obj = { lastName:"Einstein", | ||
jsonpatch.apply(obj2,patches); | ||
expect(obj2).toEqualInJSON(obj); | ||
expect(obj2).toEqualInJson(obj); | ||
}); | ||
it('should generate delete', function() { | ||
it('should generate remove', function() { | ||
obj = { lastName:"Einstein", firstName:"Albert", | ||
@@ -132,4 +202,114 @@ phoneNumbers:[ {number:"12345"}, {number:"4234"} ]}; | ||
jsonpatch.apply(obj2,patches); | ||
expect(obj2).toEqualInJSON(obj); | ||
expect(obj2).toEqualInJson(obj); | ||
}); | ||
it('should generate remove (array indexes should be sorted descending)', function() { | ||
obj = { items: ["a", "b", "c"]}; | ||
var observer = jsonpatch.observe(obj); | ||
obj.items.pop(); | ||
obj.items.pop(); | ||
patches = jsonpatch.generate(observer); | ||
//array indexes must be sorted descending, otherwise there is an index collision in apply | ||
expect(patches).toEqual([ | ||
{ op: 'remove', path: '/items/2' }, | ||
{ op: 'remove', path: '/items/1' } | ||
]); | ||
obj2 = { items: ["a", "b", "c"]}; | ||
jsonpatch.apply(obj2,patches); | ||
expect(obj).toEqualInJson(obj2); | ||
}); | ||
it('should not generate the same patch twice (replace)', function() { | ||
obj = { lastName:"Einstein" }; | ||
var observer = jsonpatch.observe(obj); | ||
obj.lastName = "Wester"; | ||
patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([ | ||
{ op: 'replace', path: '/lastName', value: 'Wester' } | ||
]); | ||
patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([]); | ||
}); | ||
it('should not generate the same patch twice (add)', function() { | ||
obj = { lastName:"Einstein" }; | ||
var observer = jsonpatch.observe(obj); | ||
obj.firstName = "Albert"; | ||
patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([ | ||
{ op: 'add', path: '/firstName', value: 'Albert' } | ||
]); | ||
patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([]); | ||
}); | ||
it('should not generate the same patch twice (remove)', function() { | ||
obj = { lastName:"Einstein" }; | ||
var observer = jsonpatch.observe(obj); | ||
delete obj.lastName; | ||
patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([ | ||
{ op: 'remove', path: '/lastName' } | ||
]); | ||
patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([]); | ||
}); | ||
//related issue: https://github.com/Starcounter-Jack/JSON-Patch/issues/14 | ||
it('should generate the same patch using Object.observe and shim', function() { | ||
var arr1 = [ | ||
["Albert", "Einstein"], | ||
["Erwin", "Shrodinger"] | ||
]; | ||
var arr2 = arr1.slice(); | ||
var newRecord = ['Niels', 'Bohr']; | ||
var observer1 = jsonpatch.observe(arr1); | ||
arr1.push(newRecord); | ||
var objectObservePatches = jsonpatch.generate(observer1); | ||
var _observe = Object.observe; | ||
Object.observe = undefined; | ||
var observer2 = jsonpatch.observe(arr2); | ||
arr2.push(newRecord); | ||
var shimPatches = jsonpatch.generate(observer2); | ||
expect(objectObservePatches).toEqual(shimPatches); | ||
Object.observe = _observe; | ||
}); | ||
/*it('should not generate the same patch twice (move)', function() { //"move" is not implemented yet in jsonpatch.generate | ||
obj = { lastName: {str: "Einstein"} }; | ||
var observer = jsonpatch.observe(obj); | ||
obj.lastName2 = obj.lastName; | ||
delete obj.lastName; | ||
patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([ | ||
{ op: 'move', from: '/lastName', to: '/lastName2' } | ||
]); | ||
patches = jsonpatch.generate(observer); | ||
expect(patches).toEqual([]); | ||
});*/ | ||
}); | ||
@@ -269,3 +449,224 @@ | ||
}); | ||
it('should unobserve then observe again', function() { | ||
var called = 0; | ||
obj = { firstName:"Albert", lastName:"Einstein", | ||
phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; | ||
jsonpatch.intervals = [10]; | ||
var observer = jsonpatch.observe(obj, function(patches) { | ||
called++; | ||
}); | ||
obj.firstName = 'Malvin'; | ||
waits(20); | ||
runs(function(){ | ||
expect(called).toEqual(1); | ||
jsonpatch.unobserve(obj, observer); | ||
obj.firstName = 'Wilfred'; | ||
}); | ||
waits(20); | ||
runs(function(){ | ||
expect(called).toEqual(1); | ||
observer = jsonpatch.observe(obj, function(patches) { | ||
called++; | ||
}); | ||
obj.firstName = 'Megan'; | ||
}); | ||
waits(20); | ||
runs(function(){ | ||
expect(called).toEqual(2); | ||
}); | ||
}); | ||
it('should unobserve then observe again (deep value)', function() { | ||
var called = 0; | ||
obj = { firstName:"Albert", lastName:"Einstein", | ||
phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; | ||
jsonpatch.intervals = [10]; | ||
var observer = jsonpatch.observe(obj, function(patches) { | ||
called++; | ||
}); | ||
obj.phoneNumbers[1].number = '555'; | ||
waits(20); | ||
runs(function(){ | ||
expect(called).toEqual(1); | ||
jsonpatch.unobserve(obj, observer); | ||
obj.phoneNumbers[1].number = '556'; | ||
}); | ||
waits(20); | ||
runs(function(){ | ||
expect(called).toEqual(1); | ||
observer = jsonpatch.observe(obj, function(patches) { | ||
called++; | ||
}); | ||
obj.phoneNumbers[1].number = '557'; | ||
}); | ||
waits(20); | ||
runs(function(){ | ||
expect(called).toEqual(2); | ||
}); | ||
}); | ||
it('calling unobserve should deliver pending changes synchronously', function() { | ||
var lastPatches = ''; | ||
obj = { firstName: "Albert", lastName: "Einstein", | ||
phoneNumbers: [ | ||
{number: "12345"}, | ||
{number: "45353"} | ||
]}; | ||
jsonpatch.intervals = [10]; | ||
var observer = jsonpatch.observe(obj, function (patches) { | ||
lastPatches = patches; | ||
}); | ||
obj.firstName = 'Malvin'; | ||
jsonpatch.unobserve(obj, observer); | ||
expect(lastPatches[0].value).toBe('Malvin'); | ||
obj.firstName = 'Jonathan'; | ||
waits(20); | ||
runs(function () { | ||
expect(lastPatches[0].value).toBe('Malvin'); | ||
}); | ||
}); | ||
it("should handle callbacks that calls observe() and unobserve() internally", function () { | ||
var obj = { | ||
foo: 'bar' | ||
}; | ||
var observer; | ||
var callback = jasmine.createSpy('callback'); | ||
callback.plan = function(){ | ||
jsonpatch.unobserve(obj, observer); | ||
jsonpatch.observe(obj, callback); | ||
}; | ||
observer = jsonpatch.observe(obj, callback); | ||
expect(callback.calls.length).toEqual(0); | ||
obj.foo = 'bazz'; | ||
waitsFor(function () { | ||
return callback.calls.length > 0; | ||
}, 'callback calls', 1000); | ||
runs(function () { | ||
expect(callback.calls.length).toEqual(1); | ||
callback.reset(); | ||
obj.foo = 'bazinga'; | ||
}); | ||
waitsFor(function () { | ||
return callback.calls.length > 0; | ||
}, 'callback calls', 1000); | ||
runs(function () { | ||
expect(callback.calls.length).toEqual(1); | ||
}); | ||
}); | ||
}); | ||
describe("Registering multiple observers with the same callback", function () { | ||
it("should register only one observer", function () { | ||
var obj = { | ||
foo: 'bar' | ||
}; | ||
var callback = jasmine.createSpy('callback'); | ||
jsonpatch.observe(obj, callback); | ||
jsonpatch.observe(obj, callback); | ||
expect(callback.calls.length).toEqual(0); | ||
obj.foo = 'bazz'; | ||
waitsFor(function () { | ||
return callback.calls.length > 0; | ||
}, 'callback call', 1000); | ||
runs(function () { | ||
expect(callback.calls.length).toEqual(1); | ||
}); | ||
}); | ||
it("should return the same observer if callback has been already registered)", function () { | ||
var obj = { | ||
foo: 'bar' | ||
}; | ||
var callback = jasmine.createSpy('callback'); | ||
var observer1 = jsonpatch.observe(obj, callback); | ||
var observer2 = jsonpatch.observe(obj, callback); | ||
expect(observer1).toBe(observer2); | ||
}); | ||
it("should return a different observer if callback has been unregistered and registered again", function () { | ||
var obj = { | ||
foo: 'bar' | ||
}; | ||
var callback = jasmine.createSpy('callback'); | ||
var observer1 = jsonpatch.observe(obj, callback); | ||
jsonpatch.unobserve(obj, observer1); | ||
var observer2 = jsonpatch.observe(obj, callback); | ||
expect(observer1).not.toBe(observer2); | ||
}); | ||
}); | ||
}); | ||
@@ -272,0 +673,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
245522
29
5075
1
166