diff --git a/lib/twill.js b/lib/twill.js index 8c09177..9a5d6a0 100644 --- a/lib/twill.js +++ b/lib/twill.js @@ -66,7 +66,7 @@ function _pointcut(handle) { }, remove: function (name) { var orig = target[name]; - return function (func) { + return function () { handle.aspects.push({name: name, orig: orig}); delete target[name]; }; @@ -74,88 +74,124 @@ function _pointcut(handle) { }; } -module.exports = { - aspect: function (target, advice) { - if (!target) { - throw "missing target object to aspect"; - } - - if (!advice) { - throw "missing advice"; +function _fastWeave(handle) { + return { + add: {}, + before: function(name, method) { + _pointcut(handle).before(name)(method); + }, + after: function(name, method) { + _pointcut(handle).after(name)(method); + }, + around: function(name, method) { + _pointcut(handle).around(name)(method); + }, + remove: function(name) { + if (typeof handle.target[name] === 'undefined') { + throw new Error(name + " not defined"); + } + _pointcut(handle).remove(name)(); + }, + exception: function(name, method) { + _pointcut(handle).exception(name)(method); } + }; +} - var functions = _funcs(target), - handle = { - target: target, - aspects: [], - add: [] - }, - weave = { - before: {}, - after: {}, - around: {}, - add: {}, - remove: {}, - exception: {}, - all: { - before: function (method) { - var funcs = _funcs(target); - - funcs.forEach(function (f) { - _pointcut(handle).before(f)(function () { - method(f, arguments); - }); +function _weave(handle) { + var target = handle.target, + functions = _funcs(target), + weave = { + before: {}, + after: {}, + around: {}, + add: {}, + remove: {}, + exception: {}, + all: { + before: function (method) { + var funcs = _funcs(target); + + funcs.forEach(function (f) { + _pointcut(handle).before(f)(function () { + method(f, arguments); }); - }, - after: function (method) { - var funcs = _funcs(target); - - funcs.forEach(function (f) { - _pointcut(handle).after(f)(function () { - method(f, arguments); - }); + }); + }, + after: function (method) { + var funcs = _funcs(target); + + funcs.forEach(function (f) { + _pointcut(handle).after(f)(function () { + method(f, arguments); }); - }, - around: function (method) { - var funcs = _funcs(target); - - funcs.forEach(function (f) { - _pointcut(handle).around(f)(function (args, orig) { - return method(f, args, orig); - }); + }); + }, + around: function (method) { + var funcs = _funcs(target); + + funcs.forEach(function (f) { + _pointcut(handle).around(f)(function (args, orig) { + return method(f, args, orig); }); - }, - exception: function (method) { - var funcs = _funcs(target); - - funcs.forEach(function (f) { - _pointcut(handle).around(f)(function () { - return method.apply(target, arguments); - }); + }); + }, + exception: function (method) { + var funcs = _funcs(target); + + funcs.forEach(function (f) { + _pointcut(handle).around(f)(function () { + return method.apply(target, arguments); }); - } + }); } - }; + } + }; + functions.forEach(function (f) { + weave.before[f] = _pointcut(handle).before(f); + weave.after[f] = _pointcut(handle).after(f); + weave.around[f] = _pointcut(handle).around(f); + weave.exception[f] = _pointcut(handle).exception(f); + weave.remove[f] = _pointcut(handle).remove(f); + }); + return weave; +}; - functions.forEach(function (f) { - weave.before[f] = _pointcut(handle).before(f); - weave.after[f] = _pointcut(handle).after(f); - weave.around[f] = _pointcut(handle).around(f); - weave.exception[f] = _pointcut(handle).exception(f); - weave.remove[f] = _pointcut(handle).remove(f); - }); +function _aspect(target, advice, getWeave) { + if (!target) { + throw "missing target object to aspect"; + } - advice.apply(target, [weave]); + if (!advice) { + throw "missing advice"; + } - _funcs(weave.add).forEach(function (f) { - if (target[f]) { - throw "attempt to add a method that already existed"; - } - target[f] = weave.add[f]; - handle.add.push(f); - }); + var handle = { + target: target, + aspects: [], + add: [] + }, + weave = getWeave(handle); + advice.apply(target, [weave]); - return _handles.push(handle) - 1; + _funcs(weave.add).forEach(function (f) { + if (target[f]) { + throw "attempt to add a method that already existed"; + } + target[f] = weave.add[f]; + handle.add.push(f); + }); + + return _handles.push(handle) - 1; +} + +module.exports = { + fastAspect: function(target, advice) { + return _aspect(target, advice, _fastWeave); + }, + + aspect: function (target, advice) { + return _aspect(target, advice, _weave); }, unweave: function (id) { diff --git a/test/aspect-fast-add.js b/test/aspect-fast-add.js new file mode 100644 index 0000000..9f38755 --- /dev/null +++ b/test/aspect-fast-add.js @@ -0,0 +1,46 @@ +var twill = require("../lib/twill"); + +exports["when aspecting in a new method"] = { + "it can add a new method" : function (test) { + var target = {}; + + twill.fastAspect(target, function (weave) { + weave.add.asdf = function () { + test.done(); + }; + }); + + target.asdf(); + }, + + "it throws an exception if the method already exists" : function (test) { + var target = { + woohoo: function () {} + }; + + try { + twill.fastAspect(target, function (weave) { + weave.add.woohoo = function () { + }; + }); + } catch (e) { + test.done(); + } + }, + + "it can unweave the new method" : function (test) { + var target = {}, + aspect = twill.fastAspect(target, function (weave) { + weave.add.foo = function () {}; + weave.add.bar = function () {}; + weave.add.baz = function () {}; + }); + + twill.unweave(aspect); + + test.equal(undefined, target.foo); + test.equal(undefined, target.bar); + test.equal(undefined, target.baz); + test.done(); + } +}; diff --git a/test/aspect-fast-after.js b/test/aspect-fast-after.js new file mode 100644 index 0000000..b1f2494 --- /dev/null +++ b/test/aspect-fast-after.js @@ -0,0 +1,96 @@ +var twill = require("../lib/twill"); + +exports["when aspecting after a method"] = { + "it calls the aspect after the actual" : function (test) { + var msg = "", + target = { + f: function () { + msg += "hello "; + } + }; + + twill.fastAspect(target, function (weave) { + weave.after('f', function () { + msg += "world"; + }); + }); + + target.f(); + + test.equal("hello world", msg); + test.done(); + }, + + "it passes the arguments to the aspect method and the orig method" : function (test) { + var target = { + stuff: function (a, b, c) { + test.equal(a, "1"); + test.equal(b, "2"); + test.equal(c, "3"); + test.done(); + } + }; + + twill.fastAspect(target, function (weave) { + weave.after('stuff', function (x, y, z) { + test.equal(x, "1"); + test.equal(y, "2"); + test.equal(z, "3"); + }); + }); + + target.stuff("1", "2", "3"); + }, + + "the context for both the aspect method and the original are the target method" : function (test) { + var target = { + merp: function () { + test.strictEqual(this, target); + test.done(); + } + }; + + twill.fastAspect(target, function (weave) { + weave.after('merp', function () { + test.strictEqual(this, target); + }); + }); + + target.merp(); + }, + + "it still returns the value of the original method" : function (test) { + var target = { + doubleRainbow: function () { + return "OMG OMG OMG OMG OMG"; + } + }; + + twill.fastAspect(target, function (weave) { + weave.after('doubleRainbow', function () { + return "meh"; + }); + }); + + test.equal("OMG OMG OMG OMG OMG", target.doubleRainbow()); + test.done(); + }, + + "it can unweave the methods added after" : function (test) { + var untouched = true, + target = { + soap: function () { + } + }; + + twill.unweave(twill.fastAspect(target, function (weave) { + weave.after('soap', function () { + untouched = false; + }); + })); + + target.soap(); + test.ok(untouched); + test.done(); + } +}; diff --git a/test/aspect-fast-around.js b/test/aspect-fast-around.js new file mode 100644 index 0000000..775b020 --- /dev/null +++ b/test/aspect-fast-around.js @@ -0,0 +1,89 @@ +var twill = require("../lib/twill"); + +exports["when aspecting around a method"] = { + "it passes the arguments to the aspect" : function (test) { + var target = { + f: function (one, two, three) { + } + }; + + twill.fastAspect(target, function (weave) { + weave.around('f', function (args, orig) { + test.equal(args[0], "a"); + test.equal(args[1], "b"); + test.equal(args[2], "c"); + test.done(); + }); + }); + + target.f("a", "b", "c"); + }, + + "it doesn't call the original function" : function (test) { + var target = { + doit: function () { + test.ok(false, "this shouldn't have been called"); + } + }; + + twill.fastAspect(target, function (weave) { + weave.around('doit', function () { + }); + }); + + target.doit(); + test.done(); + }, + + "the function passed into the aspect is the original function" : function (test) { + var target = { + tangent: function () { + test.done(); + } + }; + + twill.fastAspect(target, function (weave) { + weave.around('tangent', function (args, orig) { + orig(); + }); + }); + + target.tangent(); + }, + + "it returns the value from the aspect rather than the original" : function (test) { + var target = { + cow: function () { + return "says cluck"; + } + }; + + twill.fastAspect(target, function (weave) { + weave.around('cow', function () { + return "says moo"; + }); + }); + + test.equals("says moo", target.cow()); + test.done(); + }, + + "it can unweave the methods added around" : function (test) { + var target = { + three: function () { + return 3; + } + }; + + //should be a noop ;) + twill.unweave(twill.fastAspect(target, function (weave) { + weave.around('three', function () { + return 4; + }); + })); + + test.equal(target.three(), 3); + test.done(); + } + +}; diff --git a/test/aspect-fast-before.js b/test/aspect-fast-before.js new file mode 100644 index 0000000..567d200 --- /dev/null +++ b/test/aspect-fast-before.js @@ -0,0 +1,99 @@ +var twill = require("../lib/twill"); + +exports["when aspecting before a method"] = { + "it calls the aspect before the actual" : function (test) { + var msg = "", + target = { + f: function () { + msg += " world"; + } + }; + + twill.fastAspect(target, function (weave) { + weave.before('f', function () { + msg += "hello"; + }); + }); + + target.f(); + + test.equal("hello world", msg); + test.done(); + }, + + "it passes the arguments to the aspect method and the orig method" : function (test) { + var target = { + stuff: function (a, b, c) { + test.equal(a, "1"); + test.equal(b, "2"); + test.equal(c, "3"); + test.done(); + } + }; + + twill.fastAspect(target, function (weave) { + weave.before('stuff', function (x, y, z) { + test.equal(x, "1"); + test.equal(y, "2"); + test.equal(z, "3"); + }); + }); + + target.stuff("1", "2", "3"); + }, + + "the context for both the aspect method and the original are the target method" : function (test) { + var target = { + merp: function () { + test.strictEqual(this, target); + test.done(); + } + }; + + twill.fastAspect(target, function (weave) { + weave.before('merp', function () { + test.strictEqual(this, target); + }); + }); + + target.merp(); + }, + + "it still returns the return value from the original method" : function (test) { + var target = { + kungfu : function () { + return "i know it!"; + } + }; + + twill.fastAspect(target, function (weave) { + weave.before('kungfu', function () { + return "the matrix has you"; + }); + }); + + test.equal("i know it!", target.kungfu()); + test.done(); + }, + + "it can unweave the methods added before" : function (test) { + var untouched = true, + target = { + bob: function () { + } + }; + + //should be a noop ;) + twill.unweave(twill.fastAspect(target, function (weave) { + weave.before('bob', function () { + untouched = false; + }); + })); + + target.bob(); + + test.ok(untouched); + test.done(); + } +}; + diff --git a/test/aspect-fast-exception.js b/test/aspect-fast-exception.js new file mode 100644 index 0000000..9d07e64 --- /dev/null +++ b/test/aspect-fast-exception.js @@ -0,0 +1,20 @@ +var twill = require("../lib/twill"); + +exports["when aspecting exceptions on a method"] = { + "it catches the exception" : function (test) { + var target = { + boom: function () { + throw "errorz"; + } + }; + + twill.fastAspect(target, function (weave) { + weave.exception('boom', function (e) { + test.equal("errorz", e); + test.done(); + }); + }); + + target.boom(); + } +}; diff --git a/test/aspect-fast-remove.js b/test/aspect-fast-remove.js new file mode 100644 index 0000000..0d1eb19 --- /dev/null +++ b/test/aspect-fast-remove.js @@ -0,0 +1,25 @@ +var twill = require("../lib/twill"); + +exports["when removing a method"] = { + "it can remove an existing method" : function (test) { + var target = { + ninja: function () { } + }; + + twill.fastAspect(target, function (weave) { + weave.remove('ninja'); + }); + + test.ok(!target.ninja); + test.done(); + }, + + "it throws an exception if the method doesn't exist" : function (test) { + test.throws(function () { + twill.fastAspect({}, function (weave) { + weave.remove('foo'); + }); + }); + test.done(); + } +};