From 6d517a4539a72b4fcbab9f0d648d862eb1ad0a01 Mon Sep 17 00:00:00 2001 From: Jim Cowart Date: Fri, 9 Sep 2011 18:05:47 -0400 Subject: [PATCH] Adding test coverage --- spec/BindingsResolver.spec.js | 7 - spec/DistinctPredicate.spec.js | 48 +++---- spec/Postal.spec.js | 254 +++++++++++++++++++++++++++++++++ spec/runner.html | 3 + src/Bus.js | 96 +++++++++++++ src/Bus.new.js | 132 ----------------- src/DistinctPredicate.js | 13 +- src/Postal.js | 31 ++++ 8 files changed, 418 insertions(+), 166 deletions(-) create mode 100644 spec/Postal.spec.js create mode 100644 src/Bus.js delete mode 100644 src/Bus.new.js create mode 100644 src/Postal.js diff --git a/spec/BindingsResolver.spec.js b/spec/BindingsResolver.spec.js index 9a18eb4..ac717a7 100644 --- a/spec/BindingsResolver.spec.js +++ b/spec/BindingsResolver.spec.js @@ -3,49 +3,42 @@ QUnit.specify("postal.js", function(){ describe("When calling regexify", function() { describe("With a topic containing no special escape chars", function() { var result = bindingsResolver.regexify("CoolTopic"); - console.log(result); it("Should equal 'CoolTopic'", function(){ assert(result).equals("CoolTopic"); }); }); describe("With a topic containing periods", function() { var result = bindingsResolver.regexify("Top.Middle.Bottom"); - console.log(result); it("Only the periods should be escaped", function(){ assert(result).equals("Top\\.Middle\\.Bottom"); }); }); describe("With a topic containing a hash", function() { var result = bindingsResolver.regexify("Top#Bottom"); - console.log(result); it("Only the hash should be escaped", function(){ assert(result).equals("Top[A-Z,a-z,0-9]*Bottom"); }); }); describe("With a topic containing a hash and periods", function() { var result = bindingsResolver.regexify("Top.#.Bottom"); - console.log(result); it("The hash should be escaped for alphanumeric regex", function(){ assert(result).equals("Top\\.[A-Z,a-z,0-9]*\\.Bottom"); }); }); describe("With a topic containing a hash and asterisk", function() { var result = bindingsResolver.regexify("Top#Bottom*"); - console.log(result); it("The hash should be escaped for alphanumeric regex", function(){ assert(result).equals("Top[A-Z,a-z,0-9]*Bottom.*"); }); }); describe("With a topic containing a hash, asterisk and periods", function() { var result = bindingsResolver.regexify("Top.#.Bottom.*"); - console.log(result); it("The hash should be escaped for alphanumeric regex", function(){ assert(result).equals("Top\\.[A-Z,a-z,0-9]*\\.Bottom\\..*"); }); }); describe("With a topic containing an asterisk and periods", function() { var result = bindingsResolver.regexify("Top.*.Bottom"); - console.log(result); it("The asterisk should be escaped", function(){ assert(result).equals("Top\\..*\\.Bottom"); }); diff --git a/spec/DistinctPredicate.spec.js b/spec/DistinctPredicate.spec.js index b1df4f1..c9321d3 100644 --- a/spec/DistinctPredicate.spec.js +++ b/spec/DistinctPredicate.spec.js @@ -8,14 +8,14 @@ QUnit.specify("postal.js", function(){ results.push(pred(data)); results.push(pred(data)); - it("The first result should be false", function(){ - assert(results[0]).isFalse(); + it("The first result should be true", function(){ + assert(results[0]).isTrue(); }); - it("The second result should be true", function(){ - assert(results[1]).isTrue(); + it("The second result should be false", function(){ + assert(results[1]).isFalse(); }); - it("The third result should be true", function(){ - assert(results[2]).isTrue(); + it("The third result should be false", function(){ + assert(results[2]).isFalse(); }); }); describe("When calling the function with different data every time", function() { @@ -28,14 +28,14 @@ QUnit.specify("postal.js", function(){ data.name = "Martha"; res.push(predA(data)); - it("The first result should be false", function(){ - assert(res[0]).isFalse(); + it("The first result should be true", function(){ + assert(res[0]).isTrue(); }); - it("The second result should be false", function(){ - assert(res[1]).isFalse(); + it("The second result should be true", function(){ + assert(res[1]).isTrue(); }); - it("The third result should be false", function(){ - assert(res[2]).isFalse(); + it("The third result should be true", function(){ + assert(res[2]).isTrue(); }); }); describe("When calling the function with different data every two calls", function() { @@ -51,25 +51,25 @@ QUnit.specify("postal.js", function(){ res.push(predA(data)); res.push(predA(data)); - it("The first result should be false", function(){ - assert(res[0]).isFalse(); + it("The first result should be true", function(){ + assert(res[0]).isTrue(); }); - it("The second result should be true", function(){ - assert(res[1]).isTrue(); + it("The second result should be false", function(){ + assert(res[1]).isFalse(); }); - it("The third result should be false", function(){ - assert(res[2]).isFalse(); + it("The third result should be isTrue", function(){ + assert(res[2]).isTrue(); }); - it("The fourth result should be true", function(){ - assert(res[3]).isTrue(); + it("The fourth result should be false", function(){ + assert(res[3]).isFalse(); }); - it("The fifth result should be false", function(){ - assert(res[4]).isFalse(); + it("The fifth result should be true", function(){ + assert(res[4]).isTrue(); }); - it("The sixth result should be true", function(){ - assert(res[5]).isTrue(); + it("The sixth result should be false", function(){ + assert(res[5]).isFalse(); }); }); }); diff --git a/spec/Postal.spec.js b/spec/Postal.spec.js new file mode 100644 index 0000000..2f2f6aa --- /dev/null +++ b/spec/Postal.spec.js @@ -0,0 +1,254 @@ +QUnit.specify("postal.js", function(){ + describe("Postal", function(){ + var subToken, + sub; + describe("when creating basic subscription", function() { + before(function(){ + subToken = postal.exchange("MyExchange") + .topic("MyTopic") + .subscribe(function() { }); + sub = postal.configuration.bus.subscriptions.MyExchange.MyTopic[0]; + }); + after(function(){ + postal.configuration.bus.subscriptions = {}; + }); + it("should create an exchange called MyExchange", function(){ + assert(postal.configuration.bus.subscriptions["MyExchange"] !== undefined).isTrue(); + }); + it("should create a topic under MyExchange called MyTopic", function(){ + assert(postal.configuration.bus.subscriptions["MyExchange"]["MyTopic"] !== undefined).isTrue(); + }); + it("should have set subscription exchange value", function() { + assert(sub.exchange).equals("MyExchange"); + }); + it("should have set subscription topic value", function() { + assert(sub.topic).equals("MyTopic"); + }); + it("should have set subscription priority value", function() { + assert(sub.priority).equals(50); + }); + it("should have defaulted the subscription constraints array", function() { + assert(sub.constraints.length).equals(0); + }); + it("should have defaulted the subscription disposeAfter value", function() { + assert(sub.disposeAfter).equals(0); + }); + it("should have defaulted the subscription context value", function() { + assert(sub.context).isNull(); + }); + }); + describe("when unsubscribing", function() { + var subExistsBefore = false, + subExistsAfter = true; + before(function(){ + subToken = postal.exchange("MyExchange") + .topic("MyTopic") + .subscribe(function() { }); + subExistsBefore = postal.configuration.bus.subscriptions.MyExchange.MyTopic[0] !== undefined; + subToken(); + subExistsAfter = postal.configuration.bus.subscriptions.MyExchange.MyTopic.length !== 0; + }); + after(function(){ + postal.configuration.bus.subscriptions = {}; + }); + it("subscription should exist before unsubscribe", function(){ + assert(subExistsBefore).isTrue(); + }); + it("subscription should not exist after unsubscribe", function(){ + assert(subExistsAfter).isFalse(); + }); + }); + describe("When publishing a message", function(){ + var msgReceivedCnt = 0, + msgData; + before(function(){ + subToken = postal.exchange("MyExchange") + .topic("MyTopic") + .subscribe(function(data) { msgReceivedCnt++; msgData = data;}); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + subToken(); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + }); + after(function(){ + postal.configuration.bus.subscriptions = {}; + }); + it("subscription callback should be invoked once", function(){ + assert(msgReceivedCnt).equals(1); + }); + it("subscription callback should receive published data", function(){ + assert(msgData).equals("Testing123"); + }); + }); + describe("When subscribing with a disposeAfter of 5", function(){ + var msgReceivedCnt = 0; + before(function(){ + subToken = postal.exchange("MyExchange") + .topic("MyTopic") + .disposeAfter(5) + .subscribe(function(data) { msgReceivedCnt++; }); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + }); + after(function(){ + postal.configuration.bus.subscriptions = {}; + }); + it("subscription callback should be invoked 5 times", function(){ + assert(msgReceivedCnt).equals(5); + }); + }); + describe("When subscribing and ignoring duplicates", function(){ + var subInvokedCnt = 0; + before(function(){ + subToken = postal.exchange("MyExchange") + .topic("MyTopic") + .ignoreDuplicates() + .subscribe(function(data) { subInvokedCnt++; }); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + }); + after(function(){ + postal.configuration.bus.subscriptions = {}; + subInvokedCnt = 0; + }); + it("should have a constraint on the subscription", function() { + assert(postal.configuration.bus.subscriptions.MyExchange.MyTopic[0].constraints.length).equals(1); + }); + it("subscription callback should be invoked once", function(){ + assert(subInvokedCnt).equals(1); + }); + }); + describe("When subscribing and passing onHandled callback", function(){ + var whte = false; + before(function(){ + subToken = postal.exchange("MyExchange") + .topic("MyTopic") + .whenHandledThenExecute(function() { whte = true; }) + .subscribe(function(data) { }); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + }); + after(function(){ + postal.configuration.bus.subscriptions = {}; + whte = false; + }); + it("should have an onHandled callback on the subscription", function() { + assert(typeof postal.configuration.bus.subscriptions.MyExchange.MyTopic[0].onHandled).equals("function"); + }); + it("should have invoked the onHandled callback", function() { + assert(whte).isTrue(); + }); + }); + describe("When subscribing with one constraint returning true", function(){ + var recvd = false; + before(function(){ + subToken = postal.exchange("MyExchange") + .topic("MyTopic") + .withConstraint(function() { return true; }) + .subscribe(function(data) { recvd= true; }); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + }); + after(function(){ + postal.configuration.bus.subscriptions = {}; + recvd = false; + }); + it("should have a constraint on the subscription", function() { + assert(postal.configuration.bus.subscriptions.MyExchange.MyTopic[0].constraints.length).equals(1); + }); + it("should have invoked the onHandled callback", function() { + assert(recvd).isTrue(); + }); + }); + describe("When subscribing with one constraint returning false", function(){ + var recvd = false; + before(function(){ + subToken = postal.exchange("MyExchange") + .topic("MyTopic") + .withConstraint(function() { return false; }) + .subscribe(function(data) { recvd= true; }); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + }); + after(function(){ + postal.configuration.bus.subscriptions = {}; + recvd = false; + }); + it("should have a constraint on the subscription", function() { + assert(postal.configuration.bus.subscriptions.MyExchange.MyTopic[0].constraints.length).equals(1); + }); + it("should not have invoked the onHandled callback", function() { + assert(recvd).isFalse(); + }); + }); + describe("When subscribing with multiple constraints returning true", function(){ + var recvd = false; + before(function(){ + subToken = postal.exchange("MyExchange") + .topic("MyTopic") + .withConstraints([function() { return true; }, + function() { return true; }, + function() { return true; }]) + .subscribe(function(data) { recvd= true; }); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + }); + after(function(){ + postal.configuration.bus.subscriptions = {}; + recvd = false; + }); + it("should have a constraint on the subscription", function() { + assert(postal.configuration.bus.subscriptions.MyExchange.MyTopic[0].constraints.length).equals(3); + }); + it("should have invoked the onHandled callback", function() { + assert(recvd).isTrue(); + }); + }); + describe("When subscribing with multiple constraints and one returning false", function(){ + var recvd = false; + before(function(){ + subToken = postal.exchange("MyExchange") + .topic("MyTopic") + .withConstraints([function() { return true; }, + function() { return false; }, + function() { return true; }]) + .subscribe(function(data) { recvd= true; }); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + }); + after(function(){ + postal.configuration.bus.subscriptions = {}; + recvd = false; + }); + it("should have a constraint on the subscription", function() { + assert(postal.configuration.bus.subscriptions.MyExchange.MyTopic[0].constraints.length).equals(3); + }); + it("should not have invoked the onHandled callback", function() { + assert(recvd).isFalse(); + }); + }); + describe("When subscribing with the context being set", function(){ + var count = 0, + obj = { + increment: function() { + count++; + } + }; + before(function(){ + subToken = postal.exchange("MyExchange") + .topic("MyTopic") + .withContext(obj) + .subscribe(function(data) { this.increment(); }); + postal.publish({exchange: "MyExchange", topic: "MyTopic", data: "Testing123"}); + }); + after(function(){ + postal.configuration.bus.subscriptions = {}; + }); + it("should have called obj.increment", function() { + assert(count).equals(1); + }); + }); + }); +}); \ No newline at end of file diff --git a/spec/runner.html b/spec/runner.html index f0f65aa..4716dbf 100644 --- a/spec/runner.html +++ b/spec/runner.html @@ -10,9 +10,12 @@ + + + diff --git a/src/Bus.js b/src/Bus.js new file mode 100644 index 0000000..ea64408 --- /dev/null +++ b/src/Bus.js @@ -0,0 +1,96 @@ +var localBus = { + + subscriptions: {}, + + wireTaps: [], + + publish: function(envelope) { + envelope.timeStamp = new Date(); + _.each(this.wireTaps,function(tap) { + tap({ + exchange: envelope.exchange, + topic: envelope.topic, + data: envelope.data, + timeStamp: envelope.timeStamp + }); + }); + + _.each(this.subscriptions[envelope.exchange], function(topic) { + _.each(topic, function(binding){ + if(postal.configuration.resolver.compare(binding.topic, envelope.topic)) { + if(_.all(binding.constraints, function(constraint) { return constraint(envelope.data); })) { + if(typeof binding.callback === 'function') { + binding.callback.apply(binding.context, [envelope.data]); + binding.onHandled(); + } + } + } + }); + }); + }, + + subscribe: function(config) { + var idx, found; + if(config.disposeAfter && config.disposeAfter > 0) { + var fn = config.onHandled, + dispose = _.after(config.disposeAfter, _.bind(function() { + this.unsubscribe(config); + }, this)); + + config.onHandled = function() { + fn.apply(config.context, arguments); + dispose(); + } + } + + if(!this.subscriptions[config.exchange]) { + this.subscriptions[config.exchange] = {}; + } + + if(!this.subscriptions[config.exchange][config.topic]) { + this.subscriptions[config.exchange][config.topic] = [config]; + } + else { + idx = this.subscriptions[config.exchange][config.topic].length - 1; + if(!_.any(this.subscriptions[config.exchange][config.topic], function(cfg) { return cfg === config; })) { + for(; idx >= 0; idx--) { + if(this.subscriptions[config.exchange][config.topic][idx].priority <= config.priority) { + this.subscriptions[config.exchange][config.topic].splice(idx + 1, 0, config); + found = true; + break; + } + } + if(!found) { + this.subscriptions[config.exchange][config.topic].unshift(config); + } + console.log("SUBSCRIBE: " + JSON.stringify(config)); + } + } + + return _.bind(function() { this.unsubscribe(config); }, this); + }, + + unsubscribe: function(config) { + if(this.subscriptions[config.exchange][config.topic]) { + var len = this.subscriptions[config.exchange][config.topic].length, + idx = 0; + for ( ; idx < len; idx++ ) { + if (this.subscriptions[config.exchange][config.topic][idx] === config) { + this.subscriptions[config.exchange][config.topic].splice( idx, 1 ); + console.log("UNSUBSCRIBE: " + JSON.stringify(config)); + break; + } + } + } + }, + + addWireTap: function(callback) { + this.wireTaps.push(callback); + return function() { + var idx = this.wireTaps.indexOf(callback); + if(idx !== -1) { + this.wireTaps.splice(idx,1); + } + }; + } +}; \ No newline at end of file diff --git a/src/Bus.new.js b/src/Bus.new.js deleted file mode 100644 index 40b444f..0000000 --- a/src/Bus.new.js +++ /dev/null @@ -1,132 +0,0 @@ -var bus; - -var localBus = { - subscriptions: {}, - - wireTaps: [], - - publish: function(envelope) { - _.each(this.wireTaps,function(tap) { - tap({ - exchange: envelope.exchange, - topic: envelope.topic, - data: envelope.data, - timeStamp: new Date() - }); - }); - - - _.each(this.subscriptions[envelope.exchange], function(topic) { - _.each(topic, function(binding){ - if(postal.config.bindingsResolver.compare(binding.topic, envelope.topic)) { - if(typeof binding.callback === 'function') { - binding.callback.apply(binding.context, [envelope.data]); - binding.onHandled(); - } - } - }); - }); - }, - - subscribe: function(config) { - var idx, found; - if(config.disposeAfter && config.disposeAfter > 0) { - var fn = config.onHandled, - dispose = _.after(config.disposeAfter, _.bind(function() { - this.unsubscribe(config); - }, this)); - - config.onHandled = function() { - fn.apply(config.context, arguments); - dispose(); - } - } - - if(!bus.subscriptions[config.exchange]) { - bus.subscriptions[config.exchange] = {}; - } - - if(!bus.subscriptions[config.exchange][config.topic]) { - bus.subscriptions[config.exchange][config.topic] = [config]; - } - else { - idx = bus.subscriptions[config.exchange][config.topic].length - 1; - if(!_.any(bus.subscriptions[config.exchange][config.topic], function(cfg) { return cfg === config; })) { - for(; idx >= 0; idx--) { - if(bus.subscriptions[config.exchange][config.topic][idx].priority <= config.priority) { - bus.subscriptions[config.exchange][config.topic].splice(idx + 1, 0, config); - found = true; - break; - } - } - if(!found) { - bus.subscriptions[config.exchange][config.topic].unshift(config); - } - console.log("SUBSCRIBE: " + JSON.stringify(config)); - } - } - - return _.bind(function() { this.unsubscribe(config); }, this); - }, - - unsubscribe: function(config) { - if(bus.subscriptions[config.exchange][config.topic]) { - var len = bus.subscriptions[config.exchange][config.topic].length, - idx = 0; - for ( ; idx < len; idx++ ) { - if (bus.subscriptions[config.exchange][config.topic][idx] === config) { - bus.subscriptions[config.exchange][config.topic].splice( idx, 1 ); - console.log("UNSUBSCRIBE: " + JSON.stringify(config)); - break; - } - } - } - }, - - addWireTap: function(callback) { - this.wireTaps.push(callback); - return function() { - var idx = this.wireTaps.indexOf(callback); - if(idx !== -1) { - this.wireTaps.splice(idx,1); - } - }; - } -}; - -var postal = { - - config: { - setBusBehavior: function(behavior) { - bus = behavior; - }, - - bindingsResolver: bindingsResolver - }, - - exchange: function(exchange) { - return new ChannelDefinition(exchange); - }, - - topic: function(topic) { - return new ChannelDefinition(undefined, topic); - }, - - publish: function(config) { - bus.publish(config); - }, - - subscribe: function(config) { - return bus.subscribe(config); - }, - - unsubscribe: function(config) { - bus.unsubscribe(config); - }, - - addWireTap: function(callback) { - bus.addWireTap(callback); - } -}; - -postal.config.setBusBehavior(localBus); \ No newline at end of file diff --git a/src/DistinctPredicate.js b/src/DistinctPredicate.js index ddf29f4..1ddfe1e 100644 --- a/src/DistinctPredicate.js +++ b/src/DistinctPredicate.js @@ -1,8 +1,15 @@ var DistinctPredicate = function() { var previous; return function(data) { - var result = _.isEqual(data, previous); - previous = _.clone(data); - return result; + var eq = false; + if(_.isString(data)) { + eq = data === previous; + previous = data; + } + else { + eq = _.isEqual(data, previous); + previous = _.clone(data); + } + return !eq; }; }; \ No newline at end of file diff --git a/src/Postal.js b/src/Postal.js new file mode 100644 index 0000000..93aad55 --- /dev/null +++ b/src/Postal.js @@ -0,0 +1,31 @@ +var postal = { + + configuration: { + bus: localBus, + resolver: bindingsResolver + }, + + exchange: function(exchange) { + return new ChannelDefinition(exchange); + }, + + topic: function(topic) { + return new ChannelDefinition(undefined, topic); + }, + + publish: function(config) { + this.configuration.bus.publish(config); + }, + + subscribe: function(config) { + return this.configuration.bus.subscribe(config); + }, + + unsubscribe: function(config) { + this.configuration.bus.unsubscribe(config); + }, + + addWireTap: function(callback) { + this.configuration.bus.addWireTap(callback); + } +}; \ No newline at end of file