Repository: chaijs/chai (js)

Issue:
Consistency with returning new Assertion instead of this
A change was made a few months ago to return a new Assertion object with flags copied over instead of returning `this`. References: #562 #642 

However, it looks like there are still some spots where `this` is returned. The only one I've actually tested so far is [overwriteProperty](https://github.com/chaijs/chai/blob/e5e6f5cbbd0e3542f928559f2d290663facd82e5/lib/chai/utils/overwriteProperty.js#L61).

For example:

```js
const {expect, use} = require("chai");                                                                                  
                                                                                                                        
use(function (chai, utils) {                                                                                            
  utils.overwriteProperty(chai.Assertion.prototype, "and", function (_super) {                                          
    return function blah () {                                                                                           
      _super.call(this);                                                                                                
    };                                                                                                                  
  });                                                                                                                   
});                                                                                                                     
                                                                                                                        
it("but", () => {                                                                                                      
  expect([1, 2, 3]).to.be.an.but.length.below(1000)    // Passes
});

it("and", () => {                                                                                                      
  expect([1, 2, 3]).to.be.an.and.length.below(1000)    // Fails
});
```

As expected, the second assertion fails with error: "TypeError: expect(...).to.be.an.and.length.below is not a function" because the overwritten `and` is returning the function that was originally returned by the chainable method `an` instead of returning a new Assertion.

Other places where this might be a problem (but I haven't tested):

- [overwriteMethod](https://github.com/chaijs/chai/blob/e5e6f5cbbd0e3542f928559f2d290663facd82e5/lib/chai/utils/overwriteMethod.js#L60)
- [addChainableMethod](https://github.com/chaijs/chai/blob/e5e6f5cbbd0e3542f928559f2d290663facd82e5/lib/chai/utils/addChainableMethod.js#L85)
- [overwriteChainableMethod](https://github.com/chaijs/chai/blob/e5e6f5cbbd0e3542f928559f2d290663facd82e5/lib/chai/utils/overwriteChainableMethod.js#L46-L52)



Pull request:
Return new assertion with flags copied over instead of this. Fix #791
Hello!

This PR addresses #791.

- [x] `overwriteProperty`
- [x] `overwriteMethod`
- [x] `addChainableMethod`
- [x] `overwriteChainableMethod`


Patch:
diff --git a/lib/chai/utils/addChainableMethod.js b/lib/chai/utils/addChainableMethod.js
index d2c3209..2c05d33 100644
--- a/lib/chai/utils/addChainableMethod.js
+++ b/lib/chai/utils/addChainableMethod.js
@@ -8,9 +8,10 @@
  * Module dependencies
  */
 
-var transferFlags = require('./transferFlags');
+var chai = require('../../chai');
 var flag = require('./flag');
 var proxify = require('./proxify');
+var transferFlags = require('./transferFlags');
 
 /*!
  * Module variables
@@ -82,7 +83,14 @@ module.exports = function (ctx, name, method, chainingBehavior) {
           if (old_ssfi)
             flag(this, 'ssfi', assert);
           var result = chainableBehavior.method.apply(this, arguments);
-          return result === undefined ? this : result;
+
+          if (result !== undefined) {
+            return result;
+          }
+
+          var newAssertion = new chai.Assertion();
+          transferFlags(this, newAssertion);
+          return newAssertion;
         };
 
         // Use `__proto__` if available
diff --git a/lib/chai/utils/overwriteChainableMethod.js b/lib/chai/utils/overwriteChainableMethod.js
index 1ef7439..53be8a7 100644
--- a/lib/chai/utils/overwriteChainableMethod.js
+++ b/lib/chai/utils/overwriteChainableMethod.js
@@ -4,6 +4,9 @@
  * MIT Licensed
  */
 
+var chai = require('../../chai');
+var transferFlags = require('./transferFlags');
+
 /**
  * ### overwriteChainableMethod (ctx, name, method, chainingBehavior)
  *
@@ -43,12 +46,24 @@ module.exports = function (ctx, name, method, chainingBehavior) {
   var _chainingBehavior = chainableBehavior.chainingBehavior;
   chainableBehavior.chainingBehavior = function () {
     var result = chainingBehavior(_chainingBehavior).call(this);
-    return result === undefined ? this : result;
+    if (result !== undefined) {
+      return result;
+    }
+
+    var newAssertion = new chai.Assertion();
+    transferFlags(this, newAssertion);
+    return newAssertion;
   };
 
   var _method = chainableBehavior.method;
   chainableBehavior.method = function () {
     var result = method(_method).apply(this, arguments);
-    return result === undefined ? this : result;
+    if (result !== undefined) {
+      return result;
+    }
+
+    var newAssertion = new chai.Assertion();
+    transferFlags(this, newAssertion);
+    return newAssertion;
   };
 };
diff --git a/lib/chai/utils/overwriteMethod.js b/lib/chai/utils/overwriteMethod.js
index 4c61c18..9e2c112 100644
--- a/lib/chai/utils/overwriteMethod.js
+++ b/lib/chai/utils/overwriteMethod.js
@@ -4,7 +4,9 @@
  * MIT Licensed
  */
 
+var chai = require('../../chai');
 var flag = require('./flag');
+var transferFlags = require('./transferFlags');
 
 /**
  * ### overwriteMethod (ctx, name, fn)
@@ -59,6 +61,12 @@ module.exports = function (ctx, name, method) {
     var result = method(_super).apply(this, arguments);
     flag(this, 'keep_ssfi', false);
 
-    return result === undefined ? this : result;
+    if (result !== undefined) {
+      return result;
+    }
+
+    var newAssertion = new chai.Assertion();
+    transferFlags(this, newAssertion);
+    return newAssertion;
   }
 };
diff --git a/lib/chai/utils/overwriteProperty.js b/lib/chai/utils/overwriteProperty.js
index fa5452f..155ccbd 100644
--- a/lib/chai/utils/overwriteProperty.js
+++ b/lib/chai/utils/overwriteProperty.js
@@ -4,7 +4,9 @@
  * MIT Licensed
  */
 
+var chai = require('../../chai');
 var flag = require('./flag');
+var transferFlags = require('./transferFlags');
 
 /**
  * ### overwriteProperty (ctx, name, fn)
@@ -58,7 +60,13 @@ module.exports = function (ctx, name, getter) {
         var result = getter(_super).call(this);
         flag(this, 'keep_ssfi', false);
 
-        return result === undefined ? this : result;
+        if (result !== undefined) {
+          return result;
+        }
+
+        var newAssertion = new chai.Assertion();
+        transferFlags(this, newAssertion);
+        return newAssertion;
       }
     , configurable: true
   });


Test patch:
diff --git a/test/utilities.js b/test/utilities.js
index 7201eef..0f384fe 100644
--- a/test/utilities.js
+++ b/test/utilities.js
@@ -209,148 +209,100 @@ describe('utilities', function () {
     });
   });
 
-  it('addMethod', function () {
-    chai.use(function(_chai, utils) {
-      expect(_chai.Assertion).to.not.respondTo('eqqqual');
-      _chai.Assertion.addMethod('eqqqual', function (str) {
-        var object = utils.flag(this, 'object');
-        new _chai.Assertion(object).to.be.eql(str);
-      });
-      expect(_chai.Assertion).to.respondTo('eqqqual');
-    });
-
-    expect('spec').to.eqqqual('spec');
-  });
+  describe('addMethod', function() {
+    var assertionConstructor;
 
-  it('addMethod returning result', function () {
-    chai.use(function(_chai, utils) {
-      _chai.Assertion.addMethod('result', function () {
-        return 'result';
-      })
-    });
+    before(function() {
+      chai.use(function(_chai, utils) {
+        assertionConstructor = _chai.Assertion;
 
-    expect(expect('foo').result()).to.equal('result');
-  });
+        expect(_chai.Assertion).to.not.respondTo('eqqqual');
+        _chai.Assertion.addMethod('eqqqual', function (str) {
+          var object = utils.flag(this, 'object');
+          new _chai.Assertion(object).to.be.eql(str);
+        });
 
-  it('addMethod returns new assertion with flags copied over', function () {
-    var assertionConstructor;
+        _chai.Assertion.addMethod('result', function () {
+          return 'result';
+        })
 
-    chai.use(function(_chai, utils) {
-      assertionConstructor = _chai.Assertion;
-      _chai.Assertion.addMethod('returnNewAssertion', function () {
-        utils.flag(this, 'mySpecificFlag', 'value1');
-        utils.flag(this, 'ultraSpecificFlag', 'value2');
-      });
+        _chai.Assertion.addMethod('returnNewAssertion', function () {
+          utils.flag(this, 'mySpecificFlag', 'value1');
+          utils.flag(this, 'ultraSpecificFlag', 'value2');
+        });
 
-      _chai.Assertion.addMethod('checkFlags', function() {
-        this.assert(
-            utils.flag(this, 'mySpecificFlag') === 'value1' &&
-            utils.flag(this, 'ultraSpecificFlag') === 'value2'
-          , 'expected assertion to have specific flags'
-          , "this doesn't matter"
-        );
+        _chai.Assertion.addMethod('checkFlags', function() {
+          this.assert(
+              utils.flag(this, 'mySpecificFlag') === 'value1' &&
+              utils.flag(this, 'ultraSpecificFlag') === 'value2'
+            , 'expected assertion to have specific flags'
+            , "this doesn't matter"
+          );
+        });
       });
     });
 
-    assertion1 = expect('foo');
-    assertion2 = assertion1.to.returnNewAssertion();
-
-    // Checking if a new assertion was returned
-    expect(assertion1).to.not.be.equal(assertion2);
-
-    // Check if flags were copied
-    assertion2.checkFlags();
-
-    // Checking if it's really an instance of an Assertion
-    expect(assertion2).to.be.instanceOf(assertionConstructor);
+    after(function() {
+      delete chai.Assertion.prototype.eqqqual;
 
-    // Test chaining `.length` after a method to guarantee it's not a function's
-    // `length`. Note: 'instanceof' cannot be used here because the test will
-    // fail in IE 10 due to how addChainableMethod works without __proto__
-    // support. Therefore, test the constructor property of length instead.
-    var anAssertion = expect([1, 2, 3]).to.be.an.instanceof(Array);
-    expect(anAssertion.length.constructor).to.equal(assertionConstructor);
+      delete chai.Assertion.prototype.result;
 
-    var anotherAssertion = expect([1, 2, 3]).to.have.a.lengthOf(3).and.to.be.ok;
-    expect(anotherAssertion.length.constructor).to.equal(assertionConstructor);
-  });
+      delete chai.Assertion.prototype.returnNewAssertion;
+      delete chai.Assertion.prototype.checkFlags;
+    });
 
-  it('overwriteMethod', function () {
-    chai.use(function (_chai, _) {
-      expect(_chai.Assertion).to.respondTo('eqqqual');
-      _chai.Assertion.overwriteMethod('eqqqual', function (_super) {
-        return function (str) {
-          var object = _.flag(this, 'object');
-          if (object == 'cucumber' && str == 'cuke') {
-            _.flag(this, 'cucumber', true);
-          } else {
-            _super.apply(this, arguments);
-          }
-        };
-      });
+    it('addMethod', function () {
+      expect(chai.Assertion).to.respondTo('eqqqual');
+      expect('spec').to.eqqqual('spec');
+    });
 
+    it('addMethod returning result', function () {
+      expect(expect('foo').result()).to.equal('result');
     });
 
-    var vege = expect('cucumber').to.eqqqual('cucumber');
-    expect(vege.__flags).to.not.have.property('cucumber');
-    var cuke = expect('cucumber').to.eqqqual('cuke');
-    expect(cuke.__flags).to.have.property('cucumber');
+    it('addMethod returns new assertion with flags copied over', function () {
+      assertion1 = expect('foo');
+      assertion2 = assertion1.to.returnNewAssertion();
 
-    chai.use(function (_chai, _) {
-      expect(_chai.Assertion).to.not.respondTo('doesnotexist');
-      _chai.Assertion.overwriteMethod('doesnotexist', function (_super) {
-        expect(_super).to.be.a('function');
-        return function () {
-          _.flag(this, 'doesnt', true);
-        }
-      });
-    });
+      // Checking if a new assertion was returned
+      expect(assertion1).to.not.be.equal(assertion2);
 
-    var dne = expect('something').to.doesnotexist();
-    expect(dne.__flags).to.have.property('doesnt');
+      // Check if flags were copied
+      assertion2.checkFlags();
 
-    chai.use(function (_chai, _) {
-      expect(_chai.Assertion).to.not.respondTo('doesnotexistfail');
-      _chai.Assertion.overwriteMethod('doesnotexistfail', function (_super) {
-        expect(_super).to.be.a('function');
-        return function () {
-          _.flag(this, 'doesnt', true);
-          _super.apply(this, arguments);
-        }
-      });
-    });
+      // Checking if it's really an instance of an Assertion
+      expect(assertion2).to.be.instanceOf(assertionConstructor);
 
-    var dneFail = expect('something');
-    var dneError;
-    try { dneFail.doesnotexistfail(); }
-    catch (e) { dneError = e; }
-    expect(dneFail.__flags).to.have.property('doesnt');
-    expect(dneError.message).to.eql('doesnotexistfail is not a function');
-  });
+      // Test chaining `.length` after a method to guarantee it's not a function's
+      // `length`. Note: 'instanceof' cannot be used here because the test will
+      // fail in IE 10 due to how addChainableMethod works without __proto__
+      // support. Therefore, test the constructor property of length instead.
+      var anAssertion = expect([1, 2, 3]).to.be.an.instanceof(Array);
+      expect(anAssertion.length.constructor).to.equal(assertionConstructor);
 
-  it('overwriteMethod returning result', function () {
-    chai.use(function (_chai, _) {
-      _chai.Assertion.overwriteMethod('result', function (_super) {
-        return function () {
-          return 'result';
-        }
-      });
+      var anotherAssertion = expect([1, 2, 3]).to.have.a.lengthOf(3).and.to.be.ok;
+      expect(anotherAssertion.length.constructor).to.equal(assertionConstructor);
     });
-
-    expect(expect('foo').result()).to.equal('result');
   });
 
   describe('overwriteMethod', function () {
+    var assertionConstructor;
+
     before(function() {
       chai.config.includeStack = false;
 
-      chai.use(function(_chai, _) {
+      chai.use(function(_chai, utils) {
+        assertionConstructor = _chai.Assertion;
+
         _chai.Assertion.addMethod('four', function() {
           this.assert(this._obj === 4, 'expected #{this} to be 4', 'expected #{this} to not be 4', 4);
         });
 
         _chai.Assertion.overwriteMethod('four', function(_super) {
           return function() {
+            utils.flag(this, 'mySpecificFlag', 'value1');
+            utils.flag(this, 'ultraSpecificFlag', 'value2');
+
             if (typeof this._obj === 'string') {
               this.assert(this._obj === 'four', 'expected #{this} to be \'four\'', 'expected #{this} to not be \'four\'', 'four');
             } else {
@@ -358,11 +310,92 @@ describe('utilities', function () {
             }
           }
         });
+
+        _chai.Assertion.addMethod('checkFlags', function() {
+          this.assert(
+              utils.flag(this, 'mySpecificFlag') === 'value1' &&
+              utils.flag(this, 'ultraSpecificFlag') === 'value2'
+            , 'expected assertion to have specific flags'
+            , "this doesn't matter"
+          );
+        });
       });
     });
 
     after(function() {
       delete chai.Assertion.prototype.four;
+      delete chai.Assertion.prototype.checkFlags;
+      delete chai.Assertion.prototype.eqqqual;
+      delete chai.Assertion.prototype.doesnotexist;
+      delete chai.Assertion.prototype.doesnotexistfail;
+    });
+
+    it('overwriteMethod', function () {
+      chai.use(function (_chai, utils) {
+        _chai.Assertion.addMethod('eqqqual', function (str) {
+          var object = utils.flag(this, 'object');
+          new _chai.Assertion(object).to.be.eql(str);
+        });
+
+        _chai.Assertion.overwriteMethod('eqqqual', function (_super) {
+          return function (str) {
+            var object = utils.flag(this, 'object');
+            if (object == 'cucumber' && str == 'cuke') {
+              utils.flag(this, 'cucumber', true);
+            } else {
+              _super.apply(this, arguments);
+            }
+          };
+        });
+      });
+
+      var vege = expect('cucumber').to.eqqqual('cucumber');
+      expect(vege.__flags).to.not.have.property('cucumber');
+      var cuke = expect('cucumber').to.eqqqual('cuke');
+      expect(cuke.__flags).to.have.property('cucumber');
+
+      chai.use(function (_chai, _) {
+        expect(_chai.Assertion).to.not.respondTo('doesnotexist');
+        _chai.Assertion.overwriteMethod('doesnotexist', function (_super) {
+          expect(_super).to.be.a('function');
+          return function () {
+            _.flag(this, 'doesnt', true);
+          }
+        });
+      });
+
+      var dne = expect('something').to.doesnotexist();
+      expect(dne.__flags).to.have.property('doesnt');
+
+      chai.use(function (_chai, _) {
+        expect(_chai.Assertion).to.not.respondTo('doesnotexistfail');
+        _chai.Assertion.overwriteMethod('doesnotexistfail', function (_super) {
+          expect(_super).to.be.a('function');
+          return function () {
+            _.flag(this, 'doesnt', true);
+            _super.apply(this, arguments);
+          }
+        });
+      });
+
+      var dneFail = expect('something');
+      var dneError;
+      try { dneFail.doesnotexistfail(); }
+      catch (e) { dneError = e; }
+      expect(dneFail.__flags).to.have.property('doesnt');
+      expect(dneError.message).to.eql('doesnotexistfail is not a function');
+    });
+
+    it('overwriteMethod returning result', function () {
+      chai.use(function (_chai, _) {
+        _chai.Assertion.overwriteMethod('result', function (_super) {
+          return function () {
+            return 'result';
+          }
+        });
+      });
+
+      expect(expect('foo').result()).to.equal('result');
     });
 
     it('calling _super has correct stack trace', function() {
@@ -392,110 +425,130 @@ describe('utilities', function () {
         }
       }
     });
-  });
 
-  it('addProperty', function () {
-    chai.use(function (_chai, _) {
-      _chai.Assertion.addProperty('tea', function () {
-        _.flag(this, 'tea', 'chai');
-      });
-    });
+    it('should return a new assertion with flags copied over', function () {
+      var assertion1 = expect('four');
+      var assertion2 = assertion1.four();
 
-    var assert = expect('chai').to.be.tea;
-    expect(assert.__flags.tea).to.equal('chai');
-  });
+      // Checking if a new assertion was returned
+      expect(assertion1).to.not.be.equal(assertion2);
 
-  it('addProperty returns a new assertion with flags copied over', function () {
-    var assertionConstructor = chai.Assertion;
+      // Check if flags were copied
+      assertion2.checkFlags();
 
-    chai.use(function(_chai, utils) {
-      assertionConstructor = _chai.Assertion;
-      _chai.Assertion.addProperty('thing', function () {
-        utils.flag(this, 'mySpecificFlag', 'value1');
-        utils.flag(this, 'ultraSpecificFlag', 'value2');
-      });
+      // Checking if it's really an instance of an Assertion
+      expect(assertion2).to.be.instanceOf(assertionConstructor);
 
-      _chai.Assertion.addMethod('checkFlags', function() {
-        this.assert(
-            utils.flag(this, 'mySpecificFlag') === 'value1' &&
-            utils.flag(this, 'ultraSpecificFlag') === 'value2'
-          , 'expected assertion to have specific flags'
-          , "this doesn't matter"
-        );
-      });
-    });
+      // Test chaining `.length` after a method to guarantee it is not a function's `length`
+      expect('four').to.be.a.four().length.above(2);
 
-    assertion1 = expect('foo');
-    assertion2 = assertion1.is.thing;
+      // Ensure that foo returns an Assertion (not a function)
+      expect(expect('four').four()).to.be.an.instanceOf(assertionConstructor);
+    });
+  });
 
-    // Checking if a new assertion was returned
-    expect(assertion1).to.not.be.equal(assertion2);
+  describe('addProperty', function() {
+    var assertionConstructor = chai.Assertion;
 
-    // Check if flags were copied
-    assertion2.checkFlags();
+    before(function() {
+      chai.use(function (_chai, utils) {
+        assertionConstructor = _chai.Assertion;
 
-    // If it is, calling length on it should return an assertion, not a function
-    expect([1, 2, 3]).to.be.an.instanceof(Array);
+        _chai.Assertion.addProperty('tea', function () {
+          utils.flag(this, 'tea', 'chai');
+        });
 
-    // Checking if it's really an instance of an Assertion
-    expect(assertion2).to.be.instanceOf(assertionConstructor);
+        _chai.Assertion.addProperty('result', function () {
+          return 'result';
+        })
 
-    // Test chaining `.length` after a property to guarantee it is not a function's `length`
-    expect([1, 2, 3]).to.be.a.thing.with.length.above(2);
-    expect([1, 2, 3]).to.be.an.instanceOf(Array).and.have.length.below(4);
+        _chai.Assertion.addProperty('thing', function () {
+          utils.flag(this, 'mySpecificFlag', 'value1');
+          utils.flag(this, 'ultraSpecificFlag', 'value2');
+        });
 
-    expect(expect([1, 2, 3]).be).to.be.an.instanceOf(assertionConstructor);
-    expect(expect([1, 2, 3]).thing).to.be.an.instanceOf(assertionConstructor);
-  });
+        _chai.Assertion.addMethod('checkFlags', function() {
+          this.assert(
+              utils.flag(this, 'mySpecificFlag') === 'value1' &&
+              utils.flag(this, 'ultraSpecificFlag') === 'value2'
+            , 'expected assertion to have specific flags'
+            , "this doesn't matter"
+          );
+        });
+      });
+    });
 
-  it('addProperty returning result', function () {
-    chai.use(function(_chai, _) {
-      _chai.Assertion.addProperty('result', function () {
-        return 'result';
-      })
+    after(function() {
+      delete chai.Assertion.prototype.tea;
+      delete chai.Assertion.prototype.thing;
+      delete chai.Assertion.prototype.checkFlags;
+      delete chai.Assertion.prototype.result;
     });
 
-    expect(expect('foo').result).to.equal('result');
-  });
+    it('addProperty', function () {
+      var assert = expect('chai').to.be.tea;
+      expect(assert.__flags.tea).to.equal('chai');
+    });
 
-  it('overwriteProperty', function () {
-    chai.use(function (_chai, _) {
-      expect(new chai.Assertion()).to.have.property('tea');
-      _chai.Assertion.overwriteProperty('tea', function (_super) {
-        return function () {
-          var act = _.flag(this, 'object');
-          if (act === 'matcha') {
-            _.flag(this, 'tea', 'matcha');
-          } else {
-            _super.call(this);
-          }
-        }
-      });
+    it('addProperty returning result', function () {
+      expect(expect('foo').result).to.equal('result');
     });
 
-    var matcha = expect('matcha').to.be.tea;
-    expect(matcha.__flags.tea).to.equal('matcha');
-    var assert = expect('something').to.be.tea;
-    expect(assert.__flags.tea).to.equal('chai');
-  });
+    it('addProperty returns a new assertion with flags copied over', function () {
+      assertion1 = expect('foo');
+      assertion2 = assertion1.is.thing;
 
-  it('overwriteProperty returning result', function () {
-    chai.use(function(_chai, _) {
-      _chai.Assertion.overwriteProperty('result', function (_super) {
-        return function () {
-          return 'result';
-        }
-      });
-    });
+      // Checking if a new assertion was returned
+      expect(assertion1).to.not.be.equal(assertion2);
+
+      // Check if flags were copied
+      assertion2.checkFlags();
+
+      // If it is, calling length on it should return an assertion, not a function
+      expect([1, 2, 3]).to.be.an.instanceof(Array);
+
+      // Checking if it's really an instance of an Assertion
+      expect(assertion2).to.be.instanceOf(assertionConstructor);
 
-    expect(expect('foo').result).to.equal('result');
+      // Test chaining `.length` after a property to guarantee it is not a function's `length`
+      expect([1, 2, 3]).to.be.a.thing.with.length.above(2);
+      expect([1, 2, 3]).to.be.an.instanceOf(Array).and.have.length.below(4);
+
+      expect(expect([1, 2, 3]).be).to.be.an.instanceOf(assertionConstructor);
+      expect(expect([1, 2, 3]).thing).to.be.an.instanceOf(assertionConstructor);
+    });
   });
 
   describe('overwriteProperty', function () {
+    var assertionConstructor;
+
     before(function() {
       chai.config.includeStack = false;
 
-      chai.use(function(_chai, _) {
+      chai.use(function(_chai, utils) {
+        assertionConstructor = _chai.Assertion;
+
+        _chai.Assertion.addProperty('tea', function () {
+          utils.flag(this, 'tea', 'chai');
+        });
+
+        _chai.Assertion.overwriteProperty('tea', function (_super) {
+          return function () {
+            var act = utils.flag(this, 'object');
+            if (act === 'matcha') {
+              utils.flag(this, 'tea', 'matcha');
+            } else {
+              _super.call(this);
+            }
+          }
+        });
+
+        _chai.Assertion.overwriteProperty('result', function (_super) {
+          return function () {
+            return 'result';
+          }
+        });
+
         _chai.Assertion.addProperty('four', function() {
           this.assert(this._obj === 4, 'expected #{this} to be 4', 'expected #{this} to not be 4', 4);
         });
@@ -509,11 +562,45 @@ describe('utilities', function () {
             }
           }
         });
+
+        _chai.Assertion.addProperty('foo');
+
+        _chai.Assertion.overwriteProperty('foo', function (_super) {
+          return function blah () {
+            utils.flag(this, 'mySpecificFlag', 'value1');
+            utils.flag(this, 'ultraSpecificFlag', 'value2');
+            _super.call(this);
+          };
+        });
+
+        _chai.Assertion.addMethod('checkFlags', function() {
+          this.assert(
+              utils.flag(this, 'mySpecificFlag') === 'value1' &&
+              utils.flag(this, 'ultraSpecificFlag') === 'value2'
+            , 'expected assertion to have specific flags'
+            , "this doesn't matter"
+          );
+        });
       });
     });
 
     after(function() {
+      delete chai.Assertion.prototype.tea;
       delete chai.Assertion.prototype.four;
+      delete chai.Assertion.prototype.result;
+      delete chai.Assertion.prototype.foo;
+      delete chai.Assertion.prototype.checkFlags
+    });
+
+    it('overwriteProperty', function () {
+      var matcha = expect('matcha').to.be.tea;
+      expect(matcha.__flags.tea).to.equal('matcha');
+      var assert = expect('something').to.be.tea;
+      expect(assert.__flags.tea).to.equal('chai');
+    });
+
+    it('overwriteProperty returning result', function () {
+      expect(expect('foo').result).to.equal('result');
     });
 
     it('calling _super has correct stack trace', function() {
@@ -543,6 +630,30 @@ describe('utilities', function () {
         }
       }
     });
+
+    it('should return new assertion with flags copied over', function() {
+      var assertion1 = expect('foo');
+      var assertion2 = assertion1.is.foo;
+
+      // Checking if a new assertion was returned
+      expect(assertion1).to.not.be.equal(assertion2);
+
+      // Check if flags were copied
+      assertion2.checkFlags();
+
+      // If it is, calling length on it should return an assertion, not a function
+      expect([1, 2, 3]).to.be.an.foo.length.below(1000);
+
+      // Checking if it's really an instance of an Assertion
+      expect(assertion2).to.be.instanceOf(assertionConstructor);
+
+      // Test chaining `.length` after a property to guarantee it is not a function's `length`
+      expect([1, 2, 3]).to.be.a.foo.with.length.above(2);
+      expect([1, 2, 3]).to.be.an.instanceOf(Array).and.have.length.below(4);
+
+      expect(expect([1, 2, 3]).be).to.be.an.instanceOf(assertionConstructor);
+      expect(expect([1, 2, 3]).foo).to.be.an.instanceOf(assertionConstructor);
+    });
   });
 
   it('getMessage', function () {
@@ -731,24 +842,55 @@ describe('utilities', function () {
     });
   });
 
-  it('addChainableMethod', function () {
-    chai.use(function (_chai, _) {
-      _chai.Assertion.addChainableMethod('x',
-        function () {
-          new chai.Assertion(this._obj).to.be.equal('x');
-        }
-      , function () {
-          this._obj = this._obj || {};
-          this._obj.__x = 'X!'
-        }
-      );
+  describe('addChainableMethod', function() {
+    var assertionConstructor;
+
+    before(function() {
+      chai.use(function (_chai, utils) {
+        assertionConstructor = _chai.Assertion;
+
+        _chai.Assertion.addChainableMethod('x',
+          function () {
+            new chai.Assertion(this._obj).to.be.equal('x');
+          }
+        , function () {
+            this._obj = this._obj || {};
+            this._obj.__x = 'X!'
+          }
+        );
+
+        _chai.Assertion.addChainableMethod('foo', function(str) {
+          utils.flag(this, 'mySpecificFlag', 'value1');
+          utils.flag(this, 'ultraSpecificFlag', 'value2');
 
+          var obj = utils.flag(this, 'object');
+          new _chai.Assertion(obj).to.be.equal(str);
+        });
+
+        _chai.Assertion.addMethod('checkFlags', function() {
+          this.assert(
+              utils.flag(this, 'mySpecificFlag') === 'value1' &&
+              utils.flag(this, 'ultraSpecificFlag') === 'value2'
+            , 'expected assertion to have specific flags'
+            , "this doesn't matter"
+          );
+        });
+      });
+    });
+
+    after(function() {
+      delete chai.Assertion.prototype.x;
+      delete chai.Assertion.prototype.foo;
+      delete chai.Assertion.prototype.checkFlags;
+    });
+
+    it('addChainableMethod', function () {
       expect("foo").x.to.equal("foo");
       expect("x").x();
 
       expect(function () {
         expect("foo").x();
-      }).to.throw(_chai.AssertionError);
+      }).to.throw(chai.AssertionError);
 
       // Verify whether the original Function properties are present.
       // see https://github.com/chaijs/chai/commit/514dd6ce4#commitcomment-2593383
@@ -761,47 +903,130 @@ describe('utilities', function () {
       var obj = {};
       expect(obj).x.to.be.ok;
       expect(obj).to.have.property('__x', 'X!');
-    })
+    });
+
+    it('addChainableMethod should return a new assertion with flags copied over', function () {
+      chai.config.proxyExcludedKeys.push('nodeType');
+
+      var assertion1 = expect('bar');
+      var assertion2 = assertion1.foo('bar');
+
+      // Checking if a new assertion was returned
+      expect(assertion1).to.not.be.equal(assertion2);
+
+      // Check if flags were copied
+      assertion2.checkFlags();
+
+      // Checking if it's really an instance of an Assertion
+      expect(assertion2).to.be.instanceOf(assertionConstructor);
+
+      // Test chaining `.length` after a method to guarantee it is not a function's `length`
+      expect('bar').to.be.a.foo('bar').length.above(2);
+
+      // Ensure that foo returns an Assertion (not a function)
+      expect(expect('bar').foo('bar')).to.be.an.instanceOf(assertionConstructor);
+    });
   });
 
-  it('overwriteChainableMethod', function () {
-    chai.use(function (_chai, _) {
-      _chai.Assertion.overwriteChainableMethod('x',
-        function(_super) {
-          return function() {
-            if (_.flag(this, 'marked')) {
-              new chai.Assertion(this._obj).to.be.equal('spot');
-            } else {
+  describe('overwriteChainableMethod', function() {
+    var assertionConstructor;
+    var utils;
+
+    before(function() {
+      chai.use(function (_chai, _utils) {
+        assertionConstructor = _chai.Assertion;
+        utils = _utils;
+
+        _chai.Assertion.addChainableMethod('x',
+          function () {
+            new chai.Assertion(this._obj).to.be.equal('x');
+          }
+        , function () {
+            this._obj = this._obj || {};
+            this._obj.__x = 'X!'
+          }
+        );
+
+        _chai.Assertion.overwriteChainableMethod('x',
+          function(_super) {
+            return function() {
+              utils.flag(this, 'mySpecificFlag', 'value1');
+              utils.flag(this, 'ultraSpecificFlag', 'value2');
+
+              if (utils.flag(this, 'marked')) {
+                new chai.Assertion(this._obj).to.be.equal('spot');
+              } else {
+                _super.apply(this, arguments);
+              }
+            };
+          }
+        , function(_super) {
+            return function() {
+              utils.flag(this, 'message', 'x marks the spot');
               _super.apply(this, arguments);
-            }
-          };
-        }
-      , function(_super) {
-          return function() {
-            _.flag(this, 'message', 'x marks the spot');
-            _super.apply(this, arguments);
-          };
-        }
-      );
+            };
+          }
+        );
 
+        _chai.Assertion.addMethod('checkFlags', function() {
+          this.assert(
+              utils.flag(this, 'mySpecificFlag') === 'value1' &&
+              utils.flag(this, 'ultraSpecificFlag') === 'value2' &&
+              utils.flag(this, 'message') === 'x marks the spot'
+            , 'expected assertion to have specific flags'
+            , "this doesn't matter"
+          );
+        });
+      });
+    });
+
+    after(function() {
+      delete chai.Assertion.prototype.x;
+      delete chai.Assertion.prototype.checkFlags;
+    });
+
+    it('overwriteChainableMethod', function () {
       // Make sure the original behavior of 'x' remains the same
       expect('foo').x.to.equal("foo");
       expect("x").x();
       expect(function () {
         expect("foo").x();
-      }).to.throw(_chai.AssertionError);
+      }).to.throw(chai.AssertionError);
       var obj = {};
       expect(obj).x.to.be.ok;
       expect(obj).to.have.property('__x', 'X!');
 
       // Test the new behavior of 'x'
       var assertion = expect('foo').x.to.be.ok;
-      expect(_.flag(assertion, 'message')).to.equal('x marks the spot');
+      expect(utils.flag(assertion, 'message')).to.equal('x marks the spot');
       expect(function () {
         var assertion = expect('x');
-        _.flag(assertion, 'marked', true);
+        utils.flag(assertion, 'marked', true);
         assertion.x()
-      }).to.throw(_chai.AssertionError);
+      }).to.throw(chai.AssertionError);
+    });
+
+    it('should return a new assertion with flags copied over', function () {
+      var assertion1 = expect('x');
+      var assertion2 = assertion1.x();
+
+      chai.config.proxyExcludedKeys.push('nodeType');
+
+      // Checking if a new assertion was returned
+      expect(assertion1).to.not.be.equal(assertion2);
+
+      // Check if flags were copied
+      assertion2.checkFlags();
+
+      // Checking if it's really an instance of an Assertion
+      expect(assertion2).to.be.instanceOf(assertionConstructor);
+
+      // Test chaining `.length` after a method to guarantee it is not a function's `length`
+      expect('x').to.be.x().length.above(0);
+
+      // Ensure that foo returns an Assertion (not a function)
+      expect(expect('x').x()).to.be.an.instanceOf(assertionConstructor);
+      expect(expect('x').x).to.be.an.instanceOf(assertionConstructor);
     });
   });