immutablediff
Version:
Create RFC 6902 style patches between Immutable.JS data structures
452 lines (371 loc) • 12.3 kB
JavaScript
'use strict';
var diff = require('../src/diff');
var Immutable = require('immutable');
var JSC = require('jscheck');
var assert = require('assert');
describe('Map diff', function(){
var failure = null;
before(function(){
JSC.on_report(function(report){
console.log(report);
});
JSC.on_fail(function(jsc_failure){
failure = jsc_failure;
});
});
afterEach(function () {
if(failure){
console.error(failure);
throw failure;
}
});
it('returns empty diff when both values are null', function() {
var result = diff(null, null);
assert.ok(result.count() === 0);
});
it('check properties', function(){
JSC.test(
'returns [] when equal',
function(veredict, obj){
var map1 = Immutable.fromJS(obj);
var map2 = Immutable.fromJS(obj);
var result = diff(map1, map2);
return veredict(result.count() === 0);
},
[
JSC.object(5)
]
);
JSC.test(
'returns add op when missing attribute',
function(veredict, obj, obj2){
var map1 = Immutable.fromJS(obj);
var map2 = Immutable.fromJS(obj).set('key2', obj2.key2);
var result = diff(map1, map2);
var expected = Immutable.fromJS([{op: 'add', path: '/key2', value: obj2.key2}]);
return veredict(Immutable.is(result, expected));
},
[
JSC.object({
key: JSC.integer()
}),
JSC.object({
key2: JSC.integer()
})
]
);
JSC.test(
'returns replace op when same attribute with different values',
function(veredict, obj, newValue){
var map1 = Immutable.fromJS(obj);
var map2 = Immutable.fromJS(obj).set('key', newValue);
var result = diff(map1, map2);
var expected = Immutable.fromJS([{op: 'replace', path: '/key', value: newValue}]);
return veredict(Immutable.is(result, expected));
},
[
JSC.object({
key: JSC.integer(1, 100)
}),
JSC.integer(101, 200)
]
);
JSC.test(
'returns remove op when attribute is missing',
function(veredict, obj){
var map1 = Immutable.fromJS(obj);
var map2 = Immutable.Map();
var result = diff(map1, map2);
var expected = Immutable.fromJS([{op: 'remove', path: '/key'}]);
return veredict(Immutable.is(result, expected));
},
[
JSC.object({
key: JSC.integer()
})
]
);
});
it('check nested structures', function(){
JSC.test(
'returns add op when missing attribute in nested structure',
function(veredict, obj, obj2){
var map1 = Immutable.fromJS(obj);
var map2 = Immutable.fromJS(obj).setIn(['b', 'd'], obj2.d);
var result = diff(map1, map2);
var expected = Immutable.fromJS([{op: 'add', path: '/b/d', value: obj2.d}]);
return veredict(Immutable.is(result, expected));
},
[
JSC.object({
a: JSC.integer(),
b: JSC.object({
c: JSC.integer()
})
}),
JSC.object({
d: JSC.integer()
})
]
);
JSC.test(
'returns replace op when different value in nested structure',
function(veredict, obj, obj2){
var map1 = Immutable.fromJS(obj);
var map2 = Immutable.fromJS(obj).setIn(['b', 'c'], obj2.c);
var result = diff(map1, map2);
var expected = Immutable.fromJS([{op: 'replace', path: '/b/c', value: obj2.c}]);
return veredict(Immutable.is(result, expected));
},
[
JSC.object({
a: JSC.integer(),
b: JSC.object({
c: JSC.integer(1, 100)
})
}),
JSC.object({
c: JSC.integer(101, 200)
})
]
);
JSC.test(
'returns remove op when attribute removed in nested structure',
function(veredict, obj, obj2){
var map1 = Immutable.fromJS(obj).setIn(['b', 'd'], obj2.d);
var map2 = Immutable.fromJS(obj);
var result = diff(map1, map2);
var expected = Immutable.fromJS([{op: 'remove', path: '/b/d'}]);
return veredict(Immutable.is(result, expected));
},
[
JSC.object({
a: JSC.integer(),
b: JSC.object({
c: JSC.integer()
})
}),
JSC.object({
d: JSC.integer()
})
]
);
JSC.test(
'no replace in equal nested structure',
function(veredict, obj, obj2){
var map1 = Immutable.fromJS(obj);
var map2 = Immutable.fromJS(obj).set('a', obj2.a);
var result = diff(map1, map2);
var expected = Immutable.fromJS([{op: 'replace', path: '/a', value: obj2.a}]);
return veredict(Immutable.is(result, expected));
},
[
JSC.object({
a: JSC.integer(),
b: JSC.object({
c: JSC.integer()
})
}),
JSC.object({
a: JSC.integer()
})
]
);
JSC.test(
'add/remove when different nested structure',
function(veredict, obj, obj2){
var map1 = Immutable.fromJS(obj);
var map2 = Immutable.fromJS(obj).set('b', Immutable.fromJS(obj2));
var result = diff(map1, map2);
var expected = Immutable.fromJS([
{op: 'remove', path: '/b/c'},
{op: 'add', path: '/b/e', value: obj2.e},
]);
return veredict(Immutable.is(result, expected));
},
[
JSC.object({
a: JSC.integer(),
b: JSC.object({
c: JSC.integer()
})
}),
JSC.object({
e: JSC.integer()
})
]
);
});
it('check nested indexed sequences', function () {
JSC.test(
'add when value added in nested sequence',
function(veredict, obj, newInt){
var map1 = Immutable.fromJS(obj);
var map2 = Immutable.fromJS(obj).updateIn(['b', 'c'], function(list){
return list.push(newInt);
});
var result = diff(map1, map2);
var expected = Immutable.fromJS([{op: 'add', path: '/b/c/5', value: newInt}]);
return veredict(Immutable.is(result, expected));
},
[
JSC.object({
a: JSC.integer(),
b: JSC.object({
c: JSC.array(5, JSC.integer())
})
}),
JSC.integer()
]
);
JSC.test(
'remove when value removed in nested sequence',
function(veredict, obj, removeIdx){
var map1 = Immutable.fromJS(obj);
var map2 = Immutable.fromJS(obj).updateIn(['b', 'c'], function(list){
return list.splice(removeIdx, 1);
});
var result = diff(map1, map2);
var expected = Immutable.fromJS([{op: 'remove', path: '/b/c/'+removeIdx}]);
return veredict(Immutable.is(result, expected));
},
[
JSC.object({
a: JSC.integer(),
b: JSC.object({
c: JSC.array(10, JSC.integer())
})
}),
JSC.integer(0, 9)
]
);
JSC.test(
'replace when values are replaced in nested sequence',
function(veredict, obj, replaceIdx, newValue){
var map1 = Immutable.fromJS(obj);
var map2 = Immutable.fromJS(obj).updateIn(['b', 'c'], function(list){
return list.set(replaceIdx, newValue);
});
var result = diff(map1, map2);
var expected = Immutable.fromJS([
{op: 'replace', path: '/b/c/'+replaceIdx, value: newValue}
]);
return veredict(Immutable.is(result, expected));
},
[
JSC.object({
a: JSC.integer(),
b: JSC.object({
c: JSC.array(10, JSC.integer())
})
}),
JSC.integer(0, 9),
JSC.integer()
]
);
});
it('check map in indexed sequence', function(){
var array1 = [{a: 1}, {a: 2}, {a: 3}];
var array2 = [{a: 1}, {a: 2, b:2.5}, {a: 3}];
var list1 = Immutable.fromJS(array1);
var list2 = Immutable.fromJS(array2);
var result = diff(list1, list2);
var expected = Immutable.fromJS([{op: 'add', path: '/1/b', value: 2.5}]);
assert.ok(Immutable.is(result, expected));
});
describe('handling nulls', function() {
it('replaces null for immutable value', function() {
var map1 = null;
var map2 = Immutable.fromJS({a: 1});
var result = diff(map1, map2);
var expected = Immutable.fromJS([{op: 'replace', path: '/', value: map2}]);
assert.ok(Immutable.is(result, expected));
});
it('replaces value for null', function() {
var map1 = Immutable.fromJS({a: 1});
var map2 = null;
var result = diff(map1, map2);
var expected = Immutable.fromJS([{op: 'replace', path: '/', value: map2}]);
assert.ok(Immutable.is(result, expected));
});
it('replaces null value in map', function() {
var map1 = Immutable.fromJS({a: null});
var map2 = Immutable.fromJS({a: 1});
var result = diff(map1, map2);
var expected = Immutable.fromJS([{op: 'replace', path: '/a', value: 1}]);
assert.ok(Immutable.is(result, expected));
});
it('replaces null value in map for empty map', function() {
var map1 = Immutable.fromJS({a: null});
var map2 = Immutable.fromJS({});
var result = diff(map1, map2);
var expected = Immutable.fromJS([{op: 'remove', path: '/a'}]);
assert.ok(Immutable.is(result, expected));
});
});
describe('path escaping', function() {
it('add unescaped path', function() {
var map1 = Immutable.fromJS({'a': 1, 'b': {'c': 3}});
var map2 = Immutable.fromJS({'a': 1, 'b': {'c': 3, 'prop/prop': 4}});
var result = diff(map1, map2);
var expected = Immutable.fromJS([
{op: 'add', path: '/b/prop~1prop', value: 4}
]);
assert.ok(Immutable.is(result, expected));
});
it('replaces unescaped path', function() {
var map1 = Immutable.fromJS({'a': 1, 'b': {'c': 3, 'prop/prop': 4}});
var map2 = Immutable.fromJS({'a': 1, 'b': {'c': 3, 'prop/prop': 10}});
var result = diff(map1, map2);
var expected = Immutable.fromJS([
{op: 'replace', path: '/b/prop~1prop', value: 10}
]);
assert.ok(Immutable.is(result, expected));
});
it('removes unescaped path', function() {
var map1 = Immutable.fromJS({'a': 1, 'b': {'c': 3, 'prop/prop': 4}});
var map2 = Immutable.fromJS({'a': 1, 'b': {'c': 3}});
var result = diff(map1, map2);
var expected = Immutable.fromJS([
{op: 'remove', path: '/b/prop~1prop'}
]);
assert.ok(Immutable.is(result, expected));
});
it('add unescaped path in nested map', function() {
var map1 = Immutable.fromJS({'a': 1, 'test/test': {'c': 3}});
var map2 = Immutable.fromJS({'a': 1, 'test/test': {'c': 3, 'prop/prop': 4}});
var result = diff(map1, map2);
var expected = Immutable.fromJS([
{op: 'add', path: '/test~1test/prop~1prop', value: 4}
]);
assert.ok(Immutable.is(result, expected));
});
it('add unescaped path in nested sequence', function() {
var map1 = Immutable.fromJS({'a': 1, 'test/test': [0, 1, 2]});
var map2 = Immutable.fromJS({'a': 1, 'test/test': [0, 1, 2, 3]});
var result = diff(map1, map2);
var expected = Immutable.fromJS([
{op: 'add', path: '/test~1test/3', value: 3}
]);
assert.ok(Immutable.is(result, expected));
});
});
it('replace primitive value for nested map', function() {
var map1 = Immutable.fromJS({a:false});
var map2 = Immutable.fromJS({a:{b:3}});
var result = diff(map1, map2);
var expected = Immutable.fromJS([
{ op: 'replace', path: '/a', value: Immutable.fromJS({ b: 3 }) }
]);
assert.ok(Immutable.is(result, expected));
});
it('replace nested map with primitive value', function() {
var map1 = Immutable.fromJS({a:{b:3}});
var map2 = Immutable.fromJS({a:false});
var result = diff(map1, map2);
var expected = Immutable.fromJS([
{ op: 'replace', path: '/a', value: false }
]);
assert.ok(Immutable.is(result, expected));
});
});