diff --git a/lib/browser.js b/lib/browser.js index d4cc4ed22..c8f69eca7 100644 --- a/lib/browser.js +++ b/lib/browser.js @@ -16,33 +16,57 @@ var Result = function() { }; -var Browser = function(id, collection, emitter) { - var log = logger.create(id); +// The browser is ready to execute tests. +var READY = 1; + +// The browser is executing the tests/ +var EXECUTING = 2; + +// The browser is not executing, but temporarily disconnected (waiting for reconnecting). +var READY_DISCONNECTED = 3; + +// The browser is executing the tests, but temporarily disconnect (waiting for reconnecting). +var EXECUTING_DISCONNECTED = 4; + +// The browser got permanently disconnected (being removed from the collection and destroyed). +var DISCONNECTED = 5; + + +var Browser = function(id, fullName, /* capturedBrowsers */ collection, emitter, socket, timer, + /* config.browserDisconnectTimeout */ disconnectDelay) { + + var name = helper.browserFullNameToShort(fullName); + var log = logger.create(name); this.id = id; - this.name = id; - this.fullName = null; - this.isReady = true; + this.fullName = fullName; + this.name = name; + this.state = READY; this.lastResult = new Result(); + this.init = function() { + collection.add(this); - this.toString = function() { - return this.name; - }; + events.bindAll(this, socket); - this.onRegister = function(info) { - this.launchId = info.id; - this.fullName = info.name; - this.name = helper.browserFullNameToShort(this.fullName); - log = logger.create(this.name); - log.info('Connected on socket id ' + this.id); + log.info('Connected on socket %s', socket.id); - emitter.emit('browser_register', this); + // TODO(vojta): move to collection emitter.emit('browsers_change', collection); + + emitter.emit('browser_register', this); + }; + + this.isReady = function() { + return this.state === READY; + }; + + this.toString = function() { + return this.name; }; this.onError = function(error) { - if (this.isReady) { + if (this.isReady()) { return; } @@ -51,7 +75,7 @@ var Browser = function(id, collection, emitter) { }; this.onInfo = function(info) { - if (this.isReady) { + if (this.isReady()) { return; } @@ -70,11 +94,11 @@ var Browser = function(id, collection, emitter) { }; this.onComplete = function(result) { - if (this.isReady) { + if (this.isReady()) { return; } - this.isReady = true; + this.state = READY; this.lastResult.totalTimeEnd(); if (!this.lastResult.success) { @@ -85,21 +109,50 @@ var Browser = function(id, collection, emitter) { emitter.emit('browser_complete', this, result); }; + var self = this; + var disconnect = function() { + self.state = DISCONNECTED; + log.warn('Disconnected'); + collection.remove(self); + }; + + var pendingDisconnect; this.onDisconnect = function() { - if (!this.isReady) { - this.isReady = true; - this.lastResult.totalTimeEnd(); - this.lastResult.disconnected = true; - emitter.emit('browser_complete', this); + if (this.state === READY) { + disconnect(); + } else if (this.state === EXECUTING) { + log.debug('Disconnected during run, waiting for reconnecting.'); + this.state = EXECUTING_DISCONNECTED; + + pendingDisconnect = timer.setTimeout(function() { + self.lastResult.totalTimeEnd(); + self.lastResult.disconnected = true; + disconnect(); + emitter.emit('browser_complete', self); + }, disconnectDelay); } + }; - log.warn('Disconnected'); - collection.remove(this); + this.onReconnect = function(newSocket) { + if (this.state === EXECUTING_DISCONNECTED) { + this.state = EXECUTING; + log.debug('Reconnected.'); + } else if (this.state === EXECUTING || this.state === READY) { + log.debug('New connection, forgetting the old one.'); + // TODO(vojta): this should only remove this browser.onDisconnect listener + socket.removeAllListeners('disconnect'); + } + + socket = newSocket; + events.bindAll(this, newSocket); + if (pendingDisconnect) { + timer.clearTimeout(pendingDisconnect); + } }; this.onResult = function(result) { // ignore - probably results from last run (after server disconnecting) - if (this.isReady) { + if (this.isReady()) { return; } @@ -119,11 +172,17 @@ var Browser = function(id, collection, emitter) { return { id: this.id, name: this.name, - isReady: this.isReady + isReady: this.state === READY }; }; }; +Browser.STATE_READY = READY; +Browser.STATE_EXECUTING = EXECUTING; +Browser.STATE_READY_DISCONNECTED = READY_DISCONNECTED; +Browser.STATE_EXECUTING_DISCONNECTED = EXECUTING_DISCONNECTED; +Browser.STATE_DISCONNECTED = DISCONNECTED; + var Collection = function(emitter, browsers) { browsers = browsers || []; @@ -146,23 +205,29 @@ var Collection = function(emitter, browsers) { return true; }; - this.setAllIsReadyTo = function(value) { - var change = false; + this.getById = function(browserId) { + for (var i = 0; i < browsers.length; i++) { + if (browsers[i].id === browserId) { + return browsers[i]; + } + } + + return null; + }; + + this.setAllToExecuting = function() { browsers.forEach(function(browser) { - change = change || browser.isReady !== value; - browser.isReady = value; + browser.state = EXECUTING; }); - if (change) { - emitter.emit('browsers_change', this); - } + emitter.emit('browsers_change', this); }; this.areAllReady = function(nonReadyList) { nonReadyList = nonReadyList || []; browsers.forEach(function(browser) { - if (!browser.isReady) { + if (!browser.isReady()) { nonReadyList.push(browser); } }); @@ -222,12 +287,3 @@ Collection.$inject = ['emitter']; exports.Result = Result; exports.Browser = Browser; exports.Collection = Collection; - -exports.createBrowser = function(socket, collection, emitter) { - var browser = new Browser(socket.id, collection, emitter); - - events.bindAll(browser, socket); - collection.add(browser); - - return browser; -}; diff --git a/lib/config.js b/lib/config.js index 4a510e764..c06764d46 100644 --- a/lib/config.js +++ b/lib/config.js @@ -271,6 +271,7 @@ var Config = function() { this.client = { args: [] }; + this.browserDisconnectTimeout = 2000; // TODO(vojta): remove in 0.10 this.junitReporter = { diff --git a/lib/events.js b/lib/events.js index 12e7f0a00..eedc36278 100644 --- a/lib/events.js +++ b/lib/events.js @@ -15,6 +15,41 @@ var bindAllEvents = function(object, context) { } }; + +var bufferEvents = function(emitter, eventsToBuffer) { + var listeners = []; + var eventsToReply = []; + var genericListener = function() { + eventsToReply.push(Array.prototype.slice.call(arguments)); + }; + + eventsToBuffer.forEach(function(eventName) { + var listener = genericListener.bind(null, eventName); + listeners.push(listener); + emitter.on(eventName, listener); + }); + + return function() { + if (!eventsToReply) { + return; + } + + // remove all buffering listeners + listeners.forEach(function(listener, i) { + emitter.removeListener(eventsToBuffer[i], listener); + }); + + // reply + eventsToReply.forEach(function(args) { + events.EventEmitter.prototype.emit.apply(emitter, args); + }); + + // free-up + listeners = eventsToReply = null; + }; +}; + + // TODO(vojta): log.debug all events var EventEmitter = function() { this.bind = bindAllEvents; @@ -38,6 +73,8 @@ var EventEmitter = function() { util.inherits(EventEmitter, events.EventEmitter); + // PUBLISH exports.EventEmitter = EventEmitter; exports.bindAll = bindAllEvents; +exports.bufferEvents = bufferEvents; diff --git a/lib/executor.js b/lib/executor.js index e28d14156..6eaf747fe 100644 --- a/lib/executor.js +++ b/lib/executor.js @@ -18,8 +18,8 @@ var Executor = function(capturedBrowsers, config, emitter) { if (capturedBrowsers.areAllReady(nonReady)) { log.debug('All browsers are ready, executing'); executionScheduled = false; - capturedBrowsers.setAllIsReadyTo(false); capturedBrowsers.clearResults(); + capturedBrowsers.setAllToExecuting(); pendingCount = capturedBrowsers.length; runningBrowsers = capturedBrowsers.clone(); emitter.emit('run_start', runningBrowsers); diff --git a/lib/server.js b/lib/server.js index 1532d98fb..60b5f7874 100644 --- a/lib/server.js +++ b/lib/server.js @@ -14,7 +14,8 @@ var Launcher = require('./launcher').Launcher; var FileList = require('./file-list').List; var reporter = require('./reporter'); var helper = require('./helper'); -var EventEmitter = require('./events').EventEmitter; +var events = require('./events'); +var EventEmitter = events.EventEmitter; var Executor = require('./executor'); var log = logger.create(); @@ -60,8 +61,8 @@ var start = function(injector, config, launcher, globalEmitter, preprocess, file }); globalEmitter.on('browser_register', function(browser) { - if (browser.launchId) { - launcher.markCaptured(browser.launchId); + if (browser.id) { + launcher.markCaptured(browser.id); } // TODO(vojta): This is lame, browser can get captured and then crash (before other browsers get @@ -78,8 +79,31 @@ var start = function(injector, config, launcher, globalEmitter, preprocess, file }); socketServer.sockets.on('connection', function (socket) { - log.debug('New browser has connected on socket ' + socket.id); - browser.createBrowser(socket, capturedBrowsers, globalEmitter); + log.debug('A browser has connected on socket ' + socket.id); + + var replySocketEvents = events.bufferEvents(socket, ['info', 'error', 'result', 'complete']); + + socket.on('register', function(info) { + var newBrowser; + + if (info.id) { + newBrowser = capturedBrowsers.getById(info.id); + } + + if (newBrowser) { + newBrowser.onReconnect(socket); + } else { + newBrowser = injector.createChild([{ + id: ['value', info.id || null], + fullName: ['value', info.name], + socket: ['value', socket] + }]).instantiate(browser.Browser); + + newBrowser.init(); + } + + replySocketEvents(); + }); }); if (config.autoWatch) { @@ -167,7 +191,8 @@ exports.start = function(cliOptions, done) { customScriptTypes: ['value', []], reporter: ['factory', reporter.createReporters], capturedBrowsers: ['type', browser.Collection], - args: ['value', {}] + args: ['value', {}], + timer: ['value', {setTimeout: setTimeout, clearTimeout: clearTimeout}] }]; // load the plugins diff --git a/static/karma.src.js b/static/karma.src.js index 2d5b1f6fa..34ad313a9 100644 --- a/static/karma.src.js +++ b/static/karma.src.js @@ -250,9 +250,6 @@ var Karma = function(socket, context, navigator, location) { } }); - // cancel execution - socket.on('disconnect', clearContext); - // report browser name, id socket.on('connect', function() { socket.emit('register', { diff --git a/test/unit/browser.spec.coffee b/test/unit/browser.spec.coffee index fa1e5e952..1b735373e 100644 --- a/test/unit/browser.spec.coffee +++ b/test/unit/browser.spec.coffee @@ -5,6 +5,8 @@ describe 'browser', -> b = require '../../lib/browser' e = require '../../lib/events' + Timer = require('timer-shim').Timer + beforeEach -> sinon.stub(Date, 'now') afterEach -> Date.now.restore() @@ -29,48 +31,58 @@ describe 'browser', -> # browser.Browser #============================================================================ describe 'Browser', -> - browser = collection = emitter = null + browser = collection = emitter = socket = null beforeEach -> + socket = new e.EventEmitter emitter = new e.EventEmitter collection = new b.Collection emitter Date.now.returns 12345 - browser = new b.Browser 'fake-id', collection, emitter - - it 'should have toString method', -> - expect(browser.toString()).to.equal 'fake-id' - browser.name = 'Chrome 16.0' - expect(browser.toString()).to.equal 'Chrome 16.0' + it 'should set fullName and name', -> + fullName = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.7 ' + + '(KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7' + browser = new b.Browser 'id', fullName, collection, emitter, socket + expect(browser.name).to.equal 'Chrome 16.0.912 (Mac OS X 10.6.8)' + expect(browser.fullName).to.equal fullName #========================================================================== - # browser.Browser.onRegister + # browser.Browser.init #========================================================================== - describe 'onRegister', -> + describe 'init', -> - it 'should set fullName and name', -> - fullName = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.7 ' + - '(KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7' - browser.onRegister name: fullName - expect(browser.name).to.equal 'Chrome 16.0.912 (Mac OS X 10.6.8)' - expect(browser.fullName).to.equal fullName + it 'should emit "browser_register"', -> + spyRegister = sinon.spy() + emitter.on 'browser_register', spyRegister + browser = new b.Browser 12345, '', collection, emitter, socket + browser.init() + expect(spyRegister).to.have.been.called + expect(spyRegister.args[0][0]).to.equal browser - it 'should set launchId', -> - browser.onRegister id: 12345, name: 'some' - expect(browser.launchId).to.equal 12345 + it 'should ad itself into the collection', -> + browser = new b.Browser 12345, '', collection, emitter, socket + browser.init() - it 'should emit "browser_register', -> - spyRegister = sinon.spy() + expect(collection.length).to.equal 1 + collection.forEach (browserInCollection) -> + expect(browserInCollection).to.equal browser - emitter.on 'browser_register', spyRegister - browser.onRegister name : 'some' - expect(spyRegister).to.have.been.called - expect(spyRegister.args[0][0]).to.equal browser + + #========================================================================== + # browser.Browser.toString + #========================================================================== + describe 'toString', -> + + it 'should return browser name', -> + fullName = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/535.7 ' + + '(KHTML, like Gecko) Chrome/16.0.912.63 Safari/535.7' + browser = new b.Browser 'id', fullName, collection, emitter, socket + expect(browser.toString()).to.equal 'Chrome 16.0.912 (Mac OS X 10.6.8)' #========================================================================== @@ -78,10 +90,14 @@ describe 'browser', -> #========================================================================== describe 'onError', -> + beforeEach -> + browser = new b.Browser 'fake-id', 'full name', collection, emitter, socket + + it 'should set lastResult.error and fire "browser_error"', -> spy = sinon.spy() emitter.on 'browser_error', spy - browser.isReady = false + browser.state = b.Browser.STATE_EXECUTING browser.onError() expect(browser.lastResult.error).to.equal true @@ -91,7 +107,7 @@ describe 'browser', -> it 'should ignore if browser not executing', -> spy = sinon.spy() emitter.on 'browser_error', spy - browser.isReady = true + browser.state = b.Browser.STATE_READY browser.onError() expect(browser.lastResult.error).to.equal false @@ -103,8 +119,12 @@ describe 'browser', -> #========================================================================== describe 'onInfo', -> + beforeEach -> + browser = new b.Browser 'fake-id', 'full name', collection, emitter, socket + + it 'should set total count of specs', -> - browser.isReady = false + browser.state = b.Browser.STATE_EXECUTING browser.onInfo {total: 20} expect(browser.lastResult.total).to.equal 20 @@ -113,16 +133,16 @@ describe 'browser', -> spy = sinon.spy() emitter.on 'browser_log', spy - browser.isReady = false + browser.state = b.Browser.STATE_EXECUTING browser.onInfo {log: 'something', type: 'info'} expect(spy).to.have.been.calledWith browser, 'something', 'info' it 'should ignore if browser not executing', -> spy = sinon.spy() - browser.isReady = true emitter.on 'browser_dump', spy + browser.state = b.Browser.STATE_READY browser.onInfo {dump: 'something'} browser.onInfo {total: 20} @@ -135,17 +155,21 @@ describe 'browser', -> #========================================================================== describe 'onComplete', -> + beforeEach -> + browser = new b.Browser 'fake-id', 'full name', collection, emitter, socket + + it 'should set isReady to true', -> - browser.isReady = false + browser.state = b.Browser.STATE_EXECUTING browser.onComplete() - expect(browser.isReady).to.equal true + expect(browser.isReady()).to.equal true it 'should fire "browsers_change" event', -> spy = sinon.spy() emitter.on 'browsers_change', spy - browser.isReady = false + browser.state = b.Browser.STATE_EXECUTING browser.onComplete() expect(spy).to.have.been.calledWith collection @@ -155,7 +179,7 @@ describe 'browser', -> emitter.on 'browsers_change', spy emitter.on 'browser_complete', spy - browser.isReady = true + browser.state = b.Browser.STATE_READY browser.onComplete() expect(spy).not.to.have.been.called @@ -163,14 +187,14 @@ describe 'browser', -> it 'should set totalTime', -> Date.now.returns 12347 # the default spy return 12345 - browser.isReady = false + browser.state = b.Browser.STATE_EXECUTING browser.onComplete() expect(browser.lastResult.totalTime).to.equal 2 it 'should error the result if zero tests executed', -> - browser.isReady = false + browser.state = b.Browser.STATE_EXECUTING browser.onComplete() expect(browser.lastResult.error).to.equal true @@ -180,11 +204,18 @@ describe 'browser', -> # browser.Browser.onDisconnect #========================================================================== describe 'onDisconnect', -> + timer = null - it 'should remove from parent collection', -> - collection.add browser + beforeEach -> + timer = new Timer + timer.pause() + browser = new b.Browser 'fake-id', 'full name', collection, emitter, socket, timer, 10 + browser.init() + + it 'should remove from parent collection', -> expect(collection.length).to.equal 1 + browser.onDisconnect() expect(collection.length).to.equal 0 @@ -192,25 +223,65 @@ describe 'browser', -> it 'should complete if browser executing', -> spy = sinon.spy() emitter.on 'browser_complete', spy - browser.isReady = false + browser.state = b.Browser.STATE_EXECUTING browser.onDisconnect() + timer.wind 20 - expect(browser.isReady).to.equal true - expect(browser.lastResult.disconnected).to.equal true + expect(browser.lastResult.disconnected).to.equal true expect(spy).to.have.been.called it 'should not complete if browser not executing', -> spy = sinon.spy() emitter.on 'browser_complete', spy - browser.isReady = true + browser.state = b.Browser.STATE_READY browser.onDisconnect() - expect(spy).not.to.have.been.called + #========================================================================== + # browser.Browser.onReconnect + #========================================================================== + describe 'onReconnect', -> + + it 'should cancel disconnecting', -> + timer = new Timer + timer.pause() + + browser = new b.Browser 'id', 'Chrome 19.0', collection, emitter, socket, timer, 10 + browser.init() + browser.state = b.Browser.STATE_EXECUTING + + browser.onDisconnect() + browser.onReconnect new e.EventEmitter + + timer.wind 10 + expect(browser.state).to.equal b.Browser.STATE_EXECUTING + + + it 'should ignore disconnects on old sockets, but accept other messages', -> + # IE on polling sometimes reconnect on another socket (before disconnecting) + + browser = new b.Browser 'id', 'Chrome 19.0', collection, emitter, socket, timer, 10 + browser.init() + browser.state = b.Browser.STATE_EXECUTING + + browser.onReconnect new e.EventEmitter + + # still accept results on the old socket + socket.emit 'result', {success: true} + expect(browser.lastResult.success).to.equal 1 + + socket.emit 'error', {} + expect(browser.lastResult.error).to.equal true + + # should be ignored, keep executing + socket.emit 'disconnect' + expect(browser.state).to.equal b.Browser.STATE_EXECUTING + + #========================================================================== # browser.Browser.onResult #========================================================================== @@ -225,8 +296,12 @@ describe 'browser', -> createSkippedResult = -> {success: true, skipped: true, suite: [], log: []} + beforeEach -> + browser = new b.Browser 'fake-id', 'full name', collection, emitter, socket + + it 'should update lastResults', -> - browser.isReady = false + browser.state = b.Browser.STATE_EXECUTING browser.onResult createSuccessResult() browser.onResult createSuccessResult() browser.onResult createFailedResult() @@ -238,7 +313,7 @@ describe 'browser', -> it 'should ignore if not running', -> - browser.isReady = true + browser.state = b.Browser.STATE_READY browser.onResult createSuccessResult() browser.onResult createSuccessResult() browser.onResult createFailedResult() @@ -248,7 +323,7 @@ describe 'browser', -> it 'should update netTime', -> - browser.isReady = false + browser.state = b.Browser.STATE_EXECUTING browser.onResult {time: 3, suite: [], log: []} browser.onResult {time: 1, suite: [], log: []} browser.onResult {time: 5, suite: [], log: []} @@ -262,13 +337,56 @@ describe 'browser', -> describe 'serialize', -> it 'should return plain object with only name, id, isReady properties', -> - browser.isReady = true + browser = new b.Browser 'fake-id', 'full name', collection, emitter, socket + browser.state = b.Browser.STATE_READY browser.name = 'Browser 1.0' browser.id = '12345' expect(browser.serialize()).to.deep.equal {id: '12345', name: 'Browser 1.0', isReady: true} + #========================================================================== + # browser.Browser higher level tests for reconnecting + #========================================================================== + describe 'scenario:', -> + + it 'reconnecting during the run', -> + timer = new Timer + timer.pause() + browser = new b.Browser 'fake-id', 'full name', collection, emitter, socket, timer, 10 + browser.init() + browser.state = b.Browser.STATE_EXECUTING + socket.emit 'result', {success: true, suite: [], log: []} + socket.emit 'disconnect' + expect(browser.isReady()).to.equal false + + newSocket = new e.EventEmitter + browser.onReconnect newSocket + expect(browser.isReady()).to.equal false + + newSocket.emit 'result', {success: false, suite: [], log: []} + newSocket.emit 'complete' + expect(browser.isReady()).to.equal true + expect(browser.lastResult.success).to.equal 1 + expect(browser.lastResult.failed).to.equal 1 + + + it 'disconecting during the run', -> + spy = sinon.spy() + emitter.on 'browser_complete', spy + timer = new Timer + timer.pause() + browser = new b.Browser 'fake-id', 'full name', collection, emitter, socket, timer, 10 + browser.init() + browser.state = b.Browser.STATE_EXECUTING + socket.emit 'result', {success: true, suite: [], log: []} + socket.emit 'disconnect' + + timer.wind 10 + expect(browser.lastResult.disconnected).to.equal true + expect(spy).to.have.been.calledWith browser + + #============================================================================ # browser.Collection #============================================================================ @@ -329,43 +447,50 @@ describe 'browser', -> #========================================================================== - # browser.Collection.setAllIsReadyTo + # browser.Collection.getById #========================================================================== - describe 'setAllIsReadyTo', -> + describe 'getById', -> + + it 'should find the browser by id', -> + browser = new b.Browser 123 + collection.add browser + + expect(collection.getById 123).to.equal browser + + + it 'should return null if no browser with given id', -> + expect(collection.getById 123).to.equal null + + collection.add new b.Browser 456 + expect(collection.getById 123).to.equal null + + + #========================================================================== + # browser.Collection.setAllToExecuting + #========================================================================== + describe 'setAllToExecuting', -> browsers = null beforeEach -> browsers = [new b.Browser, new b.Browser, new b.Browser] browsers.forEach (browser) -> - browser.isReady = true collection.add browser - it 'should set all browsers isReady to given value', -> - collection.setAllIsReadyTo false + it 'should set all browsers state to executing', -> + collection.setAllToExecuting() browsers.forEach (browser) -> - expect(browser.isReady).to.equal false + expect(browser.isReady()).to.equal false + expect(browser.state).to.equal b.Browser.STATE_EXECUTING - collection.setAllIsReadyTo true - browsers.forEach (browser) -> - expect(browser.isReady).to.equal true - - it 'should fire "browsers_change" event if at least one browser changed', -> + it 'should fire "browsers_change" event', -> spy = sinon.spy() - browsers[0].isReady = false emitter.on 'browsers_change', spy - collection.setAllIsReadyTo true + collection.setAllToExecuting() expect(spy).to.have.been.called - it 'should not fire "browsers_change" event if no change', -> - spy = sinon.spy() - emitter.on 'browsers_change', spy - collection.setAllIsReadyTo true - expect(spy).not.to.have.been.called - - #========================================================================== # browser.Collection.areAllReady #========================================================================== @@ -375,22 +500,22 @@ describe 'browser', -> beforeEach -> browsers = [new b.Browser, new b.Browser, new b.Browser] browsers.forEach (browser) -> - browser.isReady = true + browser.state = b.Browser.STATE_READY collection.add browser it 'should return true if all browsers are ready', -> - expect(collection.areAllReady()).to.equal true + expect(collection.areAllReady()).to.equal true it 'should return false if at least one browser is not ready', -> - browsers[1].isReady = false - expect(collection.areAllReady()).to.equal false + browsers[1].state = b.Browser.STATE_EXECUTING + expect(collection.areAllReady()).to.equal false it 'should add all non-ready browsers into given array', -> - browsers[0].isReady = false - browsers[1].isReady = false + browsers[0].state = b.Browser.STATE_EXECUTING + browsers[1].state = b.Browser.STATE_EXECUTING_DISCONNECTED nonReady = [] collection.areAllReady nonReady expect(nonReady).to.deep.equal [browsers[0], browsers[1]] diff --git a/test/unit/events.spec.coffee b/test/unit/events.spec.coffee index 429aa4c07..ae1ae2ae7 100644 --- a/test/unit/events.spec.coffee +++ b/test/unit/events.spec.coffee @@ -106,3 +106,59 @@ describe 'events', -> emitter.emit 'bar' expect(object.onFoo).to.have.been.called + + #============================================================================ + # events.bufferEvents + #============================================================================ + describe 'bufferEvents', -> + + it 'should reply all events', -> + spy = sinon.spy() + replyEvents = e.bufferEvents emitter, ['foo', 'bar'] + + emitter.emit 'foo', 'foo-1' + emitter.emit 'bar', 'bar-2' + emitter.emit 'foo', 'foo-3' + + emitter.on 'foo', spy + emitter.on 'bar', spy + + replyEvents() + expect(spy).to.have.been.calledThrice + expect(spy.firstCall).to.have.been.calledWith 'foo-1' + expect(spy.secondCall).to.have.been.calledWith 'bar-2' + expect(spy.thirdCall).to.have.been.calledWith 'foo-3' + + + it 'should not buffer after reply()', -> + spy = sinon.spy() + replyEvents = e.bufferEvents emitter, ['foo', 'bar'] + replyEvents() + + emitter.emit 'foo', 'foo-1' + emitter.emit 'bar', 'bar-2' + emitter.emit 'foo', 'foo-3' + + emitter.on 'foo', spy + emitter.on 'bar', spy + + replyEvents() + expect(spy).to.not.have.been.caleed + + + it 'should work with overriden "emit" method', -> + # This is to make sure it works with socket.io sockets, + # which overrides the emit() method to send the event through the wire, + # instead of local emit. + originalEmit = emitter.emit + emitter.emit = -> null + + spy = sinon.spy() + replyEvents = e.bufferEvents emitter, ['foo'] + + originalEmit.apply emitter, ['foo', 'whatever'] + + emitter.on 'foo', spy + + replyEvents() + expect(spy).to.have.been.calledWith 'whatever'