diff --git a/lib/util.js b/lib/util.js index db24f97..003144a 100644 --- a/lib/util.js +++ b/lib/util.js @@ -171,6 +171,64 @@ exports.clone = function clone( data, deep ) }; +/** + * Copies properties from one object to another + * + * This method is designed to support very basic object extensions. The + * destination argument is first to allow extending an object without using the + * full-blown class system. + * + * @param {Object} dest destination object + * @param {Object} src source object + * + * @return {Object} dest + */ +exports.copyTo = function( dest, src ) +{ + var get, set, data; + + // sanity check + if ( !( dest instanceof Object ) || !( src instanceof Object ) ) + { + throw TypeError( + "Must provide both source and destination objects" + ); + } + + // slower; supports getters/setters + if ( can_define_prop ) + { + for ( prop in src ) + { + data = Object.getOwnPropertyDescriptor( src, prop ); + + if ( data.get || data.set ) + { + // define the property the slower way (only needed for + // getters/setters) + Object.defineProperty( dest, prop, data ); + } + else + { + // normal copy by reference + dest[ prop ] = src[ prop ]; + } + } + } + // quick (keep if statement out of the loop) + else + { + for ( prop in src ) + { + dest[ prop ] = src[ prop ]; + } + } + + // return dest for convenience (and to feel useful about ourselves) + return dest; +}; + + /** * Parses object properties to determine how they should be interpreted in an * Object Oriented manner diff --git a/test/test-util-copy.js b/test/test-util-copy.js new file mode 100644 index 0000000..2ed1b4c --- /dev/null +++ b/test/test-util-copy.js @@ -0,0 +1,144 @@ +/** + * Tests util.copy + * + * Copyright (C) 2010 Mike Gerwitz + * + * This file is part of ease.js. + * + * ease.js is free software: you can redistribute it and/or modify it under the + * terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * @author Mike Gerwitz + * @package test + */ + +var common = require( './common' ), + assert = require( 'assert' ), + util = common.require( 'util' ), + + copyTo = util.copyTo, + get_set = !( util.definePropertyFallback() ) +; + + +/** + * Just a basic copy test. Ensure the values are copied by reference. + */ +( function testCopiesReferencesToDestinationObject() +{ + var src = { + a: 'a', + b: 2, + c: true, + d: false, + e: undefined, + d: null, + f: function() {}, + }, + dest = {} + ; + + copyTo( dest, src ); + + for ( key in src ) + { + assert.deepEqual( src[ key ], dest[ key ], + "Copies by reference by default" + ); + } +} )(); + + +/** + * Same concept as above, but with getters/setters + */ +( function testGettersAndSettersAreCopiedByReferenceToDestinationObject() +{ + // no use in performing the test if the engine doesn't support it + if ( !get_set ) + { + return; + } + + var get = function() {}, + set = function() {}, + src = {}, + dest = {}, + + result = null + ; + + Object.defineProperty( src, 'foo', { + get: get, + set: set, + + // so copy can actually see the property + enumerable: true, + } ); + + copyTo( dest, src ); + + // look up the getter/setter in dest + result = Object.getOwnPropertyDescriptor( dest, 'foo' ); + + // check getter + assert.deepEqual( result.get, get, + "Getter is copied by reference by default" + ); + + // check setter + assert.deepEqual( result.set, set, + "Setter is copied by reference by default" + ) +} )(); + + +/** + * For convenience + */ +( function testOperationReturnsDest() +{ + var dest = {}; + + assert.deepEqual( copyTo( dest, {} ), dest, + "Copy operation returns dest" + ); +} )(); + + +/** + * Just one of those tests you feel silly for making but is required + */ +( function testThrowsErrorIfSourceOrDestAreNotGiven() +{ + assert.throws( function() + { + copyTo(); + }, TypeError, "Dest parameter is required" ); + + assert.throws( function() + { + copyTo( 'bla', {} ); + }, TypeError, "Dest parameter is required to be an object" ); + + assert.throws( function() + { + copyTo( {} ); + }, TypeError, "Src parameter is required" ); + + assert.throws( function() + { + copyTo( {}, 'foo' ); + }, TypeError, "Src parameter is required to be an object" ); +} )(); +