@ -2,10 +2,345 @@ var assert = require('assert');
var util = require ( '../lib/util' ) ;
describe ( 'util' , function ( ) {
/ * *
* Tests for copy and extend methods .
*
* Goal : to cover all possible paths within the tested method ( s )
*
*
* * * NOTES * *
*
* - All these methods have the inherent flaw that it ' s possible to define properties
* on an object with value 'undefined' . e . g . in ` node ` :
*
* > a = { b : undefined }
* > a . hasOwnProperty ( 'b' )
* true
*
* The logic for handling this in the code is minimal and accidental . For the time being ,
* this flaw is ignored .
* /
describe ( 'extend routines' , function ( ) {
/ * *
* Check if values have been copied over from b to a as intended
* /
function checkExtended ( a , b , checkCopyTarget = false ) {
var result = {
color : 'green' ,
sub : {
enabled : false ,
sub2 : {
font : 'awesome'
}
}
} ;
assert ( a . color !== undefined && a . color === result . color ) ;
assert ( a . notInSource === true ) ;
if ( checkCopyTarget ) {
assert ( a . notInTarget === true ) ;
} else {
assert ( a . notInTarget === undefined ) ;
}
var sub = a . sub ;
assert ( sub !== undefined ) ;
assert ( sub . enabled !== undefined && sub . enabled === result . sub . enabled ) ;
assert ( sub . notInSource === true ) ;
if ( checkCopyTarget ) {
assert ( sub . notInTarget === true ) ;
} else {
assert ( sub . notInTarget === undefined ) ;
}
sub = a . sub . sub2 ;
assert ( sub !== undefined ) ;
assert ( sub !== undefined && sub . font !== undefined && sub . font === result . sub . sub2 . font ) ;
assert ( sub . notInSource === true ) ;
assert ( a . subNotInSource !== undefined ) ;
if ( checkCopyTarget ) {
assert ( a . subNotInTarget . enabled === true ) ;
assert ( sub . notInTarget === true ) ;
} else {
assert ( a . subNotInTarget === undefined ) ;
assert ( sub . notInTarget === undefined ) ;
}
}
/ * *
* Spot check on values of a unchanged as intended
* /
function testAUnchanged ( a ) {
var sub = a . sub ;
assert ( sub !== undefined ) ;
assert ( sub . enabled !== undefined && sub . enabled === true ) ;
assert ( sub . notInSource === true ) ;
assert ( sub . notInTarget === undefined ) ;
assert ( sub . deleteThis === true ) ;
sub = a . sub . sub2 ;
assert ( sub !== undefined ) ;
assert ( sub !== undefined && sub . font !== undefined && sub . font === 'arial' ) ;
assert ( sub . notInSource === true ) ;
assert ( sub . notInTarget === undefined ) ;
assert ( a . subNotInSource !== undefined ) ;
assert ( a . subNotInTarget === undefined ) ;
}
function initA ( ) {
return {
color : 'red' ,
notInSource : true ,
sub : {
enabled : true ,
notInSource : true ,
sub2 : {
font : 'arial' ,
notInSource : true ,
} ,
deleteThis : true ,
} ,
subNotInSource : {
enabled : true ,
} ,
deleteThis : true ,
subDeleteThis : {
enabled : true ,
} ,
} ;
}
beforeEach ( function ( ) {
this . a = initA ( ) ;
this . b = {
color : 'green' ,
notInTarget : true ,
sub : {
enabled : false ,
notInTarget : true ,
sub2 : {
font : 'awesome' ,
notInTarget : true ,
} ,
deleteThis : null ,
} ,
subNotInTarget : {
enabled : true ,
} ,
deleteThis : null ,
subDeleteThis : null
} ;
} ) ;
it ( 'performs fillIfDefined() as advertized' , function ( ) {
var a = this . a ;
var b = this . b ;
util . fillIfDefined ( a , b ) ;
checkExtended ( a , b ) ;
// NOTE: if allowDeletion === false, null values are copied over!
// This is due to existing logic; it might not be the intention and hence a bug
assert ( a . sub . deleteThis === null ) ;
assert ( a . deleteThis === null ) ;
assert ( a . subDeleteThis === null ) ;
} ) ;
it ( 'performs fillIfDefined() as advertized with deletion' , function ( ) {
var a = this . a ;
var b = this . b ;
util . fillIfDefined ( a , b , true ) ; // thrid param: allowDeletion
checkExtended ( a , b ) ;
// Following should be removed now
assert ( a . sub . deleteThis === undefined ) ;
assert ( a . deleteThis === undefined ) ;
assert ( a . subDeleteThis === undefined ) ;
} ) ;
it ( 'performs selectiveDeepExtend() as advertized' , function ( ) {
var a = this . a ;
var b = this . b ;
// pedantic: copy nothing
util . selectiveDeepExtend ( [ ] , a , b ) ;
assert ( a . color !== undefined && a . color === 'red' ) ;
assert ( a . notInSource === true ) ;
assert ( a . notInTarget === undefined ) ;
// pedantic: copy nonexistent property (nothing happens)
assert ( b . iDontExist === undefined ) ;
util . selectiveDeepExtend ( [ 'iDontExist' ] , a , b , true ) ;
assert ( a . iDontExist === undefined ) ;
// At this point nothing should have changed yet.
testAUnchanged ( a ) ;
// Copy one property
util . selectiveDeepExtend ( [ 'color' ] , a , b ) ;
assert ( a . color !== undefined && a . color === 'green' ) ;
// Copy property Object
var sub = a . sub ;
assert ( sub . deleteThis === true ) ; // pre
util . selectiveDeepExtend ( [ 'sub' ] , a , b ) ;
assert ( sub !== undefined ) ;
assert ( sub . enabled !== undefined && sub . enabled === false ) ;
assert ( sub . notInSource === true ) ;
assert ( sub . notInTarget === true ) ;
assert ( sub . deleteThis === null ) ;
// Copy new Objects
assert ( a . notInTarget === undefined ) ; // pre
assert ( a . subNotInTarget === undefined ) ; // pre
util . selectiveDeepExtend ( [ 'notInTarget' , 'subNotInTarget' ] , a , b ) ;
assert ( a . notInTarget === true ) ;
assert ( a . subNotInTarget . enabled === true ) ;
// Copy null objects
assert ( a . deleteThis !== null ) ; // pre
assert ( a . subDeleteThis !== null ) ; // pre
util . selectiveDeepExtend ( [ 'deleteThis' , 'subDeleteThis' ] , a , b ) ;
// NOTE: if allowDeletion === false, null values are copied over!
// This is due to existing logic; it might not be the intention and hence a bug
assert ( a . deleteThis === null ) ;
assert ( a . subDeleteThis === null ) ;
} ) ;
it ( 'performs selectiveDeepExtend() as advertized with deletion' , function ( ) {
var a = this . a ;
var b = this . b ;
// Only test expected differences here with test allowDeletion === false
// Copy object property with properties to be deleted
var sub = a . sub ;
assert ( sub . deleteThis === true ) ; // pre
util . selectiveDeepExtend ( [ 'sub' ] , a , b , true ) ;
assert ( sub . deleteThis === undefined ) ; // should be deleted
// Spot check on rest of properties in `a.sub` - there should have been copied
sub = a . sub ;
assert ( sub !== undefined ) ;
assert ( sub . enabled !== undefined && sub . enabled === false ) ;
assert ( sub . notInSource === true ) ;
assert ( sub . notInTarget === true ) ;
// Copy null objects
assert ( a . deleteThis === true ) ; // pre
assert ( a . subDeleteThis !== undefined ) ; // pre
assert ( a . subDeleteThis . enabled === true ) ; // pre
util . selectiveDeepExtend ( [ 'deleteThis' , 'subDeleteThis' ] , a , b , true ) ;
assert ( a . deleteThis === undefined ) ; // should be deleted
assert ( a . subDeleteThis === undefined ) ; // should be deleted
} ) ;
it ( 'performs selectiveNotDeepExtend() as advertized' , function ( ) {
var a = this . a ;
var b = this . b ;
// Exclude all properties, nothing copied
util . selectiveNotDeepExtend ( Object . keys ( b ) , a , b ) ;
testAUnchanged ( a ) ;
// Exclude nothing, everything copied
util . selectiveNotDeepExtend ( [ ] , a , b ) ;
checkExtended ( a , b , true ) ;
// Exclude some
a = initA ( ) ;
assert ( a . notInTarget === undefined ) ; // pre
assert ( a . subNotInTarget === undefined ) ; // pre
util . selectiveNotDeepExtend ( [ 'notInTarget' , 'subNotInTarget' ] , a , b ) ;
assert ( a . notInTarget === undefined ) ; // not copied
assert ( a . subNotInTarget === undefined ) ; // not copied
assert ( a . sub . notInTarget === true ) ; // copied!
} ) ;
it ( 'performs selectiveNotDeepExtend() as advertized with deletion' , function ( ) {
var a = this . a ;
var b = this . b ;
// Exclude all properties, nothing copied
util . selectiveNotDeepExtend ( Object . keys ( b ) , a , b , true ) ;
testAUnchanged ( a ) ;
// Exclude nothing, everything copied and some deleted
util . selectiveNotDeepExtend ( [ ] , a , b , true ) ;
checkExtended ( a , b , true ) ;
// Exclude some
a = initA ( ) ;
assert ( a . notInTarget === undefined ) ; // pre
assert ( a . subNotInTarget === undefined ) ; // pre
assert ( a . deleteThis === true ) ; // pre
assert ( a . subDeleteThis !== undefined ) ; // pre
assert ( a . sub . deleteThis === true ) ; // pre
assert ( a . subDeleteThis . enabled === true ) ; // pre
util . selectiveNotDeepExtend ( [ 'notInTarget' , 'subNotInTarget' ] , a , b , true ) ;
assert ( a . deleteThis === undefined ) ; // should be deleted
assert ( a . sub . deleteThis !== undefined ) ; // not deleted! Original logic, could be a bug
assert ( a . subDeleteThis === undefined ) ; // should be deleted
// Spot check: following should be same as allowDeletion === false
assert ( a . notInTarget === undefined ) ; // not copied
assert ( a . subNotInTarget === undefined ) ; // not copied
assert ( a . sub . notInTarget === true ) ; // copied!
} ) ;
/ * *
* NOTE : parameter ` protoExtend ` not tested here !
* /
it ( 'performs deepExtend() as advertized' , function ( ) {
var a = this . a ;
var b = this . b ;
util . deepExtend ( a , b ) ;
checkExtended ( a , b , true ) ;
} ) ;
/ * *
* NOTE : parameter ` protoExtend ` not tested here !
* /
it ( 'performs deepExtend() as advertized with delete' , function ( ) {
var a = this . a ;
var b = this . b ;
// Copy null objects
assert ( a . deleteThis === true ) ; // pre
assert ( a . subDeleteThis !== undefined ) ; // pre
assert ( a . subDeleteThis . enabled === true ) ; // pre
util . deepExtend ( a , b , false , true ) ;
checkExtended ( a , b , true ) ; // Normal copy should be good
assert ( a . deleteThis === undefined ) ; // should be deleted
assert ( a . subDeleteThis === undefined ) ; // should be deleted
assert ( a . sub . deleteThis !== undefined ) ; // not deleted!!! Original logic, could be a bug
} ) ;
} ) ; // extend routines
//
// The important thing with mergeOptions() is that 'enabled' is always set in target option.
//
describe ( 'util.mergeOptions' , function ( ) {
describe ( 'mergeOptions' , function ( ) {
it ( 'handles good input without global options' , function ( ) {
var options = {
@ -131,4 +466,6 @@ describe('util.mergeOptions', function () {
util . mergeOptions ( mergeTarget , options , 'alsoMissingEnabled' , globalOptions ) ;
assert ( mergeTarget . alsoMissingEnabled . enabled === true ) ;
} ) ;
} ) ;
} ) ; // mergeOptions
} ) ; // util