Skip to content
This repository has been archived by the owner on Aug 23, 2018. It is now read-only.

Commit

Permalink
tests for before and after
Browse files Browse the repository at this point in the history
  • Loading branch information
mna committed Jul 9, 2012
1 parent 3974e79 commit a524e32
Show file tree
Hide file tree
Showing 9 changed files with 328 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,3 +1,4 @@
node_modules/
lib-cov/
*.sublime-*
.DS_Store
8 changes: 8 additions & 0 deletions .npmignore
@@ -0,0 +1,8 @@
lib-cov/
*.sublime-*
.DS_Store
test/
Makefile
*.md
.jshintrc
.git*
4 changes: 2 additions & 2 deletions Makefile
@@ -1,14 +1,14 @@
.DEFAULT_GOAL = run
REPORTER = spec
TESTS = test/*.js
TESTS = test/*.test.js
TEST_COVERAGE = test/coverage.html
GREP = .

test:
@TEST=1 ./node_modules/.bin/mocha --reporter $(REPORTER) --grep $(GREP) $(TESTS)

test-cov: lib-cov
@XCOV=1 $(MAKE) -s test REPORTER=html-cov > $(TEST_COVERAGE) && open $(TEST_COVERAGE)
@ADV_COV=1 $(MAKE) -s test REPORTER=html-cov > $(TEST_COVERAGE) && open $(TEST_COVERAGE)

lib-cov:
@jscoverage lib lib-cov
Expand Down
18 changes: 16 additions & 2 deletions README.md
@@ -1,3 +1,17 @@
# Project Name
# Advice Functional Mixins

Project description
This tiny library implements the advice functional mixins presented in Twitter's [Angus Croll][croll]'s presentation [How we learned to stop worrying and love Javascript][slides].

## Installation

`npm install advice`

## Features

Based on Croll's [gist][], the advice library augments a target object with `after`, `before`, and `around`. I added 2 more mixins well suited for node's callback style, `hijackBefore` and `hijackAfter`.



[croll]: https://github.com/angus-c/
[slides]: https://speakerdeck.com/u/anguscroll/p/how-we-learned-to-stop-worrying-and-love-javascript
[gist]: https://gist.github.com/2864853
Empty file removed app.js
Empty file.
1 change: 1 addition & 0 deletions index.js
@@ -0,0 +1 @@
module.exports = require('./lib/advice')
160 changes: 160 additions & 0 deletions lib/advice.js
@@ -0,0 +1,160 @@
/*globals define*/

/*
**
** Copyright Martin Angers 2012, the MIT License
**
** Based on Twitter's presentation:
** https://speakerdeck.com/u/anguscroll/p/how-we-learned-to-stop-worrying-and-love-javascript
**
** And this gist by Angus Croll
** https://gist.github.com/2864853
**
** Usage:
** var withAdvice = require('./advice')
** withAdvice.call(targetObject)
**
** ...then...
** targetObject.before('someMethod', function() {
** // Something to do before someMethod
** })
**
** ...and finally...
** targetObject.someMethod() // Calls the method inserted using `before`, then `someMethod()`
*/
;(function(global) {

"use strict";

// Define the bind method (the AngularJS' bind implementation is used, see:
// https://github.com/angular/angular.js/blob/master/src/Angular.js#L664 )
function bindFn(self, fn) {
var slice = Array.prototype.slice,
curryArgs = arguments.length > 2 ? slice(arguments, 2) : []

if (typeof fn == 'function' && !(fn instanceof RegExp)) {
return curryArgs.length ? function() {
return arguments.length ?
fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) :
fn.apply(self, curryArgs)
}
: function() {
return arguments.length ?
fn.apply(self, arguments) :
fn.call(self)
}
} else {
// in IE, native methods are not functions so they cannot be bound (note: they don't need to be)
return fn
}
}

// fn is a supporting object that implements around, before, after, hijackBefore and hijackAfter
var fn = {
around: function(base, wrapped) {
return function() {
var args = Array.prototype.slice.call(arguments)

// Around calls the new method, passing the original method as the first argument.
// It is up to the new method to decide when to call the original.
return wrapped.apply(this, [bindFn(this, base)].concat(args))
}
},
before: function(base, before) {
// Before uses "around" and calls the original method AFTER the new method, returning
// the result of the original method.
return fn.around(base, function() {
var args = Array.prototype.slice.call(arguments),
orig = args.shift()

before.apply(this, args)
return (orig).apply(this, args)
})
},
after: function(base, after) {
// After uses "around" and calls the original method BEFORE the new method, returning
// the result of the original method.
return fn.around(base, function() {
var args = Array.prototype.slice.call(arguments),
orig = args.shift(),
res = orig.apply(this, args)

after.apply(this, args)
return res
})
},
hijackBefore: function(base, hijack) {
// Hijcak before calls the hijack method, intercepts the callback, and calls the
// base method. It basically chains the methods in this order: hijack->base->originalCb.
// If the hijack method returns an error as first argument, the base method is skipped
// and the error is sent to the original callback.
return function() {
var args = Array.prototype.slice.call(arguments),
origCb = args.pop(),
self = this

hijack.apply(this, args.concat(function(er) {
if (er) {
origCb(er)
} else {
base.apply(self, args.concat(origCb))
}
}))
}
},
hijackAfter: function(base, hijack) {
// Hijcak after calls the base method, hijacks the callback parameter by passing a private method
// as callback, then calls the hijack method with the arguments provided by the original, adding
// the original callback as last argument. It basically chains the methods in this order:
// base->privateCb->hijack->originalCb.
return function() {
var args = Array.prototype.slice.call(arguments),
origCb = args.pop(),
res,
self = this

res = base.apply(this, args.concat(function() {
var argscb = Array.prototype.slice.call(arguments)
// On callback, call the hijack method, passing the original callback as last argument
hijack.apply(self, argscb.concat(origCb))
}))
return res
}
}
}

// mixin augments target object with around, before and after methods
// method is the base method, advice is the augmenting function
var advice = function() {
var mixins = ['before', 'after', 'around', 'hijackAfter', 'hijackBefore'],
applyMixin = function(m) {
this[m] = function(method, advice) {
if (typeof this[method] === 'function') {
return this[method] = fn[m](this[method], advice)
} else {
return this[method] = advice
}
}
}

// Avoid using forEach since it can be used in browser
for (var i = 0; i < mixins.length; i++) {
applyMixin.call(this, mixins[i])
}
}

// Module exports
if (module && module.exports) {
module.exports = advice
} else {
global['advice'] = advice
}

// AMD registration
if (typeof define === 'function' && define.amd) {
define('advice', function() {
return advice
})
}

}(this))
9 changes: 4 additions & 5 deletions package.json
@@ -1,11 +1,10 @@
{
"name": ""
, "description": ""
"name": "advice"
, "description": "Advice functional mixin based on Twitter's Angus Croll presentation (How we learned to stop worrying and love Javascript)."
, "version": "0.1.0"
, "author": "Martin Angers <martin.n.angers@gmail.com>"
, "private": true
, "private": false
, "dependencies": {

}
, "devDependencies": {
"mocha": "1.x"
Expand All @@ -14,8 +13,8 @@
}
, "scripts": {
"test": "make test"
, "start": "make run"
}
, "main": "./index"
, "engines": {
"node": ">= 0.6"
}
Expand Down
136 changes: 136 additions & 0 deletions test/advice.test.js
@@ -0,0 +1,136 @@
var expect = require('expect.js'),
sinon = require('sinon'),
sut = require(process.env.ADV_COV ? '../lib-cov/advice' : '../lib/advice')

describe('advice', function() {
it('should export a function', function() {
expect(sut).to.be.a('function')
})

it('should augment an object with the mixins', function() {
var obj = {
fn: function() {}
}

sut.call(obj)
expect(obj).to.only.have.keys('before', 'after', 'around', 'hijackBefore', 'hijackAfter', 'fn')
})

it('should leave the object working as before', function() {
var obj = {
fn: function() {return 'test'}
}

sut.call(obj)
expect(obj.fn()).to.be('test')
})

describe('.before()', function() {
it('should be a function on the augmented object', function() {
var obj = {}
sut.call(obj)
expect(obj.before).to.be.a('function')
})

it('should call the before function as well as the base target', function() {
var num = 0,
obj = {
fn: function(val) {
num += val * 2
return num
}
},
beforeFn = function(val) {
num += val
return num
},
spy = sinon.spy(beforeFn)

sut.call(obj)
obj.before('fn', spy)

expect(obj.fn(2)).to.be(6)
expect(spy.calledOnce).to.be(true)
})

it('should call the before function before the base target', function() {
var num = 0,
obj = {
fn: function(val) {
num += val * 2
return num
}
},
beforeFn = function(val) {
num += val
return num
},
spyBase = sinon.spy(obj, 'fn'),
spyBefore = sinon.spy(beforeFn)

sut.call(obj)
obj.before('fn', spyBefore)
obj.fn(4)

expect(spyBase.calledOnce).to.be(true)
expect(spyBefore.calledOnce).to.be(true)
expect(spyBefore.calledBefore(spyBase)).to.be(true)
})
})

describe('.after()', function() {
it('should be a function on the augmented object', function() {
var obj = {}
sut.call(obj)
expect(obj.after).to.be.a('function')
})

it('should call the after function as well as the base target', function() {
var num = 0,
obj = {
fn: function(val) {
num += val * 2
console.log(num)
return num
}
},
afterFn = function(val) {
num += val
console.log(num)
return num
},
spy = sinon.spy(afterFn)

sut.call(obj)
obj.after('fn', spy)
obj.fn(5)

expect(num).to.be(15)
expect(spy.calledOnce).to.be(true)
})

it('should call the after function after the base target', function() {
var num = 0,
obj = {
fn: function(val) {
num += val * 2
return num
}
},
afterFn = function(val) {
num += val
return num
},
spyBase = sinon.spy(obj, 'fn'),
spyAfter = sinon.spy(afterFn)

sut.call(obj)
obj.after('fn', spyAfter)
obj.fn(4)

expect(spyBase.calledOnce).to.be(true)
expect(spyAfter.calledOnce).to.be(true)
expect(spyAfter.calledAfter(spyBase)).to.be(true)
})
})
})

0 comments on commit a524e32

Please sign in to comment.