Added tests for defer and delay. Removed 'whenHandledThenExecute'. Added postal.reset. Fixed bug in IE with setTimeout call in defer.

This commit is contained in:
Jim Cowart 2012-03-27 00:55:58 -04:00
parent 1cbb5f8d2a
commit d2abfd881a
19 changed files with 2288 additions and 1443 deletions

View file

@ -6,17 +6,25 @@
Postal.js is a JavaScript pub/sub library that can be used in the browser, or on the server-side using Node.js. It extends the "eventing" paradigm most JavaScript developers are already familiar with by providing an in-memory message bus to which your code/components/modules/etc can subscribe & publish.
## Why would I use it?
If you are looking to decouple the various components/libraries/plugins you use (client-or-server-side), applying messaging can enable you to not only easily separate concerns, but also enable you to more painlessly plug in additional components/functionality in the future. A pub/sub library like Postal.js can assist you in picking & choosing the libraries that best address the problems you're trying to solve, without burdening you with the requirement that those libraries have to be natively interoperable. For example:
If you are looking to decouple the various components/libraries/plugins you use (client-or-server-side), applying messaging can enable you to not only easily separate concerns, but also enable you to more painlessly plug in additional components/functionality in the future. A pub/sub library like postal.js can assist you in picking & choosing the libraries that best address the problems you're trying to solve, without burdening you with the requirement that those libraries have to be natively interoperable. For example:
* If you're using a client-side binding framework, and either don't have - or don't like - the request/communication abstractions provided, then grab a library like [amplify.js](http://amplifyjs.com) or [reqwest](https://github.com/ded/reqwest). Then, instead of tightly coupling the two, have the request success/error callbacks publish messages with the appropriate data and any subscribers you've wired up can handle applying the data to the specific objects/elements they're concerned with.
* Do you need two view models to communicate, but you don't want them to need to know about each other? Have them subscribe to the topics about which they are interested in receiving messages. From there, whenever a view model needs to alert any listeners of specific data/events, just publish a message to the bus. If the other view model is present, it will receive the notification.
* Want to wire up your own binding framework? Want to control the number of times subscription callbacks get invoked within a given time frame? Want to keep subscriptions from being fired until after data stops arriving? Want to keep events from being acted upon until the UI event loop is done processing other events? These - and more - are all things Postal can do for you.
## Wut? Another pub/sub library?
Why, yes. There are great alternatives to Postal. If you need something leaner for client-side development, look at amplify.js. If you're in Node.js and can get by with EventEmitter, great. However, I discovered that as my needs quickly grew, I wanted something that was as lean as possible, without sacrificing some of the more complex functionality that's not provided by libraries like amplify.js, and the EventEmitter object in Node.
## Philosophy
Postal.js is in good company - there are many options for pub/sub in the browser. However, I grew frustrated with most of them because they often closely followed a DOM-eventing-paradigm, instead of providing a more substantial in-memory message bus. Central to postal.js are two things:
* channels
* hierarchical topics (which allow plan string or wildcard bindings)
### Channels? WAT?
A channel is a logical partition of topics. Conceptually, it's like a dedicated highway for a specific set of communication. At first glance it might seem like that's overkill for an environment that runs in an event loop, but it actually proves to be quite useful. Every library has architectural opinions that it either imposes or nudges you toward. Channel-oriented messaging nudges you to separate your communication by bounded context, and enables you the kind of fine-tuned visibility you need into the interactions between components as your application grows.
### Hierarchical Topics
In my experience, seeing publish and subscribe calls all over application logic is usually strong code smell. Ideally, the majority of message-bus integration should be concealed within app infrastructure. Have a hierarchical-wildcard-bindable topic system makes it very easy to keep things concise (especially subscribe calls!). For example, if you have a module that needs to listen to ever message published on the ShoppingCart channel, you'd simply subscribe to "*", and never have to worry about additional subscribes on that channel again - even if you add new messages in the future. If you need to capture all messages with ".validation" at the end of the topic, you'd simply subscribe to "*.validation".
## How do I use it?
In a nutshell, Postal provides an in-memory message bus, where clients subscribe to a topic (which can include wildcards, as we'll see), and publishers publish messages (passing a topic along with it). Postal's "bindingResolver" handles matching a published message's topic to subscribers who should be notified of the message. When a client subscribes, they pass a callback that should be invoked whenever a message comes through. This callback takes one argument - the "data" payload of the message. (Messages do not *have* to include data - they can simply be used to indicate an event, and not transmit additional state). Additional options/constraints can be set on a subscription (see examples below, and check out the fluent calls available on the SubscriptionDefinition prototype).
Here are four examples of using Postal. All of these examples - AND MORE! - can be run live here: [http://jsfiddle.net/ifandelse/FdFM3/](http://jsfiddle.net/ifandelse/FdFM3/)
@ -111,8 +119,9 @@ dupSubscription.unsubscribe();
```
## How can I extend it?
There are two main ways you can extend Postal:
There are three main ways you can extend Postal:
* Write a plugin. Need more complex behavior that the built-in SubscriptionDefinition doesn't offer? Write a plugin that you can attach to the global postal object. See [postal.when]() for an example of how to do this.
* First, you can write an entirely new bus implementation (want to tie into a real broker like RabbitMQ by hitting the [experimental] JSON RPC endpoints and wrap it with Postal's API? This is how you'd do it.). If you want to do this, look over the `localBus` implementation to see how the core version works. Then, you can simply swap the bus implementation out by calling: `postal.configuration.bus = myWayBetterBusImplementation`.
* The second way you can extend Postal is to change how the `bindingResolver` works. You may not care for the RabbitMQ-style bindings functionality. No problem! Write your own resolver object that implements a `compare` method and swap the core version out with your implementation by calling: `postal.configuration.resolver = myWayBetterResolver`.

View file

@ -33,23 +33,18 @@
</div>
<div>
Example 6 - using whenHandledThenExecute(X)
Example 6 - using withConstraint() to apply a predicate to subscription callback
<ul class="results" id="example6"></ul>
</div>
<div>
Example 7 - using withConstraint() to apply a predicate to subscription callback
Example 7 - using withContext to set the "this" context
<ul class="results" id="example7"></ul>
</div>
<div>
Example 8 - using withContext to set the "this" context
Example 8 - using withDelay to delay evaluation of subscription
<ul class="results" id="example8"></ul>
</div>
<div>
Example 9 - using withDelay to delay evaluation of subscription
<ul class="results" id="example9"></ul>
</div>
</body>
</html>

View file

@ -77,22 +77,11 @@ define(['postal', 'postaldiags'], function(postal, diags){
.publish({ value:"Donna Noble has left the library." });
daSubscription.unsubscribe();
// Using whenHandledThenExecute() to invoke a function after handling a message
var whteChannel = postal.channel("Donna.Noble.*"),
whteSubscription = whteChannel.subscribe(function(data) {
$('<li>' + data.value + '</li>').appendTo("#example6");
}).whenHandledThenExecute(function() {
$('<li>[Kind of a frivolous example...but this line resulted from the whenHandledThenExecute() callback]</li>').appendTo("#example6");
});
postal.channel("Donna.Noble.*")
.publish({ value:"Donna Noble has left the library." });
whteSubscription.unsubscribe();
// Using withConstraint to apply a predicate to the subscription
var drIsInTheTardis = false,
wcChannel = postal.channel("Tardis.Depart"),
wcSubscription = wcChannel.subscribe(function(data) {
$('<li>' + data.value + '</li>').appendTo("#example7");
$('<li>' + data.value + '</li>').appendTo("#example6");
}).withConstraint(function() { return drIsInTheTardis; } );
postal.channel("Tardis.Depart")
.publish({ value:"Time for time travel....fantastic!" });
@ -107,7 +96,7 @@ define(['postal', 'postaldiags'], function(postal, diags){
var ctxChannel = postal.channel("Dalek.Meet.CyberMen"),
ctxSubscription = ctxChannel.subscribe(function(data) {
$('<li>' + data.value + '</li>').appendTo(this);
}).withContext($("#example8"));
}).withContext($("#example7"));
postal.channel("Dalek.Meet.CyberMen")
.publish({ value:"Exterminate!" });
postal.channel("Dalek.Meet.CyberMen")
@ -117,7 +106,7 @@ define(['postal', 'postaldiags'], function(postal, diags){
// Using withDelay() to delay the subscription evaluation
var wdChannel = postal.channel("He.Will.Knock.Four.Times"),
wdSubscription = wdChannel.subscribe(function(data) {
$('<li>' + data.value + '</li>').appendTo($("#example9"));
$('<li>' + data.value + '</li>').appendTo($("#example8"));
}).withDelay(5000);
postal.channel("He.Will.Knock.Four.Times")
.publish({ value:"Knock!" });

View file

@ -147,14 +147,6 @@ SubscriptionDefinition.prototype = {
return this;
},
whenHandledThenExecute: function(callback) {
if(! _.isFunction(callback)) {
throw "Value provided to 'whenHandledThenExecute' must be a function";
}
this.onHandled = callback;
return this;
},
withConstraint: function(predicate) {
if(! _.isFunction(predicate)) {
throw "Predicate constraint must be a function";
@ -191,7 +183,9 @@ SubscriptionDefinition.prototype = {
}
var fn = this.callback;
this.callback = function(data) {
setTimeout(fn, milliseconds, data);
setTimeout(function(){
fn(data);
}, milliseconds);
};
return this;
},
@ -405,6 +399,17 @@ var postal = {
});
});
return result;
},
reset: function() {
// we check first in case a custom bus or resolver
// doesn't expose subscriptions or a resolver cache
if(postal.configuration.bus.subscriptions) {
postal.configuration.bus.subscriptions = {};
}
if(postal.configuration.resolver.cache) {
postal.configuration.resolver.cache = {};
}
}
};

View file

@ -37,23 +37,18 @@
</div>
<div>
Example 6 - using whenHandledThenExecute(X)
Example 6 - using withConstraint() to apply a predicate to subscription callback
<ul class="results" id="example6"></ul>
</div>
<div>
Example 7 - using withConstraint() to apply a predicate to subscription callback
Example 7 - using withContext to set the "this" context
<ul class="results" id="example7"></ul>
</div>
<div>
Example 8 - using withContext to set the "this" context
Example 8 - using withDelay to delay evaluation of subscription
<ul class="results" id="example8"></ul>
</div>
<div>
Example 9 - using withDelay to delay evaluation of subscription
<ul class="results" id="example9"></ul>
</div>
</body>
</html>

View file

@ -77,22 +77,11 @@ $(function(){
.publish({ value:"Donna Noble has left the library." });
daSubscription.unsubscribe();
// Using whenHandledThenExecute() to invoke a function after handling a message
var whteChannel = postal.channel("Donna.Noble.*"),
whteSubscription = whteChannel.subscribe(function(data) {
$('<li>' + data.value + '</li>').appendTo("#example6");
}).whenHandledThenExecute(function() {
$('<li>[Kind of a frivolous example...but this line resulted from the whenHandledThenExecute() callback]</li>').appendTo("#example6");
});
postal.channel("Donna.Noble.*")
.publish({ value:"Donna Noble has left the library." });
whteSubscription.unsubscribe();
// Using withConstraint to apply a predicate to the subscription
var drIsInTheTardis = false,
wcChannel = postal.channel("Tardis.Depart"),
wcSubscription = wcChannel.subscribe(function(data) {
$('<li>' + data.value + '</li>').appendTo("#example7");
$('<li>' + data.value + '</li>').appendTo("#example6");
}).withConstraint(function() { return drIsInTheTardis; } );
postal.channel("Tardis.Depart")
.publish({ value:"Time for time travel....fantastic!" });
@ -107,7 +96,7 @@ $(function(){
var ctxChannel = postal.channel("Dalek.Meet.CyberMen"),
ctxSubscription = ctxChannel.subscribe(function(data) {
$('<li>' + data.value + '</li>').appendTo(this);
}).withContext($("#example8"));
}).withContext($("#example7"));
postal.channel("Dalek.Meet.CyberMen")
.publish({ value:"Exterminate!" });
postal.channel("Dalek.Meet.CyberMen")
@ -117,7 +106,7 @@ $(function(){
// Using withDelay() to delay the subscription evaluation
var wdChannel = postal.channel("He.Will.Knock.Four.Times"),
wdSubscription = wdChannel.subscribe(function(data) {
$('<li>' + data.value + '</li>').appendTo($("#example9"));
$('<li>' + data.value + '</li>').appendTo($("#example8"));
}).withDelay(5000);
postal.channel("He.Will.Knock.Four.Times")
.publish({ value:"Knock!" });

View file

@ -147,14 +147,6 @@ SubscriptionDefinition.prototype = {
return this;
},
whenHandledThenExecute: function(callback) {
if(! _.isFunction(callback)) {
throw "Value provided to 'whenHandledThenExecute' must be a function";
}
this.onHandled = callback;
return this;
},
withConstraint: function(predicate) {
if(! _.isFunction(predicate)) {
throw "Predicate constraint must be a function";
@ -191,7 +183,9 @@ SubscriptionDefinition.prototype = {
}
var fn = this.callback;
this.callback = function(data) {
setTimeout(fn, milliseconds, data);
setTimeout(function(){
fn(data);
}, milliseconds);
};
return this;
},
@ -405,6 +399,17 @@ var postal = {
});
});
return result;
},
reset: function() {
// we check first in case a custom bus or resolver
// doesn't expose subscriptions or a resolver cache
if(postal.configuration.bus.subscriptions) {
postal.configuration.bus.subscriptions = {};
}
if(postal.configuration.resolver.cache) {
postal.configuration.resolver.cache = {};
}
}
};

920
ext/pavlov.js Normal file → Executable file

File diff suppressed because it is too large Load diff

View file

@ -1,119 +1,228 @@
/**
* QUnit v1.3.0pre - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
* Copyright (c) 2011 John Resig, rn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
* Pulled Live from Git Sat Feb 11 19:20:01 UTC 2012
* Last Commit: 0712230bb203c262211649b32bd712ec7df5f857
*/
ol#qunit-tests {
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
margin:0;
padding:0;
list-style-position:inside;
/** Font Family and Sizes */
font-size: smaller;
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
}
ol#qunit-tests li{
padding:0.4em 0.5em 0.4em 2.5em;
border-bottom:1px solid #fff;
font-size:small;
list-style-position:inside;
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
#qunit-tests { font-size: smaller; }
/** Resets */
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
margin: 0;
padding: 0;
}
ol#qunit-tests li ol{
/** Header */
#qunit-header {
padding: 0.5em 0 0.5em 1em;
color: #8699a4;
background-color: #0d3349;
font-size: 1.5em;
line-height: 1em;
font-weight: normal;
border-radius: 15px 15px 0 0;
-moz-border-radius: 15px 15px 0 0;
-webkit-border-top-right-radius: 15px;
-webkit-border-top-left-radius: 15px;
}
#qunit-header a {
text-decoration: none;
color: #c2ccd1;
}
#qunit-header a:hover,
#qunit-header a:focus {
color: #fff;
}
#qunit-banner {
height: 5px;
}
#qunit-testrunner-toolbar {
padding: 0.5em 0 0.5em 2em;
color: #5E740B;
background-color: #eee;
}
#qunit-userAgent {
padding: 0.5em 0 0.5em 2.5em;
background-color: #2b81af;
color: #fff;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
}
/** Tests: Pass/Fail */
#qunit-tests {
list-style-position: inside;
}
#qunit-tests li {
padding: 0.4em 0.5em 0.4em 2.5em;
border-bottom: 1px solid #fff;
list-style-position: inside;
}
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
display: none;
}
#qunit-tests li strong {
cursor: pointer;
}
#qunit-tests li a {
padding: 0.5em;
color: #c2ccd1;
text-decoration: none;
}
#qunit-tests li a:hover,
#qunit-tests li a:focus {
color: #000;
}
#qunit-tests ol {
margin-top: 0.5em;
padding: 0.5em;
background-color: #fff;
border-radius: 15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
box-shadow: inset 0px 2px 13px #999;
-moz-box-shadow: inset 0px 2px 13px #999;
-webkit-box-shadow: inset 0px 2px 13px #999;
margin-top:0.5em;
margin-left:0;
padding:0.5em;
background-color:#fff;
border-radius:15px;
-moz-border-radius: 15px;
-webkit-border-radius: 15px;
}
ol#qunit-tests li li{
border-bottom:none;
margin:0.5em;
background-color:#fff;
list-style-position: inside;
padding:0.4em 0.5em 0.4em 0.5em;
}
ol#qunit-tests li li.pass{
border-left:26px solid #C6E746;
background-color:#fff;
color:#5E740B;
}
ol#qunit-tests li li.fail{
border-left:26px solid #EE5757;
background-color:#fff;
color:#710909;
#qunit-tests table {
border-collapse: collapse;
margin-top: .2em;
}
ol#qunit-tests li.pass{
background-color:#D2E0E6;
color:#528CE0;
#qunit-tests th {
text-align: right;
vertical-align: top;
padding: 0 .5em 0 0;
}
ol#qunit-tests li.fail{
background-color:#EE5757;
color:#000;
#qunit-tests td {
vertical-align: top;
}
ol#qunit-tests li strong {
cursor:pointer;
#qunit-tests pre {
margin: 0;
white-space: pre-wrap;
word-wrap: break-word;
}
h1#qunit-header{
background-color:#0d3349;
margin:0;
padding:0.5em 0 0.5em 1em;
color:#fff;
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
border-top-right-radius:15px;
border-top-left-radius:15px;
-moz-border-radius-topright:15px;
-moz-border-radius-topleft:15px;
-webkit-border-top-right-radius:15px;
-webkit-border-top-left-radius:15px;
text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px;
#qunit-tests del {
background-color: #e0f2be;
color: #374e0c;
text-decoration: none;
}
h2#qunit-banner{
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
height:5px;
margin:0;
padding:0;
#qunit-tests ins {
background-color: #ffcaca;
color: #500;
text-decoration: none;
}
h2#qunit-banner.qunit-pass{
background-color:#C6E746;
/*** Test Counts */
#qunit-tests b.counts { color: black; }
#qunit-tests b.passed { color: #5E740B; }
#qunit-tests b.failed { color: #710909; }
#qunit-tests li li {
margin: 0.5em;
padding: 0.4em 0.5em 0.4em 0.5em;
background-color: #fff;
border-bottom: none;
list-style-position: inside;
}
h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar {
background-color:#EE5757;
/*** Passing Styles */
#qunit-tests li li.pass {
color: #5E740B;
background-color: #fff;
border-left: 26px solid #C6E746;
}
#qunit-testrunner-toolbar {
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
padding:0;
/*width:80%;*/
padding:0em 0 0.5em 2em;
font-size: small;
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
#qunit-tests .pass .test-name { color: #366097; }
#qunit-tests .pass .test-actual,
#qunit-tests .pass .test-expected { color: #999999; }
#qunit-banner.qunit-pass { background-color: #C6E746; }
/*** Failing Styles */
#qunit-tests li li.fail {
color: #710909;
background-color: #fff;
border-left: 26px solid #EE5757;
white-space: pre;
}
h2#qunit-userAgent {
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
background-color:#2b81af;
margin:0;
padding:0;
color:#fff;
font-size: small;
padding:0.5em 0 0.5em 2.5em;
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
#qunit-tests > li:last-child {
border-radius: 0 0 15px 15px;
-moz-border-radius: 0 0 15px 15px;
-webkit-border-bottom-right-radius: 15px;
-webkit-border-bottom-left-radius: 15px;
}
p#qunit-testresult{
font-family:"Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial;
margin:0;
font-size: small;
color:#2b81af;
border-bottom-right-radius:15px;
border-bottom-left-radius:15px;
-moz-border-radius-bottomright:15px;
-moz-border-radius-bottomleft:15px;
-webkit-border-bottom-right-radius:15px;
-webkit-border-bottom-left-radius:15px;
background-color:#D2E0E6;
padding:0.5em 0.5em 0.5em 2.5em;
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
#qunit-tests .fail .test-name,
#qunit-tests .fail .module-name { color: #000000; }
#qunit-tests .fail .test-actual { color: #EE5757; }
#qunit-tests .fail .test-expected { color: green; }
#qunit-banner.qunit-fail { background-color: #EE5757; }
/** Result */
#qunit-testresult {
padding: 0.5em 0.5em 0.5em 2.5em;
color: #2b81af;
background-color: #D2E0E6;
border-bottom: 1px solid white;
}
strong b.fail{
color:#710909;
}
strong b.pass{
color:#5E740B;
}
/** Fixture */
#qunit-fixture {
position: absolute;
top: -10000px;
left: -10000px;
}

File diff suppressed because it is too large Load diff

View file

@ -147,14 +147,6 @@ SubscriptionDefinition.prototype = {
return this;
},
whenHandledThenExecute: function(callback) {
if(! _.isFunction(callback)) {
throw "Value provided to 'whenHandledThenExecute' must be a function";
}
this.onHandled = callback;
return this;
},
withConstraint: function(predicate) {
if(! _.isFunction(predicate)) {
throw "Predicate constraint must be a function";
@ -191,7 +183,9 @@ SubscriptionDefinition.prototype = {
}
var fn = this.callback;
this.callback = function(data) {
setTimeout(fn, milliseconds, data);
setTimeout(function(){
fn(data);
}, milliseconds);
};
return this;
},
@ -405,6 +399,17 @@ var postal = {
});
});
return result;
},
reset: function() {
// we check first in case a custom bus or resolver
// doesn't expose subscriptions or a resolver cache
if(postal.configuration.bus.subscriptions) {
postal.configuration.bus.subscriptions = {};
}
if(postal.configuration.resolver.cache) {
postal.configuration.resolver.cache = {};
}
}
};

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -137,14 +137,6 @@ SubscriptionDefinition.prototype = {
return this;
},
whenHandledThenExecute: function(callback) {
if(! _.isFunction(callback)) {
throw "Value provided to 'whenHandledThenExecute' must be a function";
}
this.onHandled = callback;
return this;
},
withConstraint: function(predicate) {
if(! _.isFunction(predicate)) {
throw "Predicate constraint must be a function";
@ -181,7 +173,9 @@ SubscriptionDefinition.prototype = {
}
var fn = this.callback;
this.callback = function(data) {
setTimeout(fn, milliseconds, data);
setTimeout(function(){
fn(data);
}, milliseconds);
};
return this;
},
@ -395,6 +389,17 @@ var postal = {
});
});
return result;
},
reset: function() {
// we check first in case a custom bus or resolver
// doesn't expose subscriptions or a resolver cache
if(postal.configuration.bus.subscriptions) {
postal.configuration.bus.subscriptions = {};
}
if(postal.configuration.resolver.cache) {
postal.configuration.resolver.cache = {};
}
}
};

View file

@ -28,7 +28,7 @@ QUnit.specify("postal.js", function(){
});
after(function(){
systemSubscription.unsubscribe();
postal.configuration.bus.subscriptions = {};
postal.reset();
});
it("should create an channel called MyChannel", function(){
assert(postal.configuration.bus.subscriptions["MyChannel"] !== undefined).isTrue();
@ -83,7 +83,7 @@ QUnit.specify("postal.js", function(){
});
after(function(){
systemSubscription.unsubscribe();
postal.configuration.bus.subscriptions = {};
postal.reset();
});
it("subscription should exist before unsubscribe", function(){
assert(subExistsBefore).isTrue();
@ -106,7 +106,7 @@ QUnit.specify("postal.js", function(){
channel.publish("Testing123");
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
});
it("subscription callback should be invoked once", function(){
assert(msgReceivedCnt).equals(1);
@ -129,7 +129,7 @@ QUnit.specify("postal.js", function(){
channel.publish("Testing123");
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
});
it("subscription callback should be invoked 5 times", function(){
assert(msgReceivedCnt).equals(5);
@ -149,7 +149,7 @@ QUnit.specify("postal.js", function(){
channel.publish("Testing123");
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
subInvokedCnt = 0;
});
it("should have a constraint on the subscription", function() {
@ -159,25 +159,6 @@ QUnit.specify("postal.js", function(){
assert(subInvokedCnt).equals(1);
});
});
describe("When subscribing and passing onHandled callback", function(){
var whte = false;
before(function(){
channel = postal.channel({ channel: "MyChannel", topic: "MyTopic" });
subscription = channel.subscribe(function(data) { })
.whenHandledThenExecute(function() { whte = true; });
channel.publish("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.MyChannel.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(){
@ -187,7 +168,7 @@ QUnit.specify("postal.js", function(){
channel.publish("Testing123");
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
recvd = false;
});
it("should have a constraint on the subscription", function() {
@ -206,7 +187,7 @@ QUnit.specify("postal.js", function(){
channel.publish("Testing123");
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
recvd = false;
});
it("should have a constraint on the subscription", function() {
@ -227,13 +208,13 @@ QUnit.specify("postal.js", function(){
channel.publish("Testing123");
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
recvd = false;
});
it("should have a constraint on the subscription", function() {
assert(postal.configuration.bus.subscriptions.MyChannel.MyTopic[0].constraints.length).equals(3);
});
it("should have invoked the onHandled callback", function() {
it("should have invoked the callback", function() {
assert(recvd).isTrue();
});
});
@ -248,13 +229,13 @@ QUnit.specify("postal.js", function(){
channel.publish("Testing123");
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
recvd = false;
});
it("should have a constraint on the subscription", function() {
assert(postal.configuration.bus.subscriptions.MyChannel.MyTopic[0].constraints.length).equals(3);
});
it("should not have invoked the onHandled callback", function() {
it("should not have invoked the callback", function() {
assert(recvd).isFalse();
});
});
@ -272,12 +253,50 @@ QUnit.specify("postal.js", function(){
channel.publish("Testing123");
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
});
it("should have called obj.increment", function() {
assert(count).equals(1);
});
});
describe("When subscribing with defer", function(){
var results = [];
before(function(){
channel = postal.channel({ channel: "MyChannel", topic: "MyTopic" });
subscription = channel.subscribe(function(data) { results.push("second"); }).defer();
});
after(function(){
postal.reset();
});
it("should have called obj.increment", function() {
channel.publish("Testing123");
results.push("first");
wait(500, function(){
assert(results[0]).equals("first");
assert(results[1]).equals("second");
});
});
});
describe("When subscribing with delay", function(){
var results = [];
before(function(){
channel = postal.channel({ channel: "MyChannel", topic: "MyTopic" });
subscription = channel.subscribe(function(data) { results.push("second"); }).withDelay(500);
});
after(function(){
postal.reset();
});
it("should have called obj.increment", function() {
channel.publish("Testing123");
results.push("first");
wait(1000, function(){
assert(results[0]).equals("first");
assert(results[1]).equals("second");
});
});
});
// TODO: Add test for debounce
// TODO: Add test for throttle
describe("When subscribing with a hierarchical binding, no wildcards", function(){
var count = 0, channelB, channelC;
before(function(){
@ -290,7 +309,7 @@ QUnit.specify("postal.js", function(){
channelC.publish("Testing123");
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
count = 0;
});
it("should have invoked subscription callback only once", function() {
@ -310,7 +329,7 @@ QUnit.specify("postal.js", function(){
channelD.publish({channel: "MyChannel", topic: "MyTopic.MiddleTopic.SubTopic.YetAnother", data: "Testing123"});
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
count = 0;
});
it("should have invoked subscription callback only once", function() {
@ -331,7 +350,7 @@ QUnit.specify("postal.js", function(){
channelD.publish("Testing123");
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
count = 0;
});
it("should have invoked subscription callback twice", function() {
@ -354,7 +373,7 @@ QUnit.specify("postal.js", function(){
channelE.publish({channel: "MyChannel", topic: "OtherTopic.MiddleTopic.SubTopic.YetAnother", data: "Testing123"});
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
count = 0;
});
it("should have invoked subscription callback twice", function() {
@ -372,7 +391,7 @@ QUnit.specify("postal.js", function(){
postal.publish("MyChannel", "MyTopic", "Testing123");
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
});
it("subscription callback should be invoked once", function(){
assert(msgReceivedCnt).equals(1);
@ -391,7 +410,7 @@ QUnit.specify("postal.js", function(){
sub = postal.configuration.bus.subscriptions.MyChannel.MyTopic[0];
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
});
it("should create an channel called MyChannel", function(){
assert(postal.configuration.bus.subscriptions["MyChannel"] !== undefined).isTrue();
@ -435,7 +454,7 @@ QUnit.specify("postal.js", function(){
postal.publish({ topic: "Oh.Hai.There", data: "I'm in yer bus, tappin' yer subscriptionz..."});
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
});
it("wire tap should have been invoked only once", function(){
assert(wireTapData.length).equals(1);
@ -466,7 +485,7 @@ QUnit.specify("postal.js", function(){
postal.publish("sourceChannel", "Oh.Hai.There", { data: "I'm in yer bus, linkin' to yer subscriptionz..."});
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
});
it("linked subscription should only have been invoked once", function(){
assert(destData.length).equals(1);
@ -495,7 +514,7 @@ QUnit.specify("postal.js", function(){
postal.publish("sourceChannel", "Oh.Hai.There", { data: "I'm in yer bus, linkin' to yer subscriptionz..."});
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
});
it("linked subscription should only have been invoked once", function(){
assert(destData.length).equals(1);
@ -524,7 +543,7 @@ QUnit.specify("postal.js", function(){
postal.publish("sourceChannel", "Oh.Hai.There", { data: "I'm in yer bus, linkin' to yer subscriptionz..."});
});
after(function(){
postal.configuration.bus.subscriptions = {};
postal.reset();
});
it("linked subscription should only have been invoked once", function(){
assert(destData.length).equals(1);

View file

@ -69,17 +69,10 @@ QUnit.specify("postal.js", function(){
});
});
describe("When setting whenHandledThenExecute", function(){
var sDeff = new SubscriptionDefinition("TestChannel", "TestTopic", NO_OP).whenHandledThenExecute(function() { });
it("Should add an onHandled callback", function() {
assert(typeof sDeff.onHandled).equals("function");
});
it("Should not equal NO_OP", function() {
assert(sDeff.onHandled).isNotEqualTo(NO_OP);
});
});
//TODO: need to determine best way to add tests for defer, debounce, throttle, delay & disposeAfter
// TODO: Add test for defer
// TODO: Add test for debounce
// TODO: Add test for throttle
// TODO: Add test for delay
// TODO: Add test for disposeAfter
});
});

View file

@ -87,5 +87,16 @@ var postal = {
});
});
return result;
},
reset: function() {
// we check first in case a custom bus or resolver
// doesn't expose subscriptions or a resolver cache
if(postal.configuration.bus.subscriptions) {
postal.configuration.bus.subscriptions = {};
}
if(postal.configuration.resolver.cache) {
postal.configuration.resolver.cache = {};
}
}
};

View file

@ -67,14 +67,6 @@ SubscriptionDefinition.prototype = {
return this;
},
whenHandledThenExecute: function(callback) {
if(! _.isFunction(callback)) {
throw "Value provided to 'whenHandledThenExecute' must be a function";
}
this.onHandled = callback;
return this;
},
withConstraint: function(predicate) {
if(! _.isFunction(predicate)) {
throw "Predicate constraint must be a function";
@ -111,7 +103,9 @@ SubscriptionDefinition.prototype = {
}
var fn = this.callback;
this.callback = function(data) {
setTimeout(fn, milliseconds, data);
setTimeout(function(){
fn(data);
}, milliseconds);
};
return this;
},