Adding test coverage

This commit is contained in:
Jim Cowart 2011-09-09 18:05:47 -04:00
parent 18ee34b4f8
commit 6d517a4539
8 changed files with 418 additions and 166 deletions

View file

@ -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");
});

View file

@ -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();
});
});
});

254
spec/Postal.spec.js Normal file
View file

@ -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);
});
});
});
});

View file

@ -10,9 +10,12 @@
<script type="text/javascript" src="../src/BindingsResolver.js"></script>
<script type="text/javascript" src="../src/DistinctPredicate.js"></script>
<script type="text/javascript" src="../src/ChannelDefinition.js"></script>
<script type="text/javascript" src="../src/Bus.js"></script>
<script type="text/javascript" src="../src/Postal.js"></script>
<script type="text/javascript" src="BindingsResolver.spec.js"></script>
<script type="text/javascript" src="DistinctPredicate.spec.js"></script>
<script type="text/javascript" src="ChannelDefinition.spec.js"></script>
<script type="text/javascript" src="Postal.spec.js"></script>
<link rel="stylesheet" href="../lib/qunit.css" type="text/css" media="screen" />
</head>
<body>

96
src/Bus.js Normal file
View file

@ -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);
}
};
}
};

View file

@ -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);

View file

@ -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;
};
};

31
src/Postal.js Normal file
View file

@ -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);
}
};