You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by ag...@apache.org on 2012/09/18 17:42:52 UTC
js commit: [all] Separate Channel into sticky and non-sticky versions
Updated Branches:
refs/heads/master d30179b30 -> c6917ee59
[all] Separate Channel into sticky and non-sticky versions
- This is a retry of aa15ac60d6cbabae83f619880a3a6e8be14817ce.
- It fixes forgetting to update channel.fired -> channel.state == 2 in iOS/exec.js
- It removes an extra ) in android/platform.js
Project: http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/commit/c6917ee5
Tree: http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/tree/c6917ee5
Diff: http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/diff/c6917ee5
Branch: refs/heads/master
Commit: c6917ee59031ec2cb11dc46d73f3e7b7a8a1cd34
Parents: d30179b
Author: Andrew Grieve <ag...@chromium.org>
Authored: Tue Sep 18 11:39:58 2012 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Tue Sep 18 11:39:58 2012 -0400
----------------------------------------------------------------------
lib/android/platform.js | 2 +-
lib/bada/plugin/bada/device.js | 2 +-
lib/common/channel.js | 166 ++++++++---------
lib/common/plugin/device.js | 2 +-
lib/common/plugin/network.js | 2 +-
lib/cordova.js | 13 +-
lib/ios/exec.js | 2 +-
lib/scripts/bootstrap.js | 2 +-
lib/tizen/plugin/tizen/Device.js | 2 +-
test/test.channel.js | 327 +++++++++++++++++++--------------
10 files changed, 280 insertions(+), 240 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/c6917ee5/lib/android/platform.js
----------------------------------------------------------------------
diff --git a/lib/android/platform.js b/lib/android/platform.js
index af5ab4f..b2cde22 100644
--- a/lib/android/platform.js
+++ b/lib/android/platform.js
@@ -32,7 +32,7 @@ module.exports = {
// If we just attached the first handler or detached the last handler,
// let native know we need to override the back button.
exec(null, null, "App", "overrideBackbutton", [this.numHandlers == 1]);
- });
+ };
// Add hardware MENU and SEARCH button handlers
cordova.addDocumentEventHandler('menubutton');
http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/c6917ee5/lib/bada/plugin/bada/device.js
----------------------------------------------------------------------
diff --git a/lib/bada/plugin/bada/device.js b/lib/bada/plugin/bada/device.js
index 3d34166..b1da422 100644
--- a/lib/bada/plugin/bada/device.js
+++ b/lib/bada/plugin/bada/device.js
@@ -34,7 +34,7 @@ function Device() {
var me = this;
- channel.onCordovaReady.subscribeOnce(function() {
+ channel.onCordovaReady.subscribe(function() {
me.getDeviceInfo(function (device) {
me.platform = device.platform;
me.version = device.version;
http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/c6917ee5/lib/common/channel.js
----------------------------------------------------------------------
diff --git a/lib/common/channel.js b/lib/common/channel.js
index 21f0e6e..1ad7227 100644
--- a/lib/common/channel.js
+++ b/lib/common/channel.js
@@ -25,19 +25,22 @@ var utils = require('cordova/utils'),
/**
* Custom pub-sub "channel" that can have functions subscribed to it
* This object is used to define and control firing of events for
- * cordova initialization.
+ * cordova initialization, as well as for custom events thereafter.
*
* The order of events during page load and Cordova startup is as follows:
*
- * onDOMContentLoaded Internal event that is received when the web page is loaded and parsed.
- * onNativeReady Internal event that indicates the Cordova native side is ready.
- * onCordovaReady Internal event fired when all Cordova JavaScript objects have been created.
- * onCordovaInfoReady Internal event fired when device properties are available.
- * onCordovaConnectionReady Internal event fired when the connection property has been set.
- * onDeviceReady User event fired to indicate that Cordova is ready
- * onResume User event fired to indicate a start/resume lifecycle event
- * onPause User event fired to indicate a pause lifecycle event
- * onDestroy Internal event fired when app is being destroyed (User should use window.onunload event, not this one).
+ * onDOMContentLoaded* Internal event that is received when the web page is loaded and parsed.
+ * onNativeReady* Internal event that indicates the Cordova native side is ready.
+ * onCordovaReady* Internal event fired when all Cordova JavaScript objects have been created.
+ * onCordovaInfoReady* Internal event fired when device properties are available.
+ * onCordovaConnectionReady* Internal event fired when the connection property has been set.
+ * onDeviceReady* User event fired to indicate that Cordova is ready
+ * onResume User event fired to indicate a start/resume lifecycle event
+ * onPause User event fired to indicate a pause lifecycle event
+ * onDestroy* Internal event fired when app is being destroyed (User should use window.onunload event, not this one).
+ *
+ * The events marked with an * are sticky. Once they have fired, they will stay in the fired state.
+ * All listeners that subscribe after the event is fired will be executed right away.
*
* The only Cordova events that user code should register for are:
* deviceready Cordova native code is initialized and Cordova APIs can be called from JavaScript
@@ -60,12 +63,16 @@ var utils = require('cordova/utils'),
* @constructor
* @param type String the channel name
*/
-var Channel = function(type) {
+var Channel = function(type, sticky) {
this.type = type;
+ // Map of guid -> function.
this.handlers = {};
+ // 0 = Non-sticky, 1 = Sticky non-fired, 2 = Sticky fired.
+ this.state = sticky ? 1 : 0;
+ // Used in sticky mode to remember args passed to fire().
+ this.fireArgs = null;
+ // Used by onHasSubscribersChange to know if there are any listeners.
this.numHandlers = 0;
- this.fired = false;
- this.enabled = true;
// Function that is called when the first listener is subscribed, or when
// the last listener is unsubscribed.
this.onHasSubscribersChange = null;
@@ -73,22 +80,27 @@ var Channel = function(type) {
channel = {
/**
* Calls the provided function only after all of the channels specified
- * have been fired.
+ * have been fired. All channels must be sticky channels.
*/
- join: function (h, c) {
- var i = c.length;
- var len = i;
- var f = function() {
- if (!(--i)) h();
- };
+ join: function(h, c) {
+ var len = c.length,
+ i = len,
+ f = function() {
+ if (!(--i)) h();
+ };
for (var j=0; j<len; j++) {
- !c[j].fired?c[j].subscribeOnce(f):i--;
+ if (c[j].state == 0) {
+ throw Error('Can only use join with sticky channels.')
+ }
+ c[j].subscribe(f);
}
- if (!i) h();
+ if (!len) h();
+ },
+ create: function(type) {
+ return channel[type] = new Channel(type, false);
},
- create: function (type, opts) {
- channel[type] = new Channel(type, opts);
- return channel[type];
+ createSticky: function(type) {
+ return channel[type] = new Channel(type, true);
},
/**
@@ -106,13 +118,7 @@ var Channel = function(type) {
*/
waitForInitialization: function(feature) {
if (feature) {
- var c = null;
- if (this[feature]) {
- c = this[feature];
- }
- else {
- c = this.create(feature);
- }
+ var c = channel[feature] || this.createSticky(feature);
this.deviceReadyChannelsMap[feature] = c;
this.deviceReadyChannelsArray.push(c);
}
@@ -132,7 +138,7 @@ var Channel = function(type) {
};
function forceFunction(f) {
- if (f === null || f === undefined || typeof f != 'function') throw "Function required as first argument!";
+ if (typeof f != 'function') throw "Function required as first argument!";
}
/**
@@ -142,67 +148,46 @@ function forceFunction(f) {
* and a guid that can be used to stop subscribing to the channel.
* Returns the guid.
*/
-Channel.prototype.subscribe = function(f, c, g) {
+Channel.prototype.subscribe = function(f, c) {
// need a function to call
forceFunction(f);
+ if (this.state == 2) {
+ f.apply(c || this, this.fireArgs);
+ return;
+ }
- var func = f;
+ var func = f,
+ guid = f.observer_guid;
if (typeof c == "object") { func = utils.close(c, f); }
- g = g || func.observer_guid || f.observer_guid;
- if (!g) {
+ if (!guid) {
// first time any channel has seen this subscriber
- g = nextGuid++;
+ guid = '' + nextGuid++;
}
- func.observer_guid = g;
- f.observer_guid = g;
+ func.observer_guid = guid;
+ f.observer_guid = guid;
// Don't add the same handler more than once.
- if (!this.handlers[g]) {
- this.handlers[g] = func;
+ if (!this.handlers[guid]) {
+ this.handlers[guid] = func;
this.numHandlers++;
if (this.numHandlers == 1) {
this.onHasSubscribersChange && this.onHasSubscribersChange();
}
- if (this.fired) func.apply(this, this.fireArgs);
}
- return g;
-};
-
-/**
- * Like subscribe but the function is only called once and then it
- * auto-unsubscribes itself.
- */
-Channel.prototype.subscribeOnce = function(f, c) {
- // need a function to call
- forceFunction(f);
-
- var g = null;
- var _this = this;
- if (this.fired) {
- f.apply(c || null, this.fireArgs);
- } else {
- g = this.subscribe(function() {
- _this.unsubscribe(g);
- f.apply(c || null, arguments);
- });
- f.observer_guid = g;
- }
- return g;
};
/**
* Unsubscribes the function with the given guid from the channel.
*/
-Channel.prototype.unsubscribe = function(g) {
+Channel.prototype.unsubscribe = function(f) {
// need a function to unsubscribe
- if (g === null || g === undefined) { throw "You must pass _something_ into Channel.unsubscribe"; }
+ forceFunction(f);
- if (typeof g == 'function') { g = g.observer_guid; }
- var handler = this.handlers[g];
+ var guid = f.observer_guid,
+ handler = this.handlers[guid];
if (handler) {
- if (handler.observer_guid) handler.observer_guid=null;
- delete this.handlers[g];
+ delete this.handlers[guid];
this.numHandlers--;
if (this.numHandlers == 0) {
this.onHasSubscribersChange && this.onHasSubscribersChange();
@@ -214,10 +199,14 @@ Channel.prototype.unsubscribe = function(g) {
* Calls all functions subscribed to this channel.
*/
Channel.prototype.fire = function(e) {
- if (this.enabled) {
- var fail = false;
- this.fired = true;
- this.fireArgs = arguments;
+ var fail = false,
+ fireArgs = Array.prototype.slice.call(arguments);
+ // Apply stickiness.
+ if (this.state == 1) {
+ this.state = 2;
+ this.fireArgs = fireArgs;
+ }
+ if (this.numHandlers) {
// Copy the values first so that it is safe to modify it from within
// callbacks.
var toCall = [];
@@ -225,33 +214,36 @@ Channel.prototype.fire = function(e) {
toCall.push(this.handlers[item]);
}
for (var i = 0; i < toCall.length; ++i) {
- var rv = (toCall[i].apply(this, arguments)===false);
- fail = fail || rv;
+ toCall[i].apply(this, fireArgs);
+ }
+ if (this.state == 2 && this.numHandlers) {
+ this.numHandlers = 0;
+ this.handlers = {};
+ this.onHasSubscribersChange && this.onHasSubscribersChange();
}
- return !fail;
}
- return true;
};
+
// defining them here so they are ready super fast!
// DOM event that is received when the web page is loaded and parsed.
-channel.create('onDOMContentLoaded');
+channel.createSticky('onDOMContentLoaded');
// Event to indicate the Cordova native side is ready.
-channel.create('onNativeReady');
+channel.createSticky('onNativeReady');
// Event to indicate that all Cordova JavaScript objects have been created
// and it's time to run plugin constructors.
-channel.create('onCordovaReady');
+channel.createSticky('onCordovaReady');
// Event to indicate that device properties are available
-channel.create('onCordovaInfoReady');
+channel.createSticky('onCordovaInfoReady');
// Event to indicate that the connection property has been set.
-channel.create('onCordovaConnectionReady');
+channel.createSticky('onCordovaConnectionReady');
// Event to indicate that Cordova is ready
-channel.create('onDeviceReady');
+channel.createSticky('onDeviceReady');
// Event to indicate a resume lifecycle event
channel.create('onResume');
@@ -260,7 +252,7 @@ channel.create('onResume');
channel.create('onPause');
// Event to indicate a destroy lifecycle event
-channel.create('onDestroy');
+channel.createSticky('onDestroy');
// Channels that must fire before "deviceready" is fired.
channel.waitForInitialization('onCordovaReady');
http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/c6917ee5/lib/common/plugin/device.js
----------------------------------------------------------------------
diff --git a/lib/common/plugin/device.js b/lib/common/plugin/device.js
index 3477ff8..905bfe1 100644
--- a/lib/common/plugin/device.js
+++ b/lib/common/plugin/device.js
@@ -41,7 +41,7 @@ function Device() {
var me = this;
- channel.onCordovaReady.subscribeOnce(function() {
+ channel.onCordovaReady.subscribe(function() {
me.getInfo(function(info) {
me.available = true;
me.platform = info.platform;
http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/c6917ee5/lib/common/plugin/network.js
----------------------------------------------------------------------
diff --git a/lib/common/plugin/network.js b/lib/common/plugin/network.js
index 637ea89..adaba5a 100644
--- a/lib/common/plugin/network.js
+++ b/lib/common/plugin/network.js
@@ -41,7 +41,7 @@ var NetworkConnection = function () {
var me = this;
- channel.onCordovaReady.subscribeOnce(function() {
+ channel.onCordovaReady.subscribe(function() {
me.getInfo(function (info) {
me.type = info;
if (info === "none") {
http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/c6917ee5/lib/cordova.js
----------------------------------------------------------------------
diff --git a/lib/cordova.js b/lib/cordova.js
index e1650fa..fc4a744 100644
--- a/lib/cordova.js
+++ b/lib/cordova.js
@@ -50,11 +50,7 @@ var documentEventHandlers = {},
document.addEventListener = function(evt, handler, capture) {
var e = evt.toLowerCase();
if (typeof documentEventHandlers[e] != 'undefined') {
- if (evt === 'deviceready') {
- documentEventHandlers[e].subscribeOnce(handler);
- } else {
- documentEventHandlers[e].subscribe(handler);
- }
+ documentEventHandlers[e].subscribe(handler);
} else {
m_document_addEventListener.call(document, evt, handler, capture);
}
@@ -117,6 +113,9 @@ var cordova = {
addWindowEventHandler:function(event) {
return (windowEventHandlers[event] = channel.create(event));
},
+ addStickyDocumentEventHandler:function(event) {
+ return (documentEventHandlers[event] = channel.createSticky(event));
+ },
addDocumentEventHandler:function(event) {
return (documentEventHandlers[event] = channel.create(event));
},
@@ -234,7 +233,7 @@ var cordova = {
}
},
addConstructor: function(func) {
- channel.onCordovaReady.subscribeOnce(function() {
+ channel.onCordovaReady.subscribe(function() {
try {
func();
} catch(e) {
@@ -247,6 +246,6 @@ var cordova = {
// Register pause, resume and deviceready channels as events on document.
channel.onPause = cordova.addDocumentEventHandler('pause');
channel.onResume = cordova.addDocumentEventHandler('resume');
-channel.onDeviceReady = cordova.addDocumentEventHandler('deviceready');
+channel.onDeviceReady = cordova.addStickyDocumentEventHandler('deviceready');
module.exports = cordova;
http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/c6917ee5/lib/ios/exec.js
----------------------------------------------------------------------
diff --git a/lib/ios/exec.js b/lib/ios/exec.js
index 95b49e8..5d91540 100644
--- a/lib/ios/exec.js
+++ b/lib/ios/exec.js
@@ -65,7 +65,7 @@ function shouldBundleCommandJson() {
}
function iOSExec() {
- if (!channel.onCordovaReady.fired) {
+ if (channel.onCordovaReady.state != 2) {
utils.alert("ERROR: Attempting to call cordova.exec()" +
" before 'deviceready'. Ignoring.");
return;
http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/c6917ee5/lib/scripts/bootstrap.js
----------------------------------------------------------------------
diff --git a/lib/scripts/bootstrap.js b/lib/scripts/bootstrap.js
index 2bf8075..6aa08af 100644
--- a/lib/scripts/bootstrap.js
+++ b/lib/scripts/bootstrap.js
@@ -69,7 +69,7 @@
};
// boot up once native side is ready
- channel.onNativeReady.subscribeOnce(_self.boot);
+ channel.onNativeReady.subscribe(_self.boot);
// _nativeReady is global variable that the native side can set
// to signify that the native code is ready. It is a global since
http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/c6917ee5/lib/tizen/plugin/tizen/Device.js
----------------------------------------------------------------------
diff --git a/lib/tizen/plugin/tizen/Device.js b/lib/tizen/plugin/tizen/Device.js
index 908f05d..c5532fb 100644
--- a/lib/tizen/plugin/tizen/Device.js
+++ b/lib/tizen/plugin/tizen/Device.js
@@ -45,7 +45,7 @@ function Device() {
console.log("error initializing cordova: " + error);
}
- channel.onCordovaReady.subscribeOnce(function() {
+ channel.onCordovaReady.subscribe(function() {
me.getDeviceInfo(onSuccessCallback, onErrorCallback);
});
}
http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/c6917ee5/test/test.channel.js
----------------------------------------------------------------------
diff --git a/test/test.channel.js b/test/test.channel.js
index e71f993..cd14d38 100644
--- a/test/test.channel.js
+++ b/test/test.channel.js
@@ -21,266 +21,315 @@
describe("channel", function () {
var channel = require('cordova/channel'),
- c;
-
+ multiChannel,
+ stickyChannel;
+
+ function callCount(spy) {
+ return spy.argsForCall.length;
+ }
+ function expectCallCount(spy, count) {
+ expect(callCount(spy)).toEqual(count);
+ }
beforeEach(function() {
- c = channel.create('masterexploder');
+ multiChannel = channel.create('multiChannel');
+ stickyChannel = channel.createSticky('stickyChannel');
});
describe("subscribe method", function() {
it("should throw an exception if no function is provided", function() {
expect(function() {
- c.subscribe();
+ multiChannel.subscribe();
}).toThrow();
expect(function() {
- c.subscribe(null);
+ multiChannel.subscribe(null);
}).toThrow();
expect(function() {
- c.subscribe(undefined);
+ multiChannel.subscribe(undefined);
}).toThrow();
expect(function() {
- c.subscribe({apply:function(){},call:function(){}});
+ multiChannel.subscribe({apply:function(){},call:function(){}});
}).toThrow();
});
it("should not change number of handlers if no function is provided", function() {
- var initialLength = c.numHandlers;
+ var initialLength = multiChannel.numHandlers;
try {
- c.subscribe();
+ multiChannel.subscribe();
} catch(e) {}
- expect(c.numHandlers).toEqual(initialLength);
+ expect(multiChannel.numHandlers).toEqual(initialLength);
try {
- c.subscribe(null);
+ multiChannel.subscribe(null);
} catch(e) {}
- expect(c.numHandlers).toEqual(initialLength);
+ expect(multiChannel.numHandlers).toEqual(initialLength);
});
it("should not change number of handlers when subscribing same function multiple times", function() {
- var initialLength = c.numHandlers;
- var handler = function(){};
-
- c.subscribe(handler);
- c.subscribe(handler);
- c.subscribe(handler);
-
- expect(c.numHandlers).toEqual(initialLength+1);
- });
- it("should be able to use the same function with multiple channels.", function() {
- var c2 = channel.create('jables');
var handler = function(){};
- c.subscribe(handler);
- c2.subscribe(handler);
+ multiChannel.subscribe(handler);
+ multiChannel.subscribe(handler);
+ stickyChannel.subscribe(handler);
+ stickyChannel.subscribe(handler);
- expect(c.numHandlers).toEqual(1);
- expect(c2.numHandlers).toEqual(1);
+ expect(multiChannel.numHandlers).toEqual(1);
+ expect(stickyChannel.numHandlers).toEqual(1);
});
});
describe("unsubscribe method", function() {
it("should throw an exception if passed in null or undefined", function() {
expect(function() {
- c.unsubscribe();
+ multiChannel.unsubscribe();
}).toThrow();
expect(function() {
- c.unsubscribe(null);
+ multiChannel.unsubscribe(null);
}).toThrow();
});
it("should not decrement numHandlers if unsubscribing something that does not exist", function() {
- var initialLength = c.numHandlers;
- c.unsubscribe('blah');
- expect(c.numHandlers).toEqual(initialLength);
- c.unsubscribe(2);
- expect(c.numHandlers).toEqual(initialLength);
- c.unsubscribe({balls:false});
- expect(c.numHandlers).toEqual(initialLength);
+ multiChannel.subscribe(function() {});
+ multiChannel.unsubscribe(function() {});
+ expect(multiChannel.numHandlers).toEqual(1);
});
it("should change the handlers length appropriately", function() {
var firstHandler = function() {};
var secondHandler = function() {};
var thirdHandler = function() {};
- c.subscribe(firstHandler);
- c.subscribe(secondHandler);
- c.subscribe(thirdHandler);
-
- var initialLength = c.numHandlers;
+ multiChannel.subscribe(firstHandler);
+ multiChannel.subscribe(secondHandler);
+ multiChannel.subscribe(thirdHandler);
+ expect(multiChannel.numHandlers).toEqual(3);
- c.unsubscribe(thirdHandler);
+ multiChannel.unsubscribe(thirdHandler);
+ expect(multiChannel.numHandlers).toEqual(2);
- expect(c.numHandlers).toEqual(initialLength - 1);
+ multiChannel.unsubscribe(firstHandler);
+ multiChannel.unsubscribe(secondHandler);
- c.unsubscribe(firstHandler);
- c.unsubscribe(secondHandler);
-
- expect(c.numHandlers).toEqual(0);
+ expect(multiChannel.numHandlers).toEqual(0);
});
it("should not decrement handlers length more than once if unsubing a single handler", function() {
var firstHandler = function(){};
- c.subscribe(firstHandler);
+ multiChannel.subscribe(firstHandler);
- expect(c.numHandlers).toEqual(1);
+ expect(multiChannel.numHandlers).toEqual(1);
- c.unsubscribe(firstHandler);
- c.unsubscribe(firstHandler);
- c.unsubscribe(firstHandler);
- c.unsubscribe(firstHandler);
+ multiChannel.unsubscribe(firstHandler);
+ multiChannel.unsubscribe(firstHandler);
+ multiChannel.unsubscribe(firstHandler);
+ multiChannel.unsubscribe(firstHandler);
- expect(c.numHandlers).toEqual(0);
+ expect(multiChannel.numHandlers).toEqual(0);
});
it("should not unregister a function registered with a different handler", function() {
var cHandler = function(){};
var c2Handler = function(){};
var c2 = channel.create('jables');
- c.subscribe(cHandler);
+ multiChannel.subscribe(cHandler);
c2.subscribe(c2Handler);
- expect(c.numHandlers).toEqual(1);
+ expect(multiChannel.numHandlers).toEqual(1);
expect(c2.numHandlers).toEqual(1);
- c.unsubscribe(c2Handler);
+ multiChannel.unsubscribe(c2Handler);
c2.unsubscribe(cHandler);
- expect(c.numHandlers).toEqual(1);
+ expect(multiChannel.numHandlers).toEqual(1);
expect(c2.numHandlers).toEqual(1);
});
- it("should be able to unsubscribe a subscribeOnce.", function() {
- var handler = function(){};
- c.subscribeOnce(handler);
-
- expect(c.numHandlers).toEqual(1);
-
- c.unsubscribe(handler);
-
- expect(c.numHandlers).toEqual(0);
- });
});
- describe("fire method", function() {
+ function commonFireTests(multi) {
it("should fire all subscribed handlers", function() {
+ var testChannel = multi ? multiChannel : stickyChannel;
var handler = jasmine.createSpy();
var anotherOne = jasmine.createSpy();
- c.subscribe(handler);
- c.subscribe(anotherOne);
+ testChannel.subscribe(handler);
+ testChannel.subscribe(anotherOne);
+
+ testChannel.fire();
- c.fire();
+ expectCallCount(handler, 1);
+ expectCallCount(anotherOne, 1);
+ });
+ it("should pass params to handlers", function() {
+ var testChannel = multi ? multiChannel : stickyChannel;
+ var handler = jasmine.createSpy();
+
+ testChannel.subscribe(handler);
- expect(handler).toHaveBeenCalled();
- expect(anotherOne).toHaveBeenCalled();
+ testChannel.fire(1, 2, 3);
+ expect(handler.argsForCall[0]).toEqual({0:1, 1:2, 2:3});
});
it("should not fire a handler that was unsubscribed", function() {
+ var testChannel = multi ? multiChannel : stickyChannel;
var handler = jasmine.createSpy();
var anotherOne = jasmine.createSpy();
- c.subscribe(handler);
- c.subscribe(anotherOne);
- c.unsubscribe(handler);
+ testChannel.subscribe(handler);
+ testChannel.subscribe(anotherOne);
+ testChannel.unsubscribe(handler);
- c.fire();
+ testChannel.fire();
- expect(handler).not.toHaveBeenCalled();
- expect(anotherOne).toHaveBeenCalled();
+ expectCallCount(handler, 0);
+ expectCallCount(anotherOne, 1);
});
it("should not fire a handler more than once if it was subscribed more than once", function() {
- var count = 0;
- var handler = jasmine.createSpy().andCallFake(function() { count++; });
+ var testChannel = multi ? multiChannel : stickyChannel;
+ var handler = jasmine.createSpy();
- c.subscribe(handler);
- c.subscribe(handler);
- c.subscribe(handler);
+ testChannel.subscribe(handler);
+ testChannel.subscribe(handler);
+ testChannel.subscribe(handler);
- c.fire();
+ testChannel.fire();
- expect(handler).toHaveBeenCalled();
- expect(count).toEqual(1);
+ expectCallCount(handler, 1);
});
it("handler should be called when subscribed, removed, and subscribed again", function() {
- var count = 0;
- var handler = jasmine.createSpy().andCallFake(function() { count++; });
-
- c.subscribe(handler);
- c.unsubscribe(handler);
- c.subscribe(handler);
+ var testChannel = multi ? multiChannel : stickyChannel;
+ var handler = jasmine.createSpy();
- c.fire();
+ testChannel.subscribe(handler);
+ testChannel.unsubscribe(handler);
+ testChannel.subscribe(handler);
- expect(handler).toHaveBeenCalled();
- expect(count).toEqual(1);
+ testChannel.fire();
+ expectCallCount(handler, 1);
+ });
+ it("should not prevent a callback from firing when it is removed during firing.", function() {
+ var testChannel = multi ? multiChannel : stickyChannel;
+ var handler = jasmine.createSpy().andCallFake(function() { testChannel.unsubscribe(handler2); });
+ var handler2 = jasmine.createSpy();
+ testChannel.subscribe(handler);
+ testChannel.subscribe(handler2);
+ testChannel.fire();
+ expectCallCount(handler, 1);
+ expectCallCount(handler2, 1);
});
+ }
+ describe("fire method for sticky channels", function() {
+ commonFireTests(false);
it("should instantly trigger the callback if the event has already been fired", function () {
- var chan = channel.create("foo"),
- before = jasmine.createSpy('before'),
+ var before = jasmine.createSpy('before'),
after = jasmine.createSpy('after');
- chan.subscribe(before);
- chan.fire();
- chan.subscribe(after);
+ stickyChannel.subscribe(before);
+ stickyChannel.fire(1, 2, 3);
+ stickyChannel.subscribe(after);
- expect(before).toHaveBeenCalled();
- expect(after).toHaveBeenCalled();
+ expectCallCount(before, 1);
+ expectCallCount(after, 1);
+ expect(after.argsForCall[0]).toEqual({0:1, 1:2, 2:3});
});
it("should instantly trigger the callback if the event is currently being fired.", function () {
- var handler1 = jasmine.createSpy().andCallFake(function() { c.subscribe(handler2); }),
+ var handler1 = jasmine.createSpy().andCallFake(function() { stickyChannel.subscribe(handler2); }),
handler2 = jasmine.createSpy().andCallFake(function(arg1) { expect(arg1).toEqual('foo');});
- c.subscribe(handler1);
- c.fire('foo');
+ stickyChannel.subscribe(handler1);
+ stickyChannel.fire('foo');
- expect(handler2).toHaveBeenCalled();
+ expectCallCount(handler2, 1);
+ });
+ it("should unregister all handlers after being fired.", function() {
+ var handler = jasmine.createSpy();
+ stickyChannel.subscribe(handler);
+ stickyChannel.fire();
+ stickyChannel.fire();
+ expectCallCount(handler, 1);
});
});
- describe("subscribeOnce method", function() {
- it("should be unregistered after being fired.", function() {
- var count = 0;
- var handler = jasmine.createSpy().andCallFake(function() { count++; });
- c.subscribeOnce(handler);
- c.fire();
- c.fire();
- expect(count).toEqual(1);
+ describe("fire method for multi channels", function() {
+ commonFireTests(true);
+ it("should not trigger the callback if the event has already been fired", function () {
+ var before = jasmine.createSpy('before'),
+ after = jasmine.createSpy('after');
+
+ multiChannel.subscribe(before);
+ multiChannel.fire();
+ multiChannel.subscribe(after);
+
+ expectCallCount(before, 1);
+ expectCallCount(after, 0);
});
- it("should be safe to add listeners from within callback.", function() {
- var count = 0;
- var handler = jasmine.createSpy().andCallFake(function() { count++; c.subscribeOnce(handler2); });
- var handler2 = jasmine.createSpy().andCallFake(function() { count++; });
- c.subscribeOnce(handler);
- c.fire();
- expect(count).toEqual(2);
+ it("should not trigger the callback if the event is currently being fired.", function () {
+ var handler1 = jasmine.createSpy().andCallFake(function() { multiChannel.subscribe(handler2); }),
+ handler2 = jasmine.createSpy();
+
+ multiChannel.subscribe(handler1);
+ multiChannel.fire();
+ multiChannel.fire();
+
+ expectCallCount(handler1, 2);
+ expectCallCount(handler2, 1);
});
- it("should not prevent a callback from firing when it is removed during firing.", function() {
- var count = 0;
- var handler = jasmine.createSpy().andCallFake(function() { count++; c.unsubscribe(handler2); });
- var handler2 = jasmine.createSpy().andCallFake(function() { count++; });
- c.subscribeOnce(handler);
- c.subscribeOnce(handler2);
- c.fire();
- expect(count).toEqual(2);
+ it("should not unregister handlers after being fired.", function() {
+ var handler = jasmine.createSpy();
+ multiChannel.subscribe(handler);
+ multiChannel.fire();
+ multiChannel.fire();
+ expectCallCount(handler, 2);
+ });
+ });
+ describe("channel.join()", function() {
+ it("should be called when all functions start unfired", function() {
+ var handler = jasmine.createSpy(),
+ stickyChannel2 = channel.createSticky('stickyChannel');
+ channel.join(handler, [stickyChannel, stickyChannel2]);
+ expectCallCount(handler, 0);
+ stickyChannel.fire();
+ expectCallCount(handler, 0);
+ stickyChannel2.fire();
+ expectCallCount(handler, 1);
+ });
+ it("should be called when one functions start fired", function() {
+ var handler = jasmine.createSpy(),
+ stickyChannel2 = channel.createSticky('stickyChannel');
+ stickyChannel.fire();
+ channel.join(handler, [stickyChannel, stickyChannel2]);
+ expectCallCount(handler, 0);
+ stickyChannel2.fire();
+ expectCallCount(handler, 1);
+ });
+ it("should be called when all functions start fired", function() {
+ var handler = jasmine.createSpy(),
+ stickyChannel2 = channel.createSticky('stickyChannel');
+ stickyChannel.fire();
+ stickyChannel2.fire();
+ channel.join(handler, [stickyChannel, stickyChannel2]);
+ expectCallCount(handler, 1);
+ });
+ it("should throw if a channel is not sticky", function() {
+ expect(function() {
+ channel.join(function(){}, [stickyChannel, multiChannel]);
+ }).toThrow();
});
});
describe("onHasSubscribersChange", function() {
it("should be called only when the first subscriber is added and last subscriber is removed.", function() {
var handler = jasmine.createSpy().andCallFake(function() {
- var callCount = handler.argsForCall.length;
- if (callCount == 1) {
+ if (callCount(handler) == 1) {
expect(this.numHandlers).toEqual(1);
} else {
expect(this.numHandlers).toEqual(0);
}
});
- c.onHasSubscribersChange = handler;
+ multiChannel.onHasSubscribersChange = handler;
function foo1() {}
function foo2() {}
- c.subscribe(foo1);
- c.subscribe(foo2);
- c.unsubscribe(foo1);
- c.unsubscribe(foo2);
- expect(handler.argsForCall.length).toEqual(2);
+ multiChannel.subscribe(foo1);
+ multiChannel.subscribe(foo2);
+ multiChannel.unsubscribe(foo1);
+ multiChannel.unsubscribe(foo2);
+ expectCallCount(handler, 2);
});
});
});