pax_global_header 0000666 0000000 0000000 00000000064 11707135103 0014510 g ustar 00root root 0000000 0000000 52 comment=0b13af69b8d92c59908f60f98eaea15208ca520c
javascript-hooker-0.2.3/ 0000775 0000000 0000000 00000000000 11707135103 0015145 5 ustar 00root root 0000000 0000000 javascript-hooker-0.2.3/LICENSE-MIT 0000664 0000000 0000000 00000002046 11707135103 0016603 0 ustar 00root root 0000000 0000000 Copyright (c) 2012 "Cowboy" Ben Alman
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
javascript-hooker-0.2.3/README.md 0000664 0000000 0000000 00000013363 11707135103 0016432 0 ustar 00root root 0000000 0000000 # JavaScript Hooker
Monkey-patch (hook) functions for debugging and stuff.
## Getting Started
This code should work just fine in Node.js:
First, install the module with: `npm install hooker`
```javascript
var hooker = require('hooker');
hooker.hook(Math, "max", function() {
console.log(arguments.length + " arguments passed");
});
Math.max(5, 6, 7) // logs: "3 arguments passed", returns 7
```
Or in the browser:
```html
```
In the browser, you can attach Hooker's methods to any object.
```html
```
## Documentation
### hooker.hook
Monkey-patch (hook) one or more methods of an object.
#### Signature:
`hooker.hook(object, [ props, ] [options | prehookFunction])`
#### `props`
The optional `props` argument can be a method name, array of method names or null. If null (or omitted), all enumerable methods of `object` will be hooked.
#### `options`
* `pre` - (Function) a pre-hook function to be executed before the original function. Arguments passed into the method will be passed into the pre-hook function as well.
* `post` - (Function) a post-hook function to be executed after the original function. The original function's result is passed into the post-hook function as its first argument, followed by the method arguments.
* `once` - (Boolean) if true, auto-unhook the function after the first execution.
* `passName` - (Boolean) if true, pass the name of the method into the pre-hook function as its first arg (preceding all other arguments), and into the post-hook function as the second arg (after result but preceding all other arguments).
#### Returns:
An array of hooked method names.
### hooker.unhook
Un-monkey-patch (unhook) one or more methods of an object.
#### Signature:
`hooker.unhook(object [, props ])`
#### `props`
The optional `props` argument can be a method name, array of method names or null. If null (or omitted), all methods of `object` will be unhooked.
#### Returns:
An array of unhooked method names.
### hooker.orig
Get a reference to the original method from a hooked function.
#### Signature:
`hooker.orig(object, props)`
### hooker.override
When a pre- or post-hook returns the result of this function, the value
passed will be used in place of the original function's return value. Any
post-hook override value will take precedence over a pre-hook override value.
#### Signature:
`hooker.override(value)`
### hooker.preempt
When a pre-hook returns the result of this function, the value passed will
be used in place of the original function's return value, and the original
function will NOT be executed.
#### Signature:
`hooker.preempt(value)`
### hooker.filter
When a pre-hook returns the result of this function, the context and
arguments passed will be applied into the original function.
#### Signature:
`hooker.filter(context, arguments)`
## Examples
See the unit tests for more examples.
```javascript
var hooker = require('hooker');
// Simple logging.
hooker.hook(Math, "max", function() {
console.log(arguments.length + " arguments passed");
});
Math.max(5, 6, 7) // logs: "3 arguments passed", returns 7
hooker.unhook(Math, "max"); // (This is assumed between all further examples)
Math.max(5, 6, 7) // 7
// Returning hooker.override(value) overrides the original value.
hooker.hook(Math, "max", function() {
if (arguments.length === 0) {
return hooker.override(9000);
}
});
Math.max(5, 6, 7) // 7
Math.max() // 9000
// Auto-unhook after one execution.
hooker.hook(Math, "max", {
once: true,
pre: function() {
console.log("Init something here");
}
});
Math.max(5, 6, 7) // logs: "Init something here", returns 7
Math.max(5, 6, 7) // 7
// Filter `this` and arguments through a pre-hook function.
hooker.hook(Math, "max", {
pre: function() {
var args = [].map.call(arguments, function(num) {
return num * 2;
});
return hooker.filter(this, args); // thisValue, arguments
}
});
Math.max(5, 6, 7) // 14
// Modify the original function's result with a post-hook function.
hooker.hook(Math, "max", {
post: function(result) {
return hooker.override(result * 100);
}
});
Math.max(5, 6, 7) // 700
// Hook every Math method. Note: if Math's methods were enumerable, the second
// argument could be omitted. Since they aren't, an array of properties to hook
// must be explicitly passed. Non-method properties will be skipped.
// See a more generic example here: http://bit.ly/vvJlrS
hooker.hook(Math, Object.getOwnPropertyNames(Math), {
passName: true,
pre: function(name) {
console.log("=> Math." + name, [].slice.call(arguments, 1));
},
post: function(result, name) {
console.log("<= Math." + name, result);
}
});
var result = Math.max(5, 6, 7);
// => Math.max [ 5, 6, 7 ]
// <= Math.max 7
result // 7
result = Math.ceil(3.456);
// => Math.ceil [ 3.456 ]
// <= Math.ceil 4
result // 4
```
## Contributing
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [grunt](https://github.com/cowboy/grunt).
_Also, please don't edit files in the "dist" subdirectory as they are generated via grunt. You'll find source code in the "lib" subdirectory!_
## Release History
2012/01/09 - v0.2.3 - First official release.
## License
Copyright (c) 2012 "Cowboy" Ben Alman
Licensed under the MIT license.
javascript-hooker-0.2.3/dist/ 0000775 0000000 0000000 00000000000 11707135103 0016110 5 ustar 00root root 0000000 0000000 javascript-hooker-0.2.3/dist/ba-hooker.js 0000664 0000000 0000000 00000014532 11707135103 0020322 0 ustar 00root root 0000000 0000000 /*! JavaScript Hooker - v0.2.3 - 1/22/2012
* http://github.com/cowboy/javascript-hooker
* Copyright (c) 2012 "Cowboy" Ben Alman; Licensed MIT */
(function(exports) {
// Get an array from an array-like object with slice.call(arrayLikeObject).
var slice = [].slice;
// Get an "[object [[Class]]]" string with toString.call(value).
var toString = {}.toString;
// I can't think of a better way to ensure a value is a specific type other
// than to create instances and use the `instanceof` operator.
function HookerOverride(v) { this.value = v; }
function HookerPreempt(v) { this.value = v; }
function HookerFilter(c, a) { this.context = c; this.args = a; }
// When a pre- or post-hook returns the result of this function, the value
// passed will be used in place of the original function's return value. Any
// post-hook override value will take precedence over a pre-hook override
// value.
exports.override = function(value) {
return new HookerOverride(value);
};
// When a pre-hook returns the result of this function, the value passed will
// be used in place of the original function's return value, and the original
// function will NOT be executed.
exports.preempt = function(value) {
return new HookerPreempt(value);
};
// When a pre-hook returns the result of this function, the context and
// arguments passed will be applied into the original function.
exports.filter = function(context, args) {
return new HookerFilter(context, args);
};
// Execute callback(s) for properties of the specified object.
function forMethods(obj, props, callback) {
var prop;
if (typeof props === "string") {
// A single prop string was passed. Create an array.
props = [props];
} else if (props == null) {
// No props were passed, so iterate over all properties, building an
// array. Unfortunately, Object.keys(obj) doesn't work everywhere yet, so
// this has to be done manually.
props = [];
for (prop in obj) {
if (obj.hasOwnProperty(prop)) {
props.push(prop);
}
}
}
// Execute callback for every method in the props array.
var i = props.length;
while (i--) {
// If the property isn't a function...
if (toString.call(obj[props[i]]) !== "[object Function]" ||
// ...or the callback returns false...
callback(obj, props[i]) === false) {
// ...remove it from the props array to be returned.
props.splice(i, 1);
}
}
// Return an array of method names for which the callback didn't fail.
return props;
}
// Monkey-patch (hook) a method of an object.
exports.hook = function(obj, props, options) {
// If the props argument was omitted, shuffle the arguments.
if (options == null) {
options = props;
props = null;
}
// If just a function is passed instead of an options hash, use that as a
// pre-hook function.
if (typeof options === "function") {
options = {pre: options};
}
// Hook the specified method of the object.
return forMethods(obj, props, function(obj, prop) {
// The original (current) method.
var orig = obj[prop];
// The new hooked function.
function hooked() {
var result, origResult, tmp;
// Get an array of arguments.
var args = slice.call(arguments);
// If passName option is specified, prepend prop to the args array,
// passing it as the first argument to any specified hook functions.
if (options.passName) {
args.unshift(prop);
}
// If a pre-hook function was specified, invoke it in the current
// context with the passed-in arguments, and store its result.
if (options.pre) {
result = options.pre.apply(this, args);
}
if (result instanceof HookerFilter) {
// If the pre-hook returned hooker.filter(context, args), invoke the
// original function with that context and arguments, and store its
// result.
origResult = result = orig.apply(result.context, result.args);
} else if (result instanceof HookerPreempt) {
// If the pre-hook returned hooker.preempt(value) just use the passed
// value and don't execute the original function.
origResult = result = result.value;
} else {
// Invoke the original function in the current context with the
// passed-in arguments, and store its result.
origResult = orig.apply(this, arguments);
// If the pre-hook returned hooker.override(value), use the passed
// value, otherwise use the original function's result.
result = result instanceof HookerOverride ? result.value : origResult;
}
if (options.post) {
// If a post-hook function was specified, invoke it in the current
// context, passing in the result of the original function as the
// first argument, followed by any passed-in arguments.
tmp = options.post.apply(this, [origResult].concat(args));
if (tmp instanceof HookerOverride) {
// If the post-hook returned hooker.override(value), use the passed
// value, otherwise use the previously computed result.
result = tmp.value;
}
}
// Unhook if the "once" option was specified.
if (options.once) {
exports.unhook(obj, prop);
}
// Return the result!
return result;
}
// Re-define the method.
obj[prop] = hooked;
// Fail if the function couldn't be hooked.
if (obj[prop] !== hooked) { return false; }
// Store a reference to the original method as a property on the new one.
obj[prop]._orig = orig;
});
};
// Get a reference to the original method from a hooked function.
exports.orig = function(obj, prop) {
return obj[prop]._orig;
};
// Un-monkey-patch (unhook) a method of an object.
exports.unhook = function(obj, props) {
return forMethods(obj, props, function(obj, prop) {
// Get a reference to the original method, if it exists.
var orig = exports.orig(obj, prop);
// If there's no original method, it can't be unhooked, so fail.
if (!orig) { return false; }
// Unhook the method.
obj[prop] = orig;
});
};
}(typeof exports === "object" && exports || this));
javascript-hooker-0.2.3/dist/ba-hooker.min.js 0000664 0000000 0000000 00000002414 11707135103 0021100 0 ustar 00root root 0000000 0000000 /*! JavaScript Hooker - v0.2.3 - 1/22/2012
* http://github.com/cowboy/javascript-hooker
* Copyright (c) 2012 "Cowboy" Ben Alman; Licensed MIT */
(function(a){function d(a){this.value=a}function e(a){this.value=a}function f(a,b){this.context=a,this.args=b}function g(a,b,d){var e;if(typeof b=="string")b=[b];else if(b==null){b=[];for(e in a)a.hasOwnProperty(e)&&b.push(e)}var f=b.length;while(f--)(c.call(a[b[f]])!=="[object Function]"||d(a,b[f])===!1)&&b.splice(f,1);return b}var b=[].slice,c={}.toString;a.override=function(a){return new d(a)},a.preempt=function(a){return new e(a)},a.filter=function(a,b){return new f(a,b)},a.hook=function(c,h,i){return i==null&&(i=h,h=null),typeof i=="function"&&(i={pre:i}),g(c,h,function(c,g){function j(){var j,k,l,m=b.call(arguments);return i.passName&&m.unshift(g),i.pre&&(j=i.pre.apply(this,m)),j instanceof f?k=j=h.apply(j.context,j.args):j instanceof e?k=j=j.value:(k=h.apply(this,arguments),j=j instanceof d?j.value:k),i.post&&(l=i.post.apply(this,[k].concat(m)),l instanceof d&&(j=l.value)),i.once&&a.unhook(c,g),j}var h=c[g];c[g]=j;if(c[g]!==j)return!1;c[g]._orig=h})},a.orig=function(a,b){return a[b]._orig},a.unhook=function(b,c){return g(b,c,function(b,c){var d=a.orig(b,c);if(!d)return!1;b[c]=d})}})(typeof exports=="object"&&exports||this) javascript-hooker-0.2.3/grunt.js 0000664 0000000 0000000 00000002167 11707135103 0016650 0 ustar 00root root 0000000 0000000 /*global config:true, task:true*/
config.init({
pkg: '',
meta: {
name: 'JavaScript Hooker',
banner: '/*! <%= meta.name %> - v<%= pkg.version %> - <%= template.today("m/d/yyyy") %>\n' +
'* <%= pkg.homepage %>\n' +
'* Copyright (c) <%= template.today("yyyy") %> <%= pkg.author.name %>;' +
' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */'
},
concat: {
'dist/ba-hooker.js': ['', '']
},
min: {
'dist/ba-hooker.min.js': ['', 'dist/ba-hooker.js']
},
test: {
files: ['test/**/*.js']
},
lint: {
files: ['grunt.js', 'lib/**/*.js', 'test/**/*.js']
},
watch: {
files: '',
tasks: 'lint:files test:files'
},
jshint: {
options: {
curly: true,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
eqnull: true
},
globals: {
exports: true
}
},
uglify: {}
});
// Default task.
task.registerTask('default', 'lint:files test:files concat min');
javascript-hooker-0.2.3/lib/ 0000775 0000000 0000000 00000000000 11707135103 0015713 5 ustar 00root root 0000000 0000000 javascript-hooker-0.2.3/lib/hooker.js 0000664 0000000 0000000 00000014610 11707135103 0017542 0 ustar 00root root 0000000 0000000 /*
* JavaScript Hooker
* http://github.com/cowboy/javascript-hooker
*
* Copyright (c) 2012 "Cowboy" Ben Alman
* Licensed under the MIT license.
* http://benalman.com/about/license/
*/
(function(exports) {
// Get an array from an array-like object with slice.call(arrayLikeObject).
var slice = [].slice;
// Get an "[object [[Class]]]" string with toString.call(value).
var toString = {}.toString;
// I can't think of a better way to ensure a value is a specific type other
// than to create instances and use the `instanceof` operator.
function HookerOverride(v) { this.value = v; }
function HookerPreempt(v) { this.value = v; }
function HookerFilter(c, a) { this.context = c; this.args = a; }
// When a pre- or post-hook returns the result of this function, the value
// passed will be used in place of the original function's return value. Any
// post-hook override value will take precedence over a pre-hook override
// value.
exports.override = function(value) {
return new HookerOverride(value);
};
// When a pre-hook returns the result of this function, the value passed will
// be used in place of the original function's return value, and the original
// function will NOT be executed.
exports.preempt = function(value) {
return new HookerPreempt(value);
};
// When a pre-hook returns the result of this function, the context and
// arguments passed will be applied into the original function.
exports.filter = function(context, args) {
return new HookerFilter(context, args);
};
// Execute callback(s) for properties of the specified object.
function forMethods(obj, props, callback) {
var prop;
if (typeof props === "string") {
// A single prop string was passed. Create an array.
props = [props];
} else if (props == null) {
// No props were passed, so iterate over all properties, building an
// array. Unfortunately, Object.keys(obj) doesn't work everywhere yet, so
// this has to be done manually.
props = [];
for (prop in obj) {
if (obj.hasOwnProperty(prop)) {
props.push(prop);
}
}
}
// Execute callback for every method in the props array.
var i = props.length;
while (i--) {
// If the property isn't a function...
if (toString.call(obj[props[i]]) !== "[object Function]" ||
// ...or the callback returns false...
callback(obj, props[i]) === false) {
// ...remove it from the props array to be returned.
props.splice(i, 1);
}
}
// Return an array of method names for which the callback didn't fail.
return props;
}
// Monkey-patch (hook) a method of an object.
exports.hook = function(obj, props, options) {
// If the props argument was omitted, shuffle the arguments.
if (options == null) {
options = props;
props = null;
}
// If just a function is passed instead of an options hash, use that as a
// pre-hook function.
if (typeof options === "function") {
options = {pre: options};
}
// Hook the specified method of the object.
return forMethods(obj, props, function(obj, prop) {
// The original (current) method.
var orig = obj[prop];
// The new hooked function.
function hooked() {
var result, origResult, tmp;
// Get an array of arguments.
var args = slice.call(arguments);
// If passName option is specified, prepend prop to the args array,
// passing it as the first argument to any specified hook functions.
if (options.passName) {
args.unshift(prop);
}
// If a pre-hook function was specified, invoke it in the current
// context with the passed-in arguments, and store its result.
if (options.pre) {
result = options.pre.apply(this, args);
}
if (result instanceof HookerFilter) {
// If the pre-hook returned hooker.filter(context, args), invoke the
// original function with that context and arguments, and store its
// result.
origResult = result = orig.apply(result.context, result.args);
} else if (result instanceof HookerPreempt) {
// If the pre-hook returned hooker.preempt(value) just use the passed
// value and don't execute the original function.
origResult = result = result.value;
} else {
// Invoke the original function in the current context with the
// passed-in arguments, and store its result.
origResult = orig.apply(this, arguments);
// If the pre-hook returned hooker.override(value), use the passed
// value, otherwise use the original function's result.
result = result instanceof HookerOverride ? result.value : origResult;
}
if (options.post) {
// If a post-hook function was specified, invoke it in the current
// context, passing in the result of the original function as the
// first argument, followed by any passed-in arguments.
tmp = options.post.apply(this, [origResult].concat(args));
if (tmp instanceof HookerOverride) {
// If the post-hook returned hooker.override(value), use the passed
// value, otherwise use the previously computed result.
result = tmp.value;
}
}
// Unhook if the "once" option was specified.
if (options.once) {
exports.unhook(obj, prop);
}
// Return the result!
return result;
}
// Re-define the method.
obj[prop] = hooked;
// Fail if the function couldn't be hooked.
if (obj[prop] !== hooked) { return false; }
// Store a reference to the original method as a property on the new one.
obj[prop]._orig = orig;
});
};
// Get a reference to the original method from a hooked function.
exports.orig = function(obj, prop) {
return obj[prop]._orig;
};
// Un-monkey-patch (unhook) a method of an object.
exports.unhook = function(obj, props) {
return forMethods(obj, props, function(obj, prop) {
// Get a reference to the original method, if it exists.
var orig = exports.orig(obj, prop);
// If there's no original method, it can't be unhooked, so fail.
if (!orig) { return false; }
// Unhook the method.
obj[prop] = orig;
});
};
}(typeof exports === "object" && exports || this));
javascript-hooker-0.2.3/package.json 0000664 0000000 0000000 00000001531 11707135103 0017433 0 ustar 00root root 0000000 0000000 {
"name": "hooker",
"description": "Monkey-patch (hook) functions for debugging and stuff.",
"version": "0.2.3",
"homepage": "http://github.com/cowboy/javascript-hooker",
"author": {
"name": "\"Cowboy\" Ben Alman",
"url": "http://benalman.com/"
},
"repository": {
"type": "git",
"url": "git://github.com/cowboy/javascript-hooker.git"
},
"bugs": {
"url": "https://github.com/cowboy/javascript-hooker/issues"
},
"licenses": [
{
"type": "MIT",
"url": "https://github.com/cowboy/javascript-hooker/blob/master/LICENSE-MIT"
}
],
"dependencies": {},
"devDependencies": {
"grunt": "~0.2.1"
},
"keywords": [
"patch",
"hook",
"function",
"debug",
"aop"
],
"engines": {
"node": ">= 0.6.7"
},
"main": "lib/hooker",
"scripts": {
"test": "grunt test"
}
}
javascript-hooker-0.2.3/test/ 0000775 0000000 0000000 00000000000 11707135103 0016124 5 ustar 00root root 0000000 0000000 javascript-hooker-0.2.3/test/hooker_test.js 0000664 0000000 0000000 00000047321 11707135103 0021017 0 ustar 00root root 0000000 0000000 /*global require:true */
var hooker = require('../lib/hooker');
exports['hook'] = {
setUp: function(done) {
this.order = [];
this.track = function() {
[].push.apply(this.order, arguments);
};
this.prop = 1;
this.add = function(a, b) {
this.track("add", this.prop, a, b);
return this.prop + a + b;
};
this.obj = {
that: this,
prop: 1,
add1: function(a, b) {
this.that.track("add1", this.prop, a, b);
return this.prop + a + b;
},
add2: function(a, b) {
this.that.track("add2", this.prop, a, b);
return this.prop + a + b;
},
add3: function(a, b) {
this.that.track("add3", this.prop, a, b);
return this.prop + a + b;
}
};
done();
},
'orig': function(test) {
test.expect(1);
var orig = this.add;
hooker.hook(this, "add", function() {});
test.strictEqual(hooker.orig(this, "add"), orig, "should return a refernce to the original function.");
test.done();
},
'once': function(test) {
test.expect(5);
var orig = this.add;
hooker.hook(this, "add", {
once: true,
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.track("before", this.prop, a, b);
}
});
test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3], "functions should execute in-order.");
test.strictEqual(this.add, orig, "should automatically unhook when once is specified.");
this.order = [];
test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["add", 1, 2, 3], "only the original function should execute.");
test.done();
},
'pre-hook (simple syntax)': function(test) {
test.expect(3);
// Pre-hook.
var result = hooker.hook(this, "add", function(a, b) {
// Arguments are passed into pre-hook as specified.
this.track("before", this.prop, a, b);
});
test.deepEqual(result, ["add"], "add should have been hooked.");
test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3], "functions should execute in-order.");
test.done();
},
'pre-hook': function(test) {
test.expect(3);
// Pre-hook.
var result = hooker.hook(this, "add", {
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.track("before", this.prop, a, b);
}
});
test.deepEqual(result, ["add"], "add should have been hooked.");
test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3], "functions should execute in-order.");
test.done();
},
'post-hook': function(test) {
test.expect(3);
// Post-hook.
var result = hooker.hook(this, "add", {
post: function(result, a, b) {
// Arguments to post-hook are the original function's return value,
// followed by the specified function arguments.
this.track("after", this.prop, a, b, result);
}
});
test.deepEqual(result, ["add"], "add should have been hooked.");
test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["add", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
test.done();
},
'pre- & post-hook': function(test) {
test.expect(2);
// Pre- & post-hook.
hooker.hook(this, "add", {
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.track("before", this.prop, a, b);
},
post: function(result, a, b) {
// Arguments to post-hook are the original function's return value,
// followed by the specified function arguments.
this.track("after", this.prop, a, b, result);
}
});
test.strictEqual(this.add(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
test.done();
},
'pre-hook, return value override': function(test) {
test.expect(2);
// Pre-hook.
hooker.hook(this, "add", {
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.track("before", this.prop, a, b);
// This return value will override the original function's return value.
return hooker.override("b" + this.prop + a + b);
}
});
test.strictEqual(this.add(2, 3), "b123", "should return the overridden result.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3], "functions should execute in-order.");
test.done();
},
'post-hook, return value override': function(test) {
test.expect(2);
// Post-hook.
hooker.hook(this, "add", {
post: function(result, a, b) {
// Arguments to post-hook are the original function's return value,
// followed by the specified function arguments.
this.track("after", this.prop, a, b, result);
// This return value will override the original function's return value.
return hooker.override("a" + this.prop + a + b + result);
}
});
test.strictEqual(this.add(2, 3), "a1236", "should return the post-hook overridden result.");
test.deepEqual(this.order, ["add", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
test.done();
},
'pre- & post-hook, return value override': function(test) {
test.expect(2);
// Pre- & post-hook.
hooker.hook(this, "add", {
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.track("before", this.prop, a, b);
// This return value will override the original function's return value.
return hooker.override("b" + this.prop + a + b);
},
post: function(result, a, b) {
// Arguments to post-hook are the original function's return value,
// followed by the specified function arguments.
this.track("after", this.prop, a, b, result);
// This return value will override the original function's return value
// AND the pre-hook's return value.
return hooker.override("a" + this.prop + a + b + result);
}
});
test.strictEqual(this.add(2, 3), "a1236", "should return the overridden result, and post-hook result should take precedence over pre-hook result.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
test.done();
},
'pre-hook, filtering arguments': function(test) {
test.expect(2);
// Pre-hook.
hooker.hook(this, "add", {
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.track("before", this.prop, a, b);
// Return hooker.filter(context, arguments) and they will be passed into
// the original function. The "track" and "order" propterites are just
// set here for the same of this unit test.
return hooker.filter({prop: "x", track: this.track, order: this.order}, ["y", "z"]);
}
});
test.strictEqual(this.add(2, 3), "xyz", "should return the original function's result, given filtered context and arguments.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add", "x", "y", "z"], "functions should execute in-order.");
test.done();
},
'pre- & post-hook, filtering arguments': function(test) {
test.expect(2);
// Pre- & post-hook.
hooker.hook(this, "add", {
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.track("before", this.prop, a, b);
// Return hooker.filter(context, arguments) and they will be passed into
// the original function. The "track" and "order" propterites are just
// set here for the same of this unit test.
return hooker.filter({prop: "x", track: this.track, order: this.order}, ["y", "z"]);
},
post: function(result, a, b) {
// Arguments to post-hook are the original function's return value,
// followed by the specified function arguments.
this.track("after", this.prop, a, b, result);
}
});
test.strictEqual(this.add(2, 3), "xyz", "should return the original function's result, given filtered context and arguments.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add", "x", "y", "z", "after", 1, 2, 3, "xyz"], "functions should execute in-order.");
test.done();
},
'pre- & post-hook, filtering arguments, return value override': function(test) {
test.expect(2);
// Pre- & post-hook.
hooker.hook(this, "add", {
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.track("before", this.prop, a, b);
// Return hooker.filter(context, arguments) and they will be passed into
// the original function. The "track" and "order" propterites are just
// set here for the same of this unit test.
return hooker.filter({prop: "x", track: this.track, order: this.order}, ["y", "z"]);
},
post: function(result, a, b) {
// Arguments to post-hook are the original function's return value,
// followed by the specified function arguments.
this.track("after", this.prop, a, b, result);
// This return value will override the original function's return value
// AND the pre-hook's return value.
return hooker.override("a" + this.prop + a + b + result);
}
});
test.strictEqual(this.add(2, 3), "a123xyz", "should return the post-hook overridden result.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add", "x", "y", "z", "after", 1, 2, 3, "xyz"], "functions should execute in-order.");
test.done();
},
'pre-hook, preempt original function': function(test) {
test.expect(2);
// Pre-hook.
hooker.hook(this, "add", {
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.track("before", this.prop, a, b);
// Returning hooker.preempt will prevent the original function from being
// invoked and optionally set a return value.
return hooker.preempt();
}
});
test.strictEqual(this.add(2, 3), undefined, "should return the value passed to preempt.");
test.deepEqual(this.order, ["before", 1, 2, 3], "functions should execute in-order.");
test.done();
},
'pre-hook, preempt original function with value': function(test) {
test.expect(2);
// Pre-hook.
hooker.hook(this, "add", {
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.track("before", this.prop, a, b);
// Returning hooker.preempt will prevent the original function from being
// invoked and optionally set a return value.
return hooker.preempt(9000);
}
});
test.strictEqual(this.add(2, 3), 9000, "should return the value passed to preempt.");
test.deepEqual(this.order, ["before", 1, 2, 3], "functions should execute in-order.");
test.done();
},
'pre- & post-hook, preempt original function with value': function(test) {
test.expect(2);
// Pre- & post-hook.
hooker.hook(this, "add", {
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.track("before", this.prop, a, b);
// Returning hooker.preempt will prevent the original function from being
// invoked and optionally set a return value.
return hooker.preempt(9000);
},
post: function(result, a, b) {
// Arguments to post-hook are the original function's return value,
// followed by the specified function arguments.
this.track("after", this.prop, a, b, result);
}
});
test.strictEqual(this.add(2, 3), 9000, "should return the value passed to preempt.");
test.deepEqual(this.order, ["before", 1, 2, 3, "after", 1, 2, 3, 9000], "functions should execute in-order.");
test.done();
},
'pre- & post-hook, preempt original function with value, return value override': function(test) {
test.expect(2);
// Pre- & post-hook.
hooker.hook(this, "add", {
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.track("before", this.prop, a, b);
// Returning hooker.preempt will prevent the original function from being
// invoked and optionally set a return value.
return hooker.preempt(9000);
},
post: function(result, a, b) {
// Arguments to post-hook are the original function's return value,
// followed by the specified function arguments.
this.track("after", this.prop, a, b, result);
// This return value will override any preempt value set in pre-hook.
return hooker.override("a" + this.prop + a + b + result);
}
});
test.strictEqual(this.add(2, 3), "a1239000", "should return the overridden result, and post-hook result should take precedence over preempt value.");
test.deepEqual(this.order, ["before", 1, 2, 3, "after", 1, 2, 3, 9000], "functions should execute in-order.");
test.done();
},
'pre- & post-hook, some properties': function(test) {
test.expect(7);
// Pre- & post-hook.
var result = hooker.hook(this.obj, ["add1", "add2"], {
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.that.track("before", this.prop, a, b);
},
post: function(result, a, b) {
// Arguments to post-hook are the original function's return value,
// followed by the specified function arguments.
this.that.track("after", this.prop, a, b, result);
}
});
test.deepEqual(result.sort(), ["add1", "add2"], "both functions should have been hooked.");
test.strictEqual(this.obj.add1(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add1", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
this.order = [];
test.strictEqual(this.obj.add2(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add2", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
this.order = [];
test.strictEqual(this.obj.add3(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["add3", 1, 2, 3], "functions should execute in-order.");
test.done();
},
'pre- & post-hook, all properties': function(test) {
test.expect(7);
// Pre- & post-hook.
var result = hooker.hook(this.obj, {
pre: function(a, b) {
// Arguments are passed into pre-hook as specified.
this.that.track("before", this.prop, a, b);
},
post: function(result, a, b) {
// Arguments to post-hook are the original function's return value,
// followed by the specified function arguments.
this.that.track("after", this.prop, a, b, result);
}
});
test.deepEqual(result.sort(), ["add1", "add2", "add3"], "all functions should have been hooked.");
test.strictEqual(this.obj.add1(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add1", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
this.order = [];
test.strictEqual(this.obj.add2(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add2", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
this.order = [];
test.strictEqual(this.obj.add3(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["before", 1, 2, 3, "add3", 1, 2, 3, "after", 1, 2, 3, 6], "functions should execute in-order.");
test.done();
},
'pre- & post-hook, all properties, passName': function(test) {
test.expect(6);
// Pre- & post-hook.
hooker.hook(this.obj, {
passName: true,
pre: function(name, a, b) {
// Arguments are passed into pre-hook as specified.
this.that.track("before", this.prop, name, a, b);
},
post: function(result, name, a, b) {
// Arguments to post-hook are the original function's return value,
// followed by the specified function arguments.
this.that.track("after", this.prop, name, a, b, result);
}
});
test.strictEqual(this.obj.add1(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["before", 1, "add1", 2, 3, "add1", 1, 2, 3, "after", 1, "add1", 2, 3, 6], "functions should execute in-order.");
this.order = [];
test.strictEqual(this.obj.add2(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["before", 1, "add2", 2, 3, "add2", 1, 2, 3, "after", 1, "add2", 2, 3, 6], "functions should execute in-order.");
this.order = [];
test.strictEqual(this.obj.add3(2, 3), 6, "should return the original function's result.");
test.deepEqual(this.order, ["before", 1, "add3", 2, 3, "add3", 1, 2, 3, "after", 1, "add3", 2, 3, 6], "functions should execute in-order.");
test.done();
},
'unhook one property': function(test) {
test.expect(5);
var orig = this.add;
hooker.hook(this, "add", function() {});
var result = hooker.unhook(this, "add");
test.deepEqual(result, ["add"], "one function should have been unhooked.");
test.strictEqual(this.add, orig, "should have unhooked, restoring the original function");
result = hooker.unhook(this, "add");
test.deepEqual(result, [], "nothing should have been unhooked.");
test.strictEqual(this.add, orig, "shouldn't explode if already unhooked");
test.strictEqual(this.add.orig, undefined, "original function shouldn't have an orig property");
test.done();
},
'unhook some properties': function(test) {
test.expect(6);
var add1 = this.obj.add1;
var add2 = this.obj.add2;
hooker.hook(this.obj, ["add1", "add2"], function() {});
test.strictEqual(hooker.orig(this.obj, "add1"), add1, "should return a refernce to the original function");
test.strictEqual(hooker.orig(this.obj, "add2"), add2, "should return a refernce to the original function");
test.strictEqual(hooker.orig(this.obj, "add3"), undefined, "should not have been hooked, so should not have an original function");
var result = hooker.unhook(this.obj, ["add1", "add2"]);
test.deepEqual(result.sort(), ["add1", "add2"], "both functions should have been unhooked.");
test.strictEqual(this.obj.add1, add1, "should have unhooked, restoring the original function");
test.strictEqual(this.obj.add2, add2, "should have unhooked, restoring the original function");
test.done();
},
'unhook all properties': function(test) {
test.expect(7);
var add1 = this.obj.add1;
var add2 = this.obj.add2;
var add3 = this.obj.add3;
hooker.hook(this.obj, function() {});
test.strictEqual(hooker.orig(this.obj, "add1"), add1, "should return a refernce to the original function");
test.strictEqual(hooker.orig(this.obj, "add2"), add2, "should return a refernce to the original function");
test.strictEqual(hooker.orig(this.obj, "add3"), add3, "should return a refernce to the original function");
var result = hooker.unhook(this.obj);
test.deepEqual(result.sort(), ["add1", "add2", "add3"], "all functions should have been unhooked.");
test.strictEqual(this.obj.add1, add1, "should have unhooked, restoring the original function");
test.strictEqual(this.obj.add2, add2, "should have unhooked, restoring the original function");
test.strictEqual(this.obj.add3, add3, "should have unhooked, restoring the original function");
test.done();
}
};