diff --git a/.travis.yml b/.travis.yml index b41bd9f..ea3ec10 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,4 +17,5 @@ node_js: matrix: fast_finish: true allow_failures: + - node_js: 'node' - node_js: '0.8' diff --git a/index.js b/index.js index 16b4690..08db87a 100644 --- a/index.js +++ b/index.js @@ -32,7 +32,7 @@ module.exports = function mergeDeep(orig, objects) { function merge(target, obj) { for (var key in obj) { - if (key === '__proto__' || !hasOwn(obj, key)) { + if (!isValidKey(key) || !hasOwn(obj, key)) { continue; } @@ -57,3 +57,7 @@ function hasOwn(obj, key) { function isObject(val) { return typeOf(val) === 'object' || typeOf(val) === 'function'; } + +function isValidKey(key) { + return key !== '__proto__' && key !== 'constructor' && key !== 'prototype'; +} \ No newline at end of file diff --git a/test.js b/test.js index 926bbae..f454cf7 100644 --- a/test.js +++ b/test.js @@ -134,4 +134,68 @@ describe('mergeDeep', function() { var actual = merge(fixture); assert.deepEqual(actual, fixture); }); + + it('should not clone invalid keys', function() { + var obj1 = { a: { b: 1 } }; + var obj2 = JSON.parse('{ "a": { "c": 2 }, "constructor": { "keys": 42 } }'); + + var actual = merge({}, obj1, obj2); + assert.deepEqual(actual, { a: { b: 1, c: 2 } }); + assert.notDeepEqual(actual.a, obj1.a); + assert.notDeepEqual(actual.a, obj2.a); + assert.notEqual(actual.keys, 42); + assert.notEqual(actual.constructor.keys, 42); + }); + + it('should allow being used for custom constructors', function() { + // The following setup code is a simple way to demonstrate multiple inheritance by merging the prototype of one class onto another + function Shape() { + this.type = ''; + } + + function Position(x, y) { + this.x = x || 0; + this.y = y || 0; + } + + Position.prototype.stringify = function() { + return '(' + this.x + ', ' + this.y + ')'; + }; + + function Moveable(x, y) { + Position.call(this, x, y); + } + + // By making Moveable inherit from Position, allows us to test what happens when `constructor` is passed to `isValidKey`. + Moveable.prototype = Object.create(Position.prototype); + Moveable.prototype.constructor = Moveable; + Moveable.prototype = merge(Moveable.prototype, Position.prototype); + + Moveable.prototype.move = function(x, y) { + this.x += x; + this.y += y; + }; + + Moveable.prototype.position = function() { + return this.stringify(); + }; + + function Rectangle() { + Shape.call(this); + Moveable.call(this); + this.type = 'rectangle'; + } + + // Single inheritance using Object.create + Rectangle.prototype = Object.create(Shape.prototype); + Rectangle.prototype.constructor = Rectangle; + + // This is the test to ensure that `merge-deep` can be used with prototypal inheritance + Rectangle.prototype = merge(Rectangle.prototype, Moveable.prototype); + + var rectangle = new Rectangle(); + assert.equal(rectangle.position(), '(0, 0)'); + rectangle.move(10, 20); + assert.equal(rectangle.position(), '(10, 20)'); + }); });