Repository: rtfeldman/seamless-immutable
Pull request #51 (2015-07-23 06:17:01)

Issue#48 Optionally support object prototypes
Couple notes:
- I'm still using ```Object.getPrototypeOf``` in ```instantiateEmptyObject``` because coming from any of the ImmutableObject methods, I wouldn't have options. An alternative, but not as clean, would be to dynamically generate ```instantiateEmptyObject``` in ```makeImmutableObject()```, and then reattach it to results in the ImmutableObject methods (probably by passing some options arg to ```makeImmutableObject()```). If you have a better idea, I'm all ears.
- I spent some time trying to generate prototypes with jscheck, but I'm unsure how, and it was hard to find documentation/examples online. Let me know if there's anything you'd like me to do there as the tests are probably lacking, and pointers to docs would be helpful.


Patch:
diff --git a/README.md b/README.md
index 3b08d6b..d6e4265 100644
--- a/README.md
+++ b/README.md
@@ -69,18 +69,12 @@ Immutable({all: "your base", are: {belong: "to them"}}).merge({are: {belong: "to
 
 ## Immutable Array
 
-Like a regular Array, but immutable! You can construct these either by passing
-an array to `Immutable()`, or simply by passing it multiple arguments:
+Like a regular Array, but immutable! You can construct these by passing
+an array to `Immutable()`:
 
 ```javascript
 Immutable([1, 2, 3])
 // An immutable array containing 1, 2, and 3.
-
-Immutable(1, 2, 3)
-// Also an immutable array containing 1, 2, and 3.
-
-Immutable(1)
-// Just the number 1 (not an array), as numbers are already immutable in JS.
 ```
 
 Beyond [the usual Array fare](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#Accessor_methods), the following methods have been added.
@@ -144,6 +138,19 @@ Immutable({foo: "bar"})
 // An immutable object containing the key "foo" and the value "bar".
 ```
 
+To construct an Immutable Object with a custom prototype, simply specify the
+prototype in `options` (while useful for preserving prototypes, please note
+that custom mutator methods will not work as the object will be immutable):
+
+```
+function Square(length) { this.length = length };
+Square.prototype.area = function() { return Math.pow(this.length, 2) };
+
+Immutable(new Square(2), {prototype: Square.prototype}).area();
+// An immutable object, with prototype Square,
+// containing the key "length" and method `area()` returning 4
+```
+
 Beyond [the usual Object fare](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object#Methods_of_Object_instances), the following methods have been added.
 
 ### merge
diff --git a/seamless-immutable.development.js b/seamless-immutable.development.js
index bbe0f8c..eef1f8e 100644
--- a/seamless-immutable.development.js
+++ b/seamless-immutable.development.js
@@ -157,7 +157,7 @@
       keysToRemove = Array.prototype.slice.call(arguments);
     }
 
-    var result = {};
+    var result = this.instantiateEmptyObject();
 
     for (var key in this) {
       if (this.hasOwnProperty(key) && (keysToRemove.indexOf(key) === -1)) {
@@ -165,7 +165,8 @@
       }
     }
 
-    return makeImmutableObject(result);
+    return makeImmutableObject(result,
+      {instantiateEmptyObject: this.instantiateEmptyObject});
   }
 
   function asMutableArray(opts) {
@@ -248,7 +249,7 @@
     }
 
     var anyChanges    = false,
-        result        = quickCopy(this, {}), // A shallow clone of this object.
+        result        = quickCopy(this, this.instantiateEmptyObject()), // A shallow clone of this object.
         receivedArray = (other instanceof Array),
         deep          = config && config.deep,
         merger        = config && config.merger,
@@ -300,14 +301,15 @@
     }
 
     if (anyChanges) {
-      return makeImmutableObject(result);
+      return makeImmutableObject(result, 
+        {instantiateEmptyObject: this.instantiateEmptyObject});
     } else {
       return this;
     }
   }
 
   function asMutableObject(opts) {
-    var result = {}, key;
+    var result = this.instantiateEmptyObject(), key;
 
     if(opts && opts.deep) {
       for (key in this) {
@@ -326,20 +328,28 @@
     return result;
   }
 
+  // Creates plain object to be used for cloning
+  function instantiatePlainObject() {
+    return {};
+  }
+
   // Finalizes an object with immutable methods, freezes it, and returns it.
-  function makeImmutableObject(obj) {
+  function makeImmutableObject(obj, options) {
+    var instantiateEmptyObject =
+      (options && options.instantiateEmptyObject) 
+        ? options.instantiateEmptyObject
+        : instantiatePlainObject;
+
     addPropertyTo(obj, "merge", merge);
     addPropertyTo(obj, "without", without);
     addPropertyTo(obj, "asMutable", asMutableObject);
+    addPropertyTo(obj, "instantiateEmptyObject", instantiateEmptyObject);
 
     return makeImmutable(obj, mutatingObjectMethods);
   }
 
-  function Immutable(obj) {
-    // If the user passes multiple arguments, assume what they want is an array.
-    if (arguments.length > 1) {
-      return makeImmutableArray(Array.prototype.slice.call(arguments));
-    } else if (isImmutable(obj)) {
+  function Immutable(obj, options) {
+    if (isImmutable(obj)) {
       return obj;
     } else if (obj instanceof Array) {
       return makeImmutableArray(obj.slice());
@@ -347,7 +357,12 @@
       return makeImmutable(new Date(obj.getTime()));
     } else {
       // Don't freeze the object we were given; make a clone and use that.
-      var clone = {};
+      var prototype = options && options.prototype;
+      var instantiateEmptyObject =
+        (!prototype || prototype === Object.prototype)
+          ? instantiatePlainObject
+          : (function() { return Object.create(prototype); });
+      var clone = instantiateEmptyObject();
 
       for (var key in obj) {
         if (obj.hasOwnProperty(key)) {
@@ -355,7 +370,8 @@
         }
       }
 
-      return makeImmutableObject(clone);
+      return makeImmutableObject(clone, 
+        {instantiateEmptyObject: instantiateEmptyObject});
     }
   }
 
diff --git a/seamless-immutable.production.min.js b/seamless-immutable.production.min.js
index 15c43aa..3e2e159 100644
--- a/seamless-immutable.production.min.js
+++ b/seamless-immutable.production.min.js
@@ -1,1 +1,1 @@
-!function(){"use strict";function a(a,b,c){Object.defineProperty(a,b,{enumerable:!1,configurable:!1,writable:!1,value:c})}function b(b){a(b,s,!0)}function c(a){return"object"==typeof a?null===a||a.hasOwnProperty(s):!0}function d(a){return!(null===a||"object"!=typeof a||a instanceof Array||a instanceof Date)}function e(a){var b=new Error(a);return b.__proto__=e,b}function f(a,c){b(a);return a}function g(b,c){var d=b[c];a(b,c,function(){return r(d.apply(b,arguments))})}function h(b){for(var c in w)if(w.hasOwnProperty(c)){var d=w[c];g(b,d)}a(b,"flatMap",i),a(b,"asObject",l),a(b,"asMutable",k);for(var e=0,h=b.length;h>e;e++)b[e]=r(b[e]);return f(b,v)}function i(a){if(0===arguments.length)return this;var b,c=[],d=this.length;for(b=0;d>b;b++){var e=a(this[b],b,this);e instanceof Array?c.push.apply(c,e):c.push(e)}return h(c)}function j(a){if(0===arguments.length)return this;a instanceof Array||(a=Array.prototype.slice.call(arguments));var b={};for(var c in this)this.hasOwnProperty(c)&&-1===a.indexOf(c)&&(b[c]=this[c]);return q(b)}function k(a){var b,c,d=[];if(a&&a.deep)for(b=0,c=this.length;c>b;b++)d.push(m(this[b]));else for(b=0,c=this.length;c>b;b++)d.push(this[b]);return d}function l(a){"function"!=typeof a&&(a=function(a){return a});var b,c={},d=this.length;for(b=0;d>b;b++){var e=a(this[b],b,this),f=e[0],g=e[1];c[f]=g}return q(c)}function m(a){return!a||!a.hasOwnProperty(s)||a instanceof Date?a:a.asMutable({deep:!0})}function n(a,b){for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}function o(a,b){function c(a,c,e){var h=r(c[e]),k=j&&j(a[e],h,b);j&&k&&k===a[e]||(f=f||void 0!==k||!a.hasOwnProperty(e)||h!==a[e]&&h===h,g[e]=k?k:i&&d(a[e])&&d(h)?a[e].merge(h,b):h)}if(0===arguments.length)return this;if(null===a||"object"!=typeof a)throw new TypeError("Immutable#merge can only be invoked with objects or arrays, not "+JSON.stringify(a));var e,f=!1,g=n(this,{}),h=a instanceof Array,i=b&&b.deep,j=b&&b.merger;if(h)for(var k=0;k<a.length;k++){var l=a[k];for(e in l)l.hasOwnProperty(e)&&c(this,l,e)}else for(e in a)a.hasOwnProperty(e)&&c(this,a,e);return f?q(g):this}function p(a){var b,c={};if(a&&a.deep)for(b in this)this.hasOwnProperty(b)&&(c[b]=m(this[b]));else for(b in this)this.hasOwnProperty(b)&&(c[b]=this[b]);return c}function q(b){return a(b,"merge",o),a(b,"without",j),a(b,"asMutable",p),f(b,t)}function r(a){if(arguments.length>1)return h(Array.prototype.slice.call(arguments));if(c(a))return a;if(a instanceof Array)return h(a.slice());if(a instanceof Date)return f(new Date(a.getTime()));var b={};for(var d in a)a.hasOwnProperty(d)&&(b[d]=r(a[d]));return q(b)}var s="__immutable_invariants_hold",t=["setPrototypeOf"],u=["keys"],v=t.concat(["push","pop","sort","splice","shift","unshift","reverse"]),w=u.concat(["map","filter","slice","concat","reduce","reduceRight"]);e.prototype=Error.prototype,r.isImmutable=c,r.ImmutableError=e,Object.freeze(r),"object"==typeof module?module.exports=r:"object"==typeof exports?exports.Immutable=r:"object"==typeof window?window.Immutable=r:"object"==typeof global&&(global.Immutable=r)}();
\ No newline at end of file
+!function(){"use strict";function a(a,b,c){Object.defineProperty(a,b,{enumerable:!1,configurable:!1,writable:!1,value:c})}function b(b){a(b,t,!0)}function c(a){return"object"==typeof a?null===a||a.hasOwnProperty(t):!0}function d(a){return!(null===a||"object"!=typeof a||a instanceof Array||a instanceof Date)}function e(a){var b=new Error(a);return b.__proto__=e,b}function f(a,c){b(a);return a}function g(b,c){var d=b[c];a(b,c,function(){return s(d.apply(b,arguments))})}function h(b){for(var c in x)if(x.hasOwnProperty(c)){var d=x[c];g(b,d)}a(b,"flatMap",i),a(b,"asObject",l),a(b,"asMutable",k);for(var e=0,h=b.length;h>e;e++)b[e]=s(b[e]);return f(b,w)}function i(a){if(0===arguments.length)return this;var b,c=[],d=this.length;for(b=0;d>b;b++){var e=a(this[b],b,this);e instanceof Array?c.push.apply(c,e):c.push(e)}return h(c)}function j(a){if(0===arguments.length)return this;a instanceof Array||(a=Array.prototype.slice.call(arguments));var b=this.instantiateEmptyObject();for(var c in this)this.hasOwnProperty(c)&&-1===a.indexOf(c)&&(b[c]=this[c]);return r(b,{instantiateEmptyObject:this.instantiateEmptyObject})}function k(a){var b,c,d=[];if(a&&a.deep)for(b=0,c=this.length;c>b;b++)d.push(m(this[b]));else for(b=0,c=this.length;c>b;b++)d.push(this[b]);return d}function l(a){"function"!=typeof a&&(a=function(a){return a});var b,c={},d=this.length;for(b=0;d>b;b++){var e=a(this[b],b,this),f=e[0],g=e[1];c[f]=g}return r(c)}function m(a){return!a||!a.hasOwnProperty(t)||a instanceof Date?a:a.asMutable({deep:!0})}function n(a,b){for(var c in a)a.hasOwnProperty(c)&&(b[c]=a[c]);return b}function o(a,b){function c(a,c,e){var h=s(c[e]),k=j&&j(a[e],h,b);j&&k&&k===a[e]||(f=f||void 0!==k||!a.hasOwnProperty(e)||h!==a[e]&&h===h,g[e]=k?k:i&&d(a[e])&&d(h)?a[e].merge(h,b):h)}if(0===arguments.length)return this;if(null===a||"object"!=typeof a)throw new TypeError("Immutable#merge can only be invoked with objects or arrays, not "+JSON.stringify(a));var e,f=!1,g=n(this,this.instantiateEmptyObject()),h=a instanceof Array,i=b&&b.deep,j=b&&b.merger;if(h)for(var k=0;k<a.length;k++){var l=a[k];for(e in l)l.hasOwnProperty(e)&&c(this,l,e)}else for(e in a)a.hasOwnProperty(e)&&c(this,a,e);return f?r(g,{instantiateEmptyObject:this.instantiateEmptyObject}):this}function p(a){var b,c=this.instantiateEmptyObject();if(a&&a.deep)for(b in this)this.hasOwnProperty(b)&&(c[b]=m(this[b]));else for(b in this)this.hasOwnProperty(b)&&(c[b]=this[b]);return c}function q(){return{}}function r(b,c){var d=c&&c.instantiateEmptyObject?c.instantiateEmptyObject:q;return a(b,"merge",o),a(b,"without",j),a(b,"asMutable",p),a(b,"instantiateEmptyObject",d),f(b,u)}function s(a,b){if(c(a))return a;if(a instanceof Array)return h(a.slice());if(a instanceof Date)return f(new Date(a.getTime()));var d=b&&b.prototype,e=d&&d!==Object.prototype?function(){return Object.create(d)}:q,g=e();for(var i in a)a.hasOwnProperty(i)&&(g[i]=s(a[i]));return r(g,{instantiateEmptyObject:e})}var t="__immutable_invariants_hold",u=["setPrototypeOf"],v=["keys"],w=u.concat(["push","pop","sort","splice","shift","unshift","reverse"]),x=v.concat(["map","filter","slice","concat","reduce","reduceRight"]);e.prototype=Error.prototype,s.isImmutable=c,s.ImmutableError=e,Object.freeze(s),"object"==typeof module?module.exports=s:"object"==typeof exports?exports.Immutable=s:"object"==typeof window?window.Immutable=s:"object"==typeof global&&(global.Immutable=s)}();
\ No newline at end of file
diff --git a/src/seamless-immutable.js b/src/seamless-immutable.js
index aa602c0..8cf3e08 100644
--- a/src/seamless-immutable.js
+++ b/src/seamless-immutable.js
@@ -157,7 +157,7 @@
       keysToRemove = Array.prototype.slice.call(arguments);
     }
 
-    var result = {};
+    var result = this.instantiateEmptyObject();
 
     for (var key in this) {
       if (this.hasOwnProperty(key) && (keysToRemove.indexOf(key) === -1)) {
@@ -165,7 +165,8 @@
       }
     }
 
-    return makeImmutableObject(result);
+    return makeImmutableObject(result,
+      {instantiateEmptyObject: this.instantiateEmptyObject});
   }
 
   function asMutableArray(opts) {
@@ -248,7 +249,7 @@
     }
 
     var anyChanges    = false,
-        result        = quickCopy(this, {}), // A shallow clone of this object.
+        result        = quickCopy(this, this.instantiateEmptyObject()), // A shallow clone of this object.
         receivedArray = (other instanceof Array),
         deep          = config && config.deep,
         merger        = config && config.merger,
@@ -300,14 +301,15 @@
     }
 
     if (anyChanges) {
-      return makeImmutableObject(result);
+      return makeImmutableObject(result, 
+        {instantiateEmptyObject: this.instantiateEmptyObject});
     } else {
       return this;
     }
   }
 
   function asMutableObject(opts) {
-    var result = {}, key;
+    var result = this.instantiateEmptyObject(), key;
 
     if(opts && opts.deep) {
       for (key in this) {
@@ -326,20 +328,28 @@
     return result;
   }
 
+  // Creates plain object to be used for cloning
+  function instantiatePlainObject() {
+    return {};
+  }
+
   // Finalizes an object with immutable methods, freezes it, and returns it.
-  function makeImmutableObject(obj) {
+  function makeImmutableObject(obj, options) {
+    var instantiateEmptyObject =
+      (options && options.instantiateEmptyObject) 
+        ? options.instantiateEmptyObject
+        : instantiatePlainObject;
+
     addPropertyTo(obj, "merge", merge);
     addPropertyTo(obj, "without", without);
     addPropertyTo(obj, "asMutable", asMutableObject);
+    addPropertyTo(obj, "instantiateEmptyObject", instantiateEmptyObject);
 
     return makeImmutable(obj, mutatingObjectMethods);
   }
 
-  function Immutable(obj) {
-    // If the user passes multiple arguments, assume what they want is an array.
-    if (arguments.length > 1) {
-      return makeImmutableArray(Array.prototype.slice.call(arguments));
-    } else if (isImmutable(obj)) {
+  function Immutable(obj, options) {
+    if (isImmutable(obj)) {
       return obj;
     } else if (obj instanceof Array) {
       return makeImmutableArray(obj.slice());
@@ -347,7 +357,12 @@
       return makeImmutable(new Date(obj.getTime()));
     } else {
       // Don't freeze the object we were given; make a clone and use that.
-      var clone = {};
+      var prototype = options && options.prototype;
+      var instantiateEmptyObject =
+        (!prototype || prototype === Object.prototype)
+          ? instantiatePlainObject
+          : (function() { return Object.create(prototype); });
+      var clone = instantiateEmptyObject();
 
       for (var key in obj) {
         if (obj.hasOwnProperty(key)) {
@@ -355,7 +370,8 @@
         }
       }
 
-      return makeImmutableObject(clone);
+      return makeImmutableObject(clone, 
+        {instantiateEmptyObject: instantiateEmptyObject});
     }
   }
 


Test patch:
diff --git a/test/Immutable.spec.js b/test/Immutable.spec.js
index eca6179..ab9af04 100644
--- a/test/Immutable.spec.js
+++ b/test/Immutable.spec.js
@@ -14,20 +14,7 @@ var getTestUtils = require("./TestUtils.js");
 
   describe(config.name, function () {
     describe("Immutable", function () {
-      it("converts multiple arguments into an array, but leaves single arguments alone", function () {
-        TestUtils.check(100, [JSC.array()], function (args) {
-          var immutable = Immutable.apply(Immutable, args);
-
-          if (args.length > 1) {
-            assert.deepEqual(immutable, Immutable(args));
-          } else if (isNaN(immutable) && isNaN(args[0])) {
-            assert.ok(true);
-          } else {
-            assert.strictEqual(immutable, args[0]);
-          }
-        })
-      });
-
+      
       it("makes an Immutable Array when passed a mutable array", function () {
         TestUtils.check(100, [JSC.array()], function (mutable) {
           var immutable = Immutable(mutable);
@@ -64,7 +51,17 @@ var getTestUtils = require("./TestUtils.js");
         Immutable([]);
         delete Array.prototype.veryEvilFunction;
       });
+                                                                  
+      it("returns an object with the given optional prototype", function() {
+        function TestClass(o) { _.extend(this, o); };
+        var data = new TestClass({a: 1, b: 2});
 
+        var immutable = Immutable(data, {prototype: TestClass.prototype});
+
+        assert.deepEqual(immutable, data);
+        TestUtils.assertHasPrototype(immutable, TestClass.prototype);
+      });
+                                                                  
       // These are already immutable, and should pass through Immutable() untouched.
       _.each({
         "string": JSC.string(),
@@ -76,6 +73,9 @@ var getTestUtils = require("./TestUtils.js");
         it("simply returns its argument when passed a value of type " + type, function () {
           TestUtils.check(100, [specifier], function (value) {
             assert.strictEqual(Immutable(value), value);
+
+            // should still pass through with a faulty prototype option
+            assert.strictEqual(Immutable(value, {prototype: Object.prototype}), value);
           });
         });
       });
diff --git a/test/ImmutableObject.spec.js b/test/ImmutableObject.spec.js
index e9561e7..56cfedc 100644
--- a/test/ImmutableObject.spec.js
+++ b/test/ImmutableObject.spec.js
@@ -2,6 +2,7 @@ var testMerge     = require("./ImmutableObject/test-merge.js");
 var testCompat    = require("./ImmutableObject/test-compat.js");
 var testWithout   = require("./ImmutableObject/test-without.js");
 var testAsMutable = require("./ImmutableObject/test-asMutable.js");
+var testInstantiateEmptyObject = require("./ImmutableObject/test-instantiateEmptyObject.js");
 var devBuild      = require("../seamless-immutable.development.js");
 var prodBuild     = require("../seamless-immutable.production.min.js");
 var getTestUtils  = require("./TestUtils.js");
@@ -19,6 +20,7 @@ var getTestUtils  = require("./TestUtils.js");
       testMerge(config);
       testWithout(config);
       testAsMutable(config);
+      testInstantiateEmptyObject(config);
     });
   });
 });
diff --git a/test/ImmutableObject/test-asMutable.js b/test/ImmutableObject/test-asMutable.js
index f76d759..3585fbb 100644
--- a/test/ImmutableObject/test-asMutable.js
+++ b/test/ImmutableObject/test-asMutable.js
@@ -31,11 +31,23 @@ module.exports = function(config) {
       });
     });
 
-      it("does not throw an error when asMutable deep = true is called on an Immutable with a nested date", function() {
-          check(100, [ TestUtils.TraversableObjectSpecifier ], function(obj) {
-              var test = Immutable({ testDate: new Date()});
-              test.asMutable({deep: true});
-          });
+    it("does not throw an error when asMutable deep = true is called on an Immutable with a nested date", function() {
+      check(100, [ TestUtils.TraversableObjectSpecifier ], function(obj) {
+        var test = Immutable({ testDate: new Date()});
+        test.asMutable({deep: true});
       });
+    });
+ 
+    it("preserves prototypes after call to asMutable", function() {
+      function TestClass(o) { _.extend(this, o); };
+      var data = new TestClass({a: 1, b: 2});
+
+      var immutable = Immutable(data, {prototype: TestClass.prototype});
+      var result = immutable.asMutable();
+
+      assert.deepEqual(result, data);
+      TestUtils.assertHasPrototype(result, TestClass.prototype);
+    });
+                                           
   });
 };
diff --git a/test/ImmutableObject/test-compat.js b/test/ImmutableObject/test-compat.js
index e5b64e3..8b4e50c 100644
--- a/test/ImmutableObject/test-compat.js
+++ b/test/ImmutableObject/test-compat.js
@@ -142,7 +142,14 @@ module.exports = function(config) {
       assert.notEqual(objectWithDate, immutableObjectWithDate);
       assert.equal(objectWithDate.date.getTime(), immutableObjectWithDate.date.getTime());
     });
+                           
+    it("supports handling dates without using prototype option", function() {
+      var date = new Date();
+      var immutableDate = Immutable(date, {prototype: Object.prototype});
 
+      assert.isTrue(immutableDate instanceof Date);
+    });
+                           
     it("makes nested content immutable as well", function() {
       checkImmutableMutable(function(immutable, mutable, innerArray, obj) {
         mutable.foo = innerArray; // Make a nested immutable array
diff --git a/test/ImmutableObject/test-instantiateEmptyObject.js b/test/ImmutableObject/test-instantiateEmptyObject.js
new file mode 100644
index 0000000..df14bec
--- /dev/null
+++ b/test/ImmutableObject/test-instantiateEmptyObject.js
@@ -0,0 +1,30 @@
+var assert       = require("chai").assert;
+var _            = require("lodash");
+var getTestUtils = require("../TestUtils.js");
+
+module.exports = function(config) {
+  var Immutable = config.implementation;
+  var TestUtils = getTestUtils(Immutable);
+
+  describe("#instantiateEmptyObject", function() {
+    it("return empty single Object if no prototype is present", function() {
+      var immutable = Immutable({a: 1, b: 2});
+
+      var obj = immutable.instantiateEmptyObject();
+      assert.isFalse(Immutable.isImmutable(obj));
+      assert.deepEqual(obj, {});
+    });
+
+    it("return object with prototype if prototype is present", function() {
+      function TestClass(o) { _.extend(this, o); };
+      var immutable = Immutable(new TestClass({a: 1, b: 2}), 
+        {prototype: TestClass.prototype});
+
+      var obj = immutable.instantiateEmptyObject();
+      assert.isFalse(Immutable.isImmutable(obj));
+      assert.deepEqual(obj, {});
+      TestUtils.assertHasPrototype(obj, TestClass.prototype); 
+    });
+  });
+};
+ 
diff --git a/test/ImmutableObject/test-merge.js b/test/ImmutableObject/test-merge.js
index aa5f81b..3316eb9 100644
--- a/test/ImmutableObject/test-merge.js
+++ b/test/ImmutableObject/test-merge.js
@@ -242,6 +242,18 @@ module.exports = function(config) {
       assert.equal(original, actualWithMerger);
     });
 
+    it("preserves prototypes across merges", function() {
+      function TestClass(o) { _.extend(this, o); };
+      var data = new TestClass({a: 1, b: 2});
+      var mergeData = {b: 3, c: 4};
+
+      var immutable = Immutable(data, {prototype: TestClass.prototype});
+      var result = immutable.merge(mergeData);
+
+      assert.deepEqual(result, _.extend({}, data, mergeData));
+      TestUtils.assertHasPrototype(result, TestClass.prototype);
+    });
+
     describe("when passed a single object", function() {
       generateMergeTestsFor([TestUtils.ComplexObjectSpecifier()]);
     });
diff --git a/test/ImmutableObject/test-without.js b/test/ImmutableObject/test-without.js
index ea28204..f28c798 100644
--- a/test/ImmutableObject/test-without.js
+++ b/test/ImmutableObject/test-without.js
@@ -86,6 +86,17 @@ module.exports = function(config) {
         assert.deepEqual(actual, expected);
       });
     });
+ 
+    it("preserves prototypes after call to without", function() {
+      function TestClass(o) { _.extend(this, o); };
+      var data = new TestClass({a: 1, b: 2});
+
+      var immutable = Immutable(data, {prototype: TestClass.prototype});
+      var result = immutable.without('b');
+
+      assert.deepEqual(result, _.omit(data, 'b'));
+      TestUtils.assertHasPrototype(result, TestClass.prototype);
+    });
 
     describe("when passed a single key", function() {
       generateWithoutTestsFor([JSC.string()]);
diff --git a/test/TestUtils.js b/test/TestUtils.js
index b4c056e..6971e75 100644
--- a/test/TestUtils.js
+++ b/test/TestUtils.js
@@ -23,6 +23,10 @@ function wrapAssertIsDeeplyImmutable(Immutable) {
   }
 }
 
+function assertHasPrototype(obj, expectedPrototype) {
+  assert.strictEqual(Object.getPrototypeOf(obj), expectedPrototype);
+}
+
 function withoutItengerKeys(obj) {
   return _.object(_.map(obj, function(value, key) {
     // Don't choose keys that can be parsed as 32-bit unsigned integers,
@@ -112,6 +116,7 @@ module.exports = function(Immutable) {
   return {
     assertImmutable:         wrapAssertImmutable(Immutable),
     assertIsDeeplyImmutable: wrapAssertIsDeeplyImmutable(Immutable),
+    assertHasPrototype:      assertHasPrototype,
     ImmutableArraySpecifier: wrapImmutableArraySpecifier(Immutable),
     ComplexObjectSpecifier:  ComplexObjectSpecifier,
     TraversableObjectSpecifier: TraversableObjectSpecifier,