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 <agri...@chromium.org> Authored: Tue Sep 18 11:16:25 2012 -0400 Committer: Andrew Grieve <agri...@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); }); }); });