├── .gitignore ├── .npmignore ├── LICENSE.txt ├── README.md ├── package.json └── src ├── json-patch-duplex.js ├── json-patch-duplex.js.map ├── json-patch-duplex.ts ├── json-patch.js ├── json-patch.js.map ├── json-patch.ts ├── lib ├── jasmine │ ├── MIT.LICENSE │ ├── jasmine-html.js │ ├── jasmine.css │ ├── jasmine.js │ └── jasmine_favicon.png └── jslitmus.js ├── patchtest.html ├── test-duplex.html ├── test-duplex.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | .DS_Store 3 | 4 | # Node.js 5 | node_modules/* 6 | npm-debug.log 7 | 8 | # WebStorm 9 | .idea 10 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | README.md 2 | Makefile 3 | doc/ 4 | examples/ 5 | test/ 6 | 7 | # Mac OS X 8 | .DS_Store 9 | 10 | # Node.js 11 | .npmignore 12 | node_modules/ 13 | npm-debug.log 14 | 15 | # Git 16 | .git* 17 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2013, 2014 Joachim Wester 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JSON-Patch 2 | =============== 3 | 4 | A leaner and meaner implementation of JSON-Patch. Small footprint. High performance. 5 | Also support dual directions! I.e. you can both apply patches and generate patches. 6 | 7 | ## Why you should use JSON-Patch 8 | 9 | JSON-Patch [(RFC6902)](http://tools.ietf.org/html/rfc6902) is a standard format that 10 | allows you to update a JSON document by sending the changes rather than the whole document. 11 | JSON Patch plays well with the HTTP PATCH verb (method) and REST style programming. 12 | 13 | Mark Nottingham has a [nice blog]( http://www.mnot.net/blog/2012/09/05/patch) about it. 14 | 15 | ## Footprint 16 | 0.5 KB minified and gzipped (1.1 KB minified) 17 | 18 | ## Performance 19 | ![Fast](http://www.rebelslounge.com/res/jsonpatch/chart3.png) 20 | 21 | 22 | ## Features 23 | * Allows you to apply patches on object trees for incoming traffic. 24 | * Allows you to freely manipulate object trees and then generate patches for outgoing traffic. 25 | * ES6/7 Object.observe() is used when available. 26 | * Tested in IE 8-10, Firefox, Chrome and Node.js 27 | 28 | ## Roadmap 29 | 30 | * A /bin directory will be added with minified versions 31 | * More unit tests 32 | 33 | ## Adding to your project 34 | 35 | ### In a web browser 36 | 37 | Include `json-patch.js` if you want support for applying patches **or** 38 | include `json-patch-duplex.js` if you also want to generate patches. 39 | 40 | ### In Node.js 41 | 42 | Install the current version (and save it as a dependency in package.json): 43 | 44 | ``` 45 | $ npm install fast-json-patch --save 46 | ``` 47 | 48 | Call require to get the instance: 49 | 50 | ```js 51 | var jsonpatch = require('fast-json-patch') 52 | ``` 53 | 54 | :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. 55 | 56 | 57 | ## Usage 58 | 59 | Applying patches: 60 | ```js 61 | var myobj = { firstName:"Albert", contactDetails: { phoneNumbers: [ ] } }; 62 | var patches = [ 63 | {op:"replace", path:"/firstName", value:"Joachim" }, 64 | {op:"add", path:"/lastName", value:"Wester" }, 65 | {op:"add", path:"/contactDetails/phoneNumbers/0", value:{ number:"555-123" } } 66 | ]; 67 | jsonpatch.apply( myobj, patches ); 68 | // myobj == { firstName:"Joachim", lastName:"Wester", contactDetails:{ phoneNumbers[ {number:"555-123"} ] } }; 69 | ``` 70 | Generating patches: 71 | ```js 72 | var myobj = { firstName:"Joachim", lastName:"Wester", contactDetails: { phoneNumbers: [ { number:"555-123" }] } }; 73 | observer = jsonpatch.observe( myobj ); 74 | myobj.firstName = "Albert"; 75 | myobj.contactDetails.phoneNumbers[0].number = "123"; 76 | myobj.contactDetails.phoneNumbers.push({number:"456"}); 77 | var patches = jsonpatch.generate(observer); 78 | // patches == [ 79 | // { op:"replace", path="/firstName", value:"Albert"}, 80 | // { op:"replace", path="/contactDetails/phoneNumbers/0/number", value:"123"}, 81 | // { op:"add", path="/contactDetails/phoneNumbers/1", value:{number:"456"}}]; 82 | ``` 83 | Comparing two object trees: 84 | ``` 85 | var objA = {user: {firstName: "Albert", lastName: "Einstein"}}; 86 | var objB = {user: {firstName: "Albert", lastName: "Collins"}}; 87 | var diff = jsonpatch.compare(objA, objB)); 88 | //diff == [{op: "replace", path: "/user/lastName", value: "Collins"}] 89 | ``` 90 | 91 | ## Testing 92 | 93 | ### In a web browser 94 | 95 | 1. Testing **json-patch.js** 96 | - Load `src/patchtest.html` in your web browser 97 | 2. Testing **json-patch-duplex.js** 98 | - Load `src/test-duplex.html` in your web browser 99 | 100 | Each of the test suite files contains *Jasmine* unit test suite and *JSLitmus* performance test suite. 101 | 102 | To run *JSLitmus* performance tests, press "Run Tests" button. 103 | 104 | ### In Node.js 105 | 106 | 1. Go to directory where you have cloned the repo 107 | 2. Install Jasmine Node.js module by running command `npm install jasmine-node -g` 108 | 3. Testing **json-patch.js** 109 | - Run command `jasmine-node --matchall --config duplex no src/test.js` 110 | 4. Testing **json-patch-duplex.js** 111 | - Run command `jasmine-node --matchall --config duplex yes src/test.js src/test-duplex.js` 112 | 113 | ## API 114 | 115 | #### jsonpatch.apply (`obj` Object, `patches` Array) : boolean 116 | 117 | Available in *json-patch.js* and *json-patch-duplex.js* 118 | 119 | Applies `patches` array on `obj`. 120 | 121 | If patch was succesfully applied, returns `true`. Otherwise returns `false`. 122 | 123 | If there was a `test` patch in `patches` array, returns the result of the test. 124 | 125 | If there was more then one patch in the array, the result of the last patch is returned. 126 | 127 | #### jsonpatch.observe (`obj` Object, `callback` Function (optional)) : `observer` Object 128 | 129 | Available in *json-patch-duplex.js* 130 | 131 | Sets up an deep observer on `obj` that listens for changes in object tree. When changes are detected, the optional 132 | callback is called with the generated patches array as the parameter. 133 | 134 | Returns `observer`. 135 | 136 | #### jsonpatch.generate (`obj` Object, `observer` Object) : `patches` Array 137 | 138 | Available in *json-patch-duplex.js* 139 | 140 | If there are pending changes in `obj`, returns them synchronously. If a `callback` was defined in `observe` 141 | method, it will be triggered synchronously as well. 142 | 143 | If there are no pending changes in `obj`, returns an empty array. 144 | 145 | #### jsonpatch.unobserve (`obj` Object, `observer` Object) : void 146 | 147 | Available in *json-patch-duplex.js* 148 | 149 | Destroys the observer set up on `obj`. 150 | 151 | Any remaining changes are delivered synchronously (as in `jsonpatch.generate`). Note: this is different that ES6/7 `Object.unobserve`, which delivers remaining changes asynchronously. 152 | 153 | #### jsonpatch.compare (`obj1` Object, `obj2` Object) : `patches` Array 154 | 155 | Available in *json-patch-duplex.js* 156 | 157 | Compares object trees `obj1` and `obj2` and returns the difference relative to `obj1` as a patches array. 158 | 159 | If there are no differences, returns an empty array. 160 | 161 | ## Changelog 162 | 163 | #### 0.3.7 (May 5, 2014) 164 | 165 | Feature: 166 | - add a new method `compare` ([#24](https://github.com/Starcounter-Jack/JSON-Patch/issues/24)) 167 | 168 | #### 0.3.6 (Nov 14, 2013) 169 | 170 | Update: 171 | - use the new record type names that landed in Chrome Canary (http://wiki.ecmascript.org/doku.php?id=harmony:observe - listed at 10/29/2013) 172 | 173 | #### 0.3.5 (Oct 28, 2013) 174 | 175 | Bugfix: 176 | - 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)) 177 | 178 | #### 0.3.4 (Oct 16, 2013) 179 | 180 | Bugfix: 181 | - 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)) 182 | 183 | #### 0.3.3 (Oct 11, 2013) 184 | 185 | Bugfixes: 186 | - properly escape `~` and `/` characters in poiner paths ([#19](https://github.com/Starcounter-Jack/JSON-Patch/pull/19)) 187 | - generated patch contained array `length` (only in native Object.observe version) ([#14](https://github.com/Starcounter-Jack/JSON-Patch/issues/14)) 188 | - `jsonpatch.unobserve` now delivers pending changes immediately (previously last-minute changes could be lost) 189 | - stability fixes for browsers with native `Object.observe` (Chrome) 190 | - code cleanup 191 | - removed sourcemap reference from output js file 192 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-json-patch", 3 | "version": "0.3.7", 4 | "description": "JSON-Patch allows you to update a JSON document by sending the changes rather than the whole document.", 5 | "homepage": "https://github.com/Starcounter-Jack/JSON-Patch", 6 | "keywords": ["json", "patch", "http", "rest"], 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/Starcounter-Jack/JSON-Patch.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/Starcounter-Jack/JSON-Patch/issues" 13 | }, 14 | "author": { 15 | "name": "Joachim Wester", 16 | "email": "joachimwester@me.com", 17 | "url": "http://www.starcounter.com/" 18 | }, 19 | "licenses": [ { 20 | "type": "MIT", 21 | "url": "http://www.opensource.org/licenses/MIT" 22 | } ], 23 | "main": "./src/json-patch-duplex", 24 | "engines": { "node": ">= 0.4.0" } 25 | } 26 | -------------------------------------------------------------------------------- /src/json-patch-duplex.js: -------------------------------------------------------------------------------- 1 | // json-patch-duplex.js 0.3.7 2 | // (c) 2013 Joachim Wester 3 | // MIT license 4 | 5 | var jsonpatch; 6 | (function (jsonpatch) { 7 | /* We use a Javascript hash to store each 8 | function. Each hash entry (property) uses 9 | the operation identifiers specified in rfc6902. 10 | In this way, we can map each patch operation 11 | to its dedicated function in efficient way. 12 | */ 13 | /* The operations applicable to an object */ 14 | var objOps = { 15 | add: function (obj, key) { 16 | obj[key] = this.value; 17 | return true; 18 | }, 19 | remove: function (obj, key) { 20 | delete obj[key]; 21 | return true; 22 | }, 23 | replace: function (obj, key) { 24 | obj[key] = this.value; 25 | return true; 26 | }, 27 | move: function (obj, key, tree) { 28 | var temp = { op: "_get", path: this.from }; 29 | apply(tree, [temp]); 30 | apply(tree, [ 31 | { op: "remove", path: this.from } 32 | ]); 33 | apply(tree, [ 34 | { op: "add", path: this.path, value: temp.value } 35 | ]); 36 | return true; 37 | }, 38 | copy: function (obj, key, tree) { 39 | var temp = { op: "_get", path: this.from }; 40 | apply(tree, [temp]); 41 | apply(tree, [ 42 | { op: "add", path: this.path, value: temp.value } 43 | ]); 44 | return true; 45 | }, 46 | test: function (obj, key) { 47 | return (JSON.stringify(obj[key]) === JSON.stringify(this.value)); 48 | }, 49 | _get: function (obj, key) { 50 | this.value = obj[key]; 51 | } 52 | }; 53 | 54 | /* The operations applicable to an array. Many are the same as for the object */ 55 | var arrOps = { 56 | add: function (arr, i) { 57 | arr.splice(i, 0, this.value); 58 | return true; 59 | }, 60 | remove: function (arr, i) { 61 | arr.splice(i, 1); 62 | return true; 63 | }, 64 | replace: function (arr, i) { 65 | arr[i] = this.value; 66 | return true; 67 | }, 68 | move: objOps.move, 69 | copy: objOps.copy, 70 | test: objOps.test, 71 | _get: objOps._get 72 | }; 73 | 74 | var observeOps = { 75 | add: function (patches, path) { 76 | var patch = { 77 | op: "add", 78 | path: path + escapePathComponent(this.name), 79 | value: this.object[this.name] }; 80 | patches.push(patch); 81 | }, 82 | 'delete': function (patches, path) { 83 | var patch = { 84 | op: "remove", 85 | path: path + escapePathComponent(this.name) 86 | }; 87 | patches.push(patch); 88 | }, 89 | update: function (patches, path) { 90 | var patch = { 91 | op: "replace", 92 | path: path + escapePathComponent(this.name), 93 | value: this.object[this.name] 94 | }; 95 | patches.push(patch); 96 | } 97 | }; 98 | 99 | function escapePathComponent(str) { 100 | if (str.indexOf('/') === -1 && str.indexOf('~') === -1) 101 | return str; 102 | return str.replace(/~/g, '~0').replace(/\//g, '~1'); 103 | } 104 | 105 | function _getPathRecursive(root, obj) { 106 | var found; 107 | for (var key in root) { 108 | if (root.hasOwnProperty(key)) { 109 | if (root[key] === obj) { 110 | return escapePathComponent(key) + '/'; 111 | } else if (typeof root[key] === 'object') { 112 | found = _getPathRecursive(root[key], obj); 113 | if (found != '') { 114 | return escapePathComponent(key) + '/' + found; 115 | } 116 | } 117 | } 118 | } 119 | return ''; 120 | } 121 | 122 | function getPath(root, obj) { 123 | if (root === obj) { 124 | return '/'; 125 | } 126 | var path = _getPathRecursive(root, obj); 127 | if (path === '') { 128 | throw new Error("Object not found in root"); 129 | } 130 | return '/' + path; 131 | } 132 | 133 | var beforeDict = []; 134 | 135 | jsonpatch.intervals; 136 | 137 | var Mirror = (function () { 138 | function Mirror(obj) { 139 | this.observers = []; 140 | this.obj = obj; 141 | } 142 | return Mirror; 143 | })(); 144 | 145 | var ObserverInfo = (function () { 146 | function ObserverInfo(callback, observer) { 147 | this.callback = callback; 148 | this.observer = observer; 149 | } 150 | return ObserverInfo; 151 | })(); 152 | 153 | function getMirror(obj) { 154 | for (var i = 0, ilen = beforeDict.length; i < ilen; i++) { 155 | if (beforeDict[i].obj === obj) { 156 | return beforeDict[i]; 157 | } 158 | } 159 | } 160 | 161 | function getObserverFromMirror(mirror, callback) { 162 | for (var j = 0, jlen = mirror.observers.length; j < jlen; j++) { 163 | if (mirror.observers[j].callback === callback) { 164 | return mirror.observers[j].observer; 165 | } 166 | } 167 | } 168 | 169 | function removeObserverFromMirror(mirror, observer) { 170 | for (var j = 0, jlen = mirror.observers.length; j < jlen; j++) { 171 | if (mirror.observers[j].observer === observer) { 172 | mirror.observers.splice(j, 1); 173 | return; 174 | } 175 | } 176 | } 177 | 178 | function unobserve(root, observer) { 179 | generate(observer); 180 | if (Object.observe) { 181 | _unobserve(observer, root); 182 | } else { 183 | clearTimeout(observer.next); 184 | } 185 | 186 | var mirror = getMirror(root); 187 | removeObserverFromMirror(mirror, observer); 188 | } 189 | jsonpatch.unobserve = unobserve; 190 | 191 | function observe(obj, callback) { 192 | var patches = []; 193 | var root = obj; 194 | var observer; 195 | var mirror = getMirror(obj); 196 | 197 | if (!mirror) { 198 | mirror = new Mirror(obj); 199 | beforeDict.push(mirror); 200 | } else { 201 | observer = getObserverFromMirror(mirror, callback); 202 | } 203 | 204 | if (observer) { 205 | return observer; 206 | } 207 | 208 | if (Object.observe) { 209 | observer = function (arr) { 210 | //This "refresh" is needed to begin observing new object properties 211 | _unobserve(observer, obj); 212 | _observe(observer, obj); 213 | 214 | var a = 0, alen = arr.length; 215 | while (a < alen) { 216 | if (!(arr[a].name === 'length' && _isArray(arr[a].object)) && !(arr[a].name === '__Jasmine_been_here_before__')) { 217 | var type = arr[a].type; 218 | 219 | switch (type) { 220 | case 'new': 221 | type = 'add'; 222 | break; 223 | 224 | case 'deleted': 225 | type = 'delete'; 226 | break; 227 | 228 | case 'updated': 229 | type = 'update'; 230 | break; 231 | } 232 | 233 | observeOps[type].call(arr[a], patches, getPath(root, arr[a].object)); 234 | } 235 | a++; 236 | } 237 | 238 | if (patches) { 239 | if (callback) { 240 | callback(patches); 241 | } 242 | } 243 | observer.patches = patches; 244 | patches = []; 245 | }; 246 | } else { 247 | observer = {}; 248 | 249 | mirror.value = JSON.parse(JSON.stringify(obj)); // Faster than ES5 clone - http://jsperf.com/deep-cloning-of-objects/5 250 | 251 | if (callback) { 252 | //callbacks.push(callback); this has no purpose 253 | observer.callback = callback; 254 | observer.next = null; 255 | var intervals = this.intervals || [100, 1000, 10000, 60000]; 256 | if (intervals.push === void 0) { 257 | throw new Error("jsonpatch.intervals must be an array"); 258 | } 259 | var currentInterval = 0; 260 | 261 | var dirtyCheck = function () { 262 | generate(observer); 263 | }; 264 | var fastCheck = function () { 265 | clearTimeout(observer.next); 266 | observer.next = setTimeout(function () { 267 | dirtyCheck(); 268 | currentInterval = 0; 269 | observer.next = setTimeout(slowCheck, intervals[currentInterval++]); 270 | }, 0); 271 | }; 272 | var slowCheck = function () { 273 | dirtyCheck(); 274 | if (currentInterval == intervals.length) 275 | currentInterval = intervals.length - 1; 276 | observer.next = setTimeout(slowCheck, intervals[currentInterval++]); 277 | }; 278 | if (typeof window !== 'undefined') { 279 | if (window.addEventListener) { 280 | window.addEventListener('mousedown', fastCheck); 281 | window.addEventListener('mouseup', fastCheck); 282 | window.addEventListener('keydown', fastCheck); 283 | } else { 284 | window.attachEvent('onmousedown', fastCheck); 285 | window.attachEvent('onmouseup', fastCheck); 286 | window.attachEvent('onkeydown', fastCheck); 287 | } 288 | } 289 | observer.next = setTimeout(slowCheck, intervals[currentInterval++]); 290 | } 291 | } 292 | observer.patches = patches; 293 | observer.object = obj; 294 | 295 | mirror.observers.push(new ObserverInfo(callback, observer)); 296 | 297 | return _observe(observer, obj); 298 | } 299 | jsonpatch.observe = observe; 300 | 301 | /// Listen to changes on an object tree, accumulate patches 302 | function _observe(observer, obj) { 303 | if (Object.observe) { 304 | Object.observe(obj, observer); 305 | for (var key in obj) { 306 | if (obj.hasOwnProperty(key)) { 307 | var v = obj[key]; 308 | if (v && typeof (v) === "object") { 309 | _observe(observer, v); 310 | } 311 | } 312 | } 313 | } 314 | return observer; 315 | } 316 | 317 | function _unobserve(observer, obj) { 318 | if (Object.observe) { 319 | Object.unobserve(obj, observer); 320 | for (var key in obj) { 321 | if (obj.hasOwnProperty(key)) { 322 | var v = obj[key]; 323 | if (v && typeof (v) === "object") { 324 | _unobserve(observer, v); 325 | } 326 | } 327 | } 328 | } 329 | return observer; 330 | } 331 | 332 | function generate(observer) { 333 | if (Object.observe) { 334 | Object.deliverChangeRecords(observer); 335 | } else { 336 | var mirror; 337 | for (var i = 0, ilen = beforeDict.length; i < ilen; i++) { 338 | if (beforeDict[i].obj === observer.object) { 339 | mirror = beforeDict[i]; 340 | break; 341 | } 342 | } 343 | _generate(mirror.value, observer.object, observer.patches, ""); 344 | } 345 | var temp = observer.patches; 346 | if (temp.length > 0) { 347 | observer.patches = []; 348 | if (observer.callback) { 349 | observer.callback(temp); 350 | } 351 | } 352 | return temp; 353 | } 354 | jsonpatch.generate = generate; 355 | 356 | var _objectKeys; 357 | if (Object.keys) { 358 | _objectKeys = Object.keys; 359 | } else { 360 | _objectKeys = function (obj) { 361 | var keys = []; 362 | for (var o in obj) { 363 | if (obj.hasOwnProperty(o)) { 364 | keys.push(o); 365 | } 366 | } 367 | return keys; 368 | }; 369 | } 370 | 371 | // Dirty check if obj is different from mirror, generate patches and update mirror 372 | function _generate(mirror, obj, patches, path) { 373 | var newKeys = _objectKeys(obj); 374 | var oldKeys = _objectKeys(mirror); 375 | var changed = false; 376 | var deleted = false; 377 | 378 | for (var t = oldKeys.length - 1; t >= 0; t--) { 379 | var key = oldKeys[t]; 380 | var oldVal = mirror[key]; 381 | if (obj.hasOwnProperty(key)) { 382 | var newVal = obj[key]; 383 | if (oldVal instanceof Object) { 384 | _generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key)); 385 | } else { 386 | if (oldVal != newVal) { 387 | changed = true; 388 | patches.push({ op: "replace", path: path + "/" + escapePathComponent(key), value: newVal }); 389 | mirror[key] = newVal; 390 | } 391 | } 392 | } else { 393 | patches.push({ op: "remove", path: path + "/" + escapePathComponent(key) }); 394 | delete mirror[key]; 395 | deleted = true; // property has been deleted 396 | } 397 | } 398 | 399 | if (!deleted && newKeys.length == oldKeys.length) { 400 | return; 401 | } 402 | 403 | for (var t = 0; t < newKeys.length; t++) { 404 | var key = newKeys[t]; 405 | if (!mirror.hasOwnProperty(key)) { 406 | patches.push({ op: "add", path: path + "/" + escapePathComponent(key), value: obj[key] }); 407 | mirror[key] = JSON.parse(JSON.stringify(obj[key])); 408 | } 409 | } 410 | } 411 | 412 | var _isArray; 413 | if (Array.isArray) { 414 | _isArray = Array.isArray; 415 | } else { 416 | _isArray = function (obj) { 417 | return obj.push && typeof obj.length === 'number'; 418 | }; 419 | } 420 | 421 | /// Apply a json-patch operation on an object tree 422 | function apply(tree, patches) { 423 | var result = false, p = 0, plen = patches.length, patch; 424 | while (p < plen) { 425 | patch = patches[p]; 426 | 427 | // Find the object 428 | var keys = patch.path.split('/'); 429 | var obj = tree; 430 | var t = 1; 431 | var len = keys.length; 432 | while (true) { 433 | if (_isArray(obj)) { 434 | var index = parseInt(keys[t], 10); 435 | t++; 436 | if (t >= len) { 437 | result = arrOps[patch.op].call(patch, obj, index, tree); // Apply patch 438 | break; 439 | } 440 | obj = obj[index]; 441 | } else { 442 | var key = keys[t]; 443 | if (key.indexOf('~') != -1) 444 | key = key.replace(/~1/g, '/').replace(/~0/g, '~'); // escape chars 445 | t++; 446 | if (t >= len) { 447 | result = objOps[patch.op].call(patch, obj, key, tree); // Apply patch 448 | break; 449 | } 450 | obj = obj[key]; 451 | } 452 | } 453 | p++; 454 | } 455 | return result; 456 | } 457 | jsonpatch.apply = apply; 458 | 459 | function compare(tree1, tree2) { 460 | var patches = []; 461 | _generate(tree1, tree2, patches, ''); 462 | return patches; 463 | } 464 | jsonpatch.compare = compare; 465 | })(jsonpatch || (jsonpatch = {})); 466 | 467 | if (typeof exports !== "undefined") { 468 | exports.apply = jsonpatch.apply; 469 | exports.observe = jsonpatch.observe; 470 | exports.unobserve = jsonpatch.unobserve; 471 | exports.generate = jsonpatch.generate; 472 | } 473 | //# sourceMappingURL=json-patch-duplex.js.map 474 | -------------------------------------------------------------------------------- /src/json-patch-duplex.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"json-patch-duplex.js","sourceRoot":"","sources":["json-patch-duplex.ts"],"names":["jsonpatch","jsonpatch.escapePathComponent","jsonpatch._getPathRecursive","jsonpatch.getPath","jsonpatch.Mirror","jsonpatch.Mirror.constructor","jsonpatch.ObserverInfo","jsonpatch.ObserverInfo.constructor","jsonpatch.getMirror","jsonpatch.getObserverFromMirror","jsonpatch.removeObserverFromMirror","jsonpatch.unobserve","jsonpatch.observe","jsonpatch._observe","jsonpatch._unobserve","jsonpatch.generate","jsonpatch._generate","jsonpatch.apply","jsonpatch.compare"],"mappings":"AAAA,6BAA6B;AAC7B,0BAA0B;AAC1B,cAAc;;AAQd,IAAO,SAAS;AAgef,CAheD,UAAO,SAAS;IAEdA;;;;;MAKKA;IAELA,4CAA4CA;IAC5CA,IAAIA,MAAMA,GAAGA;QACXA,GAAGA,EAAEA,UAAUA,GAAGA,EAAEA,GAAGA;YACrBA,GAAGA,CAACA,GAAGA,CAACA,GAAGA,IAAIA,CAACA,KAAKA;YACrBA,OAAOA,IAAIA;QACbA,CAACA;QACDA,MAAMA,EAAEA,UAAUA,GAAGA,EAAEA,GAAGA;YACxBA,OAAOA,GAAGA,CAACA,GAAGA,CAACA;YACfA,OAAOA,IAAIA;QACbA,CAACA;QACDA,OAAOA,EAAEA,UAAUA,GAAGA,EAAEA,GAAGA;YACzBA,GAAGA,CAACA,GAAGA,CAACA,GAAGA,IAAIA,CAACA,KAAKA;YACrBA,OAAOA,IAAIA;QACbA,CAACA;QACDA,IAAIA,EAAEA,UAAUA,GAAGA,EAAEA,GAAGA,EAAEA,IAAIA;YAC5BA,IAAIA,IAAIA,GAAOA,EAACA,EAAEA,EAAEA,MAAMA,EAAEA,IAAIA,EAAEA,IAAIA,CAACA,IAAIA,EAACA;YAC5CA,KAAKA,CAACA,IAAIA,EAAEA,CAACA,IAAIA,CAACA,CAACA;YACnBA,KAAKA,CAACA,IAAIA,EAAEA;gBACVA,EAACA,EAAEA,EAAEA,QAAQA,EAAEA,IAAIA,EAAEA,IAAIA,CAACA,IAAIA,EAACA;aAChCA,CAACA;YACFA,KAAKA,CAACA,IAAIA,EAAEA;gBACVA,EAACA,EAAEA,EAAEA,KAAKA,EAAEA,IAAIA,EAAEA,IAAIA,CAACA,IAAIA,EAAEA,KAAKA,EAAEA,IAAIA,CAACA,KAAKA,EAACA;aAChDA,CAACA;YACFA,OAAOA,IAAIA;QACbA,CAACA;QACDA,IAAIA,EAAEA,UAAUA,GAAGA,EAAEA,GAAGA,EAAEA,IAAIA;YAC5BA,IAAIA,IAAIA,GAAOA,EAACA,EAAEA,EAAEA,MAAMA,EAAEA,IAAIA,EAAEA,IAAIA,CAACA,IAAIA,EAACA;YAC5CA,KAAKA,CAACA,IAAIA,EAAEA,CAACA,IAAIA,CAACA,CAACA;YACnBA,KAAKA,CAACA,IAAIA,EAAEA;gBACVA,EAACA,EAAEA,EAAEA,KAAKA,EAAEA,IAAIA,EAAEA,IAAIA,CAACA,IAAIA,EAAEA,KAAKA,EAAEA,IAAIA,CAACA,KAAKA,EAACA;aAChDA,CAACA;YACFA,OAAOA,IAAIA;QACbA,CAACA;QACDA,IAAIA,EAAEA,UAAUA,GAAGA,EAAEA,GAAGA;YACtBA,OAAMA,CAACA,IAAIA,CAACA,SAASA,CAACA,GAAGA,CAACA,GAAGA,CAACA,CAACA,KAAKA,IAAIA,CAACA,SAASA,CAACA,IAAIA,CAACA,KAAKA,CAACA,CAACA;QACjEA,CAACA;QACDA,IAAIA,EAAEA,UAAUA,GAAGA,EAAEA,GAAGA;YACtBA,IAAIA,CAACA,KAAKA,GAAGA,GAAGA,CAACA,GAAGA,CAACA;QACvBA,CAACA;KACFA;;IAEDA,gFAAgFA;IAChFA,IAAIA,MAAMA,GAAGA;QACXA,GAAGA,EAAEA,UAAUA,GAAGA,EAAEA,CAACA;YACnBA,GAAGA,CAACA,MAAMA,CAACA,CAACA,EAAEA,CAACA,EAAEA,IAAIA,CAACA,KAAKA,CAACA;YAC5BA,OAAOA,IAAIA;QACbA,CAACA;QACDA,MAAMA,EAAEA,UAAUA,GAAGA,EAAEA,CAACA;YACtBA,GAAGA,CAACA,MAAMA,CAACA,CAACA,EAAEA,CAACA,CAACA;YAChBA,OAAOA,IAAIA;QACbA,CAACA;QACDA,OAAOA,EAAEA,UAAUA,GAAGA,EAAEA,CAACA;YACvBA,GAAGA,CAACA,CAACA,CAACA,GAAGA,IAAIA,CAACA,KAAKA;YACnBA,OAAOA,IAAIA;QACbA,CAACA;QACDA,IAAIA,EAAEA,MAAMA,CAACA,IAAIA;QACjBA,IAAIA,EAAEA,MAAMA,CAACA,IAAIA;QACjBA,IAAIA,EAAEA,MAAMA,CAACA,IAAIA;QACjBA,IAAIA,EAAEA,MAAMA,CAACA,IAAIA;KAClBA;;IAEDA,IAAIA,UAAUA,GAAGA;QACfA,GAAGA,EAAEA,UAAUA,OAAaA,EAAEA,IAAIA;YAChCA,IAAIA,KAAKA,GAAGA;gBACVA,EAAEA,EAAEA,KAAKA;gBACTA,IAAIA,EAAEA,IAAIA,GAAGA,mBAAmBA,CAACA,IAAIA,CAACA,IAAIA,CAACA;gBAC3CA,KAAKA,EAAEA,IAAIA,CAACA,MAAMA,CAACA,IAAIA,CAACA,IAAIA,CAACA,EAACA;YAChCA,OAAOA,CAACA,IAAIA,CAACA,KAAKA,CAACA;QACrBA,CAACA;QACDA,QAAQA,EAAEA,UAAUA,OAAaA,EAAEA,IAAIA;YACrCA,IAAIA,KAAKA,GAAGA;gBACVA,EAAEA,EAAEA,QAAQA;gBACZA,IAAIA,EAAEA,IAAIA,GAAGA,mBAAmBA,CAACA,IAAIA,CAACA,IAAIA,CAACA;aAC5CA;YACDA,OAAOA,CAACA,IAAIA,CAACA,KAAKA,CAACA;QACrBA,CAACA;QACDA,MAAMA,EAAEA,UAAUA,OAAaA,EAAEA,IAAIA;YACnCA,IAAIA,KAAKA,GAAGA;gBACVA,EAAEA,EAAEA,SAASA;gBACbA,IAAIA,EAAEA,IAAIA,GAAGA,mBAAmBA,CAACA,IAAIA,CAACA,IAAIA,CAACA;gBAC3CA,KAAKA,EAAEA,IAAIA,CAACA,MAAMA,CAACA,IAAIA,CAACA,IAAIA,CAACA;aAC9BA;YACDA,OAAOA,CAACA,IAAIA,CAACA,KAAKA,CAACA;QACrBA,CAACA;KACFA;;IAEDA,SAASA,mBAAmBA,CAAEA,GAAGA;QAC/BC,IAAIA,GAAGA,CAACA,OAAOA,CAACA,GAAGA,CAACA,KAAKA,CAACA,CAACA,IAAIA,GAAGA,CAACA,OAAOA,CAACA,GAAGA,CAACA,KAAKA,CAACA,CAACA;YAAEA,OAAOA,GAAGA,CAACA;QACnEA,OAAOA,GAAGA,CAACA,OAAOA,CAACA,IAAIA,EAAEA,IAAIA,CAACA,CAACA,OAAOA,CAACA,KAAKA,EAAEA,IAAIA,CAACA;IACrDA,CAACA;;IAEDD,SAASA,iBAAiBA,CAACA,IAAWA,EAAEA,GAAUA;QAChDE,IAAIA,KAAKA;QACTA,KAAKA,IAAIA,GAAGA,IAAIA,IAAIA,CAAEA;YACpBA,IAAIA,IAAIA,CAACA,cAAcA,CAACA,GAAGA,CAACA,CAAEA;gBAC5BA,IAAIA,IAAIA,CAACA,GAAGA,CAACA,KAAKA,GAAGA,CAAEA;oBACrBA,OAAOA,mBAAmBA,CAACA,GAAGA,CAACA,GAAGA,GAAGA;iBACtCA,MACIA,IAAIA,OAAOA,IAAIA,CAACA,GAAGA,CAACA,KAAKA,QAAQA,CAAEA;oBACtCA,KAAKA,GAAGA,iBAAiBA,CAACA,IAAIA,CAACA,GAAGA,CAACA,EAAEA,GAAGA,CAACA;oBACzCA,IAAIA,KAAKA,IAAIA,EAAEA,CAAEA;wBACfA,OAAOA,mBAAmBA,CAACA,GAAGA,CAACA,GAAGA,GAAGA,GAAGA,KAAKA;qBAC9CA;iBACFA;aACFA;SACFA;QACDA,OAAOA,EAAEA;IACXA,CAACA;;IAEDF,SAASA,OAAOA,CAACA,IAAWA,EAAEA,GAAUA;QACtCG,IAAIA,IAAIA,KAAKA,GAAGA,CAAEA;YAChBA,OAAOA,GAAGA;SACXA;QACDA,IAAIA,IAAIA,GAAGA,iBAAiBA,CAACA,IAAIA,EAAEA,GAAGA,CAACA;QACvCA,IAAIA,IAAIA,KAAKA,EAAEA,CAAEA;YACfA,MAAMA,IAAIA,KAAKA,CAACA,0BAA0BA,CAACA;SAC5CA;QACDA,OAAOA,GAAGA,GAAGA,IAAIA;IACnBA,CAACA;;IAEDH,IAAIA,UAAUA,GAAGA,EAAEA;;IAEZA,UAAIA,SAASA;;IAEpBA;QAIEI,gBAAYA,GAAOA;YAFnBC,KAAAA,SAASA,GAAGA,EAAEA,CAACA;YAGbA,IAAIA,CAACA,GAAGA,GAAGA,GAAGA;QAChBA,CAACA;QACHD,cAACA;IAADA,CAACA,IAAAJ;;IAEDA;QAIEM,sBAAYA,QAAQA,EAAEA,QAAQA;YAC5BC,IAAIA,CAACA,QAAQA,GAAGA,QAAQA;YACxBA,IAAIA,CAACA,QAAQA,GAAGA,QAAQA;QAC1BA,CAACA;QACHD,oBAACA;IAADA,CAACA,IAAAN;;IAEDA,SAASA,SAASA,CAACA,GAAOA;QACxBQ,KAAKA,IAAIA,CAACA,GAAGA,CAACA,EAAEA,IAAIA,GAAGA,UAAUA,CAACA,MAAMA,EAAEA,CAACA,GAAGA,IAAIA,EAAEA,CAACA,EAAEA,CAAEA;YACvDA,IAAIA,UAAUA,CAACA,CAACA,CAACA,CAACA,GAAGA,KAAKA,GAAGA,CAAEA;gBAC7BA,OAAOA,UAAUA,CAACA,CAACA,CAACA;aACrBA;SACFA;IACHA,CAACA;;IAEDR,SAASA,qBAAqBA,CAACA,MAAUA,EAAEA,QAAQA;QACjDS,KAAKA,IAAIA,CAACA,GAAGA,CAACA,EAAEA,IAAIA,GAAGA,MAAMA,CAACA,SAASA,CAACA,MAAMA,EAAEA,CAACA,GAAGA,IAAIA,EAAEA,CAACA,EAAEA,CAAEA;YAC7DA,IAAIA,MAAMA,CAACA,SAASA,CAACA,CAACA,CAACA,CAACA,QAAQA,KAAKA,QAAQA,CAAEA;gBAC7CA,OAAOA,MAAMA,CAACA,SAASA,CAACA,CAACA,CAACA,CAACA,QAAQA;aACpCA;SACFA;IACHA,CAACA;;IAEDT,SAASA,wBAAwBA,CAACA,MAAUA,EAAEA,QAAQA;QACpDU,KAAKA,IAAIA,CAACA,GAAGA,CAACA,EAAEA,IAAIA,GAAGA,MAAMA,CAACA,SAASA,CAACA,MAAMA,EAAEA,CAACA,GAAGA,IAAIA,EAAEA,CAACA,EAAEA,CAAEA;YAC7DA,IAAIA,MAAMA,CAACA,SAASA,CAACA,CAACA,CAACA,CAACA,QAAQA,KAAKA,QAAQA,CAAEA;gBAC7CA,MAAMA,CAACA,SAASA,CAACA,MAAMA,CAACA,CAACA,EAAEA,CAACA,CAACA;gBAC7BA,MAAOA;aACRA;SACFA;IACHA,CAACA;;IAEDV,SAAgBA,SAASA,CAACA,IAAIA,EAAEA,QAAQA;QACtCW,QAAQA,CAACA,QAAQA,CAACA;QAClBA,IAAGA,MAAMA,CAACA,OAAOA,CAAEA;YACjBA,UAAUA,CAACA,QAAQA,EAAEA,IAAIA,CAACA;SAC3BA,KACIA;YACHA,YAAYA,CAACA,QAAQA,CAACA,IAAIA,CAACA;SAC5BA;;QAEDA,IAAIA,MAAMA,GAAGA,SAASA,CAACA,IAAIA,CAACA;QAC5BA,wBAAwBA,CAACA,MAAMA,EAAEA,QAAQA,CAACA;IAE5CA,CAACA;IAZDX,gCAYCA;;IAEDA,SAAgBA,OAAOA,CAACA,GAAOA,EAAEA,QAAQA;QACvCY,IAAIA,OAAOA,GAAGA,EAAEA;QAChBA,IAAIA,IAAIA,GAAGA,GAAGA;QACdA,IAAIA,QAAQA;QACZA,IAAIA,MAAMA,GAAGA,SAASA,CAACA,GAAGA,CAACA;;QAE3BA,IAAIA,CAACA,MAAMA,CAAEA;YACXA,MAAMA,GAAGA,IAAIA,MAAMA,CAACA,GAAGA,CAACA;YACxBA,UAAUA,CAACA,IAAIA,CAACA,MAAMA,CAACA;SACxBA,KAAMA;YACLA,QAAQA,GAAGA,qBAAqBA,CAACA,MAAMA,EAAEA,QAAQA,CAACA;SACnDA;;QAEDA,IAAGA,QAAQA,CAACA;YACVA,OAAOA,QAAQA;SAChBA;;QAEDA,IAAIA,MAAMA,CAACA,OAAOA,CAAEA;YAClBA,QAAQA,GAAGA,UAAUA,GAAGA;gBACtBA,mEAAmEA;gBACnEA,UAAUA,CAACA,QAAQA,EAAEA,GAAGA,CAACA;gBACzBA,QAAQA,CAACA,QAAQA,EAAEA,GAAGA,CAACA;;gBAEvBA,IAAIA,CAACA,GAAGA,CAACA,EACLA,IAAIA,GAAGA,GAAGA,CAACA,MAAMA;gBACrBA,OAAOA,CAACA,GAAGA,IAAIA,CAAEA;oBACfA,IACEA,CAACA,CAACA,GAAGA,CAACA,CAACA,CAACA,CAACA,IAAIA,KAAKA,QAAQA,IAAIA,QAAQA,CAACA,GAAGA,CAACA,CAACA,CAACA,CAACA,MAAMA,CAACA,CAACA,IACjDA,CAACA,CAACA,GAAGA,CAACA,CAACA,CAACA,CAACA,IAAIA,KAAKA,8BAA8BA,CAACA,CACpDA;wBACFA,IAAIA,IAAIA,GAAGA,GAAGA,CAACA,CAACA,CAACA,CAACA,IAAIA;;wBAItBA,QAAOA,IAAIA,CAACA;4BACVA,KAAKA,KAAKA;gCACRA,IAAIA,GAAGA,KAAKA;gCACZA,KAAMA;AAAAA;4BAERA,KAAKA,SAASA;gCACZA,IAAIA,GAAGA,QAAQA;gCACfA,KAAMA;AAAAA;4BAERA,KAAKA,SAASA;gCACZA,IAAIA,GAAGA,QAAQA;gCACfA,KAAMA;AAAAA,yBACTA;;wBAEDA,UAAUA,CAACA,IAAIA,CAACA,CAACA,IAAIA,CAACA,GAAGA,CAACA,CAACA,CAACA,EAAEA,OAAOA,EAAEA,OAAOA,CAACA,IAAIA,EAAEA,GAAGA,CAACA,CAACA,CAACA,CAACA,MAAMA,CAACA,CAACA;qBACrEA;oBACDA,CAACA,EAAEA;iBACJA;;gBAEDA,IAAIA,OAAOA,CAAEA;oBACXA,IAAIA,QAAQA,CAAEA;wBACZA,QAAQA,CAACA,OAAOA,CAACA;qBAClBA;iBACFA;gBACDA,QAAQA,CAACA,OAAOA,GAAGA,OAAOA;gBAC1BA,OAAOA,GAAGA,EAAEA;YAGdA,CAACA;SACFA,KAAMA;YACLA,QAAQA,GAAGA,EAAEA;;YAEbA,MAAMA,CAACA,KAAKA,GAAGA,IAAIA,CAACA,KAAKA,CAACA,IAAIA,CAACA,SAASA,CAACA,GAAGA,CAACA,CAACA,EAAEA,sEAAsEA;;YAEtHA,IAAIA,QAAQA,CAAEA;gBACZA,+CAA+CA;gBAC/CA,QAAQA,CAACA,QAAQA,GAAGA,QAAQA;gBAC5BA,QAAQA,CAACA,IAAIA,GAAGA,IAAIA;gBACpBA,IAAIA,SAASA,GAAGA,IAAIA,CAACA,SAASA,IAAIA,CAACA,GAAGA,EAAEA,IAAIA,EAAEA,KAAKA,EAAEA,KAAKA,CAACA;gBAC3DA,IAAIA,SAASA,CAACA,IAAIA,KAAKA,KAAKA,CAACA,CAAEA;oBAC7BA,MAAMA,IAAIA,KAAKA,CAACA,sCAAsCA,CAACA;iBACxDA;gBACDA,IAAIA,eAAeA,GAAGA,CAACA;;gBAEvBA,IAAIA,UAAUA,GAAGA;oBACfA,QAAQA,CAACA,QAAQA,CAACA;gBACpBA,CAACA;gBACDA,IAAIA,SAASA,GAAGA;oBACdA,YAAYA,CAACA,QAAQA,CAACA,IAAIA,CAACA;oBAC3BA,QAAQA,CAACA,IAAIA,GAAGA,UAAUA,CAACA;wBACzBA,UAAUA,CAACA,CAACA;wBACZA,eAAeA,GAAGA,CAACA;wBACnBA,QAAQA,CAACA,IAAIA,GAAGA,UAAUA,CAACA,SAASA,EAAEA,SAASA,CAACA,eAAeA,EAAEA,CAACA,CAACA;oBACrEA,CAACA,EAAEA,CAACA,CAACA;gBACPA,CAACA;gBACDA,IAAIA,SAASA,GAAGA;oBACdA,UAAUA,CAACA,CAACA;oBACZA,IAAIA,eAAeA,IAAIA,SAASA,CAACA,MAAMA;wBACrCA,eAAeA,GAAGA,SAASA,CAACA,MAAMA,GAAGA,CAACA,CAACA;oBACzCA,QAAQA,CAACA,IAAIA,GAAGA,UAAUA,CAACA,SAASA,EAAEA,SAASA,CAACA,eAAeA,EAAEA,CAACA,CAACA;gBACrEA,CAACA;gBACDA,IAAIA,OAAOA,MAAMA,KAAKA,WAAWA,CAAEA;oBACjCA,IAAIA,MAAMA,CAACA,gBAAgBA,CAAEA;wBAC3BA,MAAMA,CAACA,gBAAgBA,CAACA,WAAWA,EAAEA,SAASA,CAACA;wBAC/CA,MAAMA,CAACA,gBAAgBA,CAACA,SAASA,EAAEA,SAASA,CAACA;wBAC7CA,MAAMA,CAACA,gBAAgBA,CAACA,SAASA,EAAEA,SAASA,CAACA;qBAC9CA,KACIA;wBACHA,MAAMA,CAACA,WAAWA,CAACA,aAAaA,EAAEA,SAASA,CAACA;wBAC5CA,MAAMA,CAACA,WAAWA,CAACA,WAAWA,EAAEA,SAASA,CAACA;wBAC1CA,MAAMA,CAACA,WAAWA,CAACA,WAAWA,EAAEA,SAASA,CAACA;qBAC3CA;iBACFA;gBACDA,QAAQA,CAACA,IAAIA,GAAGA,UAAUA,CAACA,SAASA,EAAEA,SAASA,CAACA,eAAeA,EAAEA,CAACA,CAACA;aACpEA;SACFA;QACDA,QAAQA,CAACA,OAAOA,GAAGA,OAAOA;QAC1BA,QAAQA,CAACA,MAAMA,GAAGA,GAAGA;;QAErBA,MAAMA,CAACA,SAASA,CAACA,IAAIA,CAACA,IAAIA,YAAYA,CAACA,QAAQA,EAAEA,QAAQA,CAACA,CAACA;;QAE3DA,OAAOA,QAAQA,CAACA,QAAQA,EAAEA,GAAGA,CAACA;IAChCA,CAACA;IApHDZ,4BAoHCA;;IAGDA,2DAD2DA;IAC3DA,SAASA,QAAQA,CAACA,QAAYA,EAAEA,GAAOA;QACrCa,IAAIA,MAAMA,CAACA,OAAOA,CAAEA;YAClBA,MAAMA,CAACA,OAAOA,CAACA,GAAGA,EAAEA,QAAQA,CAACA;YAC7BA,KAAKA,IAAIA,GAAGA,IAAIA,GAAGA,CAAEA;gBACnBA,IAAIA,GAAGA,CAACA,cAAcA,CAACA,GAAGA,CAACA,CAAEA;oBAC3BA,IAAIA,CAACA,GAAOA,GAAGA,CAACA,GAAGA,CAACA;oBACpBA,IAAIA,CAACA,IAAIA,OAAOA,CAACA,CAACA,CAACA,KAAKA,QAAQA,CAAEA;wBAChCA,QAAQA,CAACA,QAAQA,EAAEA,CAACA,CAACA;qBACtBA;iBACFA;aACFA;SACFA;QACDA,OAAOA,QAAQA;IACjBA,CAACA;;IAEDb,SAASA,UAAUA,CAACA,QAAYA,EAAEA,GAAOA;QACvCc,IAAIA,MAAMA,CAACA,OAAOA,CAAEA;YAClBA,MAAMA,CAACA,SAASA,CAACA,GAAGA,EAAEA,QAAQA,CAACA;YAC/BA,KAAKA,IAAIA,GAAGA,IAAIA,GAAGA,CAAEA;gBACnBA,IAAIA,GAAGA,CAACA,cAAcA,CAACA,GAAGA,CAACA,CAAEA;oBAC3BA,IAAIA,CAACA,GAAOA,GAAGA,CAACA,GAAGA,CAACA;oBACpBA,IAAIA,CAACA,IAAIA,OAAOA,CAACA,CAACA,CAACA,KAAKA,QAAQA,CAAEA;wBAChCA,UAAUA,CAACA,QAAQA,EAAEA,CAACA,CAACA;qBACxBA;iBACFA;aACFA;SACFA;QACDA,OAAOA,QAAQA;IACjBA,CAACA;;IAEDd,SAAgBA,QAAQA,CAACA,QAAQA;QAC/Be,IAAIA,MAAMA,CAACA,OAAOA,CAAEA;YAClBA,MAAMA,CAACA,oBAAoBA,CAACA,QAAQA,CAACA;SACtCA,KACIA;YACHA,IAAIA,MAAMA;YACVA,KAAKA,IAAIA,CAACA,GAAGA,CAACA,EAAEA,IAAIA,GAAGA,UAAUA,CAACA,MAAMA,EAAEA,CAACA,GAAGA,IAAIA,EAAEA,CAACA,EAAEA,CAAEA;gBACvDA,IAAIA,UAAUA,CAACA,CAACA,CAACA,CAACA,GAAGA,KAAKA,QAAQA,CAACA,MAAMA,CAAEA;oBACzCA,MAAMA,GAAGA,UAAUA,CAACA,CAACA,CAACA;oBACtBA,KAAMA;iBACPA;aACFA;YACDA,SAASA,CAACA,MAAMA,CAACA,KAAKA,EAAEA,QAAQA,CAACA,MAAMA,EAAEA,QAAQA,CAACA,OAAOA,EAAEA,EAAEA,CAACA;SAC/DA;QACDA,IAAIA,IAAIA,GAAGA,QAAQA,CAACA,OAAOA;QAC3BA,IAAGA,IAAIA,CAACA,MAAMA,GAAGA,CAACA,CAAEA;YAClBA,QAAQA,CAACA,OAAOA,GAAGA,EAAEA;YACrBA,IAAGA,QAAQA,CAACA,QAAQA,CAAEA;gBACpBA,QAAQA,CAACA,QAAQA,CAACA,IAAIA,CAACA;aACxBA;SACFA;QACDA,OAAOA,IAAIA;IACbA,CAACA;IAtBDf,8BAsBCA;;IAEDA,IAAIA,WAAWA;IACfA,IAAIA,MAAMA,CAACA,IAAIA,CAAEA;QACfA,WAAWA,GAAGA,MAAMA,CAACA,IAAIA;KAC1BA,KACIA;QACHA,WAAWA,GAAGA,UAAUA,GAAGA;YACzBA,IAAIA,IAAIA,GAAGA,EAAEA;YACbA,KAAKA,IAAIA,CAACA,IAAIA,GAAGA,CAAEA;gBACjBA,IAAIA,GAAGA,CAACA,cAAcA,CAACA,CAACA,CAACA,CAAEA;oBACzBA,IAAIA,CAACA,IAAIA,CAACA,CAACA,CAACA;iBACbA;aACFA;YACDA,OAAOA,IAAIA;QACbA,CAACA;KACFA;;IAGDA,kFADkFA;IAClFA,SAASA,SAASA,CAACA,MAAMA,EAAEA,GAAGA,EAAEA,OAAOA,EAAEA,IAAIA;QAC3CgB,IAAIA,OAAOA,GAAGA,WAAWA,CAACA,GAAGA,CAACA;QAC9BA,IAAIA,OAAOA,GAAGA,WAAWA,CAACA,MAAMA,CAACA;QACjCA,IAAIA,OAAOA,GAAGA,KAAKA;QACnBA,IAAIA,OAAOA,GAAGA,KAAKA;;QAInBA,KAAKA,IAAIA,CAACA,GAAGA,OAAOA,CAACA,MAAMA,GAAGA,CAACA,EAAEA,CAACA,IAAIA,CAACA,EAAEA,CAACA,EAAEA,CAAEA;YAC5CA,IAAIA,GAAGA,GAAGA,OAAOA,CAACA,CAACA,CAACA;YACpBA,IAAIA,MAAMA,GAAGA,MAAMA,CAACA,GAAGA,CAACA;YACxBA,IAAIA,GAAGA,CAACA,cAAcA,CAACA,GAAGA,CAACA,CAAEA;gBAC3BA,IAAIA,MAAMA,GAAGA,GAAGA,CAACA,GAAGA,CAACA;gBACrBA,IAAIA,MAAMA,YAAYA,MAAMA,CAAEA;oBAC5BA,SAASA,CAACA,MAAMA,EAAEA,MAAMA,EAAEA,OAAOA,EAAEA,IAAIA,GAAGA,GAAGA,GAAGA,mBAAmBA,CAACA,GAAGA,CAACA,CAACA;iBAC1EA,KACIA;oBACHA,IAAIA,MAAMA,IAAIA,MAAMA,CAAEA;wBACpBA,OAAOA,GAAGA,IAAIA;wBACdA,OAAOA,CAACA,IAAIA,CAACA,EAACA,EAAEA,EAAEA,SAASA,EAAEA,IAAIA,EAAEA,IAAIA,GAAGA,GAAGA,GAAGA,mBAAmBA,CAACA,GAAGA,CAACA,EAAEA,KAAKA,EAAEA,MAAMA,EAACA,CAACA;wBACzFA,MAAMA,CAACA,GAAGA,CAACA,GAAGA,MAAMA;qBACrBA;iBACFA;aACFA,KACIA;gBACHA,OAAOA,CAACA,IAAIA,CAACA,EAACA,EAAEA,EAAEA,QAAQA,EAAEA,IAAIA,EAAEA,IAAIA,GAAGA,GAAGA,GAAGA,mBAAmBA,CAACA,GAAGA,CAACA,EAACA,CAACA;gBACzEA,OAAOA,MAAMA,CAACA,GAAGA,CAACA;gBAClBA,OAAOA,GAAGA,IAAIA,EAAEA,4BAA4BA;aAC7CA;SACFA;;QAEDA,IAAIA,CAACA,OAAOA,IAAIA,OAAOA,CAACA,MAAMA,IAAIA,OAAOA,CAACA,MAAMA,CAAEA;YAChDA,MAAOA;SACRA;;QAEDA,KAAKA,IAAIA,CAACA,GAAGA,CAACA,EAAEA,CAACA,GAAGA,OAAOA,CAACA,MAAMA,EAAEA,CAACA,EAAEA,CAAEA;YACvCA,IAAIA,GAAGA,GAAGA,OAAOA,CAACA,CAACA,CAACA;YACpBA,IAAIA,CAACA,MAAMA,CAACA,cAAcA,CAACA,GAAGA,CAACA,CAAEA;gBAC/BA,OAAOA,CAACA,IAAIA,CAACA,EAACA,EAAEA,EAAEA,KAAKA,EAAEA,IAAIA,EAAEA,IAAIA,GAAGA,GAAGA,GAAGA,mBAAmBA,CAACA,GAAGA,CAACA,EAAEA,KAAKA,EAAEA,GAAGA,CAACA,GAAGA,CAACA,EAACA,CAACA;gBACvFA,MAAMA,CAACA,GAAGA,CAACA,GAAGA,IAAIA,CAACA,KAAKA,CAACA,IAAIA,CAACA,SAASA,CAACA,GAAGA,CAACA,GAAGA,CAACA,CAACA,CAACA;aACnDA;SACFA;IACHA,CAACA;;IAEDhB,IAAIA,QAAQA;IACZA,IAAIA,KAAKA,CAACA,OAAOA,CAAEA;QACjBA,QAAQA,GAAGA,KAAKA,CAACA,OAAOA;KACzBA,KACIA;QACHA,QAAQA,GAAGA,UAAUA,GAAOA;YAC1BA,OAAOA,GAAGA,CAACA,IAAIA,IAAIA,OAAOA,GAAGA,CAACA,MAAMA,KAAKA,QAAQA;QACnDA,CAACA;KACFA;;IAGDA,kDADkDA;IAClDA,SAAgBA,KAAKA,CAACA,IAAQA,EAAEA,OAAaA;QAC3CiB,IAAIA,MAAMA,GAAGA,KAAKA,EACdA,CAACA,GAAGA,CAACA,EACLA,IAAIA,GAAGA,OAAOA,CAACA,MAAMA,EACrBA,KAAKA;QACTA,OAAOA,CAACA,GAAGA,IAAIA,CAAEA;YACfA,KAAKA,GAAGA,OAAOA,CAACA,CAACA,CAACA;;YAClBA,kBAAkBA;YAClBA,IAAIA,IAAIA,GAAGA,KAAKA,CAACA,IAAIA,CAACA,KAAKA,CAACA,GAAGA,CAACA;YAChCA,IAAIA,GAAGA,GAAGA,IAAIA;YACdA,IAAIA,CAACA,GAAGA,CAACA;YACTA,IAAIA,GAAGA,GAAGA,IAAIA,CAACA,MAAMA;YACrBA,OAAOA,IAAIA,CAAEA;gBACXA,IAAIA,QAAQA,CAACA,GAAGA,CAACA,CAAEA;oBACjBA,IAAIA,KAAKA,GAAGA,QAAQA,CAACA,IAAIA,CAACA,CAACA,CAACA,EAAEA,EAAEA,CAACA;oBACjCA,CAACA,EAAEA;oBACHA,IAAIA,CAACA,IAAIA,GAAGA,CAAEA;wBACZA,MAAMA,GAAGA,MAAMA,CAACA,KAAKA,CAACA,EAAEA,CAACA,CAACA,IAAIA,CAACA,KAAKA,EAAEA,GAAGA,EAAEA,KAAKA,EAAEA,IAAIA,CAACA,EAAEA,cAAcA;wBACvEA,KAAMA;qBACPA;oBACDA,GAAGA,GAAGA,GAAGA,CAACA,KAAKA,CAACA;iBACjBA,KACIA;oBACHA,IAAIA,GAAGA,GAAGA,IAAIA,CAACA,CAACA,CAACA;oBACjBA,IAAIA,GAAGA,CAACA,OAAOA,CAACA,GAAGA,CAACA,IAAIA,CAACA,CAACA;wBACxBA,GAAGA,GAAGA,GAAGA,CAACA,OAAOA,CAACA,KAAKA,EAAEA,GAAGA,CAACA,CAACA,OAAOA,CAACA,KAAKA,EAAEA,GAAGA,CAACA,EAAEA,eAAeA;AAAhBA,oBACpDA,CAACA,EAAEA;oBACHA,IAAIA,CAACA,IAAIA,GAAGA,CAAEA;wBACZA,MAAMA,GAAGA,MAAMA,CAACA,KAAKA,CAACA,EAAEA,CAACA,CAACA,IAAIA,CAACA,KAAKA,EAAEA,GAAGA,EAAEA,GAAGA,EAAEA,IAAIA,CAACA,EAAEA,cAAcA;wBACrEA,KAAMA;qBACPA;oBACDA,GAAGA,GAAGA,GAAGA,CAACA,GAAGA,CAACA;iBACfA;aACFA;YACDA,CAACA,EAAEA;SACJA;QACDA,OAAOA,MAAMA;IACfA,CAACA;IArCDjB,wBAqCCA;;IAEDA,SAAgBA,OAAOA,CAACA,KAASA,EAAEA,KAASA;QAC1CkB,IAAIA,OAAOA,GAAGA,EAAEA;QAChBA,SAASA,CAACA,KAAKA,EAAEA,KAAKA,EAAEA,OAAOA,EAAEA,EAAEA,CAACA;QACpCA,OAAOA,OAAOA;IAChBA,CAACA;IAJDlB,4BAICA;AACHA,CAACA,iCAAA;;AAID,IAAI,OAAO,OAAO,KAAK,WAAW,CAAE;IAClC,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,KAAK;IAC/B,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,OAAO;IACnC,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,SAAS;IACvC,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,QAAQ;CACtC"} -------------------------------------------------------------------------------- /src/json-patch-duplex.ts: -------------------------------------------------------------------------------- 1 | // json-patch-duplex.js 0.3.7 2 | // (c) 2013 Joachim Wester 3 | // MIT license 4 | 5 | interface Object { 6 | observe : any; 7 | deliverChangeRecords : any; 8 | unobserve : any; 9 | } 10 | 11 | module jsonpatch { 12 | 13 | /* We use a Javascript hash to store each 14 | function. Each hash entry (property) uses 15 | the operation identifiers specified in rfc6902. 16 | In this way, we can map each patch operation 17 | to its dedicated function in efficient way. 18 | */ 19 | 20 | /* The operations applicable to an object */ 21 | var objOps = { 22 | add: function (obj, key) { 23 | obj[key] = this.value; 24 | return true; 25 | }, 26 | remove: function (obj, key) { 27 | delete obj[key]; 28 | return true; 29 | }, 30 | replace: function (obj, key) { 31 | obj[key] = this.value; 32 | return true; 33 | }, 34 | move: function (obj, key, tree) { 35 | var temp:any = {op: "_get", path: this.from}; 36 | apply(tree, [temp]); 37 | apply(tree, [ 38 | {op: "remove", path: this.from} 39 | ]); 40 | apply(tree, [ 41 | {op: "add", path: this.path, value: temp.value} 42 | ]); 43 | return true; 44 | }, 45 | copy: function (obj, key, tree) { 46 | var temp:any = {op: "_get", path: this.from}; 47 | apply(tree, [temp]); 48 | apply(tree, [ 49 | {op: "add", path: this.path, value: temp.value} 50 | ]); 51 | return true; 52 | }, 53 | test: function (obj, key) { 54 | return(JSON.stringify(obj[key]) === JSON.stringify(this.value)); 55 | }, 56 | _get: function (obj, key) { 57 | this.value = obj[key]; 58 | } 59 | }; 60 | 61 | /* The operations applicable to an array. Many are the same as for the object */ 62 | var arrOps = { 63 | add: function (arr, i) { 64 | arr.splice(i, 0, this.value); 65 | return true; 66 | }, 67 | remove: function (arr, i) { 68 | arr.splice(i, 1); 69 | return true; 70 | }, 71 | replace: function (arr, i) { 72 | arr[i] = this.value; 73 | return true; 74 | }, 75 | move: objOps.move, 76 | copy: objOps.copy, 77 | test: objOps.test, 78 | _get: objOps._get 79 | }; 80 | 81 | var observeOps = { 82 | add: function (patches:any[], path) { 83 | var patch = { 84 | op: "add", 85 | path: path + escapePathComponent(this.name), 86 | value: this.object[this.name]}; 87 | patches.push(patch); 88 | }, 89 | 'delete': function (patches:any[], path) { //single quotes needed because 'delete' is a keyword in IE8 90 | var patch = { 91 | op: "remove", 92 | path: path + escapePathComponent(this.name) 93 | }; 94 | patches.push(patch); 95 | }, 96 | update: function (patches:any[], path) { 97 | var patch = { 98 | op: "replace", 99 | path: path + escapePathComponent(this.name), 100 | value: this.object[this.name] 101 | }; 102 | patches.push(patch); 103 | } 104 | }; 105 | 106 | function escapePathComponent (str) { 107 | if (str.indexOf('/') === -1 && str.indexOf('~') === -1) return str; 108 | return str.replace(/~/g, '~0').replace(/\//g, '~1'); 109 | } 110 | 111 | function _getPathRecursive(root:Object, obj:Object):string { 112 | var found; 113 | for (var key in root) { 114 | if (root.hasOwnProperty(key)) { 115 | if (root[key] === obj) { 116 | return escapePathComponent(key) + '/'; 117 | } 118 | else if (typeof root[key] === 'object') { 119 | found = _getPathRecursive(root[key], obj); 120 | if (found != '') { 121 | return escapePathComponent(key) + '/' + found; 122 | } 123 | } 124 | } 125 | } 126 | return ''; 127 | } 128 | 129 | function getPath(root:Object, obj:Object):string { 130 | if (root === obj) { 131 | return '/'; 132 | } 133 | var path = _getPathRecursive(root, obj); 134 | if (path === '') { 135 | throw new Error("Object not found in root"); 136 | } 137 | return '/' + path; 138 | } 139 | 140 | var beforeDict = []; 141 | 142 | export var intervals; 143 | 144 | class Mirror { 145 | obj: any; 146 | observers = []; 147 | 148 | constructor(obj:any){ 149 | this.obj = obj; 150 | } 151 | } 152 | 153 | class ObserverInfo { 154 | callback: any; 155 | observer: any; 156 | 157 | constructor(callback, observer){ 158 | this.callback = callback; 159 | this.observer = observer; 160 | } 161 | } 162 | 163 | function getMirror(obj:any):any { 164 | for (var i = 0, ilen = beforeDict.length; i < ilen; i++) { 165 | if (beforeDict[i].obj === obj) { 166 | return beforeDict[i]; 167 | } 168 | } 169 | } 170 | 171 | function getObserverFromMirror(mirror:any, callback):any { 172 | for (var j = 0, jlen = mirror.observers.length; j < jlen; j++) { 173 | if (mirror.observers[j].callback === callback) { 174 | return mirror.observers[j].observer; 175 | } 176 | } 177 | } 178 | 179 | function removeObserverFromMirror(mirror:any, observer):any { 180 | for (var j = 0, jlen = mirror.observers.length; j < jlen; j++) { 181 | if (mirror.observers[j].observer === observer) { 182 | mirror.observers.splice(j, 1); 183 | return; 184 | } 185 | } 186 | } 187 | 188 | export function unobserve(root, observer) { 189 | generate(observer); 190 | if(Object.observe) { 191 | _unobserve(observer, root); 192 | } 193 | else { 194 | clearTimeout(observer.next); 195 | } 196 | 197 | var mirror = getMirror(root); 198 | removeObserverFromMirror(mirror, observer); 199 | 200 | } 201 | 202 | export function observe(obj:any, callback):any { 203 | var patches = []; 204 | var root = obj; 205 | var observer; 206 | var mirror = getMirror(obj); 207 | 208 | if (!mirror) { 209 | mirror = new Mirror(obj); 210 | beforeDict.push(mirror); 211 | } else { 212 | observer = getObserverFromMirror(mirror, callback); 213 | } 214 | 215 | if(observer){ 216 | return observer; 217 | } 218 | 219 | if (Object.observe) { 220 | observer = function (arr) { 221 | //This "refresh" is needed to begin observing new object properties 222 | _unobserve(observer, obj); 223 | _observe(observer, obj); 224 | 225 | var a = 0 226 | , alen = arr.length; 227 | while (a < alen) { 228 | if ( 229 | !(arr[a].name === 'length' && _isArray(arr[a].object)) 230 | && !(arr[a].name === '__Jasmine_been_here_before__') 231 | ) { 232 | var type = arr[a].type; 233 | 234 | //old record type names before 10/29/2013 (http://wiki.ecmascript.org/doku.php?id=harmony:observe) 235 | //this block can be removed when Chrome 33 stable is released 236 | switch(type) { 237 | case 'new': 238 | type = 'add'; 239 | break; 240 | 241 | case 'deleted': 242 | type = 'delete'; 243 | break; 244 | 245 | case 'updated': 246 | type = 'update'; 247 | break; 248 | } 249 | 250 | observeOps[type].call(arr[a], patches, getPath(root, arr[a].object)); 251 | } 252 | a++; 253 | } 254 | 255 | if (patches) { 256 | if (callback) { 257 | callback(patches); 258 | } 259 | } 260 | observer.patches = patches; 261 | patches = []; 262 | 263 | 264 | }; 265 | } else { 266 | observer = {}; 267 | 268 | mirror.value = JSON.parse(JSON.stringify(obj)); // Faster than ES5 clone - http://jsperf.com/deep-cloning-of-objects/5 269 | 270 | if (callback) { 271 | //callbacks.push(callback); this has no purpose 272 | observer.callback = callback; 273 | observer.next = null; 274 | var intervals = this.intervals || [100, 1000, 10000, 60000]; 275 | if (intervals.push === void 0) { 276 | throw new Error("jsonpatch.intervals must be an array"); 277 | } 278 | var currentInterval = 0; 279 | 280 | var dirtyCheck = function () { 281 | generate(observer); 282 | }; 283 | var fastCheck = function () { 284 | clearTimeout(observer.next); 285 | observer.next = setTimeout(function () { 286 | dirtyCheck(); 287 | currentInterval = 0; 288 | observer.next = setTimeout(slowCheck, intervals[currentInterval++]); 289 | }, 0); 290 | }; 291 | var slowCheck = function () { 292 | dirtyCheck(); 293 | if (currentInterval == intervals.length) 294 | currentInterval = intervals.length - 1; 295 | observer.next = setTimeout(slowCheck, intervals[currentInterval++]); 296 | }; 297 | if (typeof window !== 'undefined') { //not Node 298 | if (window.addEventListener) { //standards 299 | window.addEventListener('mousedown', fastCheck); 300 | window.addEventListener('mouseup', fastCheck); 301 | window.addEventListener('keydown', fastCheck); 302 | } 303 | else { //IE8 304 | window.attachEvent('onmousedown', fastCheck); 305 | window.attachEvent('onmouseup', fastCheck); 306 | window.attachEvent('onkeydown', fastCheck); 307 | } 308 | } 309 | observer.next = setTimeout(slowCheck, intervals[currentInterval++]); 310 | } 311 | } 312 | observer.patches = patches; 313 | observer.object = obj; 314 | 315 | mirror.observers.push(new ObserverInfo(callback, observer)); 316 | 317 | return _observe(observer, obj); 318 | } 319 | 320 | /// Listen to changes on an object tree, accumulate patches 321 | function _observe(observer:any, obj:any):any { 322 | if (Object.observe) { 323 | Object.observe(obj, observer); 324 | for (var key in obj) { 325 | if (obj.hasOwnProperty(key)) { 326 | var v:any = obj[key]; 327 | if (v && typeof (v) === "object") { 328 | _observe(observer, v); 329 | } 330 | } 331 | } 332 | } 333 | return observer; 334 | } 335 | 336 | function _unobserve(observer:any, obj:any):any { 337 | if (Object.observe) { 338 | Object.unobserve(obj, observer); 339 | for (var key in obj) { 340 | if (obj.hasOwnProperty(key)) { 341 | var v:any = obj[key]; 342 | if (v && typeof (v) === "object") { 343 | _unobserve(observer, v); 344 | } 345 | } 346 | } 347 | } 348 | return observer; 349 | } 350 | 351 | export function generate(observer) { 352 | if (Object.observe) { 353 | Object.deliverChangeRecords(observer); 354 | } 355 | else { 356 | var mirror; 357 | for (var i = 0, ilen = beforeDict.length; i < ilen; i++) { 358 | if (beforeDict[i].obj === observer.object) { 359 | mirror = beforeDict[i]; 360 | break; 361 | } 362 | } 363 | _generate(mirror.value, observer.object, observer.patches, ""); 364 | } 365 | var temp = observer.patches; 366 | if(temp.length > 0) { 367 | observer.patches = []; 368 | if(observer.callback) { 369 | observer.callback(temp); 370 | } 371 | } 372 | return temp; 373 | } 374 | 375 | var _objectKeys; 376 | if (Object.keys) { //standards 377 | _objectKeys = Object.keys; 378 | } 379 | else { //IE8 shim 380 | _objectKeys = function (obj) { 381 | var keys = []; 382 | for (var o in obj) { 383 | if (obj.hasOwnProperty(o)) { 384 | keys.push(o); 385 | } 386 | } 387 | return keys; 388 | } 389 | } 390 | 391 | // Dirty check if obj is different from mirror, generate patches and update mirror 392 | function _generate(mirror, obj, patches, path) { 393 | var newKeys = _objectKeys(obj); 394 | var oldKeys = _objectKeys(mirror); 395 | var changed = false; 396 | var deleted = false; 397 | 398 | //if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)" 399 | 400 | for (var t = oldKeys.length - 1; t >= 0; t--) { 401 | var key = oldKeys[t]; 402 | var oldVal = mirror[key]; 403 | if (obj.hasOwnProperty(key)) { 404 | var newVal = obj[key]; 405 | if (oldVal instanceof Object) { 406 | _generate(oldVal, newVal, patches, path + "/" + escapePathComponent(key)); 407 | } 408 | else { 409 | if (oldVal != newVal) { 410 | changed = true; 411 | patches.push({op: "replace", path: path + "/" + escapePathComponent(key), value: newVal}); 412 | mirror[key] = newVal; 413 | } 414 | } 415 | } 416 | else { 417 | patches.push({op: "remove", path: path + "/" + escapePathComponent(key)}); 418 | delete mirror[key]; 419 | deleted = true; // property has been deleted 420 | } 421 | } 422 | 423 | if (!deleted && newKeys.length == oldKeys.length) { 424 | return; 425 | } 426 | 427 | for (var t = 0; t < newKeys.length; t++) { 428 | var key = newKeys[t]; 429 | if (!mirror.hasOwnProperty(key)) { 430 | patches.push({op: "add", path: path + "/" + escapePathComponent(key), value: obj[key]}); 431 | mirror[key] = JSON.parse(JSON.stringify(obj[key])); 432 | } 433 | } 434 | } 435 | 436 | var _isArray; 437 | if (Array.isArray) { //standards; http://jsperf.com/isarray-shim/4 438 | _isArray = Array.isArray; 439 | } 440 | else { //IE8 shim 441 | _isArray = function (obj:any) { 442 | return obj.push && typeof obj.length === 'number'; 443 | } 444 | } 445 | 446 | /// Apply a json-patch operation on an object tree 447 | export function apply(tree:any, patches:any[]):boolean { 448 | var result = false 449 | , p = 0 450 | , plen = patches.length 451 | , patch; 452 | while (p < plen) { 453 | patch = patches[p]; 454 | // Find the object 455 | var keys = patch.path.split('/'); 456 | var obj = tree; 457 | var t = 1; //skip empty element - http://jsperf.com/to-shift-or-not-to-shift 458 | var len = keys.length; 459 | while (true) { 460 | if (_isArray(obj)) { 461 | var index = parseInt(keys[t], 10); 462 | t++; 463 | if (t >= len) { 464 | result = arrOps[patch.op].call(patch, obj, index, tree); // Apply patch 465 | break; 466 | } 467 | obj = obj[index]; 468 | } 469 | else { 470 | var key = keys[t]; 471 | if (key.indexOf('~') != -1) 472 | key = key.replace(/~1/g, '/').replace(/~0/g, '~'); // escape chars 473 | t++; 474 | if (t >= len) { 475 | result = objOps[patch.op].call(patch, obj, key, tree); // Apply patch 476 | break; 477 | } 478 | obj = obj[key]; 479 | } 480 | } 481 | p++; 482 | } 483 | return result; 484 | } 485 | 486 | export function compare(tree1:any, tree2:any):any[] { 487 | var patches = []; 488 | _generate(tree1, tree2, patches, ''); 489 | return patches; 490 | } 491 | } 492 | 493 | declare var exports:any; 494 | 495 | if (typeof exports !== "undefined") { 496 | exports.apply = jsonpatch.apply; 497 | exports.observe = jsonpatch.observe; 498 | exports.unobserve = jsonpatch.unobserve; 499 | exports.generate = jsonpatch.generate; 500 | } 501 | -------------------------------------------------------------------------------- /src/json-patch.js: -------------------------------------------------------------------------------- 1 | // json-patch.js 0.3.7 2 | // (c) 2013 Joachim Wester 3 | // MIT license 4 | var jsonpatch; 5 | (function (jsonpatch) { 6 | var objOps = { 7 | add: function (obj, key) { 8 | obj[key] = this.value; 9 | return true; 10 | }, 11 | remove: function (obj, key) { 12 | delete obj[key]; 13 | return true; 14 | }, 15 | replace: function (obj, key) { 16 | obj[key] = this.value; 17 | return true; 18 | }, 19 | move: function (obj, key, tree) { 20 | var temp = { op: "_get", path: this.from }; 21 | apply(tree, [temp]); 22 | apply(tree, [ 23 | { op: "remove", path: this.from } 24 | ]); 25 | apply(tree, [ 26 | { op: "add", path: this.path, value: temp.value } 27 | ]); 28 | return true; 29 | }, 30 | copy: function (obj, key, tree) { 31 | var temp = { op: "_get", path: this.from }; 32 | apply(tree, [temp]); 33 | apply(tree, [ 34 | { op: "add", path: this.path, value: temp.value } 35 | ]); 36 | return true; 37 | }, 38 | test: function (obj, key) { 39 | return (JSON.stringify(obj[key]) === JSON.stringify(this.value)); 40 | }, 41 | _get: function (obj, key) { 42 | this.value = obj[key]; 43 | } 44 | }; 45 | 46 | var arrOps = { 47 | add: function (arr, i) { 48 | arr.splice(i, 0, this.value); 49 | return true; 50 | }, 51 | remove: function (arr, i) { 52 | arr.splice(i, 1); 53 | return true; 54 | }, 55 | replace: function (arr, i) { 56 | arr[i] = this.value; 57 | return true; 58 | }, 59 | move: objOps.move, 60 | copy: objOps.copy, 61 | test: objOps.test, 62 | _get: objOps._get 63 | }; 64 | 65 | var _isArray; 66 | if (Array.isArray) { 67 | _isArray = Array.isArray; 68 | } else { 69 | _isArray = function (obj) { 70 | return obj.push && typeof obj.length === 'number'; 71 | }; 72 | } 73 | 74 | /// Apply a json-patch operation on an object tree 75 | function apply(tree, patches) { 76 | var result = false, p = 0, plen = patches.length, patch; 77 | while (p < plen) { 78 | patch = patches[p]; 79 | 80 | // Find the object 81 | var keys = patch.path.split('/'); 82 | var obj = tree; 83 | var t = 1; 84 | var len = keys.length; 85 | while (true) { 86 | if (_isArray(obj)) { 87 | var index = parseInt(keys[t], 10); 88 | t++; 89 | if (t >= len) { 90 | result = arrOps[patch.op].call(patch, obj, index, tree); // Apply patch 91 | break; 92 | } 93 | obj = obj[index]; 94 | } else { 95 | var key = keys[t]; 96 | if (key.indexOf('~') != -1) 97 | key = key.replace(/~1/g, '/').replace(/~0/g, '~'); // escape chars 98 | t++; 99 | if (t >= len) { 100 | result = objOps[patch.op].call(patch, obj, key, tree); // Apply patch 101 | break; 102 | } 103 | obj = obj[key]; 104 | } 105 | } 106 | p++; 107 | } 108 | return result; 109 | } 110 | jsonpatch.apply = apply; 111 | })(jsonpatch || (jsonpatch = {})); 112 | 113 | if (typeof exports !== "undefined") { 114 | exports.apply = jsonpatch.apply; 115 | } 116 | //# sourceMappingURL=json-patch.js.map 117 | -------------------------------------------------------------------------------- /src/json-patch.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"json-patch.js","sourceRoot":"","sources":["json-patch.ts"],"names":["jsonpatch","jsonpatch.apply"],"mappings":"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"} -------------------------------------------------------------------------------- /src/json-patch.ts: -------------------------------------------------------------------------------- 1 | // json-patch.js 0.3.7 2 | // (c) 2013 Joachim Wester 3 | // MIT license 4 | 5 | module jsonpatch { 6 | 7 | var objOps = { 8 | add: function (obj, key) { 9 | obj[key] = this.value; 10 | return true; 11 | }, 12 | remove: function (obj, key) { 13 | delete obj[key]; 14 | return true; 15 | }, 16 | replace: function (obj, key) { 17 | obj[key] = this.value; 18 | return true; 19 | }, 20 | move: function (obj, key, tree) { 21 | var temp:any = {op: "_get", path: this.from}; 22 | apply(tree, [temp]); 23 | apply(tree, [ 24 | {op: "remove", path: this.from} 25 | ]); 26 | apply(tree, [ 27 | {op: "add", path: this.path, value: temp.value} 28 | ]); 29 | return true; 30 | }, 31 | copy: function (obj, key, tree) { 32 | var temp:any = {op: "_get", path: this.from}; 33 | apply(tree, [temp]); 34 | apply(tree, [ 35 | {op: "add", path: this.path, value: temp.value} 36 | ]); 37 | return true; 38 | }, 39 | test: function (obj, key) { 40 | return(JSON.stringify(obj[key]) === JSON.stringify(this.value)); 41 | }, 42 | _get: function (obj, key) { 43 | this.value = obj[key]; 44 | } 45 | }; 46 | 47 | var arrOps = { 48 | add: function (arr, i) { 49 | arr.splice(i, 0, this.value); 50 | return true; 51 | }, 52 | remove: function (arr, i) { 53 | arr.splice(i, 1); 54 | return true; 55 | }, 56 | replace: function (arr, i) { 57 | arr[i] = this.value; 58 | return true; 59 | }, 60 | move: objOps.move, 61 | copy: objOps.copy, 62 | test: objOps.test, 63 | _get: objOps._get 64 | }; 65 | 66 | var _isArray; 67 | if (Array.isArray) { //standards; http://jsperf.com/isarray-shim/4 68 | _isArray = Array.isArray; 69 | } 70 | else { //IE8 shim 71 | _isArray = function (obj:any) { 72 | return obj.push && typeof obj.length === 'number'; 73 | } 74 | } 75 | 76 | /// Apply a json-patch operation on an object tree 77 | export function apply(tree:any, patches:any[]):boolean { 78 | var result = false 79 | , p = 0 80 | , plen = patches.length 81 | , patch; 82 | while (p < plen) { 83 | patch = patches[p]; 84 | // Find the object 85 | var keys = patch.path.split('/'); 86 | var obj = tree; 87 | var t = 1; //skip empty element - http://jsperf.com/to-shift-or-not-to-shift 88 | var len = keys.length; 89 | while (true) { 90 | if (_isArray(obj)) { 91 | var index = parseInt(keys[t], 10); 92 | t++; 93 | if (t >= len) { 94 | result = arrOps[patch.op].call(patch, obj, index, tree); // Apply patch 95 | break; 96 | } 97 | obj = obj[index]; 98 | } 99 | else { 100 | var key = keys[t]; 101 | if (key.indexOf('~') != -1) 102 | key = key.replace(/~1/g, '/').replace(/~0/g, '~'); // escape chars 103 | t++; 104 | if (t >= len) { 105 | result = objOps[patch.op].call(patch, obj, key, tree); // Apply patch 106 | break; 107 | } 108 | obj = obj[key]; 109 | } 110 | } 111 | p++; 112 | } 113 | return result; 114 | } 115 | } 116 | 117 | declare var exports:any; 118 | 119 | if (typeof exports !== "undefined") { 120 | exports.apply = jsonpatch.apply; 121 | } -------------------------------------------------------------------------------- /src/lib/jasmine/MIT.LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2011 Pivotal Labs 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/lib/jasmine/jasmine-html.js: -------------------------------------------------------------------------------- 1 | jasmine.HtmlReporterHelpers = {}; 2 | 3 | jasmine.HtmlReporterHelpers.createDom = function(type, attrs, childrenVarArgs) { 4 | var el = document.createElement(type); 5 | 6 | for (var i = 2; i < arguments.length; i++) { 7 | var child = arguments[i]; 8 | 9 | if (typeof child === 'string') { 10 | el.appendChild(document.createTextNode(child)); 11 | } else { 12 | if (child) { 13 | el.appendChild(child); 14 | } 15 | } 16 | } 17 | 18 | for (var attr in attrs) { 19 | if (attr == "className") { 20 | el[attr] = attrs[attr]; 21 | } else { 22 | el.setAttribute(attr, attrs[attr]); 23 | } 24 | } 25 | 26 | return el; 27 | }; 28 | 29 | jasmine.HtmlReporterHelpers.getSpecStatus = function(child) { 30 | var results = child.results(); 31 | var status = results.passed() ? 'passed' : 'failed'; 32 | if (results.skipped) { 33 | status = 'skipped'; 34 | } 35 | 36 | return status; 37 | }; 38 | 39 | jasmine.HtmlReporterHelpers.appendToSummary = function(child, childElement) { 40 | var parentDiv = this.dom.summary; 41 | var parentSuite = (typeof child.parentSuite == 'undefined') ? 'suite' : 'parentSuite'; 42 | var parent = child[parentSuite]; 43 | 44 | if (parent) { 45 | if (typeof this.views.suites[parent.id] == 'undefined') { 46 | this.views.suites[parent.id] = new jasmine.HtmlReporter.SuiteView(parent, this.dom, this.views); 47 | } 48 | parentDiv = this.views.suites[parent.id].element; 49 | } 50 | 51 | parentDiv.appendChild(childElement); 52 | }; 53 | 54 | 55 | jasmine.HtmlReporterHelpers.addHelpers = function(ctor) { 56 | for(var fn in jasmine.HtmlReporterHelpers) { 57 | ctor.prototype[fn] = jasmine.HtmlReporterHelpers[fn]; 58 | } 59 | }; 60 | 61 | jasmine.HtmlReporter = function(_doc) { 62 | var self = this; 63 | var doc = _doc || window.document; 64 | 65 | var reporterView; 66 | 67 | var dom = {}; 68 | 69 | // Jasmine Reporter Public Interface 70 | self.logRunningSpecs = false; 71 | 72 | self.reportRunnerStarting = function(runner) { 73 | var specs = runner.specs() || []; 74 | 75 | if (specs.length == 0) { 76 | return; 77 | } 78 | 79 | createReporterDom(runner.env.versionString()); 80 | doc.body.appendChild(dom.reporter); 81 | setExceptionHandling(); 82 | 83 | reporterView = new jasmine.HtmlReporter.ReporterView(dom); 84 | reporterView.addSpecs(specs, self.specFilter); 85 | }; 86 | 87 | self.reportRunnerResults = function(runner) { 88 | reporterView && reporterView.complete(); 89 | }; 90 | 91 | self.reportSuiteResults = function(suite) { 92 | reporterView.suiteComplete(suite); 93 | }; 94 | 95 | self.reportSpecStarting = function(spec) { 96 | if (self.logRunningSpecs) { 97 | self.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 98 | } 99 | }; 100 | 101 | self.reportSpecResults = function(spec) { 102 | reporterView.specComplete(spec); 103 | }; 104 | 105 | self.log = function() { 106 | var console = jasmine.getGlobal().console; 107 | if (console && console.log) { 108 | if (console.log.apply) { 109 | console.log.apply(console, arguments); 110 | } else { 111 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 112 | } 113 | } 114 | }; 115 | 116 | self.specFilter = function(spec) { 117 | if (!focusedSpecName()) { 118 | return true; 119 | } 120 | 121 | return spec.getFullName().indexOf(focusedSpecName()) === 0; 122 | }; 123 | 124 | return self; 125 | 126 | function focusedSpecName() { 127 | var specName; 128 | 129 | (function memoizeFocusedSpec() { 130 | if (specName) { 131 | return; 132 | } 133 | 134 | var paramMap = []; 135 | var params = jasmine.HtmlReporter.parameters(doc); 136 | 137 | for (var i = 0; i < params.length; i++) { 138 | var p = params[i].split('='); 139 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 140 | } 141 | 142 | specName = paramMap.spec; 143 | })(); 144 | 145 | return specName; 146 | } 147 | 148 | function createReporterDom(version) { 149 | dom.reporter = self.createDom('div', { id: 'HTMLReporter', className: 'jasmine_reporter' }, 150 | dom.banner = self.createDom('div', { className: 'banner' }, 151 | self.createDom('span', { className: 'title' }, "Jasmine "), 152 | self.createDom('span', { className: 'version' }, version)), 153 | 154 | dom.symbolSummary = self.createDom('ul', {className: 'symbolSummary'}), 155 | dom.alert = self.createDom('div', {className: 'alert'}, 156 | self.createDom('span', { className: 'exceptions' }, 157 | self.createDom('label', { className: 'label', 'for': 'no_try_catch' }, 'No try/catch'), 158 | self.createDom('input', { id: 'no_try_catch', type: 'checkbox' }))), 159 | dom.results = self.createDom('div', {className: 'results'}, 160 | dom.summary = self.createDom('div', { className: 'summary' }), 161 | dom.details = self.createDom('div', { id: 'details' })) 162 | ); 163 | } 164 | 165 | function noTryCatch() { 166 | return window.location.search.match(/catch=false/); 167 | } 168 | 169 | function searchWithCatch() { 170 | var params = jasmine.HtmlReporter.parameters(window.document); 171 | var removed = false; 172 | var i = 0; 173 | 174 | while (!removed && i < params.length) { 175 | if (params[i].match(/catch=/)) { 176 | params.splice(i, 1); 177 | removed = true; 178 | } 179 | i++; 180 | } 181 | if (jasmine.CATCH_EXCEPTIONS) { 182 | params.push("catch=false"); 183 | } 184 | 185 | return params.join("&"); 186 | } 187 | 188 | function setExceptionHandling() { 189 | var chxCatch = document.getElementById('no_try_catch'); 190 | 191 | if (noTryCatch()) { 192 | chxCatch.setAttribute('checked', true); 193 | jasmine.CATCH_EXCEPTIONS = false; 194 | } 195 | chxCatch.onclick = function() { 196 | window.location.search = searchWithCatch(); 197 | }; 198 | } 199 | }; 200 | jasmine.HtmlReporter.parameters = function(doc) { 201 | var paramStr = doc.location.search.substring(1); 202 | var params = []; 203 | 204 | if (paramStr.length > 0) { 205 | params = paramStr.split('&'); 206 | } 207 | return params; 208 | } 209 | jasmine.HtmlReporter.sectionLink = function(sectionName) { 210 | var link = '?'; 211 | var params = []; 212 | 213 | if (sectionName) { 214 | params.push('spec=' + encodeURIComponent(sectionName)); 215 | } 216 | if (!jasmine.CATCH_EXCEPTIONS) { 217 | params.push("catch=false"); 218 | } 219 | if (params.length > 0) { 220 | link += params.join("&"); 221 | } 222 | 223 | return link; 224 | }; 225 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter); 226 | jasmine.HtmlReporter.ReporterView = function(dom) { 227 | this.startedAt = new Date(); 228 | this.runningSpecCount = 0; 229 | this.completeSpecCount = 0; 230 | this.passedCount = 0; 231 | this.failedCount = 0; 232 | this.skippedCount = 0; 233 | 234 | this.createResultsMenu = function() { 235 | this.resultsMenu = this.createDom('span', {className: 'resultsMenu bar'}, 236 | this.summaryMenuItem = this.createDom('a', {className: 'summaryMenuItem', href: "#"}, '0 specs'), 237 | ' | ', 238 | this.detailsMenuItem = this.createDom('a', {className: 'detailsMenuItem', href: "#"}, '0 failing')); 239 | 240 | this.summaryMenuItem.onclick = function() { 241 | dom.reporter.className = dom.reporter.className.replace(/ showDetails/g, ''); 242 | }; 243 | 244 | this.detailsMenuItem.onclick = function() { 245 | showDetails(); 246 | }; 247 | }; 248 | 249 | this.addSpecs = function(specs, specFilter) { 250 | this.totalSpecCount = specs.length; 251 | 252 | this.views = { 253 | specs: {}, 254 | suites: {} 255 | }; 256 | 257 | for (var i = 0; i < specs.length; i++) { 258 | var spec = specs[i]; 259 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom, this.views); 260 | if (specFilter(spec)) { 261 | this.runningSpecCount++; 262 | } 263 | } 264 | }; 265 | 266 | this.specComplete = function(spec) { 267 | this.completeSpecCount++; 268 | 269 | if (isUndefined(this.views.specs[spec.id])) { 270 | this.views.specs[spec.id] = new jasmine.HtmlReporter.SpecView(spec, dom); 271 | } 272 | 273 | var specView = this.views.specs[spec.id]; 274 | 275 | switch (specView.status()) { 276 | case 'passed': 277 | this.passedCount++; 278 | break; 279 | 280 | case 'failed': 281 | this.failedCount++; 282 | break; 283 | 284 | case 'skipped': 285 | this.skippedCount++; 286 | break; 287 | } 288 | 289 | specView.refresh(); 290 | this.refresh(); 291 | }; 292 | 293 | this.suiteComplete = function(suite) { 294 | var suiteView = this.views.suites[suite.id]; 295 | if (isUndefined(suiteView)) { 296 | return; 297 | } 298 | suiteView.refresh(); 299 | }; 300 | 301 | this.refresh = function() { 302 | 303 | if (isUndefined(this.resultsMenu)) { 304 | this.createResultsMenu(); 305 | } 306 | 307 | // currently running UI 308 | if (isUndefined(this.runningAlert)) { 309 | this.runningAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "runningAlert bar" }); 310 | dom.alert.appendChild(this.runningAlert); 311 | } 312 | this.runningAlert.innerHTML = "Running " + this.completeSpecCount + " of " + specPluralizedFor(this.totalSpecCount); 313 | 314 | // skipped specs UI 315 | if (isUndefined(this.skippedAlert)) { 316 | this.skippedAlert = this.createDom('a', { href: jasmine.HtmlReporter.sectionLink(), className: "skippedAlert bar" }); 317 | } 318 | 319 | this.skippedAlert.innerHTML = "Skipping " + this.skippedCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 320 | 321 | if (this.skippedCount === 1 && isDefined(dom.alert)) { 322 | dom.alert.appendChild(this.skippedAlert); 323 | } 324 | 325 | // passing specs UI 326 | if (isUndefined(this.passedAlert)) { 327 | this.passedAlert = this.createDom('span', { href: jasmine.HtmlReporter.sectionLink(), className: "passingAlert bar" }); 328 | } 329 | this.passedAlert.innerHTML = "Passing " + specPluralizedFor(this.passedCount); 330 | 331 | // failing specs UI 332 | if (isUndefined(this.failedAlert)) { 333 | this.failedAlert = this.createDom('span', {href: "?", className: "failingAlert bar"}); 334 | } 335 | this.failedAlert.innerHTML = "Failing " + specPluralizedFor(this.failedCount); 336 | 337 | if (this.failedCount === 1 && isDefined(dom.alert)) { 338 | dom.alert.appendChild(this.failedAlert); 339 | dom.alert.appendChild(this.resultsMenu); 340 | } 341 | 342 | // summary info 343 | this.summaryMenuItem.innerHTML = "" + specPluralizedFor(this.runningSpecCount); 344 | this.detailsMenuItem.innerHTML = "" + this.failedCount + " failing"; 345 | }; 346 | 347 | this.complete = function() { 348 | dom.alert.removeChild(this.runningAlert); 349 | 350 | this.skippedAlert.innerHTML = "Ran " + this.runningSpecCount + " of " + specPluralizedFor(this.totalSpecCount) + " - run all"; 351 | 352 | if (this.failedCount === 0) { 353 | dom.alert.appendChild(this.createDom('span', {className: 'passingAlert bar'}, "Passing " + specPluralizedFor(this.passedCount))); 354 | } else { 355 | showDetails(); 356 | } 357 | 358 | dom.banner.appendChild(this.createDom('span', {className: 'duration'}, "finished in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s")); 359 | }; 360 | 361 | return this; 362 | 363 | function showDetails() { 364 | if (dom.reporter.className.search(/showDetails/) === -1) { 365 | dom.reporter.className += " showDetails"; 366 | } 367 | } 368 | 369 | function isUndefined(obj) { 370 | return typeof obj === 'undefined'; 371 | } 372 | 373 | function isDefined(obj) { 374 | return !isUndefined(obj); 375 | } 376 | 377 | function specPluralizedFor(count) { 378 | var str = count + " spec"; 379 | if (count > 1) { 380 | str += "s" 381 | } 382 | return str; 383 | } 384 | 385 | }; 386 | 387 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.ReporterView); 388 | 389 | 390 | jasmine.HtmlReporter.SpecView = function(spec, dom, views) { 391 | this.spec = spec; 392 | this.dom = dom; 393 | this.views = views; 394 | 395 | this.symbol = this.createDom('li', { className: 'pending' }); 396 | this.dom.symbolSummary.appendChild(this.symbol); 397 | 398 | this.summary = this.createDom('div', { className: 'specSummary' }, 399 | this.createDom('a', { 400 | className: 'description', 401 | href: jasmine.HtmlReporter.sectionLink(this.spec.getFullName()), 402 | title: this.spec.getFullName() 403 | }, this.spec.description) 404 | ); 405 | 406 | this.detail = this.createDom('div', { className: 'specDetail' }, 407 | this.createDom('a', { 408 | className: 'description', 409 | href: '?spec=' + encodeURIComponent(this.spec.getFullName()), 410 | title: this.spec.getFullName() 411 | }, this.spec.getFullName()) 412 | ); 413 | }; 414 | 415 | jasmine.HtmlReporter.SpecView.prototype.status = function() { 416 | return this.getSpecStatus(this.spec); 417 | }; 418 | 419 | jasmine.HtmlReporter.SpecView.prototype.refresh = function() { 420 | this.symbol.className = this.status(); 421 | 422 | switch (this.status()) { 423 | case 'skipped': 424 | break; 425 | 426 | case 'passed': 427 | this.appendSummaryToSuiteDiv(); 428 | break; 429 | 430 | case 'failed': 431 | this.appendSummaryToSuiteDiv(); 432 | this.appendFailureDetail(); 433 | break; 434 | } 435 | }; 436 | 437 | jasmine.HtmlReporter.SpecView.prototype.appendSummaryToSuiteDiv = function() { 438 | this.summary.className += ' ' + this.status(); 439 | this.appendToSummary(this.spec, this.summary); 440 | }; 441 | 442 | jasmine.HtmlReporter.SpecView.prototype.appendFailureDetail = function() { 443 | this.detail.className += ' ' + this.status(); 444 | 445 | var resultItems = this.spec.results().getItems(); 446 | var messagesDiv = this.createDom('div', { className: 'messages' }); 447 | 448 | for (var i = 0; i < resultItems.length; i++) { 449 | var result = resultItems[i]; 450 | 451 | if (result.type == 'log') { 452 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 453 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 454 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 455 | 456 | if (result.trace.stack) { 457 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 458 | } 459 | } 460 | } 461 | 462 | if (messagesDiv.childNodes.length > 0) { 463 | this.detail.appendChild(messagesDiv); 464 | this.dom.details.appendChild(this.detail); 465 | } 466 | }; 467 | 468 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SpecView);jasmine.HtmlReporter.SuiteView = function(suite, dom, views) { 469 | this.suite = suite; 470 | this.dom = dom; 471 | this.views = views; 472 | 473 | this.element = this.createDom('div', { className: 'suite' }, 474 | this.createDom('a', { className: 'description', href: jasmine.HtmlReporter.sectionLink(this.suite.getFullName()) }, this.suite.description) 475 | ); 476 | 477 | this.appendToSummary(this.suite, this.element); 478 | }; 479 | 480 | jasmine.HtmlReporter.SuiteView.prototype.status = function() { 481 | return this.getSpecStatus(this.suite); 482 | }; 483 | 484 | jasmine.HtmlReporter.SuiteView.prototype.refresh = function() { 485 | this.element.className += " " + this.status(); 486 | }; 487 | 488 | jasmine.HtmlReporterHelpers.addHelpers(jasmine.HtmlReporter.SuiteView); 489 | 490 | /* @deprecated Use jasmine.HtmlReporter instead 491 | */ 492 | jasmine.TrivialReporter = function(doc) { 493 | this.document = doc || document; 494 | this.suiteDivs = {}; 495 | this.logRunningSpecs = false; 496 | }; 497 | 498 | jasmine.TrivialReporter.prototype.createDom = function(type, attrs, childrenVarArgs) { 499 | var el = document.createElement(type); 500 | 501 | for (var i = 2; i < arguments.length; i++) { 502 | var child = arguments[i]; 503 | 504 | if (typeof child === 'string') { 505 | el.appendChild(document.createTextNode(child)); 506 | } else { 507 | if (child) { el.appendChild(child); } 508 | } 509 | } 510 | 511 | for (var attr in attrs) { 512 | if (attr == "className") { 513 | el[attr] = attrs[attr]; 514 | } else { 515 | el.setAttribute(attr, attrs[attr]); 516 | } 517 | } 518 | 519 | return el; 520 | }; 521 | 522 | jasmine.TrivialReporter.prototype.reportRunnerStarting = function(runner) { 523 | var showPassed, showSkipped; 524 | 525 | this.outerDiv = this.createDom('div', { id: 'TrivialReporter', className: 'jasmine_reporter' }, 526 | this.createDom('div', { className: 'banner' }, 527 | this.createDom('div', { className: 'logo' }, 528 | this.createDom('span', { className: 'title' }, "Jasmine"), 529 | this.createDom('span', { className: 'version' }, runner.env.versionString())), 530 | this.createDom('div', { className: 'options' }, 531 | "Show ", 532 | showPassed = this.createDom('input', { id: "__jasmine_TrivialReporter_showPassed__", type: 'checkbox' }), 533 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showPassed__" }, " passed "), 534 | showSkipped = this.createDom('input', { id: "__jasmine_TrivialReporter_showSkipped__", type: 'checkbox' }), 535 | this.createDom('label', { "for": "__jasmine_TrivialReporter_showSkipped__" }, " skipped") 536 | ) 537 | ), 538 | 539 | this.runnerDiv = this.createDom('div', { className: 'runner running' }, 540 | this.createDom('a', { className: 'run_spec', href: '?' }, "run all"), 541 | this.runnerMessageSpan = this.createDom('span', {}, "Running..."), 542 | this.finishedAtSpan = this.createDom('span', { className: 'finished-at' }, "")) 543 | ); 544 | 545 | this.document.body.appendChild(this.outerDiv); 546 | 547 | var suites = runner.suites(); 548 | for (var i = 0; i < suites.length; i++) { 549 | var suite = suites[i]; 550 | var suiteDiv = this.createDom('div', { className: 'suite' }, 551 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, "run"), 552 | this.createDom('a', { className: 'description', href: '?spec=' + encodeURIComponent(suite.getFullName()) }, suite.description)); 553 | this.suiteDivs[suite.id] = suiteDiv; 554 | var parentDiv = this.outerDiv; 555 | if (suite.parentSuite) { 556 | parentDiv = this.suiteDivs[suite.parentSuite.id]; 557 | } 558 | parentDiv.appendChild(suiteDiv); 559 | } 560 | 561 | this.startedAt = new Date(); 562 | 563 | var self = this; 564 | showPassed.onclick = function(evt) { 565 | if (showPassed.checked) { 566 | self.outerDiv.className += ' show-passed'; 567 | } else { 568 | self.outerDiv.className = self.outerDiv.className.replace(/ show-passed/, ''); 569 | } 570 | }; 571 | 572 | showSkipped.onclick = function(evt) { 573 | if (showSkipped.checked) { 574 | self.outerDiv.className += ' show-skipped'; 575 | } else { 576 | self.outerDiv.className = self.outerDiv.className.replace(/ show-skipped/, ''); 577 | } 578 | }; 579 | }; 580 | 581 | jasmine.TrivialReporter.prototype.reportRunnerResults = function(runner) { 582 | var results = runner.results(); 583 | var className = (results.failedCount > 0) ? "runner failed" : "runner passed"; 584 | this.runnerDiv.setAttribute("class", className); 585 | //do it twice for IE 586 | this.runnerDiv.setAttribute("className", className); 587 | var specs = runner.specs(); 588 | var specCount = 0; 589 | for (var i = 0; i < specs.length; i++) { 590 | if (this.specFilter(specs[i])) { 591 | specCount++; 592 | } 593 | } 594 | var message = "" + specCount + " spec" + (specCount == 1 ? "" : "s" ) + ", " + results.failedCount + " failure" + ((results.failedCount == 1) ? "" : "s"); 595 | message += " in " + ((new Date().getTime() - this.startedAt.getTime()) / 1000) + "s"; 596 | this.runnerMessageSpan.replaceChild(this.createDom('a', { className: 'description', href: '?'}, message), this.runnerMessageSpan.firstChild); 597 | 598 | this.finishedAtSpan.appendChild(document.createTextNode("Finished at " + new Date().toString())); 599 | }; 600 | 601 | jasmine.TrivialReporter.prototype.reportSuiteResults = function(suite) { 602 | var results = suite.results(); 603 | var status = results.passed() ? 'passed' : 'failed'; 604 | if (results.totalCount === 0) { // todo: change this to check results.skipped 605 | status = 'skipped'; 606 | } 607 | this.suiteDivs[suite.id].className += " " + status; 608 | }; 609 | 610 | jasmine.TrivialReporter.prototype.reportSpecStarting = function(spec) { 611 | if (this.logRunningSpecs) { 612 | this.log('>> Jasmine Running ' + spec.suite.description + ' ' + spec.description + '...'); 613 | } 614 | }; 615 | 616 | jasmine.TrivialReporter.prototype.reportSpecResults = function(spec) { 617 | var results = spec.results(); 618 | var status = results.passed() ? 'passed' : 'failed'; 619 | if (results.skipped) { 620 | status = 'skipped'; 621 | } 622 | var specDiv = this.createDom('div', { className: 'spec ' + status }, 623 | this.createDom('a', { className: 'run_spec', href: '?spec=' + encodeURIComponent(spec.getFullName()) }, "run"), 624 | this.createDom('a', { 625 | className: 'description', 626 | href: '?spec=' + encodeURIComponent(spec.getFullName()), 627 | title: spec.getFullName() 628 | }, spec.description)); 629 | 630 | 631 | var resultItems = results.getItems(); 632 | var messagesDiv = this.createDom('div', { className: 'messages' }); 633 | for (var i = 0; i < resultItems.length; i++) { 634 | var result = resultItems[i]; 635 | 636 | if (result.type == 'log') { 637 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage log'}, result.toString())); 638 | } else if (result.type == 'expect' && result.passed && !result.passed()) { 639 | messagesDiv.appendChild(this.createDom('div', {className: 'resultMessage fail'}, result.message)); 640 | 641 | if (result.trace.stack) { 642 | messagesDiv.appendChild(this.createDom('div', {className: 'stackTrace'}, result.trace.stack)); 643 | } 644 | } 645 | } 646 | 647 | if (messagesDiv.childNodes.length > 0) { 648 | specDiv.appendChild(messagesDiv); 649 | } 650 | 651 | this.suiteDivs[spec.suite.id].appendChild(specDiv); 652 | }; 653 | 654 | jasmine.TrivialReporter.prototype.log = function() { 655 | var console = jasmine.getGlobal().console; 656 | if (console && console.log) { 657 | if (console.log.apply) { 658 | console.log.apply(console, arguments); 659 | } else { 660 | console.log(arguments); // ie fix: console.log.apply doesn't exist on ie 661 | } 662 | } 663 | }; 664 | 665 | jasmine.TrivialReporter.prototype.getLocation = function() { 666 | return this.document.location; 667 | }; 668 | 669 | jasmine.TrivialReporter.prototype.specFilter = function(spec) { 670 | var paramMap = {}; 671 | var params = this.getLocation().search.substring(1).split('&'); 672 | for (var i = 0; i < params.length; i++) { 673 | var p = params[i].split('='); 674 | paramMap[decodeURIComponent(p[0])] = decodeURIComponent(p[1]); 675 | } 676 | 677 | if (!paramMap.spec) { 678 | return true; 679 | } 680 | return spec.getFullName().indexOf(paramMap.spec) === 0; 681 | }; 682 | -------------------------------------------------------------------------------- /src/lib/jasmine/jasmine.css: -------------------------------------------------------------------------------- 1 | body { background-color: #eeeeee; padding: 0; margin: 5px; overflow-y: scroll; } 2 | 3 | #HTMLReporter { font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; } 4 | #HTMLReporter a { text-decoration: none; } 5 | #HTMLReporter a:hover { text-decoration: underline; } 6 | #HTMLReporter p, #HTMLReporter h1, #HTMLReporter h2, #HTMLReporter h3, #HTMLReporter h4, #HTMLReporter h5, #HTMLReporter h6 { margin: 0; line-height: 14px; } 7 | #HTMLReporter .banner, #HTMLReporter .symbolSummary, #HTMLReporter .summary, #HTMLReporter .resultMessage, #HTMLReporter .specDetail .description, #HTMLReporter .alert .bar, #HTMLReporter .stackTrace { padding-left: 9px; padding-right: 9px; } 8 | #HTMLReporter #jasmine_content { position: fixed; right: 100%; } 9 | #HTMLReporter .version { color: #aaaaaa; } 10 | #HTMLReporter .banner { margin-top: 14px; } 11 | #HTMLReporter .duration { color: #aaaaaa; float: right; } 12 | #HTMLReporter .symbolSummary { overflow: hidden; *zoom: 1; margin: 14px 0; } 13 | #HTMLReporter .symbolSummary li { display: block; float: left; height: 7px; width: 14px; margin-bottom: 7px; font-size: 16px; } 14 | #HTMLReporter .symbolSummary li.passed { font-size: 14px; } 15 | #HTMLReporter .symbolSummary li.passed:before { color: #5e7d00; content: "\02022"; } 16 | #HTMLReporter .symbolSummary li.failed { line-height: 9px; } 17 | #HTMLReporter .symbolSummary li.failed:before { color: #b03911; content: "x"; font-weight: bold; margin-left: -1px; } 18 | #HTMLReporter .symbolSummary li.skipped { font-size: 14px; } 19 | #HTMLReporter .symbolSummary li.skipped:before { color: #bababa; content: "\02022"; } 20 | #HTMLReporter .symbolSummary li.pending { line-height: 11px; } 21 | #HTMLReporter .symbolSummary li.pending:before { color: #aaaaaa; content: "-"; } 22 | #HTMLReporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; } 23 | #HTMLReporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } 24 | #HTMLReporter .runningAlert { background-color: #666666; } 25 | #HTMLReporter .skippedAlert { background-color: #aaaaaa; } 26 | #HTMLReporter .skippedAlert:first-child { background-color: #333333; } 27 | #HTMLReporter .skippedAlert:hover { text-decoration: none; color: white; text-decoration: underline; } 28 | #HTMLReporter .passingAlert { background-color: #a6b779; } 29 | #HTMLReporter .passingAlert:first-child { background-color: #5e7d00; } 30 | #HTMLReporter .failingAlert { background-color: #cf867e; } 31 | #HTMLReporter .failingAlert:first-child { background-color: #b03911; } 32 | #HTMLReporter .results { margin-top: 14px; } 33 | #HTMLReporter #details { display: none; } 34 | #HTMLReporter .resultsMenu, #HTMLReporter .resultsMenu a { background-color: #fff; color: #333333; } 35 | #HTMLReporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; } 36 | #HTMLReporter.showDetails .summaryMenuItem:hover { text-decoration: underline; } 37 | #HTMLReporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; } 38 | #HTMLReporter.showDetails .summary { display: none; } 39 | #HTMLReporter.showDetails #details { display: block; } 40 | #HTMLReporter .summaryMenuItem { font-weight: bold; text-decoration: underline; } 41 | #HTMLReporter .summary { margin-top: 14px; } 42 | #HTMLReporter .summary .suite .suite, #HTMLReporter .summary .specSummary { margin-left: 14px; } 43 | #HTMLReporter .summary .specSummary.passed a { color: #5e7d00; } 44 | #HTMLReporter .summary .specSummary.failed a { color: #b03911; } 45 | #HTMLReporter .description + .suite { margin-top: 0; } 46 | #HTMLReporter .suite { margin-top: 14px; } 47 | #HTMLReporter .suite a { color: #333333; } 48 | #HTMLReporter #details .specDetail { margin-bottom: 28px; } 49 | #HTMLReporter #details .specDetail .description { display: block; color: white; background-color: #b03911; } 50 | #HTMLReporter .resultMessage { padding-top: 14px; color: #333333; } 51 | #HTMLReporter .resultMessage span.result { display: block; } 52 | #HTMLReporter .stackTrace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; } 53 | 54 | #TrivialReporter { padding: 8px 13px; position: absolute; top: 0; bottom: 0; left: 0; right: 0; overflow-y: scroll; background-color: white; font-family: "Helvetica Neue Light", "Lucida Grande", "Calibri", "Arial", sans-serif; /*.resultMessage {*/ /*white-space: pre;*/ /*}*/ } 55 | #TrivialReporter a:visited, #TrivialReporter a { color: #303; } 56 | #TrivialReporter a:hover, #TrivialReporter a:active { color: blue; } 57 | #TrivialReporter .run_spec { float: right; padding-right: 5px; font-size: .8em; text-decoration: none; } 58 | #TrivialReporter .banner { color: #303; background-color: #fef; padding: 5px; } 59 | #TrivialReporter .logo { float: left; font-size: 1.1em; padding-left: 5px; } 60 | #TrivialReporter .logo .version { font-size: .6em; padding-left: 1em; } 61 | #TrivialReporter .runner.running { background-color: yellow; } 62 | #TrivialReporter .options { text-align: right; font-size: .8em; } 63 | #TrivialReporter .suite { border: 1px outset gray; margin: 5px 0; padding-left: 1em; } 64 | #TrivialReporter .suite .suite { margin: 5px; } 65 | #TrivialReporter .suite.passed { background-color: #dfd; } 66 | #TrivialReporter .suite.failed { background-color: #fdd; } 67 | #TrivialReporter .spec { margin: 5px; padding-left: 1em; clear: both; } 68 | #TrivialReporter .spec.failed, #TrivialReporter .spec.passed, #TrivialReporter .spec.skipped { padding-bottom: 5px; border: 1px solid gray; } 69 | #TrivialReporter .spec.failed { background-color: #fbb; border-color: red; } 70 | #TrivialReporter .spec.passed { background-color: #bfb; border-color: green; } 71 | #TrivialReporter .spec.skipped { background-color: #bbb; } 72 | #TrivialReporter .messages { border-left: 1px dashed gray; padding-left: 1em; padding-right: 1em; } 73 | #TrivialReporter .passed { background-color: #cfc; display: none; } 74 | #TrivialReporter .failed { background-color: #fbb; } 75 | #TrivialReporter .skipped { color: #777; background-color: #eee; display: none; } 76 | #TrivialReporter .resultMessage span.result { display: block; line-height: 2em; color: black; } 77 | #TrivialReporter .resultMessage .mismatch { color: black; } 78 | #TrivialReporter .stackTrace { white-space: pre; font-size: .8em; margin-left: 10px; max-height: 5em; overflow: auto; border: 1px inset red; padding: 1em; background: #eef; } 79 | #TrivialReporter .finished-at { padding-left: 1em; font-size: .6em; } 80 | #TrivialReporter.show-passed .passed, #TrivialReporter.show-skipped .skipped { display: block; } 81 | #TrivialReporter #jasmine_content { position: fixed; right: 100%; } 82 | #TrivialReporter .runner { border: 1px solid gray; display: block; margin: 5px 0; padding: 2px 0 2px 10px; } 83 | -------------------------------------------------------------------------------- /src/lib/jasmine/jasmine_favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/martindale/fast-json-patch/acfbea68d19c6b77f58006940191e462888d64ea/src/lib/jasmine/jasmine_favicon.png -------------------------------------------------------------------------------- /src/lib/jslitmus.js: -------------------------------------------------------------------------------- 1 | // JSLitmus.js 2 | // 3 | // Copyright (c) 2010, Robert Kieffer, http://broofa.com 4 | // Available under MIT license (http://en.wikipedia.org/wiki/MIT_License) 5 | 6 | (function() { 7 | // Private methods and state 8 | 9 | // Get platform info but don't go crazy trying to recognize everything 10 | // that's out there. This is just for the major platforms and OSes. 11 | var platform = 'unknown platform', ua = navigator.userAgent; 12 | 13 | // Detect OS 14 | var oses = ['Windows','iPhone OS','(Intel |PPC )?Mac OS X','Linux'].join('|'); 15 | var pOS = new RegExp('((' + oses + ') [^ \);]*)').test(ua) ? RegExp.$1 : null; 16 | if (!pOS) pOS = new RegExp('((' + oses + ')[^ \);]*)').test(ua) ? RegExp.$1 : null; 17 | 18 | // Detect browser 19 | var pName = /(Chrome|MSIE|Safari|Opera|Firefox)/.test(ua) ? RegExp.$1 : null; 20 | 21 | // Detect version 22 | var vre = new RegExp('(Version|' + pName + ')[ \/]([^ ;]*)'); 23 | var pVersion = (pName && vre.test(ua)) ? RegExp.$2 : null; 24 | var platform = (pOS && pName && pVersion) ? pName + ' ' + pVersion + ' on ' + pOS : 'unknown platform'; 25 | 26 | /** 27 | * A smattering of methods that are needed to implement the JSLitmus testbed. 28 | */ 29 | var jsl = { 30 | /** 31 | * Enhanced version of escape() 32 | */ 33 | escape: function(s) { 34 | s = s.replace(/,/g, '\\,'); 35 | s = escape(s); 36 | s = s.replace(/\+/g, '%2b'); 37 | s = s.replace(/ /g, '+'); 38 | return s; 39 | }, 40 | 41 | /** 42 | * Get an element by ID. 43 | */ 44 | $: function(id) { 45 | return document.getElementById(id); 46 | }, 47 | 48 | /** 49 | * Null function 50 | */ 51 | F: function() {}, 52 | 53 | /** 54 | * Set the status shown in the UI 55 | */ 56 | status: function(msg) { 57 | var el = jsl.$('jsl_status'); 58 | if (el) el.innerHTML = msg || ''; 59 | }, 60 | 61 | /** 62 | * Convert a number to an abbreviated string like, "15K" or "10M" 63 | */ 64 | toLabel: function(n) { 65 | if (n == Infinity) { 66 | return 'Infinity'; 67 | } else if (n > 1e9) { 68 | n = Math.round(n/1e8); 69 | return n/10 + 'B'; 70 | } else if (n > 1e6) { 71 | n = Math.round(n/1e5); 72 | return n/10 + 'M'; 73 | } else if (n > 1e3) { 74 | n = Math.round(n/1e2); 75 | return n/10 + 'K'; 76 | } 77 | return n; 78 | }, 79 | 80 | /** 81 | * Copy properties from src to dst 82 | */ 83 | extend: function(dst, src) { 84 | for (var k in src) dst[k] = src[k]; return dst; 85 | }, 86 | 87 | /** 88 | * Like Array.join(), but for the key-value pairs in an object 89 | */ 90 | join: function(o, delimit1, delimit2) { 91 | if (o.join) return o.join(delimit1); // If it's an array 92 | var pairs = []; 93 | for (var k in o) pairs.push(k + delimit1 + o[k]); 94 | return pairs.join(delimit2); 95 | }, 96 | 97 | /** 98 | * Array#indexOf isn't supported in IE, so we use this as a cross-browser solution 99 | */ 100 | indexOf: function(arr, o) { 101 | if (arr.indexOf) return arr.indexOf(o); 102 | for (var i = 0; i < this.length; i++) if (arr[i] === o) return i; 103 | return -1; 104 | } 105 | }; 106 | 107 | /** 108 | * Test manages a single test (created with 109 | * JSLitmus.test()) 110 | * 111 | * @private 112 | */ 113 | var Test = function (name, f) { 114 | if (!f) throw new Error('Undefined test function'); 115 | if (!/function[^\(]*\(([^,\)]*)/.test(f.toString())) { 116 | throw new Error('"' + name + '" test: Test is not a valid Function object'); 117 | } 118 | this.loopArg = RegExp.$1; 119 | this.name = name; 120 | this.f = f; 121 | }; 122 | 123 | jsl.extend(Test, /** @lends Test */ { 124 | /** Calibration tests for establishing iteration loop overhead */ 125 | CALIBRATIONS: [ 126 | new Test('calibrating loop', function(count) {while (count--);}), 127 | new Test('calibrating function', jsl.F) 128 | ], 129 | 130 | /** 131 | * Run calibration tests. Returns true if calibrations are not yet 132 | * complete (in which case calling code should run the tests yet again). 133 | * onCalibrated - Callback to invoke when calibrations have finished 134 | */ 135 | calibrate: function(onCalibrated) { 136 | for (var i = 0; i < Test.CALIBRATIONS.length; i++) { 137 | var cal = Test.CALIBRATIONS[i]; 138 | if (cal.running) return true; 139 | if (!cal.count) { 140 | cal.isCalibration = true; 141 | cal.onStop = onCalibrated; 142 | //cal.MIN_TIME = .1; // Do calibrations quickly 143 | cal.run(2e4); 144 | return true; 145 | } 146 | } 147 | return false; 148 | } 149 | }); 150 | 151 | jsl.extend(Test.prototype, {/** @lends Test.prototype */ 152 | /** Initial number of iterations */ 153 | INIT_COUNT: 10, 154 | /** Max iterations allowed (i.e. used to detect bad looping functions) */ 155 | MAX_COUNT: 1e9, 156 | /** Minimum time a test should take to get valid results (secs) */ 157 | MIN_TIME: .5, 158 | 159 | /** Callback invoked when test state changes */ 160 | onChange: jsl.F, 161 | 162 | /** Callback invoked when test is finished */ 163 | onStop: jsl.F, 164 | 165 | /** 166 | * Reset test state 167 | */ 168 | reset: function() { 169 | delete this.count; 170 | delete this.time; 171 | delete this.running; 172 | delete this.error; 173 | }, 174 | 175 | /** 176 | * Run the test (in a timeout). We use a timeout to make sure the browser 177 | * has a chance to finish rendering any UI changes we've made, like 178 | * updating the status message. 179 | */ 180 | run: function(count) { 181 | count = count || this.INIT_COUNT; 182 | jsl.status(this.name + ' x ' + count); 183 | this.running = true; 184 | var me = this; 185 | setTimeout(function() {me._run(count);}, 200); 186 | }, 187 | 188 | /** 189 | * The nuts and bolts code that actually runs a test 190 | */ 191 | _run: function(count) { 192 | var me = this; 193 | 194 | // Make sure calibration tests have run 195 | if (!me.isCalibration && Test.calibrate(function() {me.run(count);})) return; 196 | this.error = null; 197 | 198 | try { 199 | var start, f = this.f, now, i = count; 200 | 201 | // Start the timer 202 | start = new Date(); 203 | 204 | // Now for the money shot. If this is a looping function ... 205 | if (this.loopArg) { 206 | // ... let it do the iteration itself 207 | f(count); 208 | } else { 209 | // ... otherwise do the iteration for it 210 | while (i--) f(); 211 | } 212 | 213 | // Get time test took (in secs) 214 | this.time = Math.max(1,new Date() - start)/1000; 215 | 216 | // Store iteration count and per-operation time taken 217 | this.count = count; 218 | this.period = this.time/count; 219 | 220 | // Do we need to do another run? 221 | this.running = this.time <= this.MIN_TIME; 222 | 223 | // ... if so, compute how many times we should iterate 224 | if (this.running) { 225 | // Bump the count to the nearest power of 2 226 | var x = this.MIN_TIME/this.time; 227 | var pow = Math.pow(2, Math.max(1, Math.ceil(Math.log(x)/Math.log(2)))); 228 | count *= pow; 229 | if (count > this.MAX_COUNT) { 230 | throw new Error('Max count exceeded. If this test uses a looping function, make sure the iteration loop is working properly.'); 231 | } 232 | } 233 | } catch (e) { 234 | // Exceptions are caught and displayed in the test UI 235 | this.reset(); 236 | this.error = e; 237 | } 238 | 239 | // Figure out what to do next 240 | if (this.running) { 241 | me.run(count); 242 | } else { 243 | jsl.status(''); 244 | me.onStop(me); 245 | } 246 | 247 | // Finish up 248 | this.onChange(this); 249 | }, 250 | 251 | /** 252 | * Get the number of operations per second for this test. 253 | * 254 | * @param normalize if true, iteration loop overhead taken into account 255 | */ 256 | getHz: function(/**Boolean*/ normalize) { 257 | var p = this.period; 258 | 259 | // Adjust period based on the calibration test time 260 | if (normalize && !this.isCalibration) { 261 | var cal = Test.CALIBRATIONS[this.loopArg ? 0 : 1]; 262 | 263 | // If the period is within 20% of the calibration time, then zero the 264 | // it out 265 | p = p < cal.period*1.2 ? 0 : p - cal.period; 266 | } 267 | 268 | return Math.round(1/p); 269 | }, 270 | 271 | /** 272 | * Get a friendly string describing the test 273 | */ 274 | toString: function() { 275 | return this.name + ' - ' + this.time/this.count + ' secs'; 276 | } 277 | }); 278 | 279 | // CSS we need for the UI 280 | var STYLESHEET = ''; 360 | 361 | // HTML markup for the UI 362 | var MARKUP = '
\ 363 | \ 364 | \ 365 |
\ 366 |
\ 367 | Normalize results \ 368 | \ 369 | \ 370 | \ 371 | \ 372 | \ 373 | \ 374 | \ 375 | \ 376 | \ 377 | \ 378 | \ 379 |
' + platform + '
TestOps/sec
\ 380 |
\ 381 | \ 386 | Powered by JSLitmus \ 387 |
'; 388 | 389 | /** 390 | * The public API for creating and running tests 391 | */ 392 | window.JSLitmus = { 393 | /** The list of all tests that have been registered with JSLitmus.test */ 394 | _tests: [], 395 | /** The queue of tests that need to be run */ 396 | _queue: [], 397 | 398 | /** 399 | * The parsed query parameters the current page URL. This is provided as a 400 | * convenience for test functions - it's not used by JSLitmus proper 401 | */ 402 | params: {}, 403 | 404 | /** 405 | * Initialize 406 | */ 407 | _init: function() { 408 | // Parse query params into JSLitmus.params[] hash 409 | var match = (location + '').match(/([^?#]*)(#.*)?$/); 410 | if (match) { 411 | var pairs = match[1].split('&'); 412 | for (var i = 0; i < pairs.length; i++) { 413 | var pair = pairs[i].split('='); 414 | if (pair.length > 1) { 415 | var key = pair.shift(); 416 | var value = pair.length > 1 ? pair.join('=') : pair[0]; 417 | this.params[key] = value; 418 | } 419 | } 420 | } 421 | 422 | // Write out the stylesheet. We have to do this here because IE 423 | // doesn't honor sheets written after the document has loaded. 424 | document.write(STYLESHEET); 425 | 426 | // Setup the rest of the UI once the document is loaded 427 | if (window.addEventListener) { 428 | window.addEventListener('load', this._setup, false); 429 | } else if (document.addEventListener) { 430 | document.addEventListener('load', this._setup, false); 431 | } else if (window.attachEvent) { 432 | window.attachEvent('onload', this._setup); 433 | } 434 | 435 | return this; 436 | }, 437 | 438 | /** 439 | * Set up the UI 440 | */ 441 | _setup: function() { 442 | var el = jsl.$('jslitmus_container'); 443 | if (!el) document.body.appendChild(el = document.createElement('div')); 444 | 445 | el.innerHTML = MARKUP; 446 | 447 | // Render the UI for all our tests 448 | for (var i=0; i < JSLitmus._tests.length; i++) 449 | JSLitmus.renderTest(JSLitmus._tests[i]); 450 | }, 451 | 452 | /** 453 | * (Re)render all the test results 454 | */ 455 | renderAll: function() { 456 | for (var i = 0; i < JSLitmus._tests.length; i++) 457 | JSLitmus.renderTest(JSLitmus._tests[i]); 458 | JSLitmus.renderChart(); 459 | }, 460 | 461 | /** 462 | * (Re)render the chart graphics 463 | */ 464 | renderChart: function() { 465 | var url = JSLitmus.chartUrl(); 466 | jsl.$('chart_link').href = url; 467 | jsl.$('chart_image').src = url; 468 | jsl.$('chart').style.display = ''; 469 | 470 | // Update the tiny URL 471 | jsl.$('tiny_url').src = 'http://tinyurl.com/api-create.php?url='+escape(url); 472 | }, 473 | 474 | /** 475 | * (Re)render the results for a specific test 476 | */ 477 | renderTest: function(test) { 478 | // Make a new row if needed 479 | if (!test._row) { 480 | var trow = jsl.$('test_row_template'); 481 | if (!trow) return; 482 | 483 | test._row = trow.cloneNode(true); 484 | test._row.style.display = ''; 485 | test._row.id = ''; 486 | test._row.onclick = function() {JSLitmus._queueTest(test);}; 487 | test._row.title = 'Run ' + test.name + ' test'; 488 | trow.parentNode.appendChild(test._row); 489 | test._row.cells[0].innerHTML = test.name; 490 | } 491 | 492 | var cell = test._row.cells[1]; 493 | var cns = [test.loopArg ? 'test_looping' : 'test_nonlooping']; 494 | 495 | if (test.error) { 496 | cns.push('test_error'); 497 | cell.innerHTML = 498 | '
' + test.error + '
' + 499 | ''; 502 | } else { 503 | if (test.running) { 504 | cns.push('test_running'); 505 | cell.innerHTML = 'running'; 506 | } else if (jsl.indexOf(JSLitmus._queue, test) >= 0) { 507 | cns.push('test_pending'); 508 | cell.innerHTML = 'pending'; 509 | } else if (test.count) { 510 | cns.push('test_done'); 511 | var hz = test.getHz(jsl.$('test_normalize').checked); 512 | cell.innerHTML = hz != Infinity ? hz : '∞'; 513 | cell.title = 'Looped ' + test.count + ' times in ' + test.time + ' seconds'; 514 | } else { 515 | cell.innerHTML = 'ready'; 516 | } 517 | } 518 | cell.className = cns.join(' '); 519 | }, 520 | 521 | /** 522 | * Create a new test 523 | */ 524 | test: function(name, f) { 525 | // Create the Test object 526 | var test = new Test(name, f); 527 | JSLitmus._tests.push(test); 528 | 529 | // Re-render if the test state changes 530 | test.onChange = JSLitmus.renderTest; 531 | 532 | // Run the next test if this one finished 533 | test.onStop = function(test) { 534 | if (JSLitmus.onTestFinish) JSLitmus.onTestFinish(test); 535 | JSLitmus.currentTest = null; 536 | JSLitmus._nextTest(); 537 | }; 538 | 539 | // Render the new test 540 | this.renderTest(test); 541 | }, 542 | 543 | /** 544 | * Add all tests to the run queue 545 | */ 546 | runAll: function(e) { 547 | e = e || window.event; 548 | var reverse = e && e.shiftKey, len = JSLitmus._tests.length; 549 | for (var i = 0; i < len; i++) { 550 | JSLitmus._queueTest(JSLitmus._tests[!reverse ? i : (len - i - 1)]); 551 | } 552 | }, 553 | 554 | /** 555 | * Remove all tests from the run queue. The current test has to finish on 556 | * it's own though 557 | */ 558 | stop: function() { 559 | while (JSLitmus._queue.length) { 560 | var test = JSLitmus._queue.shift(); 561 | JSLitmus.renderTest(test); 562 | } 563 | }, 564 | 565 | /** 566 | * Run the next test in the run queue 567 | */ 568 | _nextTest: function() { 569 | if (!JSLitmus.currentTest) { 570 | var test = JSLitmus._queue.shift(); 571 | if (test) { 572 | jsl.$('stop_button').disabled = false; 573 | JSLitmus.currentTest = test; 574 | test.run(); 575 | JSLitmus.renderTest(test); 576 | if (JSLitmus.onTestStart) JSLitmus.onTestStart(test); 577 | } else { 578 | jsl.$('stop_button').disabled = true; 579 | JSLitmus.renderChart(); 580 | } 581 | } 582 | }, 583 | 584 | /** 585 | * Add a test to the run queue 586 | */ 587 | _queueTest: function(test) { 588 | if (jsl.indexOf(JSLitmus._queue, test) >= 0) return; 589 | JSLitmus._queue.push(test); 590 | JSLitmus.renderTest(test); 591 | JSLitmus._nextTest(); 592 | }, 593 | 594 | /** 595 | * Generate a Google Chart URL that shows the data for all tests 596 | */ 597 | chartUrl: function() { 598 | var n = JSLitmus._tests.length, markers = [], data = []; 599 | var d, min = 0, max = -1e10; 600 | var normalize = jsl.$('test_normalize').checked; 601 | 602 | // Gather test data 603 | for (var i=0; i < JSLitmus._tests.length; i++) { 604 | var test = JSLitmus._tests[i]; 605 | if (test.count) { 606 | var hz = test.getHz(normalize); 607 | var v = hz != Infinity ? hz : 0; 608 | data.push(v); 609 | markers.push('t' + jsl.escape(test.name + '(' + jsl.toLabel(hz)+ ')') + ',000000,0,' + 610 | markers.length + ',10'); 611 | max = Math.max(v, max); 612 | } 613 | } 614 | if (markers.length <= 0) return null; 615 | 616 | // Build chart title 617 | var title = document.getElementsByTagName('title'); 618 | title = (title && title.length) ? title[0].innerHTML : null; 619 | var chart_title = []; 620 | if (title) chart_title.push(title); 621 | chart_title.push('Ops/sec (' + platform + ')'); 622 | 623 | // Build labels 624 | var labels = [jsl.toLabel(min), jsl.toLabel(max)]; 625 | 626 | var w = 250, bw = 15; 627 | var bs = 5; 628 | var h = markers.length*(bw + bs) + 30 + chart_title.length*20; 629 | 630 | var params = { 631 | chtt: escape(chart_title.join('|')), 632 | chts: '000000,10', 633 | cht: 'bhg', // chart type 634 | chd: 't:' + data.join(','), // data set 635 | chds: min + ',' + max, // max/min of data 636 | chxt: 'x', // label axes 637 | chxl: '0:|' + labels.join('|'), // labels 638 | chsp: '0,1', 639 | chm: markers.join('|'), // test names 640 | chbh: [bw, 0, bs].join(','), // bar widths 641 | // chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient 642 | chs: w + 'x' + h 643 | }; 644 | return 'http://chart.apis.google.com/chart?' + jsl.join(params, '=', '&'); 645 | } 646 | }; 647 | 648 | JSLitmus._init(); 649 | })(); 650 | -------------------------------------------------------------------------------- /src/patchtest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Jasmine Spec Runner 5 | 6 | 7 | 8 | 9 | 10 | 11 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/test-duplex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Jasmine Spec Runner 5 | 6 | 7 | 8 | 9 | 10 | 11 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/test-duplex.js: -------------------------------------------------------------------------------- 1 | var obj, obj2, patches; 2 | 3 | if(typeof jsonpatch === 'undefined') { 4 | if(process.env.duplex === 'yes') { //required by `jasmine-node` test runner in Node.js 5 | jsonpatch = require('./json-patch-duplex.js'); 6 | } 7 | else { 8 | jsonpatch = require('./json-patch.js'); 9 | } 10 | } 11 | 12 | describe("JSON-Patch-Duplex", function () { 13 | beforeEach(function () { 14 | this.addMatchers({ 15 | /** 16 | * 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) 17 | * @param obj 18 | * @returns {boolean} 19 | */ 20 | toEqualInJson: function (expected) { 21 | return JSON.stringify(this.actual) == JSON.stringify(expected); 22 | } 23 | }); 24 | }); 25 | 26 | describe("generate", function () { 27 | it('should generate replace', function() { 28 | obj = { firstName:"Albert", lastName:"Einstein", 29 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; 30 | 31 | var observer = jsonpatch.observe(obj); 32 | obj.firstName = "Joachim"; 33 | obj.lastName = "Wester"; 34 | obj.phoneNumbers[0].number = "123"; 35 | obj.phoneNumbers[1].number = "456"; 36 | 37 | var patches = jsonpatch.generate(observer); 38 | obj2 = { firstName:"Albert", lastName:"Einstein", 39 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; 40 | 41 | jsonpatch.apply(obj2,patches); 42 | expect(obj2).toEqual(obj); 43 | }); 44 | 45 | it('should generate replace (escaped chars)', function() { 46 | obj = { "/name/first":"Albert", "/name/last":"Einstein", 47 | "~phone~/numbers":[ {number:"12345"}, {number:"45353"} ]}; 48 | 49 | var observer = jsonpatch.observe(obj); 50 | obj['/name/first'] = "Joachim"; 51 | obj['/name/last'] = "Wester"; 52 | obj['~phone~/numbers'][0].number = "123"; 53 | obj['~phone~/numbers'][1].number = "456"; 54 | 55 | var patches = jsonpatch.generate(observer); 56 | obj2 = { "/name/first":"Albert", "/name/last":"Einstein", 57 | "~phone~/numbers":[ {number:"12345"}, {number:"45353"} ]}; 58 | 59 | jsonpatch.apply(obj2,patches); 60 | expect(obj2).toEqual(obj); 61 | }); 62 | 63 | it('should generate replace (2 observers)', function() { 64 | var person1 = {firstName: "Alexandra", lastName: "Galbreath"}; 65 | var person2 = {firstName: "Lisa", lastName: "Mendoza"}; 66 | 67 | var observer1 = jsonpatch.observe(person1); 68 | var observer2 = jsonpatch.observe(person2); 69 | 70 | person1.firstName = "Alexander"; 71 | person2.firstName = "Lucas"; 72 | 73 | var patch1 = jsonpatch.generate(observer1); 74 | var patch2 = jsonpatch.generate(observer2); 75 | 76 | expect(patch1).toEqual([{"op": "replace", "path": "/firstName", "value": "Alexander"}]); 77 | expect(patch2).toEqual([{"op": "replace", "path": "/firstName", "value": "Lucas"}]); 78 | }); 79 | 80 | it('should generate replace (double change, shallow object)', function() { 81 | obj = { firstName:"Albert", lastName:"Einstein", 82 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; 83 | 84 | var observer = jsonpatch.observe(obj); 85 | obj.firstName = "Marcin"; 86 | 87 | var patches = jsonpatch.generate(observer); 88 | expect(patches).toEqual([{op: 'replace', path: '/firstName', value: 'Marcin'}]); 89 | 90 | obj.lastName = "Warp"; 91 | patches = jsonpatch.generate(observer); //first patch should NOT be reported again here 92 | expect(patches).toEqual([{op: 'replace', path: '/lastName', value: 'Warp'}]); 93 | 94 | expect(obj).toEqual({ firstName:"Marcin", lastName:"Warp", 95 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}); //objects should be still the same 96 | }); 97 | 98 | it('should generate replace (double change, deep object)', function() { 99 | obj = { firstName:"Albert", lastName:"Einstein", 100 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; 101 | 102 | var observer = jsonpatch.observe(obj); 103 | obj.phoneNumbers[0].number = "123"; 104 | 105 | var patches = jsonpatch.generate(observer); 106 | expect(patches).toEqual([{op: 'replace', path: '/phoneNumbers/0/number', value: '123'}]); 107 | 108 | obj.phoneNumbers[1].number = "456"; 109 | patches = jsonpatch.generate(observer); //first patch should NOT be reported again here 110 | expect(patches).toEqual([{op: 'replace', path: '/phoneNumbers/1/number', value: '456'}]); 111 | 112 | expect(obj).toEqual({ firstName:"Albert", lastName:"Einstein", 113 | phoneNumbers:[ {number:"123"}, {number:"456"} ]}); //objects should be still the same 114 | }); 115 | 116 | it('should generate replace (changes in new array cell, primitive values)', function() { 117 | arr = [1]; 118 | 119 | var observer = jsonpatch.observe(arr); 120 | arr.push(2); 121 | 122 | var patches = jsonpatch.generate(observer); 123 | expect(patches).toEqual([{op: 'add', path: '/1', value: 2}]); 124 | 125 | arr[0] = 3; 126 | 127 | var patches = jsonpatch.generate(observer); 128 | expect(patches).toEqual([{op: 'replace', path: '/0', value: 3}]); 129 | 130 | arr[1] = 4; 131 | 132 | var patches = jsonpatch.generate(observer); 133 | expect(patches).toEqual([{op: 'replace', path: '/1', value: 4}]); 134 | 135 | }); 136 | 137 | 138 | it('should generate replace (changes in new array cell, complex values)', function() { 139 | arr = [ 140 | { 141 | id: 1, 142 | name: 'Ted' 143 | } 144 | ]; 145 | 146 | var observer = jsonpatch.observe(arr); 147 | arr.push({id: 2, name: 'Jerry'}); 148 | 149 | var patches = jsonpatch.generate(observer); 150 | expect(patches).toEqual([{op: 'add', path: '/1', value: {id: 2, name: 'Jerry'}}]); 151 | 152 | arr[0].id = 3; 153 | 154 | var patches = jsonpatch.generate(observer); 155 | expect(patches).toEqual([{op: 'replace', path: '/0/id', value: 3}]); 156 | 157 | arr[1].id = 4; 158 | 159 | var patches = jsonpatch.generate(observer); 160 | expect(patches).toEqual([{op: 'replace', path: '/1/id', value: 4}]); 161 | 162 | }); 163 | 164 | it('should generate add', function() { 165 | obj = { lastName:"Einstein", 166 | phoneNumbers:[ {number:"12345"} ]}; 167 | var observer = jsonpatch.observe(obj); 168 | 169 | obj.firstName = "Joachim"; 170 | obj.lastName = "Wester"; 171 | obj.phoneNumbers[0].number = "123"; 172 | obj.phoneNumbers.push( { number: "456" } ); 173 | 174 | patches = jsonpatch.generate(observer); 175 | obj2 = { lastName:"Einstein", 176 | phoneNumbers:[ {number:"12345"} ]}; 177 | 178 | jsonpatch.apply(obj2,patches); 179 | expect(obj2).toEqualInJson(obj); 180 | }); 181 | 182 | it('should generate remove', function() { 183 | obj = { lastName:"Einstein", firstName:"Albert", 184 | phoneNumbers:[ {number:"12345"}, {number:"4234"} ]}; 185 | var observer = jsonpatch.observe(obj); 186 | 187 | delete obj.firstName; 188 | obj.lastName = "Wester"; 189 | obj.phoneNumbers[0].number = "123"; 190 | obj.phoneNumbers.pop(1); 191 | 192 | patches = jsonpatch.generate(observer); 193 | obj2 = { lastName:"Einstein", firstName:"Albert", 194 | phoneNumbers:[ {number:"12345"}, {number:"4234"} ]}; 195 | 196 | jsonpatch.apply(obj2,patches); 197 | expect(obj2).toEqualInJson(obj); 198 | }); 199 | 200 | it('should generate remove (array indexes should be sorted descending)', function() { 201 | obj = { items: ["a", "b", "c"]}; 202 | var observer = jsonpatch.observe(obj); 203 | 204 | obj.items.pop(); 205 | obj.items.pop(); 206 | 207 | patches = jsonpatch.generate(observer); 208 | 209 | //array indexes must be sorted descending, otherwise there is an index collision in apply 210 | expect(patches).toEqual([ 211 | { op: 'remove', path: '/items/2' }, 212 | { op: 'remove', path: '/items/1' } 213 | ]); 214 | 215 | obj2 = { items: ["a", "b", "c"]}; 216 | jsonpatch.apply(obj2,patches); 217 | expect(obj).toEqualInJson(obj2); 218 | }); 219 | 220 | it('should not generate the same patch twice (replace)', function() { 221 | obj = { lastName:"Einstein" }; 222 | var observer = jsonpatch.observe(obj); 223 | 224 | obj.lastName = "Wester"; 225 | 226 | patches = jsonpatch.generate(observer); 227 | expect(patches).toEqual([ 228 | { op: 'replace', path: '/lastName', value: 'Wester' } 229 | ]); 230 | 231 | patches = jsonpatch.generate(observer); 232 | expect(patches).toEqual([]); 233 | }); 234 | 235 | it('should not generate the same patch twice (add)', function() { 236 | obj = { lastName:"Einstein" }; 237 | var observer = jsonpatch.observe(obj); 238 | 239 | obj.firstName = "Albert"; 240 | 241 | patches = jsonpatch.generate(observer); 242 | expect(patches).toEqual([ 243 | { op: 'add', path: '/firstName', value: 'Albert' } 244 | ]); 245 | 246 | patches = jsonpatch.generate(observer); 247 | expect(patches).toEqual([]); 248 | }); 249 | 250 | it('should not generate the same patch twice (remove)', function() { 251 | obj = { lastName:"Einstein" }; 252 | var observer = jsonpatch.observe(obj); 253 | 254 | delete obj.lastName; 255 | 256 | patches = jsonpatch.generate(observer); 257 | expect(patches).toEqual([ 258 | { op: 'remove', path: '/lastName' } 259 | ]); 260 | 261 | patches = jsonpatch.generate(observer); 262 | expect(patches).toEqual([]); 263 | }); 264 | 265 | //related issue: https://github.com/Starcounter-Jack/JSON-Patch/issues/14 266 | it('should generate the same patch using Object.observe and shim', function() { 267 | var arr1 = [ 268 | ["Albert", "Einstein"], 269 | ["Erwin", "Shrodinger"] 270 | ]; 271 | 272 | var arr2 = arr1.slice(); 273 | 274 | var newRecord = ['Niels', 'Bohr']; 275 | 276 | var observer1 = jsonpatch.observe(arr1); 277 | arr1.push(newRecord); 278 | 279 | var objectObservePatches = jsonpatch.generate(observer1); 280 | 281 | var _observe = Object.observe; 282 | Object.observe = undefined; 283 | 284 | var observer2 = jsonpatch.observe(arr2); 285 | arr2.push(newRecord); 286 | 287 | var shimPatches = jsonpatch.generate(observer2); 288 | 289 | expect(objectObservePatches).toEqual(shimPatches); 290 | 291 | Object.observe = _observe; 292 | }); 293 | 294 | /*it('should not generate the same patch twice (move)', function() { //"move" is not implemented yet in jsonpatch.generate 295 | obj = { lastName: {str: "Einstein"} }; 296 | var observer = jsonpatch.observe(obj); 297 | 298 | obj.lastName2 = obj.lastName; 299 | delete obj.lastName; 300 | 301 | patches = jsonpatch.generate(observer); 302 | expect(patches).toEqual([ 303 | { op: 'move', from: '/lastName', to: '/lastName2' } 304 | ]); 305 | 306 | patches = jsonpatch.generate(observer); 307 | expect(patches).toEqual([]); 308 | });*/ 309 | }); 310 | 311 | describe('callback', function() { 312 | it('should generate replace', function() { 313 | var patches; 314 | 315 | obj = { firstName:"Albert", lastName:"Einstein", 316 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; 317 | 318 | jsonpatch.observe(obj, function(_patches) { 319 | patches = _patches; 320 | }); 321 | obj.firstName = "Joachim"; 322 | obj.lastName = "Wester"; 323 | obj.phoneNumbers[0].number = "123"; 324 | obj.phoneNumbers[1].number = "456"; 325 | 326 | waitsFor(function(){ 327 | return typeof patches === 'object'; 328 | }, 100); 329 | 330 | runs(function(){ 331 | obj2 = { firstName:"Albert", lastName:"Einstein", 332 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; 333 | 334 | jsonpatch.apply(obj2,patches); 335 | expect(obj2).toEqual(obj); 336 | }); 337 | }); 338 | 339 | it('should generate replace (double change, shallow object)', function() { 340 | var lastPatches 341 | , called = 0; 342 | 343 | obj = { firstName:"Albert", lastName:"Einstein", 344 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; 345 | 346 | jsonpatch.intervals = [50]; 347 | jsonpatch.observe(obj, function(patches) { 348 | called++; 349 | lastPatches = patches; 350 | }); 351 | obj.firstName = "Marcin"; 352 | 353 | waitsFor(function(){ 354 | return called > 0; 355 | }, 100, 'firstName change to Marcin'); 356 | 357 | runs(function(){ 358 | expect(called).toEqual(1); 359 | expect(lastPatches).toEqual([{op: 'replace', path: '/firstName', value: 'Marcin'}]); 360 | 361 | obj.lastName = "Warp"; 362 | }); 363 | 364 | waitsFor(function(){ 365 | return called > 1; 366 | }, 100, 'lastName change to Warp'); 367 | 368 | runs(function(){ 369 | expect(called).toEqual(2); 370 | expect(lastPatches).toEqual([{op: 'replace', path: '/lastName', value: 'Warp'}]); //first patch should NOT be reported again here 371 | 372 | expect(obj).toEqual({ firstName:"Marcin", lastName:"Warp", 373 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}); //objects should be still the same 374 | }); 375 | }); 376 | 377 | it('should generate replace (double change, deep object)', function() { 378 | var lastPatches 379 | , called = 0; 380 | 381 | obj = { firstName:"Albert", lastName:"Einstein", 382 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; 383 | 384 | jsonpatch.intervals = [50]; 385 | jsonpatch.observe(obj, function(patches) { 386 | called++; 387 | lastPatches = patches; 388 | }); 389 | obj.phoneNumbers[0].number = "123"; 390 | 391 | waitsFor(function(){ 392 | return called > 0; 393 | }, 100, 'phoneNumbers[0].number change to 123'); 394 | 395 | runs(function(){ 396 | expect(called).toEqual(1); 397 | expect(lastPatches).toEqual([{op: 'replace', path: '/phoneNumbers/0/number', value: '123'}]); 398 | 399 | obj.phoneNumbers[1].number = "456"; 400 | }); 401 | 402 | waitsFor(function(){ 403 | return called > 1; 404 | }, 100, 'phoneNumbers[1].number change to 456'); 405 | 406 | runs(function(){ 407 | expect(called).toEqual(2); 408 | expect(lastPatches).toEqual([{op: 'replace', path: '/phoneNumbers/1/number', value: '456'}]); //first patch should NOT be reported again here 409 | 410 | expect(obj).toEqual({ firstName:"Albert", lastName:"Einstein", 411 | phoneNumbers:[ {number:"123"}, {number:"456"} ]}); //objects should be still the same 412 | }); 413 | }); 414 | 415 | it('generate should execute callback synchronously', function() { 416 | var lastPatches 417 | , called = 0 418 | , res; 419 | 420 | obj = { firstName:"Albert", lastName:"Einstein", 421 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; 422 | 423 | jsonpatch.intervals = [10]; 424 | var observer = jsonpatch.observe(obj, function(patches) { 425 | called++; 426 | lastPatches = patches; 427 | }); 428 | obj.phoneNumbers[0].number = "123"; 429 | 430 | waits(100); 431 | expect(called).toEqual(0); 432 | 433 | res = jsonpatch.generate(observer); 434 | expect(called).toEqual(1); 435 | expect(lastPatches).toEqual([{op: 'replace', path: '/phoneNumbers/0/number', value: '123'}]); 436 | expect(lastPatches).toEqual(res); 437 | 438 | res = jsonpatch.generate(observer); 439 | expect(called).toEqual(1); 440 | expect(lastPatches).toEqual([{op: 'replace', path: '/phoneNumbers/0/number', value: '123'}]); 441 | expect(res).toEqual([]); 442 | }); 443 | 444 | it('should unobserve then observe again', function() { 445 | var called = 0; 446 | 447 | obj = { firstName:"Albert", lastName:"Einstein", 448 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; 449 | 450 | jsonpatch.intervals = [10]; 451 | var observer = jsonpatch.observe(obj, function(patches) { 452 | called++; 453 | }); 454 | 455 | obj.firstName = 'Malvin'; 456 | 457 | waits(20); 458 | 459 | runs(function(){ 460 | expect(called).toEqual(1); 461 | 462 | jsonpatch.unobserve(obj, observer); 463 | 464 | obj.firstName = 'Wilfred'; 465 | }); 466 | 467 | waits(20); 468 | 469 | runs(function(){ 470 | expect(called).toEqual(1); 471 | 472 | observer = jsonpatch.observe(obj, function(patches) { 473 | called++; 474 | }); 475 | 476 | obj.firstName = 'Megan'; 477 | }); 478 | 479 | waits(20); 480 | 481 | runs(function(){ 482 | expect(called).toEqual(2); 483 | }); 484 | }); 485 | 486 | it('should unobserve then observe again (deep value)', function() { 487 | var called = 0; 488 | 489 | obj = { firstName:"Albert", lastName:"Einstein", 490 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; 491 | 492 | jsonpatch.intervals = [10]; 493 | var observer = jsonpatch.observe(obj, function(patches) { 494 | called++; 495 | }); 496 | 497 | obj.phoneNumbers[1].number = '555'; 498 | 499 | waits(20); 500 | 501 | runs(function(){ 502 | expect(called).toEqual(1); 503 | 504 | jsonpatch.unobserve(obj, observer); 505 | 506 | obj.phoneNumbers[1].number = '556'; 507 | }); 508 | 509 | waits(20); 510 | 511 | runs(function(){ 512 | expect(called).toEqual(1); 513 | 514 | observer = jsonpatch.observe(obj, function(patches) { 515 | called++; 516 | }); 517 | 518 | obj.phoneNumbers[1].number = '557'; 519 | }); 520 | 521 | waits(20); 522 | 523 | runs(function(){ 524 | expect(called).toEqual(2); 525 | }); 526 | }); 527 | 528 | it('calling unobserve should deliver pending changes synchronously', function() { 529 | var lastPatches = ''; 530 | 531 | obj = { firstName: "Albert", lastName: "Einstein", 532 | phoneNumbers: [ 533 | {number: "12345"}, 534 | {number: "45353"} 535 | ]}; 536 | 537 | jsonpatch.intervals = [10]; 538 | var observer = jsonpatch.observe(obj, function (patches) { 539 | lastPatches = patches; 540 | }); 541 | 542 | obj.firstName = 'Malvin'; 543 | 544 | jsonpatch.unobserve(obj, observer); 545 | 546 | expect(lastPatches[0].value).toBe('Malvin'); 547 | 548 | obj.firstName = 'Jonathan'; 549 | 550 | waits(20); 551 | 552 | runs(function () { 553 | expect(lastPatches[0].value).toBe('Malvin'); 554 | }); 555 | }); 556 | 557 | it("should handle callbacks that calls observe() and unobserve() internally", function () { 558 | 559 | var obj = { 560 | foo: 'bar' 561 | }; 562 | 563 | var observer; 564 | 565 | var callback = jasmine.createSpy('callback'); 566 | callback.plan = function(){ 567 | jsonpatch.unobserve(obj, observer); 568 | 569 | jsonpatch.observe(obj, callback); 570 | }; 571 | 572 | observer = jsonpatch.observe(obj, callback); 573 | 574 | expect(callback.calls.length).toEqual(0); 575 | 576 | obj.foo = 'bazz'; 577 | 578 | waitsFor(function () { 579 | return callback.calls.length > 0; 580 | }, 'callback calls', 1000); 581 | 582 | runs(function () { 583 | expect(callback.calls.length).toEqual(1); 584 | 585 | callback.reset(); 586 | 587 | obj.foo = 'bazinga'; 588 | }); 589 | 590 | waitsFor(function () { 591 | return callback.calls.length > 0; 592 | }, 'callback calls', 1000); 593 | 594 | runs(function () { 595 | expect(callback.calls.length).toEqual(1); 596 | }); 597 | 598 | 599 | }); 600 | 601 | }); 602 | 603 | describe('compare', function () { 604 | it('should return an add for a property that does not exist in the first obj', function () { 605 | var objA = {user: {firstName: "Albert"}}; 606 | var objB = {user: {firstName: "Albert", lastName: "Einstein"}}; 607 | 608 | expect(jsonpatch.compare(objA, objB)).toEqual([ 609 | {op: "add", path: "/user/lastName", value: "Einstein"} 610 | ]); 611 | }); 612 | 613 | it('should return a remove for a property that does not exist in the second obj', function () { 614 | var objA = {user: {firstName: "Albert", lastName: "Einstein"}}; 615 | var objB = {user: {firstName: "Albert"}}; 616 | 617 | expect(jsonpatch.compare(objA, objB)).toEqual([ 618 | {op: "remove", path: "/user/lastName"} 619 | ]); 620 | }); 621 | 622 | it('should return a replace for a property that exists in both', function () { 623 | var objA = {user: {firstName: "Albert", lastName: "Einstein"}}; 624 | var objB = {user: {firstName: "Albert", lastName: "Collins"}}; 625 | 626 | expect(jsonpatch.compare(objA, objB)).toEqual([ 627 | {op: "replace", path: "/user/lastName", value: "Collins"} 628 | ]); 629 | }); 630 | }); 631 | 632 | 633 | describe("Registering multiple observers with the same callback", function () { 634 | 635 | it("should register only one observer", function () { 636 | 637 | var obj = { 638 | foo: 'bar' 639 | }; 640 | 641 | var callback = jasmine.createSpy('callback'); 642 | 643 | jsonpatch.observe(obj, callback); 644 | jsonpatch.observe(obj, callback); 645 | 646 | expect(callback.calls.length).toEqual(0); 647 | 648 | obj.foo = 'bazz'; 649 | 650 | waitsFor(function () { 651 | return callback.calls.length > 0; 652 | }, 'callback call', 1000); 653 | 654 | runs(function () { 655 | expect(callback.calls.length).toEqual(1); 656 | }); 657 | 658 | }); 659 | 660 | it("should return the same observer if callback has been already registered)", function () { 661 | var obj = { 662 | foo: 'bar' 663 | }; 664 | 665 | var callback = jasmine.createSpy('callback'); 666 | 667 | var observer1 = jsonpatch.observe(obj, callback); 668 | var observer2 = jsonpatch.observe(obj, callback); 669 | 670 | expect(observer1).toBe(observer2); 671 | 672 | 673 | }); 674 | 675 | it("should return a different observer if callback has been unregistered and registered again", function () { 676 | var obj = { 677 | foo: 'bar' 678 | }; 679 | 680 | var callback = jasmine.createSpy('callback'); 681 | 682 | var observer1 = jsonpatch.observe(obj, callback); 683 | 684 | jsonpatch.unobserve(obj, observer1); 685 | 686 | var observer2 = jsonpatch.observe(obj, callback); 687 | 688 | expect(observer1).not.toBe(observer2); 689 | 690 | }); 691 | }); 692 | 693 | }); 694 | 695 | // JSLitmus performance test 696 | if(typeof JSLitmus !== 'undefined') { 697 | JSLitmus.test('should Generate replace', function() { 698 | obj = { firstName:"Albert", lastName:"Einstein", 699 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; 700 | var observer = jsonpatch.observe(obj); 701 | 702 | obj.firstName = "Joachim"; 703 | obj.lastName = "Wester"; 704 | obj.phoneNumbers[0].number = "123"; 705 | obj.phoneNumbers[1].number = "456"; 706 | 707 | var patches = jsonpatch.generate(observer); 708 | obj2 = { firstName:"Albert", lastName:"Einstein", 709 | phoneNumbers:[ {number:"12345"}, {number:"45353"} ]}; 710 | 711 | jsonpatch.apply(obj2,patches); 712 | }); 713 | } 714 | -------------------------------------------------------------------------------- /src/test.js: -------------------------------------------------------------------------------- 1 | var obj, compiled; 2 | 3 | if(typeof jsonpatch === 'undefined') { 4 | if(process.env.duplex === 'yes') { //required by `jasmine-node` test runner in Node.js 5 | jsonpatch = require('./json-patch-duplex.js'); 6 | } 7 | else { 8 | jsonpatch = require('./json-patch.js'); 9 | } 10 | } 11 | 12 | describe("JSON-Patch", function () { 13 | it('should apply add', function() { 14 | obj = {foo: 1, baz: [{qux: 'hello'}]}; 15 | 16 | jsonpatch.apply(obj, [{op: 'add', path: '/bar', value: [1, 2, 3, 4]}]); 17 | expect(obj).toEqual({foo: 1, baz: [{qux: 'hello'}], bar: [1, 2, 3, 4]}); 18 | 19 | jsonpatch.apply(obj, [{op: 'add', path: '/baz/0/foo', value: 'world'}]); 20 | expect(obj).toEqual({foo: 1, baz: [{qux: 'hello', foo: 'world'}], bar: [1, 2, 3, 4]}); 21 | 22 | 23 | obj = {foo: 1, baz: [{qux: 'hello'}]}; 24 | jsonpatch.apply(obj, [{op: 'add', path: '/bar', value: true}]); 25 | expect(obj).toEqual({foo: 1, baz: [{qux: 'hello'}], bar: true}); 26 | 27 | obj = {foo: 1, baz: [{qux: 'hello'}]}; 28 | jsonpatch.apply(obj, [{op: 'add', path: '/bar', value: false}]); 29 | expect(obj).toEqual({foo: 1, baz: [{qux: 'hello'}], bar: false}); 30 | 31 | obj = {foo: 1, baz: [{qux: 'hello'}]}; 32 | jsonpatch.apply(obj, [{op: 'add', path: '/bar', value: null}]); 33 | expect(obj).toEqual({foo: 1, baz: [{qux: 'hello'}], bar: null}); 34 | }); 35 | 36 | 37 | it('should apply remove', function() { 38 | obj = {foo: 1, baz: [{qux: 'hello'}], bar: [1, 2, 3, 4]}; 39 | //jsonpatch.listenTo(obj,[]); 40 | 41 | jsonpatch.apply(obj, [{op: 'remove', path: '/bar'}]); 42 | expect(obj).toEqual({foo: 1, baz: [{qux: 'hello'}]}); 43 | 44 | jsonpatch.apply(obj, [{op: 'remove', path: '/baz/0/qux'}]); 45 | expect(obj).toEqual({foo: 1, baz: [{}]}); 46 | }); 47 | 48 | 49 | it('should apply replace', function() { 50 | obj = {foo: 1, baz: [{qux: 'hello'}]}; 51 | 52 | jsonpatch.apply(obj, [{op: 'replace', path: '/foo', value: [1, 2, 3, 4]}]); 53 | expect(obj).toEqual({foo: [1, 2, 3, 4], baz: [{qux: 'hello'}]}); 54 | 55 | jsonpatch.apply(obj, [{op: 'replace', path: '/baz/0/qux', value: 'world'}]); 56 | expect(obj).toEqual({foo: [1, 2, 3, 4], baz: [{qux: 'world'}]}); 57 | }); 58 | 59 | 60 | it('should apply test', function() { 61 | obj = {foo: {bar: [1, 2, 5, 4]}}; 62 | expect(jsonpatch.apply(obj, [{op: 'test', path: '/foo', value: {bar: [1, 2, 5, 4]}}])).toBe(true); 63 | expect(!jsonpatch.apply(obj, [{op: 'test', path: '/foo', value: [1, 2]}])).toBe(true); 64 | }); 65 | 66 | 67 | it('should apply move', function() { 68 | obj = {foo: 1, baz: [{qux: 'hello'}]}; 69 | 70 | jsonpatch.apply(obj, [{op: 'move', from: '/foo', path: '/bar'}]); 71 | expect(obj).toEqual({baz: [{qux: 'hello'}], bar: 1}); 72 | 73 | jsonpatch.apply(obj, [{op: 'move', from: '/baz/0/qux', path: '/baz/1'}]); 74 | expect(obj).toEqual({baz: [{}, 'hello'], bar: 1}); 75 | }); 76 | 77 | 78 | it('should apply copy', function() { 79 | obj = {foo: 1, baz: [{qux: 'hello'}]}; 80 | 81 | jsonpatch.apply(obj, [{op: 'copy', from: '/foo', path: '/bar'}]); 82 | expect(obj).toEqual({foo: 1, baz: [{qux: 'hello'}], bar: 1}); 83 | 84 | jsonpatch.apply(obj, [{op: 'copy', from: '/baz/0/qux', path: '/baz/1'}]); 85 | expect(obj).toEqual({foo: 1, baz: [{qux: 'hello'}, 'hello'], bar: 1}); 86 | }); 87 | }); 88 | 89 | // JSLitmus performance test 90 | if(typeof JSLitmus !== 'undefined') { 91 | JSLitmus.test('should Add Operation', function() { 92 | obj = {foo: 1, baz: [{qux: 'hello'}]}; 93 | jsonpatch.apply(obj, [{op: 'add', path: '/bar', value: [1, 2, 3, 4]}]); 94 | }); 95 | 96 | JSLitmus.test('should Remove Operation', function() { 97 | obj = {foo: 1, baz: [{qux: 'hello'}], bar: [1, 2, 3, 4]}; 98 | jsonpatch.apply(obj, [{op: 'remove', path: '/bar'}]); 99 | }); 100 | 101 | JSLitmus.test('should Replace Operation', function() { 102 | obj = {foo: 1, baz: [{qux: 'hello'}]}; 103 | jsonpatch.apply(obj, [{op: 'replace', path: '/foo', value: [1, 2, 3, 4]}]); 104 | }); 105 | 106 | JSLitmus.test('should Move Operation', function() { 107 | obj = {foo: 1, baz: [{qux: 'hello'}], bar: [1, 2, 3, 4]}; 108 | jsonpatch.apply(obj, [{op: 'move', from: '/baz/0', path: '/bar/0'}]); 109 | }); 110 | 111 | JSLitmus.test('should Copy Operation', function() { 112 | obj = {foo: 1, baz: [{qux: 'hello'}], bar: [1, 2, 3, 4]}; 113 | jsonpatch.apply(obj, [{op: 'copy', from: '/baz/0', path: '/bar/0'}]); 114 | }); 115 | 116 | JSLitmus.test('should Test Operation', function() { 117 | obj = {foo: 1, baz: [{qux: 'hello'}]}; 118 | jsonpatch.apply(obj, [{op: 'test', path: '/baz', value: [{qux: 'hello'}]}]); 119 | }); 120 | } --------------------------------------------------------------------------------