UNPKG

karma

Version:

Spectacular Test Runner for JavaScript.

240 lines (200 loc) 6.58 kB
var stringify = require('./stringify'); var constant = require('./constants'); var util = require('./util'); /* jshint unused: false */ var Karma = function(socket, iframe, opener, navigator, location) { var hasError = false; var startEmitted = false; var reloadingContext = false; var store = {}; var self = this; var queryParams = util.parseQueryParams(location.search); var browserId = queryParams.id || util.generateId('manual-'); var returnUrl = queryParams.return_url || null; var currentTransport; var resultsBufferLimit = 1; var resultsBuffer = []; this.VERSION = constant.VERSION; this.config = {}; var childWindow = null; var navigateContextTo = function(url) { if (self.config.useIframe === false) { if (childWindow === null || childWindow.closed === true) { // If this is the first time we are opening the window, or the window is closed childWindow = opener('about:blank'); } childWindow.location = url; } else { iframe.src = url; } }; this.setupContext = function(contextWindow) { if (hasError) { return; } var getConsole = function(currentWindow) { return currentWindow.console || { log: function() {}, info: function() {}, warn: function() {}, error: function() {}, debug: function() {} }; }; contextWindow.__karma__ = this; // This causes memory leak in Chrome (17.0.963.66) contextWindow.onerror = function() { return contextWindow.__karma__.error.apply(contextWindow.__karma__, arguments); }; contextWindow.onbeforeunload = function(e, b) { if (!reloadingContext) { // TODO(vojta): show what test (with explanation about jasmine.UPDATE_INTERVAL) contextWindow.__karma__.error('Some of your tests did a full page reload!'); } }; if (self.config.captureConsole) { // patch the console var localConsole = contextWindow.console = getConsole(contextWindow); var browserConsoleLog = localConsole.log; var logMethods = ['log', 'info', 'warn', 'error', 'debug']; var patchConsoleMethod = function(method) { var orig = localConsole[method]; if (!orig) { return; } localConsole[method] = function() { self.log(method, arguments); return Function.prototype.apply.call(orig, localConsole, arguments); }; }; for (var i = 0; i < logMethods.length; i++) { patchConsoleMethod(logMethods[i]); } } contextWindow.dump = function() { self.log('dump', arguments); }; contextWindow.alert = function(msg) { self.log('alert', [msg]); }; }; this.log = function(type, args) { var values = []; for (var i = 0; i < args.length; i++) { values.push(this.stringify(args[i], 3)); } this.info({log: values.join(', '), type: type}); }; this.stringify = stringify; var clearContext = function() { reloadingContext = true; navigateContextTo('about:blank'); }; // error during js file loading (most likely syntax error) // we are not going to execute at all this.error = function(msg, url, line) { hasError = true; socket.emit('error', url ? msg + '\nat ' + url + (line ? ':' + line : '') : msg); this.complete(); return false; }; this.result = function(result) { if (!startEmitted) { socket.emit('start', {total: null}); startEmitted = true; } if (resultsBufferLimit === 1) { return socket.emit('result', result); } resultsBuffer.push(result); if (resultsBuffer.length === resultsBufferLimit) { socket.emit('result', resultsBuffer); resultsBuffer = []; } }; this.complete = function(result) { if (resultsBuffer.length) { socket.emit('result', resultsBuffer); resultsBuffer = []; } // give the browser some time to breath, there could be a page reload, but because a bunch of // tests could run in the same event loop, we wouldn't notice. setTimeout(function() { socket.emit('complete', result || {}); clearContext(); // Redirect to the return_url, however we need to give the browser some time, // so that all the messages are sent. // TODO(vojta): can we rather get notification from socket.io? if (returnUrl) { setTimeout(function() { location.href = returnUrl; }, (currentTransport === 'websocket' || currentTransport === 'flashsocket') ? 0 : 3000); } }, 0); }; this.info = function(info) { // TODO(vojta): introduce special API for this if (!startEmitted && util.isDefined(info.total)) { socket.emit('start', info); startEmitted = true; } else { socket.emit('info', info); } }; // all files loaded, let's start the execution this.loaded = function() { // has error -> cancel if (!hasError) { this.start(this.config); } // remove reference to child iframe this.start = null; }; this.store = function(key, value) { if (util.isUndefined(value)) { return store[key]; } if (util.instanceOf(value, 'Array')) { var s = store[key] = []; for (var i = 0; i < value.length; i++) { s.push(value[i]); } } else { // TODO(vojta): clone objects + deep store[key] = value; } }; // supposed to be overriden by the context // TODO(vojta): support multiple callbacks (queue) this.start = function() { this.error('You need to include some adapter that implements __karma__.start method!'); }; socket.on('execute', function(cfg) { // reset hasError and reload the iframe hasError = false; startEmitted = false; reloadingContext = false; self.config = cfg; navigateContextTo(constant.CONTEXT_URL); // clear the console before run // works only on FF (Safari, Chrome do not allow to clear console from js source) if (window.console && window.console.clear) { window.console.clear(); } }); // report browser name, id socket.on('connect', function() { currentTransport = socket.socket.transport.name; // TODO(vojta): make resultsBufferLimit configurable if (currentTransport === 'websocket' || currentTransport === 'flashsocket') { resultsBufferLimit = 1; } else { resultsBufferLimit = 50; } socket.emit('register', { name: navigator.userAgent, id: browserId }); }); }; module.exports = Karma;