From 8fc63c75a24f634a6c77212d48946be57ef926af Mon Sep 17 00:00:00 2001 From: Vojta Jina Date: Sun, 18 Aug 2013 14:19:22 -0700 Subject: [PATCH] feat: allow browser to reconnect during the test run When a browser disconnects during a test run, Karma waits for reconnecting (configurable by `browserDisconnectTimeout`). If the browser reconnects in this timeout frame, nothing happpens - Karma replies the events (results) and the test run continues. If the browser does not reconnect in the timeout frame, Karma fails the build. This should solve the connection issues with IE on polling. - add browserDisconnectTimeout config property (defaults to 2000) Internal changes: - `Browser.isReady` is a function now, as browser has multiple states - `BrowserCollection.setAllIsReadyTo` -> `setAllToExecuting` - remove `Browser.launchId`, we use `Browser.id` instead Closes #82 Closes #590 --- lib/browser.js | 146 ++++++++++++------ lib/config.js | 1 + lib/events.js | 37 +++++ lib/executor.js | 2 +- lib/server.js | 37 ++++- static/karma.src.js | 3 - test/unit/browser.spec.coffee | 269 +++++++++++++++++++++++++--------- test/unit/events.spec.coffee | 56 +++++++ 8 files changed, 424 insertions(+), 127 deletions(-) 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'