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:17:43 UTC

js commit: Revert "Separate Channel into sticky and non-sticky versions."

Updated Branches:
  refs/heads/master 34239a8c1 -> d30179b30


Revert "Separate Channel into sticky and non-sticky versions."

Something is broken with this... Going to revert for now and try again
later.

This reverts commit aa15ac60d6cbabae83f619880a3a6e8be14817ce.


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/d30179b3
Tree: http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/tree/d30179b3
Diff: http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/diff/d30179b3

Branch: refs/heads/master
Commit: d30179b30152b9383a80637e609cf2d785e1aa3e
Parents: 34239a8
Author: Andrew Grieve <ag...@chromium.org>
Authored: Tue Sep 18 11:16:25 2012 -0400
Committer: Andrew Grieve <ag...@chromium.org>
Committed: Tue Sep 18 11:16:25 2012 -0400

----------------------------------------------------------------------
 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/scripts/bootstrap.js         |    2 +-
 lib/tizen/plugin/tizen/Device.js |    2 +-
 test/test.channel.js             |  327 ++++++++++++++-------------------
 8 files changed, 238 insertions(+), 278 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/d30179b3/lib/bada/plugin/bada/device.js
----------------------------------------------------------------------
diff --git a/lib/bada/plugin/bada/device.js b/lib/bada/plugin/bada/device.js
index b1da422..3d34166 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.subscribe(function() {
+    channel.onCordovaReady.subscribeOnce(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/d30179b3/lib/common/channel.js
----------------------------------------------------------------------
diff --git a/lib/common/channel.js b/lib/common/channel.js
index 1ad7227..21f0e6e 100644
--- a/lib/common/channel.js
+++ b/lib/common/channel.js
@@ -25,22 +25,19 @@ 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, as well as for custom events thereafter.
+ * cordova initialization.
  *
  * 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).
- *
- * 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.
+ * 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 only Cordova events that user code should register for are:
  *      deviceready           Cordova native code is initialized and Cordova APIs can be called from JavaScript
@@ -63,16 +60,12 @@ var utils = require('cordova/utils'),
  * @constructor
  * @param type  String the channel name
  */
-var Channel = function(type, sticky) {
+var Channel = function(type) {
     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;
@@ -80,27 +73,22 @@ var Channel = function(type, sticky) {
     channel = {
         /**
          * Calls the provided function only after all of the channels specified
-         * have been fired. All channels must be sticky channels.
+         * have been fired.
          */
-        join: function(h, c) {
-            var len = c.length,
-                i = len,
-                f = function() {
-                    if (!(--i)) h();
-                };
+        join: function (h, c) {
+            var i = c.length;
+            var len = i;
+            var f = function() {
+                if (!(--i)) h();
+            };
             for (var j=0; j<len; j++) {
-                if (c[j].state == 0) {
-                    throw Error('Can only use join with sticky channels.')
-                }
-                c[j].subscribe(f);
+                !c[j].fired?c[j].subscribeOnce(f):i--;
             }
-            if (!len) h();
-        },
-        create: function(type) {
-            return channel[type] = new Channel(type, false);
+            if (!i) h();
         },
-        createSticky: function(type) {
-            return channel[type] = new Channel(type, true);
+        create: function (type, opts) {
+            channel[type] = new Channel(type, opts);
+            return channel[type];
         },
 
         /**
@@ -118,7 +106,13 @@ var Channel = function(type, sticky) {
          */
         waitForInitialization: function(feature) {
             if (feature) {
-                var c = channel[feature] || this.createSticky(feature);
+                var c = null;
+                if (this[feature]) {
+                    c = this[feature];
+                }
+                else {
+                    c = this.create(feature);
+                }
                 this.deviceReadyChannelsMap[feature] = c;
                 this.deviceReadyChannelsArray.push(c);
             }
@@ -138,7 +132,7 @@ var Channel = function(type, sticky) {
     };
 
 function forceFunction(f) {
-    if (typeof f != 'function') throw "Function required as first argument!";
+    if (f === null || f === undefined || typeof f != 'function') throw "Function required as first argument!";
 }
 
 /**
@@ -148,46 +142,67 @@ 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) {
+Channel.prototype.subscribe = function(f, c, g) {
     // need a function to call
     forceFunction(f);
-    if (this.state == 2) {
-        f.apply(c || this, this.fireArgs);
-        return;
-    }
 
-    var func = f,
-        guid = f.observer_guid;
+    var func = f;
     if (typeof c == "object") { func = utils.close(c, f); }
 
-    if (!guid) {
+    g = g || func.observer_guid || f.observer_guid;
+    if (!g) {
         // first time any channel has seen this subscriber
-        guid = '' + nextGuid++;
+        g = nextGuid++;
     }
-    func.observer_guid = guid;
-    f.observer_guid = guid;
+    func.observer_guid = g;
+    f.observer_guid = g;
 
     // Don't add the same handler more than once.
-    if (!this.handlers[guid]) {
-        this.handlers[guid] = func;
+    if (!this.handlers[g]) {
+        this.handlers[g] = 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(f) {
+Channel.prototype.unsubscribe = function(g) {
     // need a function to unsubscribe
-    forceFunction(f);
+    if (g === null || g === undefined) { throw "You must pass _something_ into Channel.unsubscribe"; }
 
-    var guid = f.observer_guid,
-        handler = this.handlers[guid];
+    if (typeof g == 'function') { g = g.observer_guid; }
+    var handler = this.handlers[g];
     if (handler) {
-        delete this.handlers[guid];
+        if (handler.observer_guid) handler.observer_guid=null;
+        delete this.handlers[g];
         this.numHandlers--;
         if (this.numHandlers == 0) {
             this.onHasSubscribersChange && this.onHasSubscribersChange();
@@ -199,14 +214,10 @@ Channel.prototype.unsubscribe = function(f) {
  * Calls all functions subscribed to this channel.
  */
 Channel.prototype.fire = function(e) {
-    var fail = false,
-        fireArgs = Array.prototype.slice.call(arguments);
-    // Apply stickiness.
-    if (this.state == 1) {
-        this.state = 2;
-        this.fireArgs = fireArgs;
-    }
-    if (this.numHandlers) {
+    if (this.enabled) {
+        var fail = false;
+        this.fired = true;
+        this.fireArgs = arguments;
         // Copy the values first so that it is safe to modify it from within
         // callbacks.
         var toCall = [];
@@ -214,36 +225,33 @@ Channel.prototype.fire = function(e) {
             toCall.push(this.handlers[item]);
         }
         for (var i = 0; i < toCall.length; ++i) {
-            toCall[i].apply(this, fireArgs);
-        }
-        if (this.state == 2 && this.numHandlers) {
-            this.numHandlers = 0;
-            this.handlers = {};
-            this.onHasSubscribersChange && this.onHasSubscribersChange();
+            var rv = (toCall[i].apply(this, arguments)===false);
+            fail = fail || rv;
         }
+        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.createSticky('onDOMContentLoaded');
+channel.create('onDOMContentLoaded');
 
 // Event to indicate the Cordova native side is ready.
-channel.createSticky('onNativeReady');
+channel.create('onNativeReady');
 
 // Event to indicate that all Cordova JavaScript objects have been created
 // and it's time to run plugin constructors.
-channel.createSticky('onCordovaReady');
+channel.create('onCordovaReady');
 
 // Event to indicate that device properties are available
-channel.createSticky('onCordovaInfoReady');
+channel.create('onCordovaInfoReady');
 
 // Event to indicate that the connection property has been set.
-channel.createSticky('onCordovaConnectionReady');
+channel.create('onCordovaConnectionReady');
 
 // Event to indicate that Cordova is ready
-channel.createSticky('onDeviceReady');
+channel.create('onDeviceReady');
 
 // Event to indicate a resume lifecycle event
 channel.create('onResume');
@@ -252,7 +260,7 @@ channel.create('onResume');
 channel.create('onPause');
 
 // Event to indicate a destroy lifecycle event
-channel.createSticky('onDestroy');
+channel.create('onDestroy');
 
 // Channels that must fire before "deviceready" is fired.
 channel.waitForInitialization('onCordovaReady');

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/d30179b3/lib/common/plugin/device.js
----------------------------------------------------------------------
diff --git a/lib/common/plugin/device.js b/lib/common/plugin/device.js
index 905bfe1..3477ff8 100644
--- a/lib/common/plugin/device.js
+++ b/lib/common/plugin/device.js
@@ -41,7 +41,7 @@ function Device() {
 
     var me = this;
 
-    channel.onCordovaReady.subscribe(function() {
+    channel.onCordovaReady.subscribeOnce(function() {
         me.getInfo(function(info) {
             me.available = true;
             me.platform = info.platform;

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/d30179b3/lib/common/plugin/network.js
----------------------------------------------------------------------
diff --git a/lib/common/plugin/network.js b/lib/common/plugin/network.js
index adaba5a..637ea89 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.subscribe(function() {
+    channel.onCordovaReady.subscribeOnce(function() {
         me.getInfo(function (info) {
             me.type = info;
             if (info === "none") {

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/d30179b3/lib/cordova.js
----------------------------------------------------------------------
diff --git a/lib/cordova.js b/lib/cordova.js
index fc4a744..e1650fa 100644
--- a/lib/cordova.js
+++ b/lib/cordova.js
@@ -50,7 +50,11 @@ var documentEventHandlers = {},
 document.addEventListener = function(evt, handler, capture) {
     var e = evt.toLowerCase();
     if (typeof documentEventHandlers[e] != 'undefined') {
-        documentEventHandlers[e].subscribe(handler);
+        if (evt === 'deviceready') {
+            documentEventHandlers[e].subscribeOnce(handler);
+        } else {
+            documentEventHandlers[e].subscribe(handler);
+        }
     } else {
         m_document_addEventListener.call(document, evt, handler, capture);
     }
@@ -113,9 +117,6 @@ 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));
     },
@@ -233,7 +234,7 @@ var cordova = {
         }
     },
     addConstructor: function(func) {
-        channel.onCordovaReady.subscribe(function() {
+        channel.onCordovaReady.subscribeOnce(function() {
             try {
                 func();
             } catch(e) {
@@ -246,6 +247,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.addStickyDocumentEventHandler('deviceready');
+channel.onDeviceReady = cordova.addDocumentEventHandler('deviceready');
 
 module.exports = cordova;

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/d30179b3/lib/scripts/bootstrap.js
----------------------------------------------------------------------
diff --git a/lib/scripts/bootstrap.js b/lib/scripts/bootstrap.js
index 6aa08af..2bf8075 100644
--- a/lib/scripts/bootstrap.js
+++ b/lib/scripts/bootstrap.js
@@ -69,7 +69,7 @@
         };
 
     // boot up once native side is ready
-    channel.onNativeReady.subscribe(_self.boot);
+    channel.onNativeReady.subscribeOnce(_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/d30179b3/lib/tizen/plugin/tizen/Device.js
----------------------------------------------------------------------
diff --git a/lib/tizen/plugin/tizen/Device.js b/lib/tizen/plugin/tizen/Device.js
index c5532fb..908f05d 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.subscribe(function() {
+    channel.onCordovaReady.subscribeOnce(function() {
         me.getDeviceInfo(onSuccessCallback, onErrorCallback);
     });
 }

http://git-wip-us.apache.org/repos/asf/incubator-cordova-js/blob/d30179b3/test/test.channel.js
----------------------------------------------------------------------
diff --git a/test/test.channel.js b/test/test.channel.js
index cd14d38..e71f993 100644
--- a/test/test.channel.js
+++ b/test/test.channel.js
@@ -21,315 +21,266 @@
 
 describe("channel", function () {
     var channel = require('cordova/channel'),
-        multiChannel,
-        stickyChannel;
-
-    function callCount(spy) {
-        return spy.argsForCall.length;
-    }
-    function expectCallCount(spy, count) {
-        expect(callCount(spy)).toEqual(count);
-    }
+        c;
+
     beforeEach(function() {
-        multiChannel = channel.create('multiChannel');
-        stickyChannel = channel.createSticky('stickyChannel');
+        c = channel.create('masterexploder');
     });
 
     describe("subscribe method", function() {
         it("should throw an exception if no function is provided", function() {
             expect(function() {
-                multiChannel.subscribe();
+                c.subscribe();
             }).toThrow();
 
             expect(function() {
-                multiChannel.subscribe(null);
+                c.subscribe(null);
             }).toThrow();
 
             expect(function() {
-                multiChannel.subscribe(undefined);
+                c.subscribe(undefined);
             }).toThrow();
 
             expect(function() {
-                multiChannel.subscribe({apply:function(){},call:function(){}});
+                c.subscribe({apply:function(){},call:function(){}});
             }).toThrow();
         });
         it("should not change number of handlers if no function is provided", function() {
-            var initialLength = multiChannel.numHandlers;
+            var initialLength = c.numHandlers;
 
             try {
-                multiChannel.subscribe();
+                c.subscribe();
             } catch(e) {}
 
-            expect(multiChannel.numHandlers).toEqual(initialLength);
+            expect(c.numHandlers).toEqual(initialLength);
 
             try {
-                multiChannel.subscribe(null);
+                c.subscribe(null);
             } catch(e) {}
 
-            expect(multiChannel.numHandlers).toEqual(initialLength);
+            expect(c.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(){};
 
-            multiChannel.subscribe(handler);
-            multiChannel.subscribe(handler);
-            stickyChannel.subscribe(handler);
-            stickyChannel.subscribe(handler);
+            c.subscribe(handler);
+            c2.subscribe(handler);
 
-            expect(multiChannel.numHandlers).toEqual(1);
-            expect(stickyChannel.numHandlers).toEqual(1);
+            expect(c.numHandlers).toEqual(1);
+            expect(c2.numHandlers).toEqual(1);
         });
     });
 
     describe("unsubscribe method", function() {
         it("should throw an exception if passed in null or undefined", function() {
             expect(function() {
-                multiChannel.unsubscribe();
+                c.unsubscribe();
             }).toThrow();
             expect(function() {
-                multiChannel.unsubscribe(null);
+                c.unsubscribe(null);
             }).toThrow();
         });
         it("should not decrement numHandlers if unsubscribing something that does not exist", function() {
-            multiChannel.subscribe(function() {});
-            multiChannel.unsubscribe(function() {});
-            expect(multiChannel.numHandlers).toEqual(1);
+            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);
         });
         it("should change the handlers length appropriately", function() {
             var firstHandler = function() {};
             var secondHandler = function() {};
             var thirdHandler = function() {};
 
-            multiChannel.subscribe(firstHandler);
-            multiChannel.subscribe(secondHandler);
-            multiChannel.subscribe(thirdHandler);
-            expect(multiChannel.numHandlers).toEqual(3);
+            c.subscribe(firstHandler);
+            c.subscribe(secondHandler);
+            c.subscribe(thirdHandler);
+
+            var initialLength = c.numHandlers;
 
-            multiChannel.unsubscribe(thirdHandler);
-            expect(multiChannel.numHandlers).toEqual(2);
+            c.unsubscribe(thirdHandler);
 
-            multiChannel.unsubscribe(firstHandler);
-            multiChannel.unsubscribe(secondHandler);
+            expect(c.numHandlers).toEqual(initialLength - 1);
 
-            expect(multiChannel.numHandlers).toEqual(0);
+            c.unsubscribe(firstHandler);
+            c.unsubscribe(secondHandler);
+
+            expect(c.numHandlers).toEqual(0);
         });
         it("should not decrement handlers length more than once if unsubing a single handler", function() {
             var firstHandler = function(){};
-            multiChannel.subscribe(firstHandler);
+            c.subscribe(firstHandler);
 
-            expect(multiChannel.numHandlers).toEqual(1);
+            expect(c.numHandlers).toEqual(1);
 
-            multiChannel.unsubscribe(firstHandler);
-            multiChannel.unsubscribe(firstHandler);
-            multiChannel.unsubscribe(firstHandler);
-            multiChannel.unsubscribe(firstHandler);
+            c.unsubscribe(firstHandler);
+            c.unsubscribe(firstHandler);
+            c.unsubscribe(firstHandler);
+            c.unsubscribe(firstHandler);
 
-            expect(multiChannel.numHandlers).toEqual(0);
+            expect(c.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');
-            multiChannel.subscribe(cHandler);
+            c.subscribe(cHandler);
             c2.subscribe(c2Handler);
 
-            expect(multiChannel.numHandlers).toEqual(1);
+            expect(c.numHandlers).toEqual(1);
             expect(c2.numHandlers).toEqual(1);
 
-            multiChannel.unsubscribe(c2Handler);
+            c.unsubscribe(c2Handler);
             c2.unsubscribe(cHandler);
 
-            expect(multiChannel.numHandlers).toEqual(1);
+            expect(c.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);
+        });
     });
 
-    function commonFireTests(multi) {
+    describe("fire method", function() {
         it("should fire all subscribed handlers", function() {
-            var testChannel = multi ? multiChannel : stickyChannel;
             var handler = jasmine.createSpy();
             var anotherOne = jasmine.createSpy();
 
-            testChannel.subscribe(handler);
-            testChannel.subscribe(anotherOne);
-
-            testChannel.fire();
+            c.subscribe(handler);
+            c.subscribe(anotherOne);
 
-            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);
+            c.fire();
 
-            testChannel.fire(1, 2, 3);
-            expect(handler.argsForCall[0]).toEqual({0:1, 1:2, 2:3});
+            expect(handler).toHaveBeenCalled();
+            expect(anotherOne).toHaveBeenCalled();
         });
         it("should not fire a handler that was unsubscribed", function() {
-            var testChannel = multi ? multiChannel : stickyChannel;
             var handler = jasmine.createSpy();
             var anotherOne = jasmine.createSpy();
 
-            testChannel.subscribe(handler);
-            testChannel.subscribe(anotherOne);
-            testChannel.unsubscribe(handler);
+            c.subscribe(handler);
+            c.subscribe(anotherOne);
+            c.unsubscribe(handler);
 
-            testChannel.fire();
+            c.fire();
 
-            expectCallCount(handler, 0);
-            expectCallCount(anotherOne, 1);
+            expect(handler).not.toHaveBeenCalled();
+            expect(anotherOne).toHaveBeenCalled();
         });
         it("should not fire a handler more than once if it was subscribed more than once", function() {
-            var testChannel = multi ? multiChannel : stickyChannel;
-            var handler = jasmine.createSpy();
+            var count = 0;
+            var handler = jasmine.createSpy().andCallFake(function() { count++; });
 
-            testChannel.subscribe(handler);
-            testChannel.subscribe(handler);
-            testChannel.subscribe(handler);
+            c.subscribe(handler);
+            c.subscribe(handler);
+            c.subscribe(handler);
 
-            testChannel.fire();
+            c.fire();
 
-            expectCallCount(handler, 1);
+            expect(handler).toHaveBeenCalled();
+            expect(count).toEqual(1);
         });
         it("handler should be called when subscribed, removed, and subscribed again", function() {
-            var testChannel = multi ? multiChannel : stickyChannel;
-            var handler = jasmine.createSpy();
+            var count = 0;
+            var handler = jasmine.createSpy().andCallFake(function() { count++; });
 
-            testChannel.subscribe(handler);
-            testChannel.unsubscribe(handler);
-            testChannel.subscribe(handler);
+            c.subscribe(handler);
+            c.unsubscribe(handler);
+            c.subscribe(handler);
 
-            testChannel.fire();
+            c.fire();
+
+            expect(handler).toHaveBeenCalled();
+            expect(count).toEqual(1);
 
-            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 before = jasmine.createSpy('before'),
+            var chan = channel.create("foo"),
+                before = jasmine.createSpy('before'),
                 after = jasmine.createSpy('after');
 
-            stickyChannel.subscribe(before);
-            stickyChannel.fire(1, 2, 3);
-            stickyChannel.subscribe(after);
+            chan.subscribe(before);
+            chan.fire();
+            chan.subscribe(after);
 
-            expectCallCount(before, 1);
-            expectCallCount(after, 1);
-            expect(after.argsForCall[0]).toEqual({0:1, 1:2, 2:3});
+            expect(before).toHaveBeenCalled();
+            expect(after).toHaveBeenCalled();
         });
         it("should instantly trigger the callback if the event is currently being fired.", function () {
-            var handler1 = jasmine.createSpy().andCallFake(function() { stickyChannel.subscribe(handler2); }),
+            var handler1 = jasmine.createSpy().andCallFake(function() { c.subscribe(handler2); }),
                 handler2 = jasmine.createSpy().andCallFake(function(arg1) { expect(arg1).toEqual('foo');});
 
-            stickyChannel.subscribe(handler1);
-            stickyChannel.fire('foo');
+            c.subscribe(handler1);
+            c.fire('foo');
 
-            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);
+            expect(handler2).toHaveBeenCalled();
         });
     });
-    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);
+    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);
         });
-        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 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 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();
+        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);
         });
     });
     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() {
-                if (callCount(handler) == 1) {
+                var callCount = handler.argsForCall.length;
+                if (callCount == 1) {
                     expect(this.numHandlers).toEqual(1);
                 } else {
                     expect(this.numHandlers).toEqual(0);
                 }
             });
-            multiChannel.onHasSubscribersChange = handler;
+            c.onHasSubscribersChange = handler;
             function foo1() {}
             function foo2() {}
-            multiChannel.subscribe(foo1);
-            multiChannel.subscribe(foo2);
-            multiChannel.unsubscribe(foo1);
-            multiChannel.unsubscribe(foo2);
-            expectCallCount(handler, 2);
+            c.subscribe(foo1);
+            c.subscribe(foo2);
+            c.unsubscribe(foo1);
+            c.unsubscribe(foo2);
+            expect(handler.argsForCall.length).toEqual(2);
         });
     });
 });