From 21275259332e963eb69f5026ef9a70cba724d1c9 Mon Sep 17 00:00:00 2001 From: Max Edmands Date: Sat, 30 Nov 2013 09:17:48 -0800 Subject: [PATCH 1/3] util: add overwriteChainableMethod utility (for #215) --- component.json | 1 + lib/chai/assertion.js | 4 ++ lib/chai/utils/addChainableMethod.js | 20 ++++++- lib/chai/utils/index.js | 6 ++ lib/chai/utils/overwriteChainableMethod.js | 65 ++++++++++++++++++++++ test/utilities.js | 44 ++++++++++++++- 6 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 lib/chai/utils/overwriteChainableMethod.js diff --git a/component.json b/component.json index d199455bf..3c885e30e 100644 --- a/component.json +++ b/component.json @@ -35,6 +35,7 @@ , "lib/chai/utils/objDisplay.js" , "lib/chai/utils/overwriteMethod.js" , "lib/chai/utils/overwriteProperty.js" + , "lib/chai/utils/overwriteChainableMethod.js" , "lib/chai/utils/test.js" , "lib/chai/utils/transferFlags.js" , "lib/chai/utils/type.js" diff --git a/lib/chai/assertion.js b/lib/chai/assertion.js index 56223a8c6..0f0fc35cc 100644 --- a/lib/chai/assertion.js +++ b/lib/chai/assertion.js @@ -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) * diff --git a/lib/chai/utils/addChainableMethod.js b/lib/chai/utils/addChainableMethod.js index 1d5c1d655..83059db5c 100644 --- a/lib/chai/utils/addChainableMethod.js +++ b/lib/chai/utils/addChainableMethod.js @@ -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; }; @@ -92,3 +104,5 @@ module.exports = function (ctx, name, method, chainingBehavior) { , configurable: true }); }; + +addChainableMethod.methods = {}; diff --git a/lib/chai/utils/index.js b/lib/chai/utils/index.js index 63b30d3ce..974c7afea 100644 --- a/lib/chai/utils/index.js +++ b/lib/chai/utils/index.js @@ -106,3 +106,9 @@ exports.overwriteMethod = require('./overwriteMethod'); exports.addChainableMethod = require('./addChainableMethod'); +/*! + * Overwrite chainable method + */ + +exports.overwriteChainableMethod = require('./overwriteChainableMethod'); + diff --git a/lib/chai/utils/overwriteChainableMethod.js b/lib/chai/utils/overwriteChainableMethod.js new file mode 100644 index 000000000..b297e2476 --- /dev/null +++ b/lib/chai/utils/overwriteChainableMethod.js @@ -0,0 +1,65 @@ +/*! + * Chai - overwriteChainableMethod utility + * Copyright(c) 2012-2013 Jake Luer + * 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; + }; +}; diff --git a/test/utilities.js b/test/utilities.js index 0bee9c59e..67f7dc7f6 100644 --- a/test/utilities.js +++ b/test/utilities.js @@ -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); + }); + }); + }); From 76ac685d5ac14301ea461ce36d96b0a1783b98af Mon Sep 17 00:00:00 2001 From: Max Edmands Date: Sat, 30 Nov 2013 10:50:31 -0800 Subject: [PATCH 2/3] util: code style fix --- lib/chai/utils/addChainableMethod.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/chai/utils/addChainableMethod.js b/lib/chai/utils/addChainableMethod.js index 83059db5c..f05779726 100644 --- a/lib/chai/utils/addChainableMethod.js +++ b/lib/chai/utils/addChainableMethod.js @@ -59,9 +59,9 @@ module.exports = addChainableMethod = function (ctx, name, method, chainingBehav chainingBehavior = function () { }; var chainableBehavior = { - ctx: ctx, - method: method, - chainingBehavior: chainingBehavior + ctx: ctx + , method: method + , chainingBehavior: chainingBehavior }; // save the methods so we can overwrite them later, if we need to. From 270f9d92d95fda8adc0cf64586616eb5ca93630f Mon Sep 17 00:00:00 2001 From: Max Edmands Date: Sun, 1 Dec 2013 09:55:01 -0800 Subject: [PATCH 3/3] util: store chainable behavior in a __methods object on ctx --- lib/chai/utils/addChainableMethod.js | 18 ++++++++---------- lib/chai/utils/overwriteChainableMethod.js | 22 +++++----------------- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/lib/chai/utils/addChainableMethod.js b/lib/chai/utils/addChainableMethod.js index f05779726..34647d7a6 100644 --- a/lib/chai/utils/addChainableMethod.js +++ b/lib/chai/utils/addChainableMethod.js @@ -54,21 +54,21 @@ var call = Function.prototype.call, * @api public */ -module.exports = addChainableMethod = function (ctx, name, method, chainingBehavior) { - if (typeof chainingBehavior !== 'function') +module.exports = function (ctx, name, method, chainingBehavior) { + if (typeof chainingBehavior !== 'function') { chainingBehavior = function () { }; + } var chainableBehavior = { - ctx: ctx - , method: method + 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); + if (!ctx.__methods) { + ctx.__methods = {}; + } + ctx.__methods[name] = chainableBehavior; Object.defineProperty(ctx, name, { get: function () { @@ -104,5 +104,3 @@ module.exports = addChainableMethod = function (ctx, name, method, chainingBehav , configurable: true }); }; - -addChainableMethod.methods = {}; diff --git a/lib/chai/utils/overwriteChainableMethod.js b/lib/chai/utils/overwriteChainableMethod.js index b297e2476..0662e80c9 100644 --- a/lib/chai/utils/overwriteChainableMethod.js +++ b/lib/chai/utils/overwriteChainableMethod.js @@ -4,8 +4,6 @@ * MIT Licensed */ -var addChainableMethod = require('./addChainableMethod'); - /** * ### overwriteChainableMethod (ctx, name, fn) * @@ -39,26 +37,16 @@ var addChainableMethod = require('./addChainableMethod'); */ 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 chainableBehavior = ctx.__methods[name]; - var _chainingBehavior = chainableMethods[index].chainingBehavior; - chainableMethods[index].chainingBehavior = function () { + var _chainingBehavior = chainableBehavior.chainingBehavior; + chainableBehavior.chainingBehavior = function () { var result = chainingBehavior(_chainingBehavior).call(this); return result === undefined ? this : result; }; - var _method = chainableMethods[index].method; - chainableMethods[index].method = function () { + var _method = chainableBehavior.method; + chainableBehavior.method = function () { var result = method(_method).apply(this, arguments); return result === undefined ? this : result; };