├── .gitignore ├── .jshintrc ├── LICENSE ├── README.md ├── gulpfile.js ├── package.json ├── src ├── diff.js ├── lcs.js ├── path.js └── utils.js └── tests ├── MapDiff.test.js ├── comparisonTests.test.js ├── lcs.test.js ├── path.test.js ├── performance ├── immutableDiff.perftest.js └── veryBigArray.js ├── primitiveTypeDiff.test.js └── sequenceDiff.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # Commenting this out is preferred by some people, see 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 28 | node_modules/ 29 | 30 | .idea/ -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "browser" : true, 4 | "globalstrict": true, 5 | "unused": "vars", 6 | "undef": true, 7 | "loopfunc": true, 8 | "curly": true, 9 | "devel": true, 10 | "noempty": true, 11 | "newcap": false, 12 | "maxlen": 100, 13 | "sub": false, 14 | "eqnull": true, 15 | 16 | "globals": { 17 | "__DEV__": false, 18 | "require": false, 19 | "module": false, 20 | "exports": false, 21 | "process": false, 22 | 23 | "describe": false, 24 | "it": false, 25 | "before": false, 26 | "beforeEach": false, 27 | "afterEach": false, 28 | "sinon": false 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Intelie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Immutable Diff 2 | 3 | Create RFC 6902 style patches between Immutable.JS data structures, such as `Maps`, `Lists`, and `Sets`. 4 | 5 | ## Getting Started 6 | 7 | Install `immutablediff` using npm or yarn: 8 | 9 | ``` shell 10 | # npm... 11 | npm install --save immutablediff 12 | 13 | # ...or yarn 14 | yarn add immutablediff 15 | ``` 16 | 17 | You can then use it to get the diff ops between you Immutable.JS data structures. 18 | 19 | ``` javascript 20 | var Immutable = require('immutable'); 21 | var diff = require('immutablediff'); 22 | 23 | var map1 = Immutable.Map({a:1, b:2, c:3}); 24 | var map2 = Immutable.Map({a:1, b:2, c:3, d: 4}); 25 | 26 | diff(map1, map2); 27 | // List [ Map { op: "add", path: "/d", value: 4 } ] 28 | ``` 29 | 30 | The result of `diff` is an Immutable Sequence of operations 31 | 32 | ## Immutable Patch 33 | 34 | If you want to use the resulting diff to apply patches to other Immutable data structures, you can use the package `immutablepatch`. Source code available [here](https://github.com/intelie/immutable-js-patch) 35 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var gulp = require('gulp'); 4 | var mocha = require('gulp-mocha'); 5 | var util = require('gulp-util'); 6 | 7 | var srcPaths = { 8 | src: 'src/*.js', 9 | tests: 'tests/*.test.js' 10 | }; 11 | 12 | gulp.task('test', function() { 13 | return gulp.src([srcPaths.tests], { read: false }) 14 | .pipe(mocha({ reporter: 'list' })) 15 | .on('error', util.log); 16 | }); 17 | 18 | gulp.task('watch-tests', function() { 19 | gulp.watch([srcPaths.src, srcPaths.tests], ['test']); 20 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "immutablediff", 3 | "version": "0.4.4", 4 | "description": "Create RFC 6902 style patches between Immutable.JS data structures", 5 | "main": "src/diff.js", 6 | "directories": { 7 | "test": "tests" 8 | }, 9 | "scripts": { 10 | "test": "gulp test" 11 | }, 12 | "keywords": [ 13 | "immutable.js", 14 | "diff", 15 | "jsondiff" 16 | ], 17 | "author": "Intelie", 18 | "license": "MIT", 19 | "repository" : { 20 | "type" : "git", 21 | "url" : "http://github.com/intelie/immutable-js-diff.git" 22 | }, 23 | "devDependencies": { 24 | "gulp": "^3.8.9", 25 | "gulp-mocha": "^1.1.1", 26 | "gulp-util": "^3.0.1", 27 | "jscheck": "^0.2.0", 28 | "jsondiff": "brenoferreira/json-diff" 29 | }, 30 | "dependencies": { 31 | "immutable": "^3.2.1" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/diff.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Immutable = require('immutable'); 4 | var utils = require('./utils'); 5 | var lcs = require('./lcs'); 6 | var path = require('./path'); 7 | var concatPath = path.concat, 8 | escape = path.escape, 9 | op = utils.op, 10 | isMap = utils.isMap, 11 | isIndexed = utils.isIndexed; 12 | 13 | var mapDiff = function(a, b, p){ 14 | var ops = []; 15 | var path = p || ''; 16 | 17 | if(Immutable.is(a, b) || (a == b == null)){ return ops; } 18 | 19 | var areLists = isIndexed(a) && isIndexed(b); 20 | var lastKey = null; 21 | var removeKey = null 22 | 23 | if(a.forEach){ 24 | a.forEach(function(aValue, aKey){ 25 | if(b.has(aKey)){ 26 | if(isMap(aValue) && isMap(b.get(aKey))){ 27 | ops = ops.concat(mapDiff(aValue, b.get(aKey), concatPath(path, escape(aKey)))); 28 | } 29 | else if(isIndexed(b.get(aKey)) && isIndexed(aValue)){ 30 | ops = ops.concat(sequenceDiff(aValue, b.get(aKey), concatPath(path, escape(aKey)))); 31 | } 32 | else { 33 | var bValue = b.get ? b.get(aKey) : b; 34 | var areDifferentValues = (aValue !== bValue); 35 | if (areDifferentValues) { 36 | ops.push(op('replace', concatPath(path, escape(aKey)), bValue)); 37 | } 38 | } 39 | } 40 | else { 41 | if(areLists){ 42 | removeKey = (lastKey != null && (lastKey+1) === aKey) ? removeKey : aKey; 43 | ops.push( op('remove', concatPath(path, escape(removeKey))) ); 44 | lastKey = aKey; 45 | } 46 | else{ 47 | ops.push( op('remove', concatPath(path, escape(aKey))) ); 48 | } 49 | 50 | } 51 | }); 52 | } 53 | 54 | b.forEach(function(bValue, bKey){ 55 | if(a.has && !a.has(bKey)){ 56 | ops.push( op('add', concatPath(path, escape(bKey)), bValue) ); 57 | } 58 | }); 59 | 60 | return ops; 61 | }; 62 | 63 | var sequenceDiff = function (a, b, p) { 64 | var ops = []; 65 | var path = p || ''; 66 | if(Immutable.is(a, b) || (a == b == null)){ return ops; } 67 | if((a.count() + 1) * (b.count() + 1) >= 10000 ) { return mapDiff(a, b, p); } 68 | 69 | var lcsDiff = lcs.diff(a, b); 70 | 71 | var pathIndex = 0; 72 | 73 | lcsDiff.forEach(function (diff) { 74 | if(diff.op === '='){ pathIndex++; } 75 | else if(diff.op === '!='){ 76 | if(isMap(diff.val) && isMap(diff.newVal)){ 77 | var mapDiffs = mapDiff(diff.val, diff.newVal, concatPath(path, pathIndex)); 78 | ops = ops.concat(mapDiffs); 79 | } 80 | else{ 81 | ops.push(op('replace', concatPath(path, pathIndex), diff.newVal)); 82 | } 83 | pathIndex++; 84 | } 85 | else if(diff.op === '+'){ 86 | ops.push(op('add', concatPath(path, pathIndex), diff.val)); 87 | pathIndex++; 88 | } 89 | else if(diff.op === '-'){ ops.push(op('remove', concatPath(path, pathIndex))); } 90 | }); 91 | 92 | return ops; 93 | }; 94 | 95 | var primitiveTypeDiff = function (a, b, p) { 96 | var path = p || ''; 97 | if(a === b){ return []; } 98 | else{ 99 | return [ op('replace', concatPath(path, ''), b) ]; 100 | } 101 | }; 102 | 103 | var diff = function(a, b, p){ 104 | if(Immutable.is(a, b)){ return Immutable.List(); } 105 | if(a != b && (a == null || b == null)){ return Immutable.fromJS([op('replace', '/', b)]); } 106 | if(isIndexed(a) && isIndexed(b)){ 107 | return Immutable.fromJS(sequenceDiff(a, b)); 108 | } 109 | else if(isMap(a) && isMap(b)){ 110 | return Immutable.fromJS(mapDiff(a, b)); 111 | } 112 | else{ 113 | return Immutable.fromJS(primitiveTypeDiff(a, b, p)); 114 | } 115 | }; 116 | 117 | module.exports = diff; 118 | -------------------------------------------------------------------------------- /src/lcs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Immutable = require('immutable'); 4 | 5 | /** 6 | * Returns a two-dimensional array (an array of arrays) with dimensions n by m. 7 | * All the elements of this new matrix are initially equal to x 8 | * @param n number of rows 9 | * @param m number of columns 10 | * @param x initial element for every item in matrix 11 | */ 12 | var makeMatrix = function(n, m, x){ 13 | var matrix = []; 14 | for(var i = 0; i < n; i++) { 15 | matrix[i] = new Array(m); 16 | 17 | if(x != null){ 18 | for(var j = 0; j < m; j++){ 19 | matrix[i][j] = x; 20 | } 21 | } 22 | } 23 | 24 | return matrix; 25 | }; 26 | 27 | /** 28 | * Computes Longest Common Subsequence between two Immutable.JS Indexed Iterables 29 | * Based on Dynamic Programming http://rosettacode.org/wiki/Longest_common_subsequence#Java 30 | * @param xs ImmutableJS Indexed Sequence 1 31 | * @param ys ImmutableJS Indexed Sequence 2 32 | */ 33 | var lcs = function(xs, ys){ 34 | var matrix = computeLcsMatrix(xs, ys); 35 | 36 | return backtrackLcs(xs, ys, matrix); 37 | }; 38 | 39 | var DiffResult = Immutable.Record({op: '=', val: null}); 40 | var ReplaceResult = Immutable.Record({op: '!=', val: null, newVal: null}); 41 | 42 | /** 43 | * Returns the resulting diff operations of LCS between two sequences 44 | * @param xs Indexed Sequence 1 45 | * @param ys Indexed Sequence 2 46 | * @returns Array of DiffResult {op:'=' | '+' | '-', val:any} 47 | */ 48 | var diff = function(xs, ys){ 49 | var matrix = computeLcsMatrix(xs, ys); 50 | 51 | return printDiff(matrix, xs, ys, xs.size||0, ys.size||0); 52 | }; 53 | 54 | var printDiff = function(matrix, xs, ys, xSize, ySize) { 55 | var diffArray = []; 56 | var i = xSize - 1; 57 | var j = ySize - 1; 58 | while (i >= 0 || j >= 0) { 59 | if (i >= 0 && j >= 0 && Immutable.is(xs.get(i), ys.get(j))) { 60 | diffArray.push(new DiffResult({ 61 | op: '=', 62 | val: xs.get(i) 63 | })); 64 | i -= 1; 65 | j -= 1; 66 | } 67 | else if (i >= 0 && j >= 0 && i === j && !Immutable.is(xs.get(i), ys.get(j))) { 68 | diffArray.push(new ReplaceResult({ 69 | val: xs.get(i), 70 | newVal: ys.get(i) 71 | })); 72 | i -= 1; 73 | j -= 1; 74 | } 75 | else { 76 | if (j >= 0 && (i === -1 || matrix[i+1][j] >= matrix[i][j+1])) { 77 | diffArray.push(new DiffResult({ 78 | op: '+', 79 | val: ys.get(j) 80 | })); 81 | j -= 1; 82 | } 83 | else if (i >= 0 && (j === -1 || matrix[i+1][j] < matrix[i][j+1])){ 84 | diffArray.push(new DiffResult({ 85 | op: '-', 86 | val: xs.get(i) 87 | })); 88 | i -= 1; 89 | } 90 | } 91 | } 92 | return diffArray.reverse(); 93 | }; 94 | 95 | /** 96 | * Computes the Longest Common Subsequence table 97 | * @param xs Indexed Sequence 1 98 | * @param ys Indexed Sequence 2 99 | */ 100 | function computeLcsMatrix(xs, ys) { 101 | var n = xs.size||0; 102 | var m = ys.size||0; 103 | var a = makeMatrix(n + 1, m + 1, 0); 104 | 105 | for (var i = 0; i < n; i++) { 106 | for (var j = 0; j < m; j++) { 107 | if (Immutable.is(xs.get(i), ys.get(j))) { 108 | a[i + 1][j + 1] = a[i][j] + 1; 109 | } 110 | else { 111 | a[i + 1][j + 1] = Math.max(a[i + 1][j], a[i][j + 1]); 112 | } 113 | } 114 | } 115 | 116 | return a; 117 | } 118 | 119 | /** 120 | * Extracts a LCS from matrix M 121 | * @param xs Indexed Sequence 1 122 | * @param ys Indexed Sequence 2 123 | * @param matrix LCS Matrix 124 | * @returns {Array.} Longest Common Subsequence 125 | */ 126 | var backtrackLcs = function(xs, ys, matrix){ 127 | var lcs = []; 128 | for(var i = xs.size, j = ys.size; i !== 0 && j !== 0;){ 129 | if (matrix[i][j] === matrix[i-1][j]){ i--; } 130 | else if (matrix[i][j] === matrix[i][j-1]){ j--; } 131 | else{ 132 | if(Immutable.is(xs.get(i-1), ys.get(j-1))){ 133 | lcs.push(xs.get(i-1)); 134 | i--; 135 | j--; 136 | } 137 | } 138 | } 139 | return lcs.reverse(); 140 | }; 141 | 142 | module.exports = { 143 | lcs: lcs, 144 | computeLcsMatrix: computeLcsMatrix, 145 | diff: diff 146 | }; 147 | -------------------------------------------------------------------------------- /src/path.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var slashRe = new RegExp('/', 'g'); 4 | var escapedSlashRe = new RegExp('~1', 'g'); 5 | var tildeRe = /~/g; 6 | var escapedTildeRe = /~0/g; 7 | 8 | var Path = { 9 | escape: function (str) { 10 | if(typeof(str) === 'number'){ 11 | return str.toString(); 12 | } 13 | if(typeof(str) !== 'string'){ 14 | throw 'param str (' + str + ') is not a string'; 15 | } 16 | 17 | return str.replace(tildeRe, '~0').replace(slashRe, '~1'); 18 | }, 19 | 20 | unescape: function (str) { 21 | if(typeof(str) == 'string') { 22 | return str.replace(escapedSlashRe, '/').replace(escapedTildeRe, '~'); 23 | } 24 | else { 25 | return str; 26 | } 27 | }, 28 | concat: function(path, key){ 29 | return path + '/' + key; 30 | } 31 | }; 32 | 33 | module.exports = Path; -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Immutable = require('immutable'); 4 | 5 | var isMap = function(obj){ return Immutable.Iterable.isKeyed(obj); }; 6 | var isIndexed = function(obj) { return Immutable.Iterable.isIndexed(obj); }; 7 | 8 | var op = function(operation, path, value){ 9 | if(operation === 'remove') { return { op: operation, path: path }; } 10 | 11 | return { op: operation, path: path, value: value }; 12 | }; 13 | 14 | module.exports = { 15 | isMap: isMap, 16 | isIndexed: isIndexed, 17 | op: op 18 | }; -------------------------------------------------------------------------------- /tests/MapDiff.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var diff = require('../src/diff'); 4 | var Immutable = require('immutable'); 5 | var JSC = require('jscheck'); 6 | var assert = require('assert'); 7 | 8 | describe('Map diff', function(){ 9 | var failure = null; 10 | 11 | before(function(){ 12 | JSC.on_report(function(report){ 13 | console.log(report); 14 | }); 15 | 16 | JSC.on_fail(function(jsc_failure){ 17 | failure = jsc_failure; 18 | }); 19 | }); 20 | 21 | afterEach(function () { 22 | if(failure){ 23 | console.error(failure); 24 | throw failure; 25 | } 26 | }); 27 | 28 | it('returns empty diff when both values are null', function() { 29 | var result = diff(null, null); 30 | 31 | assert.ok(result.count() === 0); 32 | }); 33 | 34 | it('check properties', function(){ 35 | JSC.test( 36 | 'returns [] when equal', 37 | function(veredict, obj){ 38 | var map1 = Immutable.fromJS(obj); 39 | var map2 = Immutable.fromJS(obj); 40 | 41 | var result = diff(map1, map2); 42 | 43 | return veredict(result.count() === 0); 44 | }, 45 | [ 46 | JSC.object(5) 47 | ] 48 | ); 49 | 50 | 51 | JSC.test( 52 | 'returns add op when missing attribute', 53 | function(veredict, obj, obj2){ 54 | var map1 = Immutable.fromJS(obj); 55 | var map2 = Immutable.fromJS(obj).set('key2', obj2.key2); 56 | 57 | var result = diff(map1, map2); 58 | var expected = Immutable.fromJS([{op: 'add', path: '/key2', value: obj2.key2}]); 59 | 60 | return veredict(Immutable.is(result, expected)); 61 | }, 62 | [ 63 | JSC.object({ 64 | key: JSC.integer() 65 | }), 66 | JSC.object({ 67 | key2: JSC.integer() 68 | }) 69 | ] 70 | ); 71 | 72 | JSC.test( 73 | 'returns replace op when same attribute with different values', 74 | function(veredict, obj, newValue){ 75 | var map1 = Immutable.fromJS(obj); 76 | var map2 = Immutable.fromJS(obj).set('key', newValue); 77 | 78 | var result = diff(map1, map2); 79 | var expected = Immutable.fromJS([{op: 'replace', path: '/key', value: newValue}]); 80 | 81 | return veredict(Immutable.is(result, expected)); 82 | }, 83 | [ 84 | JSC.object({ 85 | key: JSC.integer(1, 100) 86 | }), 87 | JSC.integer(101, 200) 88 | ] 89 | ); 90 | 91 | JSC.test( 92 | 'returns remove op when attribute is missing', 93 | function(veredict, obj){ 94 | var map1 = Immutable.fromJS(obj); 95 | var map2 = Immutable.Map(); 96 | 97 | var result = diff(map1, map2); 98 | var expected = Immutable.fromJS([{op: 'remove', path: '/key'}]); 99 | 100 | return veredict(Immutable.is(result, expected)); 101 | }, 102 | [ 103 | JSC.object({ 104 | key: JSC.integer() 105 | }) 106 | ] 107 | ); 108 | }); 109 | 110 | it('check nested structures', function(){ 111 | JSC.test( 112 | 'returns add op when missing attribute in nested structure', 113 | function(veredict, obj, obj2){ 114 | var map1 = Immutable.fromJS(obj); 115 | var map2 = Immutable.fromJS(obj).setIn(['b', 'd'], obj2.d); 116 | 117 | var result = diff(map1, map2); 118 | var expected = Immutable.fromJS([{op: 'add', path: '/b/d', value: obj2.d}]); 119 | 120 | return veredict(Immutable.is(result, expected)); 121 | }, 122 | [ 123 | JSC.object({ 124 | a: JSC.integer(), 125 | b: JSC.object({ 126 | c: JSC.integer() 127 | }) 128 | }), 129 | JSC.object({ 130 | d: JSC.integer() 131 | }) 132 | ] 133 | ); 134 | 135 | JSC.test( 136 | 'returns replace op when different value in nested structure', 137 | function(veredict, obj, obj2){ 138 | var map1 = Immutable.fromJS(obj); 139 | var map2 = Immutable.fromJS(obj).setIn(['b', 'c'], obj2.c); 140 | 141 | var result = diff(map1, map2); 142 | var expected = Immutable.fromJS([{op: 'replace', path: '/b/c', value: obj2.c}]); 143 | 144 | return veredict(Immutable.is(result, expected)); 145 | }, 146 | [ 147 | JSC.object({ 148 | a: JSC.integer(), 149 | b: JSC.object({ 150 | c: JSC.integer(1, 100) 151 | }) 152 | }), 153 | JSC.object({ 154 | c: JSC.integer(101, 200) 155 | }) 156 | ] 157 | ); 158 | 159 | JSC.test( 160 | 'returns remove op when attribute removed in nested structure', 161 | function(veredict, obj, obj2){ 162 | var map1 = Immutable.fromJS(obj).setIn(['b', 'd'], obj2.d); 163 | var map2 = Immutable.fromJS(obj); 164 | 165 | var result = diff(map1, map2); 166 | var expected = Immutable.fromJS([{op: 'remove', path: '/b/d'}]); 167 | 168 | return veredict(Immutable.is(result, expected)); 169 | }, 170 | [ 171 | JSC.object({ 172 | a: JSC.integer(), 173 | b: JSC.object({ 174 | c: JSC.integer() 175 | }) 176 | }), 177 | JSC.object({ 178 | d: JSC.integer() 179 | }) 180 | ] 181 | ); 182 | 183 | JSC.test( 184 | 'no replace in equal nested structure', 185 | function(veredict, obj, obj2){ 186 | var map1 = Immutable.fromJS(obj); 187 | var map2 = Immutable.fromJS(obj).set('a', obj2.a); 188 | 189 | var result = diff(map1, map2); 190 | var expected = Immutable.fromJS([{op: 'replace', path: '/a', value: obj2.a}]); 191 | 192 | return veredict(Immutable.is(result, expected)); 193 | }, 194 | [ 195 | JSC.object({ 196 | a: JSC.integer(), 197 | b: JSC.object({ 198 | c: JSC.integer() 199 | }) 200 | }), 201 | JSC.object({ 202 | a: JSC.integer() 203 | }) 204 | ] 205 | ); 206 | 207 | JSC.test( 208 | 'add/remove when different nested structure', 209 | function(veredict, obj, obj2){ 210 | var map1 = Immutable.fromJS(obj); 211 | var map2 = Immutable.fromJS(obj).set('b', Immutable.fromJS(obj2)); 212 | 213 | var result = diff(map1, map2); 214 | var expected = Immutable.fromJS([ 215 | {op: 'remove', path: '/b/c'}, 216 | {op: 'add', path: '/b/e', value: obj2.e}, 217 | ]); 218 | 219 | return veredict(Immutable.is(result, expected)); 220 | }, 221 | [ 222 | JSC.object({ 223 | a: JSC.integer(), 224 | b: JSC.object({ 225 | c: JSC.integer() 226 | }) 227 | }), 228 | JSC.object({ 229 | e: JSC.integer() 230 | }) 231 | ] 232 | ); 233 | }); 234 | 235 | it('check nested indexed sequences', function () { 236 | JSC.test( 237 | 'add when value added in nested sequence', 238 | function(veredict, obj, newInt){ 239 | var map1 = Immutable.fromJS(obj); 240 | var map2 = Immutable.fromJS(obj).updateIn(['b', 'c'], function(list){ 241 | return list.push(newInt); 242 | }); 243 | 244 | var result = diff(map1, map2); 245 | var expected = Immutable.fromJS([{op: 'add', path: '/b/c/5', value: newInt}]); 246 | 247 | return veredict(Immutable.is(result, expected)); 248 | }, 249 | [ 250 | JSC.object({ 251 | a: JSC.integer(), 252 | b: JSC.object({ 253 | c: JSC.array(5, JSC.integer()) 254 | }) 255 | }), 256 | JSC.integer() 257 | ] 258 | ); 259 | 260 | JSC.test( 261 | 'remove when value removed in nested sequence', 262 | function(veredict, obj, removeIdx){ 263 | var map1 = Immutable.fromJS(obj); 264 | var map2 = Immutable.fromJS(obj).updateIn(['b', 'c'], function(list){ 265 | return list.splice(removeIdx, 1); 266 | }); 267 | 268 | var result = diff(map1, map2); 269 | var expected = Immutable.fromJS([{op: 'remove', path: '/b/c/'+removeIdx}]); 270 | 271 | return veredict(Immutable.is(result, expected)); 272 | }, 273 | [ 274 | JSC.object({ 275 | a: JSC.integer(), 276 | b: JSC.object({ 277 | c: JSC.array(10, JSC.integer()) 278 | }) 279 | }), 280 | JSC.integer(0, 9) 281 | ] 282 | ); 283 | 284 | JSC.test( 285 | 'replace when values are replaced in nested sequence', 286 | function(veredict, obj, replaceIdx, newValue){ 287 | var map1 = Immutable.fromJS(obj); 288 | var map2 = Immutable.fromJS(obj).updateIn(['b', 'c'], function(list){ 289 | return list.set(replaceIdx, newValue); 290 | }); 291 | 292 | var result = diff(map1, map2); 293 | var expected = Immutable.fromJS([ 294 | {op: 'replace', path: '/b/c/'+replaceIdx, value: newValue} 295 | ]); 296 | 297 | return veredict(Immutable.is(result, expected)); 298 | }, 299 | [ 300 | JSC.object({ 301 | a: JSC.integer(), 302 | b: JSC.object({ 303 | c: JSC.array(10, JSC.integer()) 304 | }) 305 | }), 306 | JSC.integer(0, 9), 307 | JSC.integer() 308 | ] 309 | ); 310 | }); 311 | 312 | it('check map in indexed sequence', function(){ 313 | var array1 = [{a: 1}, {a: 2}, {a: 3}]; 314 | var array2 = [{a: 1}, {a: 2, b:2.5}, {a: 3}]; 315 | 316 | var list1 = Immutable.fromJS(array1); 317 | var list2 = Immutable.fromJS(array2); 318 | 319 | var result = diff(list1, list2); 320 | var expected = Immutable.fromJS([{op: 'add', path: '/1/b', value: 2.5}]); 321 | 322 | assert.ok(Immutable.is(result, expected)); 323 | }); 324 | 325 | describe('handling nulls', function() { 326 | it('replaces null for immutable value', function() { 327 | var map1 = null; 328 | var map2 = Immutable.fromJS({a: 1}); 329 | 330 | var result = diff(map1, map2); 331 | var expected = Immutable.fromJS([{op: 'replace', path: '/', value: map2}]); 332 | 333 | assert.ok(Immutable.is(result, expected)); 334 | }); 335 | 336 | it('replaces value for null', function() { 337 | var map1 = Immutable.fromJS({a: 1}); 338 | var map2 = null; 339 | 340 | var result = diff(map1, map2); 341 | var expected = Immutable.fromJS([{op: 'replace', path: '/', value: map2}]); 342 | 343 | assert.ok(Immutable.is(result, expected)); 344 | }); 345 | 346 | it('replaces null value in map', function() { 347 | var map1 = Immutable.fromJS({a: null}); 348 | var map2 = Immutable.fromJS({a: 1}); 349 | 350 | var result = diff(map1, map2); 351 | var expected = Immutable.fromJS([{op: 'replace', path: '/a', value: 1}]); 352 | 353 | assert.ok(Immutable.is(result, expected)); 354 | }); 355 | 356 | it('replaces null value in map for empty map', function() { 357 | var map1 = Immutable.fromJS({a: null}); 358 | var map2 = Immutable.fromJS({}); 359 | 360 | var result = diff(map1, map2); 361 | var expected = Immutable.fromJS([{op: 'remove', path: '/a'}]); 362 | 363 | assert.ok(Immutable.is(result, expected)); 364 | }); 365 | }); 366 | 367 | describe('path escaping', function() { 368 | it('add unescaped path', function() { 369 | var map1 = Immutable.fromJS({'a': 1, 'b': {'c': 3}}); 370 | var map2 = Immutable.fromJS({'a': 1, 'b': {'c': 3, 'prop/prop': 4}}); 371 | 372 | var result = diff(map1, map2); 373 | var expected = Immutable.fromJS([ 374 | {op: 'add', path: '/b/prop~1prop', value: 4} 375 | ]); 376 | 377 | assert.ok(Immutable.is(result, expected)); 378 | }); 379 | 380 | it('replaces unescaped path', function() { 381 | var map1 = Immutable.fromJS({'a': 1, 'b': {'c': 3, 'prop/prop': 4}}); 382 | var map2 = Immutable.fromJS({'a': 1, 'b': {'c': 3, 'prop/prop': 10}}); 383 | 384 | var result = diff(map1, map2); 385 | var expected = Immutable.fromJS([ 386 | {op: 'replace', path: '/b/prop~1prop', value: 10} 387 | ]); 388 | 389 | assert.ok(Immutable.is(result, expected)); 390 | }); 391 | 392 | it('removes unescaped path', function() { 393 | var map1 = Immutable.fromJS({'a': 1, 'b': {'c': 3, 'prop/prop': 4}}); 394 | var map2 = Immutable.fromJS({'a': 1, 'b': {'c': 3}}); 395 | 396 | var result = diff(map1, map2); 397 | var expected = Immutable.fromJS([ 398 | {op: 'remove', path: '/b/prop~1prop'} 399 | ]); 400 | 401 | assert.ok(Immutable.is(result, expected)); 402 | }); 403 | 404 | it('add unescaped path in nested map', function() { 405 | var map1 = Immutable.fromJS({'a': 1, 'test/test': {'c': 3}}); 406 | var map2 = Immutable.fromJS({'a': 1, 'test/test': {'c': 3, 'prop/prop': 4}}); 407 | 408 | var result = diff(map1, map2); 409 | var expected = Immutable.fromJS([ 410 | {op: 'add', path: '/test~1test/prop~1prop', value: 4} 411 | ]); 412 | 413 | assert.ok(Immutable.is(result, expected)); 414 | }); 415 | 416 | it('add unescaped path in nested sequence', function() { 417 | var map1 = Immutable.fromJS({'a': 1, 'test/test': [0, 1, 2]}); 418 | var map2 = Immutable.fromJS({'a': 1, 'test/test': [0, 1, 2, 3]}); 419 | 420 | var result = diff(map1, map2); 421 | var expected = Immutable.fromJS([ 422 | {op: 'add', path: '/test~1test/3', value: 3} 423 | ]); 424 | 425 | assert.ok(Immutable.is(result, expected)); 426 | }); 427 | }); 428 | 429 | it('replace primitive value for nested map', function() { 430 | var map1 = Immutable.fromJS({a:false}); 431 | var map2 = Immutable.fromJS({a:{b:3}}); 432 | 433 | var result = diff(map1, map2); 434 | var expected = Immutable.fromJS([ 435 | { op: 'replace', path: '/a', value: Immutable.fromJS({ b: 3 }) } 436 | ]); 437 | 438 | assert.ok(Immutable.is(result, expected)); 439 | }); 440 | 441 | it('replace nested map with primitive value', function() { 442 | var map1 = Immutable.fromJS({a:{b:3}}); 443 | var map2 = Immutable.fromJS({a:false}); 444 | 445 | var result = diff(map1, map2); 446 | var expected = Immutable.fromJS([ 447 | { op: 'replace', path: '/a', value: false } 448 | ]); 449 | 450 | assert.ok(Immutable.is(result, expected)); 451 | }); 452 | }); -------------------------------------------------------------------------------- /tests/comparisonTests.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Immutable = require('immutable'); 4 | var assert = require('assert'); 5 | var diff = require('../src/diff'); 6 | var jsonDiff = require('jsondiff'); 7 | 8 | var compareDiffs = function(a, b){ 9 | var jsonDiffResult = Immutable.fromJS(jsonDiff.diff(a, b)); 10 | var immutableDiffResult = diff(Immutable.fromJS(a), Immutable.fromJS(b)); 11 | 12 | assert.ok(Immutable.is(jsonDiffResult, immutableDiffResult)); 13 | }; 14 | 15 | describe('Comparison Tests', function() { 16 | it('equals json-diff results', function () { 17 | compareDiffs({}, {}); 18 | compareDiffs([], []); 19 | compareDiffs([1], [1]); 20 | compareDiffs(['a'], ['a']); 21 | compareDiffs([null], [null]); 22 | compareDiffs([true], [true]); 23 | compareDiffs([false], [false]); 24 | compareDiffs({a: 'a', b: 'b'}, {b: 'b', a: 'a'}); 25 | compareDiffs([1, 2, 3], [1, 2, 3]); 26 | 27 | compareDiffs({a: 'a'}, {}); 28 | compareDiffs([[1]], []); 29 | compareDiffs([], [1]); 30 | 31 | compareDiffs([], [[1]]); 32 | 33 | compareDiffs([2, 3], [1, 2, 3]); 34 | compareDiffs([1, 3], [1, 2, 3]); 35 | compareDiffs([1, 2], [1, 2, 3]); 36 | compareDiffs([1, 2, 3], [1, 4, 3]); 37 | 38 | compareDiffs({a: [9, 8, 7], b: 2, c: 3}, {a: [9, 7], b: 2, c: 4, d: 5}); 39 | 40 | compareDiffs([1, 0, 0], [1, 1, 0]); 41 | compareDiffs([1, 1, 0], [1, 0, 0]); 42 | 43 | compareDiffs({foo: 'bar'}, {baz: 'qux', foo: 'bar'}); 44 | compareDiffs({foo: ['bar', 'baz']}, {foo: ['bar', 'qux', 'baz']}); 45 | compareDiffs({baz: 'qux', foo: 'bar'}, {foo: 'bar'}); 46 | compareDiffs({foo: ['bar', 'qux', 'baz']}, {foo: ['bar', 'baz']}); 47 | compareDiffs({baz: 'qux', foo: 'bar'}, {baz: 'boo', foo: 'bar'}); 48 | compareDiffs({foo: 'bar'}, {foo: 'bar', child: {grandchild: {}}}); 49 | compareDiffs({foo: ['bar']}, {foo: ['bar', ['abc', 'def']]}); 50 | 51 | compareDiffs([0, 0], [1, 1]); 52 | }); 53 | }); -------------------------------------------------------------------------------- /tests/lcs.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Immutable = require('immutable'); 4 | var lcs = require('../src/lcs'); 5 | var assert = require('assert'); 6 | 7 | describe('lcs', function() { 8 | 9 | it('computes for list of chars', function () { 10 | var str1 = 'thisisatest'; 11 | var str2 = 'testing123testing'; 12 | var expectedStr = 'tsitest'; 13 | 14 | var list1 = Immutable.fromJS(str1.split('')); 15 | var list2 = Immutable.fromJS(str2.split('')); 16 | var expected = expectedStr.split(''); 17 | 18 | var result = lcs.lcs(list1, list2); 19 | 20 | assert.deepEqual(result, expected); 21 | }); 22 | 23 | it('computes for list of integers', function () { 24 | var array1 = [1,2,3,4]; 25 | var array2 = [1, 2, 2, 4, 5, 3, 3, 3, 2, 4]; 26 | var expected = [1,2,3,4]; 27 | 28 | var list1 = Immutable.fromJS(array1); 29 | var list2 = Immutable.fromJS(array2); 30 | 31 | var result = lcs.lcs(list1, list2); 32 | 33 | assert.deepEqual(result, expected); 34 | }); 35 | 36 | it('computes diff', function () { 37 | var array1 = [1, 2, 3, 4]; 38 | var array2 = [1, 2, 3, 5, 6, 7]; 39 | 40 | var list1 = Immutable.fromJS(array1); 41 | var list2 = Immutable.fromJS(array2); 42 | var expected = [ 43 | {op: '=', val: 1}, 44 | {op: '=', val: 2}, 45 | {op: '=', val: 3}, 46 | {op: '!=', val: 4, newVal: 5}, 47 | {op: '+', val: 6}, 48 | {op: '+', val: 7} 49 | ]; 50 | 51 | var result = lcs.diff(list1, list2); 52 | 53 | assert.ok(result.every(function(r, i){ 54 | return r.op === expected[i].op && r.val === expected[i].val; 55 | })); 56 | }); 57 | }); -------------------------------------------------------------------------------- /tests/path.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var assert = require('assert'); 4 | var path = require('../src/path'); 5 | 6 | describe('Path', function() { 7 | it('escape / in string', function () { 8 | var str = 'prop/prop'; 9 | var expected = 'prop~1prop'; 10 | 11 | var result = path.escape(str); 12 | 13 | assert.strictEqual(result, expected); 14 | }); 15 | 16 | it('unescape / in string', function () { 17 | var str = 'prop~1prop'; 18 | var expected = 'prop/prop'; 19 | 20 | var result = path.unescape(str); 21 | 22 | assert.strictEqual(result, expected); 23 | }); 24 | 25 | it('reverts to original string', function () { 26 | var str = 'prop/prop'; 27 | 28 | var result = path.unescape(path.escape(str)); 29 | 30 | assert.strictEqual(result, str); 31 | }); 32 | 33 | it('returns same string when escaping is not necessary', function() { 34 | var str = 'normalstring'; 35 | 36 | var result = path.escape(str); 37 | 38 | assert.strictEqual(result, str); 39 | }); 40 | 41 | it('concatenates path', function() { 42 | var p = '/path'; 43 | var pathToAdd = 'addedPath'; 44 | 45 | var result = path.concat(p, pathToAdd); 46 | var expected = '/path/addedPath'; 47 | 48 | assert.strictEqual(result, expected); 49 | }); 50 | }); -------------------------------------------------------------------------------- /tests/performance/immutableDiff.perftest.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Immutable = require('immutable'); 4 | var veryBigArray = require('./veryBigArray'); 5 | var immutableDiff = require('../../src/diff'); 6 | var jsondiff = require('jsondiff'); 7 | 8 | var immutableDiffPerformanceTest = function(){ 9 | var list1 = Immutable.fromJS(veryBigArray); 10 | var list2 = list1.concat({x: 10, y: 7000}); 11 | 12 | var diff = immutableDiff(list1, list2); 13 | 14 | console.log(diff); 15 | }; 16 | 17 | var jsondiffPerformanceTest = function(){ 18 | var list1 = veryBigArray; 19 | var list2 = list1.concat({x: 10, y: 7000}); 20 | 21 | var diff = jsondiff.diff(list1, list2); 22 | 23 | console.log(diff); 24 | }; 25 | 26 | immutableDiffPerformanceTest(); -------------------------------------------------------------------------------- /tests/primitiveTypeDiff.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var diff = require('../src/diff'); 4 | var Immutable = require('immutable'); 5 | var JSC = require('jscheck'); 6 | var assert = require('assert'); 7 | 8 | describe('Primitive types diff', function() { 9 | var failure = null; 10 | 11 | before(function () { 12 | JSC.on_report(function(report) { 13 | console.log(report); 14 | }); 15 | 16 | JSC.on_fail(function(jsc_failure) { 17 | failure = jsc_failure; 18 | }); 19 | }); 20 | 21 | afterEach(function () { 22 | if(failure){ 23 | console.error(failure); 24 | throw failure; 25 | } 26 | }); 27 | 28 | it('check properties', function() { 29 | JSC.test( 30 | 'returns [] when equal', 31 | function(veredict, int1){ 32 | var result = diff(int1, int1); 33 | var expected = Immutable.fromJS([]); 34 | 35 | return veredict(Immutable.is(result, expected)); 36 | }, 37 | [ 38 | JSC.integer() 39 | ] 40 | ); 41 | 42 | JSC.test( 43 | 'replaces numbers', 44 | function(veredict, int1, int2){ 45 | var result = diff(int1, int2); 46 | var expected = Immutable.fromJS([ 47 | {op: 'replace', path: '/', value: int2} 48 | ]); 49 | 50 | return veredict(Immutable.is(result, expected)); 51 | }, 52 | [ 53 | JSC.integer(), 54 | JSC.integer() 55 | ] 56 | ); 57 | 58 | JSC.test( 59 | 'replaces strings', 60 | function(veredict, str1, str2){ 61 | var result = diff(str1, str2); 62 | var expected = Immutable.fromJS([ 63 | {op: 'replace', path: '/', value: str2} 64 | ]); 65 | 66 | return veredict(Immutable.is(result, expected)); 67 | }, 68 | [ 69 | JSC.string(), 70 | JSC.string() 71 | ] 72 | ); 73 | 74 | JSC.test( 75 | 'replaces arrays', 76 | function(veredict, array1, array2){ 77 | var result = diff(array1, array2); 78 | var expected = Immutable.fromJS([ 79 | {op: 'replace', path: '/', value: array2} 80 | ]); 81 | 82 | return veredict(Immutable.is(result, expected)); 83 | }, 84 | [ 85 | JSC.array(5), 86 | JSC.array(5) 87 | ] 88 | ); 89 | 90 | JSC.test( 91 | 'replaces objects', 92 | function(veredict, object1, object2){ 93 | var result = diff(object1, object2); 94 | var expected = Immutable.fromJS([ 95 | {op: 'replace', path: '/', value: object2} 96 | ]); 97 | 98 | return veredict(Immutable.is(result, expected)); 99 | }, 100 | [ 101 | JSC.object(5), 102 | JSC.object(5) 103 | ] 104 | ); 105 | }); 106 | }); -------------------------------------------------------------------------------- /tests/sequenceDiff.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var diff = require('../src/diff'); 4 | var Immutable = require('immutable'); 5 | var JSC = require('jscheck'); 6 | var assert = require('assert'); 7 | 8 | describe('Sequence diff', function() { 9 | var failure = null; 10 | 11 | before(function () { 12 | JSC.on_report(function (report) { 13 | console.log(report); 14 | }); 15 | 16 | JSC.on_fail(function (jsc_failure) { 17 | failure = jsc_failure; 18 | }); 19 | }); 20 | 21 | afterEach(function () { 22 | if(failure){ 23 | console.error(failure); 24 | throw failure; 25 | } 26 | }); 27 | 28 | it('check properties', function () { 29 | JSC.test( 30 | 'returns [] when equal', 31 | function(veredict, array){ 32 | var list1 = Immutable.fromJS(array); 33 | var list2 = Immutable.fromJS(array); 34 | 35 | var result = diff(list1, list2); 36 | 37 | return veredict(result.count() === 0); 38 | }, 39 | [ 40 | JSC.array(5, JSC.integer()) 41 | ] 42 | ); 43 | }); 44 | 45 | it('returns add operation', function () { 46 | var list1 = Immutable.fromJS([1,2,3,4]); 47 | var list2 = Immutable.fromJS([1,2,3,4,5]); 48 | 49 | var result = diff(list1, list2); 50 | var expected = Immutable.fromJS([{op: 'add', path: '/4', value: 5}]); 51 | 52 | assert.ok(Immutable.is(result, expected)); 53 | }); 54 | 55 | it('returns remove operation', function () { 56 | var list1 = Immutable.fromJS([1,2,3,4]); 57 | var list2 = Immutable.fromJS([1,2,4]); 58 | 59 | var result = diff(list1, list2); 60 | var expected = Immutable.fromJS([{op: 'remove', path: '/2'}]); 61 | 62 | assert.ok(Immutable.is(result, expected)); 63 | }); 64 | 65 | it('returns add/remove operations', function () { 66 | var list1 = Immutable.fromJS([1,2,3,4]); 67 | var list2 = Immutable.fromJS([1,2,4,5]); 68 | 69 | var result = diff(list1, list2); 70 | var expected = Immutable.fromJS([ 71 | {op: 'replace', path: '/2', value: 4}, 72 | {op: 'replace', path: '/3', value: 5} 73 | ]); 74 | 75 | assert.ok(Immutable.is(result, expected)); 76 | }); 77 | 78 | it('returns correct diff when removing sequenced items in large list', function(){ 79 | var list1 = Immutable.Range(1, 1000); 80 | var list2 = Immutable.Range(1, 900); 81 | 82 | var expected = Immutable.Repeat(Immutable.Map({ op: 'remove', path: '/899' }), 100); 83 | 84 | var result = diff(list1, list2); 85 | 86 | assert.ok(Immutable.is(result, expected)); 87 | }); 88 | 89 | it('returns correct diff when removing multiple sequenced items in large list', function(){ 90 | var list1 = Immutable.Range(1, 1000); 91 | var list2 = Immutable.Range(1, 900).concat(Immutable.Range(950, 955)); 92 | 93 | var expected = Immutable.List([ 94 | Immutable.Map({ op: "replace", path: "/899", value: 950 }), 95 | Immutable.Map({ op: "replace", path: "/900", value: 951 }), 96 | Immutable.Map({ op: "replace", path: "/901", value: 952 }), 97 | Immutable.Map({ op: "replace", path: "/902", value: 953 }), 98 | Immutable.Map({ op: "replace", path: "/903", value: 954 }) 99 | ]).concat(Immutable.Repeat(Immutable.Map({ op: 'remove', path: '/904' }), 95)); 100 | 101 | console.log(result); 102 | var result = diff(list1, list2); 103 | 104 | assert.ok(Immutable.is(result, expected)); 105 | }); 106 | 107 | it('JSCheck', function () { 108 | JSC.test( 109 | 'returns add when value is inserted in the middle of sequence', 110 | function(veredict, array, addIdx, newValue){ 111 | var list1 = Immutable.fromJS(array); 112 | var list2 = Immutable.fromJS(array); 113 | var modifiedList = list2.splice(addIdx, 0, newValue); 114 | 115 | var result = diff(list1, modifiedList); 116 | var expected = Immutable.fromJS([ 117 | {op: 'add', path: '/'+addIdx, value: newValue} 118 | ]); 119 | 120 | return veredict(Immutable.is(result, expected)); 121 | }, 122 | [ 123 | JSC.array(10, JSC.integer()), 124 | JSC.integer(0, 9), 125 | JSC.integer() 126 | ] 127 | ); 128 | 129 | JSC.test( 130 | 'returns remove', 131 | function(veredict, array, removeIdx){ 132 | var list1 = Immutable.fromJS(array); 133 | var list2 = Immutable.fromJS(array); 134 | var modifiedList = list2.splice(removeIdx, 1); 135 | 136 | var result = diff(list1, modifiedList); 137 | var expected = Immutable.fromJS([ 138 | {op: 'remove', path: '/'+removeIdx} 139 | ]); 140 | 141 | return veredict(Immutable.is(result, expected)); 142 | }, 143 | [ 144 | JSC.array(10, JSC.integer()), 145 | JSC.integer(0, 9) 146 | ] 147 | ); 148 | 149 | JSC.test( 150 | 'returns sequential removes', 151 | function(veredict, array, nRemoves){ 152 | var list1 = Immutable.fromJS(array); 153 | var list2 = Immutable.fromJS(array); 154 | var modifiedList = list2.skip(nRemoves); 155 | 156 | var result = diff(list1, modifiedList); 157 | var expected = Immutable.Repeat(Immutable.Map({op: 'remove', path: '/0'}), nRemoves); 158 | 159 | return veredict(Immutable.is(result, expected)); 160 | }, 161 | [ 162 | JSC.array(10, JSC.integer()), 163 | JSC.integer(1, 5) 164 | ] 165 | ); 166 | 167 | JSC.test( 168 | 'returns replace operations', 169 | function(veredict, array, replaceIdx, newValue){ 170 | var list1 = Immutable.fromJS(array); 171 | var list2 = Immutable.fromJS(array); 172 | var modifiedList = list2.set(replaceIdx, newValue); 173 | 174 | var result = diff(list1, modifiedList); 175 | var expected = Immutable.fromJS([ 176 | {op: 'replace', path: '/'+replaceIdx, value: newValue} 177 | ]); 178 | 179 | return veredict(Immutable.is(result, expected)); 180 | }, 181 | [ 182 | JSC.array(10, JSC.integer()), 183 | JSC.integer(0, 9), 184 | JSC.integer() 185 | ] 186 | ); 187 | }); 188 | 189 | it('large sequence diff', function() { 190 | JSC.test( 191 | 'returns add', 192 | function(veredict, array, newValue){ 193 | var list1 = Immutable.fromJS(array); 194 | var list2 = Immutable.fromJS(array); 195 | var modifiedList = list2.push(newValue); 196 | 197 | var result = diff(list1, modifiedList); 198 | var expected = Immutable.fromJS([ 199 | {op: 'add', path: '/'+array.length, value: newValue} 200 | ]); 201 | 202 | return veredict(Immutable.is(result, expected)); 203 | }, 204 | [ 205 | JSC.array(150, JSC.integer()), 206 | JSC.integer() 207 | ] 208 | ); 209 | 210 | JSC.test( 211 | 'returns replace', 212 | function(veredict, array, newValue){ 213 | var list1 = Immutable.fromJS(array); 214 | var list2 = Immutable.fromJS(array); 215 | var idx = 100; 216 | var modifiedList = list2.set(idx, newValue); 217 | 218 | var result = diff(list1, modifiedList); 219 | var expected = Immutable.fromJS([ 220 | {op: 'replace', path: '/'+idx, value: newValue} 221 | ]); 222 | 223 | return veredict(Immutable.is(result, expected)); 224 | }, 225 | [ 226 | JSC.array(150, JSC.integer()), 227 | JSC.integer() 228 | ] 229 | ); 230 | }); 231 | 232 | it('Seq test', function(){ 233 | var setDiff = diff(Immutable.Set(['1']), Immutable.Set(['1'])); 234 | assert.equal(setDiff.size, 0); 235 | 236 | setDiff = diff(Immutable.Set([1]), Immutable.Set([1,2,3])); 237 | var expected = Immutable.fromJS([ 238 | {op: 'replace', path: '/', value: Immutable.Set([1,2,3])} 239 | ]); 240 | 241 | assert.ok(Immutable.is(setDiff, expected)); 242 | }); 243 | }); --------------------------------------------------------------------------------