started testing...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2020-10-15 03:02:46 +03:00
parent fe8fed83a4
commit a7573e8f19
3 changed files with 198 additions and 196 deletions

301
diff.js
View File

@ -64,15 +64,15 @@ var MIN_TEXT_LENGTH = 100
// //
// This is different to Object.keys(..) in that it gets both enumerable // This is different to Object.keys(..) in that it gets both enumerable
// and non-enumerable keys in the whole prototype chain... // and non-enumerable keys in the whole prototype chain...
//
// XXX should this be in object.js???
var getAllKeys = function(obj){ var getAllKeys = function(obj){
var res = new Set() var res = new Set()
while(obj.__proto__ || obj === obj.__proto__){ while(obj.__proto__ || obj === obj.__proto__){
Object.getOwnPropertyNames(obj) Object.getOwnPropertyNames(obj)
.forEach(function(n){ .forEach(function(n){
res.add(n) res.add(n) })
}) obj = obj.__proto__ }
obj = obj.__proto__
}
return res } return res }
@ -82,12 +82,12 @@ var getAllKeys = function(obj){
// zip(func, array, array, ...) // zip(func, array, array, ...)
// -> [func(i, [item, item, ...]), ...] // -> [func(i, [item, item, ...]), ...]
// //
// XXX move this to ig-types
var zip = function(func, ...arrays){ var zip = function(func, ...arrays){
var i = arrays[0] instanceof Array ? 0 : arrays.shift() var i = arrays[0] instanceof Array ? 0 : arrays.shift()
if(func instanceof Array){ if(func instanceof Array){
arrays.splice(0, 0, func) arrays.splice(0, 0, func)
func = null func = null }
}
// build the zip item... // build the zip item...
// NOTE: this is done this way to preserve array sparseness... // NOTE: this is done this way to preserve array sparseness...
var s = arrays var s = arrays
@ -149,8 +149,7 @@ var getCommonSections = function(A, B, cmp, min_chunk){
&& ((a+l in A && b+l in B) ? && ((a+l in A && b+l in B) ?
cmp(A[a+l], B[b+l]) cmp(A[a+l], B[b+l])
: (!(a+l in A) && !(b+l in B)))){ : (!(a+l in A) && !(b+l in B)))){
l = chunk.length += 1 l = chunk.length += 1 }
}
// ignore small chunks... // ignore small chunks...
l = chunk.length >= min_chunk ? l = chunk.length >= min_chunk ?
chunk.length chunk.length
@ -186,11 +185,9 @@ var getCommonSections = function(A, B, cmp, min_chunk){
cache[a] = cache[a] || [] cache[a] = cache[a] || []
cache[a][b] = res cache[a][b] = res
return res return res }
}
return _getCommonSections(0, 0) return _getCommonSections(0, 0) }
}
// Get diff sections... // Get diff sections...
@ -233,11 +230,9 @@ var getDiffSections = function(A, B, cmp, min_chunk){
]) ])
// go to next gap... // go to next gap...
a = e.A + e.length a = e.A + e.length
b = e.B + e.length b = e.B + e.length })
})
return gaps return gaps }
}
// Make a proxy method... // Make a proxy method...
@ -362,8 +357,7 @@ var LogicTypePrototype = {
// cache... // cache...
c.set(obj, !!res) c.set(obj, !!res)
return !!res return !!res },
},
} }
var Pattern = var Pattern =
@ -381,8 +375,7 @@ var makeCIPattern = function(name, check, init){
}) })
init init
&& (o.__init__ = init) && (o.__init__ = init)
return object.Constructor(name, o, o) return object.Constructor(name, o, o) }
}
// Singleton ANY... // Singleton ANY...
@ -470,7 +463,8 @@ module.STRING =
: this.value != null ? : this.value != null ?
cmp(this.value, obj) cmp(this.value, obj)
: true )) }, : true )) },
function(value){ this.value = value }) function(value){
this.value = value })
// shorthand... // shorthand...
var S = module.S = STRING var S = module.S = STRING
@ -511,7 +505,8 @@ module.NUMBER =
: this.value[0] != null ? : this.value[0] != null ?
cmp(this.value[0], obj) cmp(this.value[0], obj)
: true )) }, : true )) },
function(...value){ this.value = value }) function(...value){
this.value = value })
// shorthand... // shorthand...
var N = module.N = NUMBER var N = module.N = NUMBER
@ -556,7 +551,8 @@ module.ARRAY =
return cmp(value, e) return cmp(value, e)
}).length == obj.length) }).length == obj.length)
}).length == (this.value || []).length) }, }).length == (this.value || []).length) },
function(...value){ this.value = value }) function(...value){
this.value = value })
// shorthand... // shorthand...
// NOTE: yes, ARRAY does not even contain the letter "L" but this is // NOTE: yes, ARRAY does not even contain the letter "L" but this is
@ -583,9 +579,7 @@ object.Constructor('OR', Object.assign(Object.create(Pattern.prototype), {
__cmp__: function(obj, cmp, context){ __cmp__: function(obj, cmp, context){
for(var m of this.members){ for(var m of this.members){
if(cmp(m, obj, context)){ if(cmp(m, obj, context)){
return true return true } }
}
}
return false }, return false },
__init__: function(...members){ __init__: function(...members){
this.members = members }, this.members = members },
@ -603,10 +597,8 @@ object.Constructor('XOR', Object.assign(Object.create(Pattern.prototype), {
for(var m of this.members){ for(var m of this.members){
cur = cmp(m, obj, context) cur = cmp(m, obj, context)
if(state == cur && state){ if(state == cur && state){
return false return false }
} state = cur }
state = cur
}
return state }, return state },
__init__: function(...members){ __init__: function(...members){
this.members = members }, this.members = members },
@ -620,9 +612,7 @@ object.Constructor('AND', Object.assign(Object.create(Pattern.prototype), {
__cmp__: function(obj, cmp, context){ __cmp__: function(obj, cmp, context){
for(var m of this.members){ for(var m of this.members){
if(!cmp(m, obj, context)){ if(!cmp(m, obj, context)){
return false return false } }
}
}
return true }, return true },
__init__: function(...members){ __init__: function(...members){
this.members = members }, this.members = members },
@ -657,8 +647,7 @@ object.Constructor('VAR', Object.assign(Object.create(Pattern.prototype), {
: this.pattern : this.pattern
if(cmp(pattern, obj)){ if(cmp(pattern, obj)){
ns[this.name] = obj ns[this.name] = obj
return true return true }
}
return false }, return false },
__init__: function(name, pattern){ __init__: function(name, pattern){
this.name = name this.name = name
@ -1011,14 +1000,12 @@ module.Types = {
Object.create(e[1]) Object.create(e[1])
: e[1] : e[1]
] })) ] }))
return res return res },
},
clear: function(){ clear: function(){
// XXX should we instead this.handlers.clear() ??? // XXX should we instead this.handlers.clear() ???
//this.handlers = new Map() //this.handlers = new Map()
this.handlers.clear() this.handlers.clear()
return this return this },
},
// Placeholder objects... // Placeholder objects...
@ -1060,15 +1047,13 @@ module.Types = {
o = h.get(o) o = h.get(o)
} while(o != null && h.has(o)) } while(o != null && h.has(o))
return o return o },
},
set: proxy('handlers.set', set: proxy('handlers.set',
function(res, key, handler){ function(res, key, handler){
// auto-alias... // auto-alias...
key.name key.name
&& this.set(key.name, key) && this.set(key.name, key)
return res return res }),
}),
delete: proxy('handlers.delete'), delete: proxy('handlers.delete'),
// sorted list of types... // sorted list of types...
@ -1083,8 +1068,7 @@ module.Types = {
k = h.get(k) k = h.get(k)
return k != null return k != null
&& !h.has(k) && !h.has(k)
&& order.set(k, i++) && order.set(k, i++) })
})
.sort(function(a, b){ .sort(function(a, b){
a = h.get(a) a = h.get(a)
b = h.get(b) b = h.get(b)
@ -1096,11 +1080,11 @@ module.Types = {
a.priority > 0 ? -1 : 1 a.priority > 0 ? -1 : 1
: b.priority ? : b.priority ?
b.priority > 0 ? 1 : -1 b.priority > 0 ? 1 : -1
: order.get(a) - order.get(b) : order.get(a) - order.get(b) }) },
})
},
get typeNames(){ get typeNames(){
return this.typeKeys.map(function(e){ return e.name || e }) }, return this.typeKeys
.map(function(e){
return e.name || e }) },
get types(){ get types(){
var that = this var that = this
return this.typeKeys return this.typeKeys
@ -1149,9 +1133,7 @@ module.Types = {
if(h.compatible if(h.compatible
&& h.compatible(A, options)){ && h.compatible(A, options)){
type = t type = t
break break } }
}
}
// search instances... // search instances...
if(!type){ if(!type){
@ -1162,16 +1144,12 @@ module.Types = {
if(t === Object if(t === Object
// skip non-conctructor stuff... // skip non-conctructor stuff...
|| !(t instanceof Function)){ || !(t instanceof Function)){
continue continue }
}
// full hit -- type match... // full hit -- type match...
if(A instanceof t){ if(A instanceof t){
type = t type = t
break break } } }
}
}
}
// combinational types... // combinational types...
if(B !== undefined){ if(B !== undefined){
@ -1183,12 +1161,9 @@ module.Types = {
// partial hit -- type mismatch... // partial hit -- type mismatch...
} else { } else {
return 'Basic' return 'Basic' } }
}
}
return type return type },
},
// Handle the difference between A and B... // Handle the difference between A and B...
// //
@ -1205,24 +1180,21 @@ module.Types = {
if(handler == null if(handler == null
|| !(handler instanceof Function || !(handler instanceof Function
|| handler.handle)){ || handler.handle)){
throw new TypeError('Diff: can\'t handle: ' + type) throw new TypeError('Diff: can\'t handle: ' + type) }
}
// call the handler... // call the handler...
handler.handle ? handler.handle ?
handler.handle.call(this, obj, diff, A, B, options) handler.handle.call(this, obj, diff, A, B, options)
: handler.call(this, obj, diff, A, B, options) : handler.call(this, obj, diff, A, B, options)
return obj return obj },
},
// Diff format walker... // Diff format walker...
// //
walk: function(diff, func, path){ walk: function(diff, func, path){
// no changes... // no changes...
if(diff == null){ if(diff == null){
return null return null }
}
// flat diff... // flat diff...
if(diff instanceof Array){ if(diff instanceof Array){
return diff.map(func) return diff.map(func)
@ -1231,8 +1203,7 @@ module.Types = {
} else { } else {
var handler = this.get(diff.type) var handler = this.get(diff.type)
if(handler == null || !handler.walk){ if(handler == null || !handler.walk){
throw new TypeError('Can\'t walk type: '+ diff.type) throw new TypeError('Can\'t walk type: '+ diff.type) }
}
return handler.walk.call(this, diff, func, path || []) } }, return handler.walk.call(this, diff, func, path || []) } },
// Flatten the tree diff format... // Flatten the tree diff format...
@ -1252,8 +1223,7 @@ module.Types = {
options = options || {} options = options || {}
var res = [] var res = []
this.walk(diff, function(change){ res.push(change) }) this.walk(diff, function(change){ res.push(change) })
return res return res },
},
// User API... // User API...
@ -1275,9 +1245,7 @@ module.Types = {
type.reverse type.reverse
&& (c = type.reverse.call(that, c)) }) && (c = type.reverse.call(that, c)) })
return c return c }) },
})
},
// Filter diff changes and return a new diff... // Filter diff changes and return a new diff...
// //
@ -1320,8 +1288,9 @@ module.Types = {
return e == '*' ? ANY return e == '*' ? ANY
: e[0] == '!' ? NOT(e.slice(1)) : e[0] == '!' ? NOT(e.slice(1))
: e }) : e })
return e.length == 1 ? e[0] : OR(...e) return e.length == 1 ?
}) e[0]
: OR(...e) })
: filter : filter
// path filter (non-function)... // path filter (non-function)...
@ -1345,8 +1314,7 @@ module.Types = {
: (res.length == 0 || res[n] == '**') ? : (res.length == 0 || res[n] == '**') ?
res.push([e]) res.push([e])
: res[n].push(e) : res[n].push(e)
return res return res }, [])
}, [])
// min length... // min length...
var min = pattern var min = pattern
@ -1381,11 +1349,9 @@ module.Types = {
// XXX Q: should we ignore the last element of the path??? // XXX Q: should we ignore the last element of the path???
filter = function(change, i, lst){ filter = function(change, i, lst){
return test(change.path, pattern) } return test(change.path, pattern) } }
}
return diff.filter(filter.bind(this)) return diff.filter(filter.bind(this)) },
},
// XXX there are two approaches to this: // XXX there are two approaches to this:
// 1) naive: simply concatenate all the changes in order... // 1) naive: simply concatenate all the changes in order...
@ -1393,8 +1359,7 @@ module.Types = {
// XXX do we need a conflict resolution policy??? // XXX do we need a conflict resolution policy???
merge: function(diff, other){ merge: function(diff, other){
// XXX // XXX
return this.flatten(diff).concat(this.flatten(other)) return this.flatten(diff).concat(this.flatten(other)) },
},
// Build a diff between A and B... // Build a diff between A and B...
// //
@ -1452,12 +1417,12 @@ module.Types = {
: {}) : {})
cache = context.cache = context.cache || new Map() cache = context.cache = context.cache || new Map()
// cached diff... // cached diff...
var diff = cache.diff = cache.diff || function(a, b){ var diff = cache.diff = cache.diff
var l2 = cache.get(a) || new Map() || function(a, b){
var d = l2.get(b) || that.diff(a, b, options, context) var l2 = cache.get(a) || new Map()
cache.set(a, l2.set(b, d)) var d = l2.get(b) || that.diff(a, b, options, context)
return d cache.set(a, l2.set(b, d))
} return d }
// check: if same/matching object... // check: if same/matching object...
@ -1469,13 +1434,11 @@ module.Types = {
// check with everything but the root cached/diffed... // check with everything but the root cached/diffed...
// XXX not sure if this is efficient... // XXX not sure if this is efficient...
if(bcmp(A, B, cmp)){ if(bcmp(A, B, cmp)){
return null return null }
}
// check: builtin types... // check: builtin types...
if(this.DIFF_TYPES.has(A) || this.DIFF_TYPES.has(B)){ if(this.DIFF_TYPES.has(A) || this.DIFF_TYPES.has(B)){
return this.handle('Basic', {}, diff, A, B, options) return this.handle('Basic', {}, diff, A, B, options) }
}
// find the matching type... // find the matching type...
var type = this.detect(A, B, options) var type = this.detect(A, B, options)
@ -1485,8 +1448,7 @@ module.Types = {
if(!options.no_attributes if(!options.no_attributes
&& !this.get(type).no_attributes){ && !this.get(type).no_attributes){
// XXX need to strip array items from this... // XXX need to strip array items from this...
this.handle(Object, res, diff, A, B, options) this.handle(Object, res, diff, A, B, options) }
}
// cleanup -- remove items containing empty arrays... // cleanup -- remove items containing empty arrays...
Object.keys(res) Object.keys(res)
@ -1498,8 +1460,7 @@ module.Types = {
// return only non-empty diff states... // return only non-empty diff states...
return Object.keys(res).length == 1 ? return Object.keys(res).length == 1 ?
null null
: res : res },
},
// XXX can we split out the diff walker and simply reuse it for: // XXX can we split out the diff walker and simply reuse it for:
// .diff(..), .cmp(..), ... // .diff(..), .cmp(..), ...
@ -1569,8 +1530,7 @@ module.Types = {
var l2 = cache.get(a) || new Map() var l2 = cache.get(a) || new Map()
var d = l2.get(b) || that._diff(a, b, options, context) var d = l2.get(b) || that._diff(a, b, options, context)
cache.set(a, l2.set(b, d)) cache.set(a, l2.set(b, d))
return d return d }
}
// XXX ??? // XXX ???
options.cmp = cmp options.cmp = cmp
@ -1601,8 +1561,7 @@ module.Types = {
// we have a match -> no changes, just cache... // we have a match -> no changes, just cache...
if(cmp(A, B)){ if(cmp(A, B)){
cache.set(A, cache_l2.set(B, undefined)) cache.set(A, cache_l2.set(B, undefined))
return return }
}
// handler... // handler...
var handler = that.get( var handler = that.get(
@ -1620,8 +1579,7 @@ module.Types = {
: false : false
// unhandled type... // unhandled type...
if(!handler){ if(!handler){
throw new TypeError('Diff: can\'t handle: ' + type) throw new TypeError('Diff: can\'t handle: ' + type) }
}
// call the handler... // call the handler...
var res = handler.call(that, A, B, next, options) var res = handler.call(that, A, B, next, options)
@ -1637,15 +1595,12 @@ module.Types = {
return res == null ? return res == null ?
res res
: diff.concat(res : diff.concat(res
.map(updatePath(path))) .map(updatePath(path))) } },
}
},
// diff... // diff...
[], [],
// node format: // node format:
// [ <path>, <A>, <B> ] // [ <path>, <A>, <B> ]
[[], A, B]) [[], A, B]) },
},
// Deep-compare A and B... // Deep-compare A and B...
// //
@ -1680,8 +1635,7 @@ module.Types = {
.walk(diff.diff, function(change){ .walk(diff.diff, function(change){
// replace the object itself... // replace the object itself...
if(change.path.length == 0){ if(change.path.length == 0){
return change.B return change.B }
}
var parent var parent
var parent_key var parent_key
@ -1704,13 +1658,10 @@ module.Types = {
parent[parent_key] = res parent[parent_key] = res
} else { } else {
obj = res obj = res }
}
return obj return obj })
}) .pop()) },
.pop())
},
// Call the post-patch method of the handlers... // Call the post-patch method of the handlers...
// //
postPatch: function(res){ postPatch: function(res){
@ -1755,8 +1706,7 @@ module.Types = {
// check root... // check root...
if(key == null){ if(key == null){
return !that.cmp(change.A, target) return !that.cmp(change.A, target) }
}
// keep only the mismatching changes... // keep only the mismatching changes...
return change.type && that.get(change.type).check ? return change.type && that.get(change.type).check ?
@ -1771,9 +1721,7 @@ module.Types = {
!that.cmp(change.A, !that.cmp(change.A,
target.slice(key[0][0], key[0][0] + target.length[0])) target.slice(key[0][0], key[0][0] + target.length[0]))
: !that.cmp(change.A, target[key[0]])) : !that.cmp(change.A, target[key[0]]))
: !that.cmp(change.A, target[key]) : !that.cmp(change.A, target[key]) }) },
})
},
} }
@ -1950,8 +1898,7 @@ Types.set('Basic', {
;(!options.keep_none && B === NONE) ;(!options.keep_none && B === NONE)
|| (obj.B = B) || (obj.B = B)
return [obj] return [obj] },
},
}) })
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -1991,16 +1938,13 @@ Types.set(Object, {
delete obj[key] delete obj[key]
} else { } else {
obj[key] = change.B obj[key] = change.B }
}
// array item... // array item...
// XXX should this make this decision??? // XXX should this make this decision???
} else { } else {
this.typeCall(Array, 'patch', obj, key, change, ...rest) this.typeCall(Array, 'patch', obj, key, change, ...rest) }
} return obj },
return obj
},
// XXX EXPERIMENTAL... // XXX EXPERIMENTAL...
get: function(obj, key){ get: function(obj, key){
@ -2044,8 +1988,7 @@ Types.set(Object, {
options)] options)]
// remove seen keys... // remove seen keys...
delete B_index[ka] delete B_index[ka]
return res return res })
})
// keys present only in B... // keys present only in B...
.concat(Object.keys(B_index) .concat(Object.keys(B_index)
.map(function(kb){ .map(function(kb){
@ -2057,8 +2000,7 @@ Types.set(Object, {
// cleanup... // cleanup...
.filter(function(e){ .filter(function(e){
return e[1] !== null }) return e[1] !== null })
return items return items },
},
// XXX EXPERIMENTAL: used by Types._diff(..) // XXX EXPERIMENTAL: used by Types._diff(..)
@ -2074,8 +2016,7 @@ Types.set(Object, {
return e != null }) return e != null })
// XXX add attribute order support... // XXX add attribute order support...
// XXX // XXX
return diff return diff },
},
// return aligned attr sets... // return aligned attr sets...
// format: // format:
// [ // [
@ -2111,8 +2052,7 @@ Types.set(Object, {
] ]
// remove seen keys... // remove seen keys...
delete B_index[ka] delete B_index[ka]
return res return res })
})
// keys present only in B... // keys present only in B...
.concat(Object.keys(B_index) .concat(Object.keys(B_index)
.map(function(kb){ .map(function(kb){
@ -2121,8 +2061,7 @@ Types.set(Object, {
EMPTY, EMPTY,
B[kb], B[kb],
]})) ]}))
return items return items },
},
}) })
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -2143,8 +2082,7 @@ Types.set(Array, {
.filter(function(e){ .filter(function(e){
return e.length == 2 ? return e.length == 2 ?
attrs.push(e) && false attrs.push(e) && false
: true : true })
})
.map(function(e){ .map(function(e){
var v = e[2] var v = e[2]
@ -2154,8 +2092,7 @@ Types.set(Array, {
: [e[0], e[1]] : [e[0], e[1]]
var p = path.concat([i]) var p = path.concat([i])
return that.walk(v, func, p) return that.walk(v, func, p) })
})
// length... // length...
// NOTE: we keep this last as the length should be the last // NOTE: we keep this last as the length should be the last
// thing to get patched... // thing to get patched...
@ -2167,8 +2104,7 @@ Types.set(Array, {
}) })
: []) : [])
// attributes... // attributes...
.concat(this.typeCall(Object, 'walk', {items: attrs}, func, path)) .concat(this.typeCall(Object, 'walk', {items: attrs}, func, path)) },
},
// XXX add object compatibility checks... // XXX add object compatibility checks...
// XXX revise... // XXX revise...
patch: function(obj, key, change){ patch: function(obj, key, change){
@ -2203,9 +2139,7 @@ Types.set(Array, {
// XXX test... // XXX test...
if(!('B' in change)){ if(!('B' in change)){
for(var n=j; n <= lj + j - li; n++){ for(var n=j; n <= lj + j - li; n++){
delete obj[n] delete obj[n] } }
}
}
// item manipulation... // item manipulation...
} else { } else {
@ -2242,23 +2176,17 @@ Types.set(Array, {
delete obj[j] delete obj[j]
} else { } else {
obj[j] = change.B obj[j] = change.B }
}
// XXX this is essentially the same as the above case, do we need both?? // XXX this is essentially the same as the above case, do we need both??
} else { } else {
obj[j] = change.B obj[j] = change.B } }
}
}
return obj return obj },
},
reverse: function(change){ reverse: function(change){
if('length' in change){ if('length' in change){
change.length = change.length.slice().reverse() change.length = change.length.slice().reverse() }
} return change },
return change
},
// XXX EXPERIMENTAL... // XXX EXPERIMENTAL...
get: function(obj, key){ get: function(obj, key){
@ -2280,10 +2208,8 @@ Types.set(Array, {
// item... // item...
} else { } else {
obj[key] = value obj[key] = value }
} return this },
return this
},
// part handlers... // part handlers...
items: function(diff, A, B, options){ items: function(diff, A, B, options){
@ -2374,8 +2300,7 @@ Types.set(Array, {
: []) : [])
}) })
.reduce(function(res, e){ .reduce(function(res, e){
return res.concat(e) }, []) return res.concat(e) }, []) },
},
// XXX // XXX
order: function(diff, A, B, options){ order: function(diff, A, B, options){
// XXX // XXX
@ -2485,11 +2410,9 @@ Types.set(Array, {
path: ['length'], path: ['length'],
A: A.length, A: A.length,
B: B.length, B: B.length,
}) }) }
}
return diff return diff },
},
}) })
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -2580,8 +2503,7 @@ Types.set('Text', {
var res = cache[path] = this.typeCall(Array, 'patch', lines, key, change) var res = cache[path] = this.typeCall(Array, 'patch', lines, key, change)
return res return res },
},
// XXX EXPERIMENTAL... // XXX EXPERIMENTAL...
get: function(obj, key){ get: function(obj, key){
@ -2605,12 +2527,9 @@ Types.set('Text', {
} else { } else {
path.slice(0, -1) path.slice(0, -1)
.reduce(function(res, k){ .reduce(function(res, k){
return res[k] }, res)[path.pop()] = text return res[k] }, res)[path.pop()] = text } })
}
})
return res return res },
},
}) })
@ -2723,16 +2642,14 @@ var DiffClassPrototype = {
clone: function(name){ clone: function(name){
var cls = Object.create(this.__proto__) var cls = Object.create(this.__proto__)
cls.types = this.types.clone() cls.types = this.types.clone()
return object.Constructor(name || 'EDiff', cls, this()) return object.Constructor(name || 'EDiff', cls, this()) },
},
// proxy generic stuff to .types... // proxy generic stuff to .types...
cmp: proxy('types.cmp'), cmp: proxy('types.cmp'),
vars: function(pattern, obj){ vars: function(pattern, obj){
var o = {} var o = {}
this.cmp(pattern, obj, null, o) this.cmp(pattern, obj, null, o)
return o.ns || {} return o.ns || {} },
},
// XXX do format/version conversion... // XXX do format/version conversion...
fromJSON: function(json){ fromJSON: function(json){
@ -2809,8 +2726,7 @@ var DiffPrototype = {
diff.diff(A, B, options) diff.diff(A, B, options)
: diff.flatten(diff.diff(A, B, options), options) : diff.flatten(diff.diff(A, B, options), options)
this.timestamp = Date.now() this.timestamp = Date.now() },
},
// XXX should this be a deep copy??? // XXX should this be a deep copy???
clone: function(){ clone: function(){
@ -2825,8 +2741,7 @@ var DiffPrototype = {
: this.diff ? : this.diff ?
Object.assign({}, this.diff) Object.assign({}, this.diff)
: null : null
return res return res },
},
check: function(obj){ check: function(obj){
return Object.assign( return Object.assign(
@ -2848,22 +2763,19 @@ var DiffPrototype = {
var res = this.clone() var res = this.clone()
res.diff = Object.create(this.constructor.types).reverse(this.diff) res.diff = Object.create(this.constructor.types).reverse(this.diff)
res.parent = this res.parent = this
return res return res },
},
filter: function(filter){ filter: function(filter){
var res = this.clone() var res = this.clone()
res.diff = this.constructor.types.filter.call(this, this.diff, filter) res.diff = this.constructor.types.filter.call(this, this.diff, filter)
res.parent = this res.parent = this
return res return res },
},
// XXX should this set .parent ???? // XXX should this set .parent ????
merge: function(diff){ merge: function(diff){
var res = this.clone() var res = this.clone()
res.diff = this.constructor.types.merge.call(this, this.diff, diff.diff) res.diff = this.constructor.types.merge.call(this, this.diff, diff.diff)
res.parent = this res.parent = this
return res return res },
},
// XXX EXPERIMENTAL... // XXX EXPERIMENTAL...
end: function(){ end: function(){
@ -2880,8 +2792,7 @@ var DiffPrototype = {
// XXX these need to be prepared for JSON compatibility... // XXX these need to be prepared for JSON compatibility...
options: this.options, options: this.options,
diff: this.diff, diff: this.diff,
} } },
},
} }
var Diff = var Diff =

View File

@ -25,6 +25,9 @@
"homepage": "https://github.com/flynx/object-diff.js#readme", "homepage": "https://github.com/flynx/object-diff.js#readme",
"dependencies": { "dependencies": {
"generic-walk": "^1.4.0", "generic-walk": "^1.4.0",
"ig-object": "^2.0.0" "ig-object": "^2.2.0"
},
"devDependencies": {
"ig-test": "*"
} }
} }

88
test.js Normal file
View File

@ -0,0 +1,88 @@
#!/usr/bin/env node
/**********************************************************************
*
* test.js
*
* Repo and docs:
* https://github.com/flynx/test.js
*
***********************************************/ /* c8 ignore next 2 */
((typeof define)[0]=='u'?function(f){module.exports=f(require)}:define)
(function(require){ var module={} // make module AMD/node compatible...
/*********************************************************************/
var test = require('ig-test')
var diff = require('./diff')
var format = require('./format')
//---------------------------------------------------------------------
test.Setups({
// XXX make this return a list...
basic: function(assert){
return [
{
A: {}, B: {},
cmp: true,
},
{
A: [], B: [],
cmp: true,
},
{
A: 0, B: 0,
cmp: true,
},
{
A: 123, B: 123,
cmp: true,
},
{
A: false, B: false,
cmp: true,
},
{
A: undefined, B: undefined,
cmp: true,
},
// XXX special case -- fails....
{
A: NaN, B: NaN,
cmp: true,
},
] },
})
test.Tests({
cmp: function(assert, setup){
setup = setup instanceof Array ? setup : [setup]
setup.forEach(function(e){
var res
'cmp' in e
&& assert(
(res = diff.cmp(e.A, e.B)) == e.cmp,
`cmp(..): cmp(${e.A}, ${e.B}) should be ${e.cmp} got ${res}`) }) },
})
test.Cases({
'basics': function(assert){
// XXX move reference objects + expected diffs to setups
a = {}
b = {}
assert(diff.Diff(a, b), 'Diff(..)')
},
})
//---------------------------------------------------------------------
typeof(__filename) != 'undefined'
&& __filename == (require.main || {}).filename
&& test.run()
/**********************************************************************
* vim:set ts=4 sw=4 : */ return module })