Skip to content

Commit

Permalink
util: add overwriteChainableMethod utility (for #215)
Browse files Browse the repository at this point in the history
  • Loading branch information
Max Edmands committed Nov 30, 2013
1 parent 4ce3d42 commit fe192c5
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 4 deletions.
4 changes: 4 additions & 0 deletions lib/chai/assertion.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ module.exports = function (_chai, util) {
util.overwriteMethod(this.prototype, name, fn);
};

Assertion.overwriteChainableMethod = function (name, fn, chainingBehavior) {
util.overwriteChainableMethod(this.prototype, name, fn, chainingBehavior);
};

/*!
* ### .assert(expression, message, negateMessage, expected, actual)
*
Expand Down
20 changes: 17 additions & 3 deletions lib/chai/utils/addChainableMethod.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,28 @@ var call = Function.prototype.call,
* @api public
*/

module.exports = function (ctx, name, method, chainingBehavior) {
module.exports = addChainableMethod = function (ctx, name, method, chainingBehavior) {
if (typeof chainingBehavior !== 'function')
chainingBehavior = function () { };

var chainableBehavior = {
ctx: ctx,
method: method,
chainingBehavior: chainingBehavior
};

// save the methods so we can overwrite them later, if we need to.
if (!addChainableMethod.methods[name])
addChainableMethod.methods[name] = [];

addChainableMethod.methods[name].push(chainableBehavior);

Object.defineProperty(ctx, name,
{ get: function () {
chainingBehavior.call(this);
chainableBehavior.chainingBehavior.call(this);

var assert = function () {
var result = method.apply(this, arguments);
var result = chainableBehavior.method.apply(this, arguments);
return result === undefined ? this : result;
};

Expand Down Expand Up @@ -92,3 +104,5 @@ module.exports = function (ctx, name, method, chainingBehavior) {
, configurable: true
});
};

addChainableMethod.methods = {};
6 changes: 6 additions & 0 deletions lib/chai/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,9 @@ exports.overwriteMethod = require('./overwriteMethod');

exports.addChainableMethod = require('./addChainableMethod');

/*!
* Overwrite chainable method
*/

exports.overwriteChainableMethod = require('./overwriteChainableMethod');

65 changes: 65 additions & 0 deletions lib/chai/utils/overwriteChainableMethod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*!
* Chai - overwriteChainableMethod utility
* Copyright(c) 2012-2013 Jake Luer <jake@alogicalparadox.com>
* MIT Licensed
*/

var addChainableMethod = require('./addChainableMethod');

/**
* ### overwriteChainableMethod (ctx, name, fn)
*
* Overwites an already existing chainable method
* and provides access to the previous function or
* property. Must return functions to be used for
* name.
*
* utils.overwriteChainableMethod(chai.Assertion.prototype, 'length',
* function (_super) {
* }
* , function (_super) {
* }
* );
*
* Can also be accessed directly from `chai.Assertion`.
*
* chai.Assertion.overwriteChainableMethod('foo', fn, fn);
*
* Then can be used as any other assertion.
*
* expect(myFoo).to.have.length(3);
* expect(myFoo).to.have.length.above(3);
*
* @param {Object} ctx object whose method / property is to be overwritten
* @param {String} name of method / property to overwrite
* @param {Function} method function that returns a function to be used for name
* @param {Function} chainingBehavior function that returns a function to be used for property
* @name overwriteChainableMethod
* @api public
*/

module.exports = function (ctx, name, method, chainingBehavior) {
var index = 0;
var chainableMethods = addChainableMethod.methods[name];

// doing a brute-force sequential search for the reference to the object in
// question, so we can get its original method and chaining behavior. yep.
// there is a danger of this running very slowly (O of n), but it's difficult
// for me to imagine n ever getting longer than, well, 1.
while(index < chainableMethods.length) {
if (chainableMethods[index].ctx === ctx) break;
++index;
}

var _chainingBehavior = chainableMethods[index].chainingBehavior;
chainableMethods[index].chainingBehavior = function () {
var result = chainingBehavior(_chainingBehavior).call(this);
return result === undefined ? this : result;
};

var _method = chainableMethods[index].method;
chainableMethods[index].method = function () {
var result = method(_method).apply(this, arguments);
return result === undefined ? this : result;
};
};
44 changes: 43 additions & 1 deletion test/utilities.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,5 +263,47 @@ describe('utilities', function () {
expect(obj).x.to.be.ok;
expect(obj).to.have.property('__x', 'X!');
})
})
});

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 {
_super.apply(this, arguments);
}
};
}
, function(_super) {
return function() {
_.flag(this, 'message', 'x marks the spot');
_super.apply(this, arguments);
};
}
);

// 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);
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(function () {
var assertion = expect('x');
_.flag(assertion, 'marked', true);
assertion.x()
}).to.throw(_chai.AssertionError);
});
});

});

0 comments on commit fe192c5

Please sign in to comment.