From 01fe42769f358a92d980223578755e096e151a90 Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 19:06:19 +0100 Subject: [PATCH 01/16] Scripts: add js-reporters distribution. --- lib/_patch/js-reporters.js | 1369 ++++++++++++++++++++++++++++++++++++ lib/server.js | 3 +- 2 files changed, 1371 insertions(+), 1 deletion(-) create mode 100644 lib/_patch/js-reporters.js diff --git a/lib/_patch/js-reporters.js b/lib/_patch/js-reporters.js new file mode 100644 index 0000000..9860b75 --- /dev/null +++ b/lib/_patch/js-reporters.js @@ -0,0 +1,1369 @@ +/** + * JsReporters 1.0.0 + * https://github.com/js-reporters + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: Wed Jul 06 2016 + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.JsReporters = factory()); +}(this, function () { 'use strict'; + + function __commonjs(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } + + + var babelHelpers = {}; + babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; + }; + + babelHelpers.classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + }; + + babelHelpers.createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; + }(); + + babelHelpers.inherits = function (subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; + }; + + babelHelpers.possibleConstructorReturn = function (self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; + }; + + babelHelpers.slicedToArray = function () { + function sliceIterator(arr, i) { + var _arr = []; + var _n = true; + var _d = false; + var _e = undefined; + + try { + for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { + _arr.push(_s.value); + + if (i && _arr.length === i) break; + } + } catch (err) { + _d = true; + _e = err; + } finally { + try { + if (!_n && _i["return"]) _i["return"](); + } finally { + if (_d) throw _e; + } + } + + return _arr; + } + + return function (arr, i) { + if (Array.isArray(arr)) { + return arr; + } else if (Symbol.iterator in Object(arr)) { + return sliceIterator(arr, i); + } else { + throw new TypeError("Invalid attempt to destructure non-iterable instance"); + } + }; + }(); + + babelHelpers; + + var events = __commonjs(function (module) { + // Copyright Joyent, Inc. and other Node contributors. + // + // Permission is hereby granted, free of charge, to any person obtaining a + // copy of this software and associated documentation files (the + // "Software"), to deal in the Software without restriction, including + // without limitation the rights to use, copy, modify, merge, publish, + // distribute, sublicense, and/or sell copies of the Software, and to permit + // persons to whom the Software is furnished to do so, subject to the + // following conditions: + // + // The above copyright notice and this permission notice shall be included + // in all copies or substantial portions of the Software. + // + // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + // USE OR OTHER DEALINGS IN THE SOFTWARE. + + function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; + } + module.exports = EventEmitter; + + // Backwards-compat with node 0.10.x + EventEmitter.EventEmitter = EventEmitter; + + EventEmitter.prototype._events = undefined; + EventEmitter.prototype._maxListeners = undefined; + + // By default EventEmitters will print a warning if more than 10 listeners are + // added to it. This is a useful default which helps finding memory leaks. + EventEmitter.defaultMaxListeners = 10; + + // Obviously not all Emitters should be limited to 10. This function allows + // that to be increased. Set to zero for unlimited. + EventEmitter.prototype.setMaxListeners = function (n) { + if (!isNumber(n) || n < 0 || isNaN(n)) throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; + }; + + EventEmitter.prototype.emit = function (type) { + var er, handler, len, args, i, listeners; + + if (!this._events) this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || isObject(this._events.error) && !this._events.error.length) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } + throw TypeError('Uncaught, unspecified "error" event.'); + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + args = Array.prototype.slice.call(arguments, 1); + handler.apply(this, args); + } + } else if (isObject(handler)) { + args = Array.prototype.slice.call(arguments, 1); + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) { + listeners[i].apply(this, args); + } + } + + return true; + }; + + EventEmitter.prototype.addListener = function (type, listener) { + var m; + + if (!isFunction(listener)) throw TypeError('listener must be a function'); + + if (!this._events) this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) this.emit('newListener', type, isFunction(listener.listener) ? listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener;else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener);else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; + }; + + EventEmitter.prototype.on = EventEmitter.prototype.addListener; + + EventEmitter.prototype.once = function (type, listener) { + if (!isFunction(listener)) throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; + }; + + // emits a 'removeListener' event iff the listener was removed + EventEmitter.prototype.removeListener = function (type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || isFunction(list.listener) && list.listener === listener) { + delete this._events[type]; + if (this._events.removeListener) this.emit('removeListener', type, listener); + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || list[i].listener && list[i].listener === listener) { + position = i; + break; + } + } + + if (position < 0) return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) this.emit('removeListener', type, listener); + } + + return this; + }; + + EventEmitter.prototype.removeAllListeners = function (type) { + var key, listeners; + + if (!this._events) return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) this._events = {};else if (this._events[type]) delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else if (listeners) { + // LIFO order + while (listeners.length) { + this.removeListener(type, listeners[listeners.length - 1]); + } + } + delete this._events[type]; + + return this; + }; + + EventEmitter.prototype.listeners = function (type) { + var ret; + if (!this._events || !this._events[type]) ret = [];else if (isFunction(this._events[type])) ret = [this._events[type]];else ret = this._events[type].slice(); + return ret; + }; + + EventEmitter.prototype.listenerCount = function (type) { + if (this._events) { + var evlistener = this._events[type]; + + if (isFunction(evlistener)) return 1;else if (evlistener) return evlistener.length; + } + return 0; + }; + + EventEmitter.listenerCount = function (emitter, type) { + return emitter.listenerCount(type); + }; + + function isFunction(arg) { + return typeof arg === 'function'; + } + + function isNumber(arg) { + return typeof arg === 'number'; + } + + function isObject(arg) { + return (typeof arg === 'undefined' ? 'undefined' : babelHelpers.typeof(arg)) === 'object' && arg !== null; + } + + function isUndefined(arg) { + return arg === void 0; + } + }); + + var EventEmitter = events && (typeof events === 'undefined' ? 'undefined' : babelHelpers.typeof(events)) === 'object' && 'default' in events ? events['default'] : events; + + var Test = function Test(testName, suiteName, status, runtime, errors) { + babelHelpers.classCallCheck(this, Test); + + this.testName = testName; + this.suiteName = suiteName; + this.status = status; + this.runtime = runtime; + this.errors = errors; + }; + + var Suite = function () { + + /** + * + * @param name + * @param childSuites + * @param tests: array containing tests belonging to the suite but not to a child suite + */ + + function Suite(name, childSuites, tests) { + babelHelpers.classCallCheck(this, Suite); + + this.name = name; + this.childSuites = childSuites; + this.tests = tests; + } + + babelHelpers.createClass(Suite, [{ + key: 'getAllTests', + value: function getAllTests() { + var childSuiteTests = this.childSuites.map(function (suite) { + return suite.getAllTests(); + }).reduce(function (allTests, a) { + return allTests.concat(a); + }, []); + + return this.tests.concat(childSuiteTests); + } + }, { + key: 'runtime', + get: function get() { + var status = this.status; + + if (status === 'skipped' || status === undefined) { + return undefined; + } + + var runtime = this.getAllTests().map(function (test) { + return test.status === 'skipped' ? 0 : test.runtime; + }).reduce(function (sum, testRuntime) { + return sum + testRuntime; + }, 0); + + return runtime; + } + }, { + key: 'status', + get: function get() { + var passed = 0; + var failed = 0; + var skipped = 0; + + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = this.getAllTests()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var test = _step.value; + + // If a suite contains a test whose status is still undefined, + // there is no final status for the suite as well. + if (test.status === undefined) { + return undefined; + } else if (test.status === 'passed') { + passed++; + } else if (test.status === 'skipped') { + skipped++; + } else { + failed++; + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + if (failed > 0) { + return 'failed'; + } else if (skipped > 0 && passed === 0) { + return 'skipped'; + } else { + return 'passed'; + } + } + }]); + return Suite; + }(); + + Object.defineProperties(Suite.prototype, { + toJSON: { + value: function value() { + var ret = {}; + for (var x in this) { + ret[x] = this[x]; + } + return ret; + } + }, + runtime: { + enumerable: true + }, + status: { + enumerable: true + } + }); + + var QUnitAdapter = function (_EventEmitter) { + babelHelpers.inherits(QUnitAdapter, _EventEmitter); + + function QUnitAdapter(QUnit) { + babelHelpers.classCallCheck(this, QUnitAdapter); + + var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(QUnitAdapter).call(this)); + + _this.QUnit = QUnit; + _this.tests = {}; + + QUnit.begin(_this.onBegin.bind(_this)); + QUnit.testStart(_this.onTestStart.bind(_this)); + QUnit.log(_this.onLog.bind(_this)); + QUnit.testDone(_this.onTestDone.bind(_this)); + QUnit.done(_this.onDone.bind(_this)); + return _this; + } + + babelHelpers.createClass(QUnitAdapter, [{ + key: 'convertModule', + value: function convertModule(qunitModule) { + var _this2 = this; + + return new Suite(qunitModule.name, [], qunitModule.tests.map(function (qunitTest) { + var test = new Test(qunitTest.name, qunitModule.name.replace(/> /g, '')); + + _this2.tests[qunitTest.testId] = test; + + return test; + })); + } + }, { + key: 'saveTestDetails', + value: function saveTestDetails(qunitTest) { + var test = this.tests[qunitTest.testId]; + + test.errors = this.errors; + + if (qunitTest.failed > 0) { + test.status = 'failed'; + } else if (qunitTest.skipped) { + test.status = 'skipped'; + } else { + test.status = 'passed'; + } + + // Workaround for QUnit skipped tests runtime which is a Number. + if (test.status !== 'skipped') { + test.runtime = qunitTest.runtime; + } else { + test.runtime = undefined; + } + } + }, { + key: 'createGlobalSuite', + value: function createGlobalSuite() { + var topLevelSuites = []; + var globalSuite; + var modules; + + // Access QUnit internals to get all suites and tests, working around + // missing event data. + + // Create the global suite first. + if (this.QUnit.config.modules.length > 0 && this.QUnit.config.modules[0].name === '') { + globalSuite = this.convertModule(this.QUnit.config.modules[0]); + globalSuite.name = undefined; + + // The suiteName of global tests must be undefined. + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = globalSuite.tests[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var test = _step.value; + + test.suiteName = undefined; + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + modules = this.QUnit.config.modules.slice(1); + } else { + globalSuite = new Suite(undefined, [], []); + modules = this.QUnit.config.modules; + } + + // Build a list with all suites. + var suites = modules.map(this.convertModule.bind(this)); + + // Iterate through the whole suites and check if they have composed names, + // like "suiteName1 > suiteName2 > ... > suiteNameN". + // + // If a suite has a composed name, its name will be the last in the sequence + // and its parent name will be the one right before it. Search the parent + // suite after its name and then add the suite with the composed name to the + // childSuites. + // + // If a suite does not have a composed name, add it to the topLevelSuites, + // this means that this suite is the direct child of the global suite. + var _iteratorNormalCompletion2 = true; + var _didIteratorError2 = false; + var _iteratorError2 = undefined; + + try { + for (var _iterator2 = suites[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { + var suite = _step2.value; + + var indexEnd = suite.name.lastIndexOf(' > '); + + if (indexEnd !== -1) { + // Find the ' > ' characters that appears before the parent name. + var indexStart = suite.name.substring(0, indexEnd).lastIndexOf(' > '); + // If it is -1, the parent suite name starts at 0, else escape + // this characters ' > '. + indexStart = indexStart === -1 ? 0 : indexStart + 3; + + var parentSuiteName = suite.name.substring(indexStart, indexEnd); + + // Keep only the name of the suite itself. + suite.name = suite.name.substring(indexEnd + 3); + + var _iteratorNormalCompletion3 = true; + var _didIteratorError3 = false; + var _iteratorError3 = undefined; + + try { + for (var _iterator3 = suites[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { + var parentSuite = _step3.value; + + if (parentSuite.name === parentSuiteName) { + parentSuite.childSuites.push(suite); + } + } + } catch (err) { + _didIteratorError3 = true; + _iteratorError3 = err; + } finally { + try { + if (!_iteratorNormalCompletion3 && _iterator3.return) { + _iterator3.return(); + } + } finally { + if (_didIteratorError3) { + throw _iteratorError3; + } + } + } + } else { + topLevelSuites.push(suite); + } + } + } catch (err) { + _didIteratorError2 = true; + _iteratorError2 = err; + } finally { + try { + if (!_iteratorNormalCompletion2 && _iterator2.return) { + _iterator2.return(); + } + } finally { + if (_didIteratorError2) { + throw _iteratorError2; + } + } + } + + globalSuite.childSuites = topLevelSuites; + + return globalSuite; + } + }, { + key: 'createSuiteStart', + value: function createSuiteStart(suite) { + return new Suite(suite.name, suite.childSuites.map(this.createSuiteStart.bind(this)), suite.tests.map(this.createTestStart.bind(this))); + } + }, { + key: 'createSuiteEnd', + value: function createSuiteEnd(suite) { + return new Suite(suite.name, suite.childSuites.map(this.createSuiteEnd.bind(this)), suite.tests.map(this.createTestEnd.bind(this))); + } + }, { + key: 'createTestStart', + value: function createTestStart(test) { + return new Test(test.testName, test.suiteName); + } + }, { + key: 'createTestEnd', + value: function createTestEnd(test) { + return new Test(test.testName, test.suiteName, test.status, test.runtime, test.errors); + } + }, { + key: 'emitData', + value: function emitData(suite) { + var _iteratorNormalCompletion4 = true; + var _didIteratorError4 = false; + var _iteratorError4 = undefined; + + try { + for (var _iterator4 = suite.tests[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { + var test = _step4.value; + + this.emit('testStart', this.createTestStart(test)); + this.emit('testEnd', this.createTestEnd(test)); + } + } catch (err) { + _didIteratorError4 = true; + _iteratorError4 = err; + } finally { + try { + if (!_iteratorNormalCompletion4 && _iterator4.return) { + _iterator4.return(); + } + } finally { + if (_didIteratorError4) { + throw _iteratorError4; + } + } + } + + var _iteratorNormalCompletion5 = true; + var _didIteratorError5 = false; + var _iteratorError5 = undefined; + + try { + for (var _iterator5 = suite.childSuites[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { + var _suite = _step5.value; + + this.emit('suiteStart', this.createSuiteStart(_suite)); + this.emitData(_suite); + this.emit('suiteEnd', this.createSuiteEnd(_suite)); + } + } catch (err) { + _didIteratorError5 = true; + _iteratorError5 = err; + } finally { + try { + if (!_iteratorNormalCompletion5 && _iterator5.return) { + _iterator5.return(); + } + } finally { + if (_didIteratorError5) { + throw _iteratorError5; + } + } + } + } + }, { + key: 'onBegin', + value: function onBegin() { + this.globalSuite = this.createGlobalSuite(); + } + }, { + key: 'onTestStart', + value: function onTestStart(details) { + this.errors = []; + } + }, { + key: 'onLog', + value: function onLog(details) { + if (!details.result) { + this.errors.push(details); + } + } + }, { + key: 'onTestDone', + value: function onTestDone(details) { + this.saveTestDetails(details); + } + }, { + key: 'onDone', + value: function onDone() { + this.emit('runStart', this.createSuiteStart(this.globalSuite)); + this.emitData(this.globalSuite); + this.emit('runEnd', this.createSuiteEnd(this.globalSuite)); + } + }]); + return QUnitAdapter; + }(EventEmitter); + + /** + * Limitations: + * - Errors in afterAll are ignored. + */ + + var JasmineAdapter = function (_EventEmitter) { + babelHelpers.inherits(JasmineAdapter, _EventEmitter); + + function JasmineAdapter(jasmine) { + babelHelpers.classCallCheck(this, JasmineAdapter); + + var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(JasmineAdapter).call(this)); + + _this.jasmine = jasmine; + jasmine.addReporter({ + jasmineStarted: _this.onJasmineStarted.bind(_this), + specDone: _this.onSpecDone.bind(_this), + specStarted: _this.onSpecStarted.bind(_this), + suiteStarted: _this.onSuiteStarted.bind(_this), + suiteDone: _this.onSuiteDone.bind(_this), + jasmineDone: _this.onJasmineDone.bind(_this) + }); + + _this.suites = {}; + _this.tests = {}; + return _this; + } + + babelHelpers.createClass(JasmineAdapter, [{ + key: 'createSuiteStart', + value: function createSuiteStart(suite) { + return new Suite(suite.name, suite.childSuites.map(this.createSuiteStart.bind(this)), suite.tests.map(this.createTestStart.bind(this))); + } + }, { + key: 'createSuiteEnd', + value: function createSuiteEnd(suite) { + return new Suite(suite.name, suite.childSuites.map(this.createSuiteEnd.bind(this)), suite.tests.map(this.createTestEnd.bind(this))); + } + }, { + key: 'createTestStart', + value: function createTestStart(test) { + return new Test(test.testName, test.suiteName); + } + }, { + key: 'createTestEnd', + value: function createTestEnd(test) { + return new Test(test.testName, test.suiteName, test.status, test.runtime, test.errors); + } + }, { + key: 'saveTestDetails', + value: function saveTestDetails(jasmineSpec) { + var test = this.tests[jasmineSpec.id]; + + test.errors = jasmineSpec.failedExpectations; + + if (jasmineSpec.status === 'pending') { + test.status = 'skipped'; + } else { + test.status = jasmineSpec.status; + test.runtime = new Date() - this.startTime; + } + } + }, { + key: 'isJasmineGlobalSuite', + value: function isJasmineGlobalSuite(suite) { + return suite.description === 'Jasmine__TopLevel__Suite'; + } + + /** + * Jasmine provides details about childSuites and tests only in the structure + * returned by "this.jasmine.topSuite()". + * + * This function creates the global suite for the runStart event, as also + * saves the created suites and tests compliant with the CRI standard in an + * object using as key their unique ids provided by Jasmine. + */ + + }, { + key: 'createGlobalSuite', + value: function createGlobalSuite(jasmineSuite) { + var childSuites = []; + var tests = []; + + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = jasmineSuite.children[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var child = _step.value; + + if (child.id.indexOf('suite') === 0) { + childSuites.push(this.createGlobalSuite(child)); + } else { + var suiteName = void 0; + var test = void 0; + + // Jasmine full description is of form "suite1 suite2 ... suiteN test", + // for the "suiteName" property we need to remove test name. + if (!this.isJasmineGlobalSuite(jasmineSuite)) { + suiteName = child.result.fullName.substring(0, child.result.fullName.indexOf(child.description) - 1); + } + + test = new Test(child.description, suiteName); + + tests.push(test); + this.tests[child.id] = test; + } + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + + var name = this.isJasmineGlobalSuite(jasmineSuite) ? undefined : jasmineSuite.description; + var suite = new Suite(name, childSuites, tests); + + this.suites[jasmineSuite.id] = suite; + + return suite; + } + }, { + key: 'onJasmineStarted', + value: function onJasmineStarted() { + this.globalSuite = this.createGlobalSuite(this.jasmine.topSuite()); + this.emit('runStart', this.createSuiteStart(this.globalSuite)); + } + }, { + key: 'onSpecStarted', + value: function onSpecStarted(details) { + this.startTime = new Date(); + this.emit('testStart', this.createTestStart(this.tests[details.id])); + } + }, { + key: 'onSpecDone', + value: function onSpecDone(details) { + this.saveTestDetails(details); + this.emit('testEnd', this.createTestEnd(this.tests[details.id])); + } + }, { + key: 'onSuiteStarted', + value: function onSuiteStarted(details) { + this.emit('suiteStart', this.createSuiteStart(this.suites[details.id])); + } + }, { + key: 'onSuiteDone', + value: function onSuiteDone(details) { + this.emit('suiteEnd', this.createSuiteEnd(this.suites[details.id])); + } + }, { + key: 'onJasmineDone', + value: function onJasmineDone() { + this.emit('runEnd', this.createSuiteEnd(this.globalSuite)); + } + }]); + return JasmineAdapter; + }(EventEmitter); + + var MochaAdapter = function (_EventEmitter) { + babelHelpers.inherits(MochaAdapter, _EventEmitter); + + function MochaAdapter(mocha) { + babelHelpers.classCallCheck(this, MochaAdapter); + + var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(MochaAdapter).call(this)); + + _this.mocha = mocha; + + mocha.reporter(function (runner) { + _this.runner = runner; + + runner.on('start', _this.onStart.bind(_this)); + runner.on('suite', _this.onSuite.bind(_this)); + runner.on('test', _this.onTest.bind(_this)); + runner.on('pending', _this.onPending.bind(_this)); + runner.on('fail', _this.onFail.bind(_this)); + runner.on('test end', _this.onTestEnd.bind(_this)); + runner.on('suite end', _this.onSuiteEnd.bind(_this)); + runner.on('end', _this.onEnd.bind(_this)); + }); + return _this; + } + + babelHelpers.createClass(MochaAdapter, [{ + key: 'convertSuite', + value: function convertSuite(mochaSuite) { + return new Suite(mochaSuite.title, mochaSuite.suites.map(this.convertSuite.bind(this)), mochaSuite.tests.map(this.convertTest.bind(this))); + } + }, { + key: 'convertTest', + value: function convertTest(mochaTest) { + var suiteName; + + if (!mochaTest.parent.root) { + suiteName = this.buildSuiteName(mochaTest.parent); + } + + // If the test has the errors attached a "test end" must be emitted, else + // a "test start". + if (mochaTest.errors !== undefined) { + var status = mochaTest.state === undefined ? 'skipped' : mochaTest.state; + + // Test end. + return new Test(mochaTest.title, suiteName, status, mochaTest.duration, mochaTest.errors); + } + + // Test start. + return new Test(mochaTest.title, suiteName); + } + + /** + * Builds a concatenated name from nested suites. + */ + + }, { + key: 'buildSuiteName', + value: function buildSuiteName(mochaSuite) { + var suiteName = mochaSuite.title; + var parent = mochaSuite.parent; + + while (!parent.root) { + suiteName = parent.title + ' ' + suiteName; + parent = parent.parent; + } + + return suiteName; + } + }, { + key: 'onStart', + value: function onStart() { + var globalSuiteStart = this.convertSuite(this.runner.suite); + globalSuiteStart.name = undefined; + + this.emit('runStart', globalSuiteStart); + } + }, { + key: 'onSuite', + value: function onSuite(mochaSuite) { + if (!mochaSuite.root) { + this.emit('suiteStart', this.convertSuite(mochaSuite)); + } + } + }, { + key: 'onTest', + value: function onTest(mochaTest) { + this.errors = []; + + this.emit('testStart', this.convertTest(mochaTest)); + } + + /** + * Emits the start of pending tests, because Mocha does not emit skipped tests + * on its "test" event. + */ + + }, { + key: 'onPending', + value: function onPending(mochaTest) { + this.emit('testStart', this.convertTest(mochaTest)); + } + }, { + key: 'onFail', + value: function onFail(test, error) { + this.errors.push(error); + } + }, { + key: 'onTestEnd', + value: function onTestEnd(mochaTest) { + // Save the errors on Mocha's test object, because when the suite that + // contains this test is emitted on the "suiteEnd" event, it should contain + // also this test with all its details (errors, status, runtime). Runtime + // and status are already attached to the test, but the errors don't. + mochaTest.errors = this.errors; + + this.emit('testEnd', this.convertTest(mochaTest)); + } + }, { + key: 'onSuiteEnd', + value: function onSuiteEnd(mochaSuite) { + if (!mochaSuite.root) { + this.emit('suiteEnd', this.convertSuite(mochaSuite)); + } + } + }, { + key: 'onEnd', + value: function onEnd() { + var globalSuiteEnd = this.convertSuite(this.runner.suite); + globalSuiteEnd.name = undefined; + + this.emit('runEnd', globalSuiteEnd); + } + }]); + return MochaAdapter; + }(EventEmitter); + + var TapReporter = function () { + function TapReporter(runner) { + babelHelpers.classCallCheck(this, TapReporter); + + this.testCount = 0; + + runner.on('runStart', this.onRunStart.bind(this)); + runner.on('testEnd', this.onTestEnd.bind(this)); + runner.on('runEnd', this.onRunEnd.bind(this)); + } + + babelHelpers.createClass(TapReporter, [{ + key: 'onRunStart', + value: function onRunStart(globalSuite) { + console.log('TAP version 13'); + } + }, { + key: 'onTestEnd', + value: function onTestEnd(test) { + this.testCount = this.testCount + 1; + + // TODO maybe switch to test.fullName + // @see https://github.com/js-reporters/js-reporters/issues/65 + if (test.status === 'passed') { + console.log('ok ' + this.testCount + ' ' + test.testName); + } else if (test.status === 'skipped') { + console.log('ok ' + this.testCount + ' ' + test.testName + ' # SKIP'); + } else { + console.log('not ok ' + this.testCount + ' ' + test.testName); + + var _iteratorNormalCompletion = true; + var _didIteratorError = false; + var _iteratorError = undefined; + + try { + for (var _iterator = test.errors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { + var error = _step.value; + + console.log(' ---'); + console.log(' message: "' + error.toString() + '"'); + console.log(' severity: failed'); + console.log(' ...'); + } + } catch (err) { + _didIteratorError = true; + _iteratorError = err; + } finally { + try { + if (!_iteratorNormalCompletion && _iterator.return) { + _iterator.return(); + } + } finally { + if (_didIteratorError) { + throw _iteratorError; + } + } + } + } + } + }, { + key: 'onRunEnd', + value: function onRunEnd(globalSuite) { + console.log('1..' + this.testCount); + } + }], [{ + key: 'init', + value: function init(runner) { + return new TapReporter(runner); + } + }]); + return TapReporter; + }(); + + // TODO: finish grouping once suiteStart is implemented + var hasGrouping = 'group' in console && 'groupEnd' in console; + + var ConsoleReporter = function () { + function ConsoleReporter(runner) { + babelHelpers.classCallCheck(this, ConsoleReporter); + + runner.on('runStart', this.onRunStart); + runner.on('suiteStart', this.onSuiteStart); + runner.on('testStart', this.onTestStart); + runner.on('testEnd', this.onTestEnd); + runner.on('suiteEnd', this.onSuiteEnd); + runner.on('runEnd', this.onRunEnd); + } + + babelHelpers.createClass(ConsoleReporter, [{ + key: 'onRunStart', + value: function onRunStart(suite) { + console.log('runStart', suite); + } + }, { + key: 'onSuiteStart', + value: function onSuiteStart(suite) { + if (hasGrouping) { + console.group(suite.name); + } + console.log('suiteStart', suite); + } + }, { + key: 'onTestStart', + value: function onTestStart(test) { + console.log('testStart', test); + } + }, { + key: 'onTestEnd', + value: function onTestEnd(test) { + console.log('testEnd', test); + } + }, { + key: 'onSuiteEnd', + value: function onSuiteEnd(suite) { + console.log('suiteEnd', suite); + if (hasGrouping) { + console.groupEnd(); + } + } + }, { + key: 'onRunEnd', + value: function onRunEnd(globalSuite) { + console.log('runEnd', globalSuite); + } + }], [{ + key: 'init', + value: function init(runner) { + return new ConsoleReporter(runner); + } + }]); + return ConsoleReporter; + }(); + + /* + The TestReporter verifies that a test runner outputs the right data in the right order. + To do so, it compares the actual output with the provided reference data. + The result is given in the ok attribute. + */ + + var TestReporter = function () { + + /** + * @param runner: standardized test runner (or adapter) + * @param referenceData: An array of all expected (eventName, eventData) tuples in the right order + */ + + function TestReporter(runner, referenceData) { + babelHelpers.classCallCheck(this, TestReporter); + + this.referenceData = referenceData.slice(); + this.error = false; + runner.on('runStart', this.onEvent.bind(this, 'runStart')); + runner.on('suiteStart', this.onEvent.bind(this, 'suiteStart')); + runner.on('testStart', this.onEvent.bind(this, 'testStart')); + runner.on('testEnd', this.onEvent.bind(this, 'testEnd')); + runner.on('suiteEnd', this.onEvent.bind(this, 'suiteEnd')); + runner.on('runEnd', this.onEvent.bind(this, 'runEnd')); + } + + /** + * Gets called on each event emitted by the runner. Checks if the actual event matches the expected event. + */ + + + babelHelpers.createClass(TestReporter, [{ + key: 'onEvent', + value: function onEvent(eventName, eventData) { + var _referenceData$shift = this.referenceData.shift(); + + var _referenceData$shift2 = babelHelpers.slicedToArray(_referenceData$shift, 2); + + var expectedEventName = _referenceData$shift2[0]; + var expectedEventData = _referenceData$shift2[1]; + + + if (eventName !== expectedEventName || !this.equal(eventData, expectedEventData)) { + this.error = true; + console.error('expected:', expectedEventName, expectedEventData, '\r\n', 'actual:', eventName, eventData); + } + } + }, { + key: 'equal', + + + /** + * Helper function to compare + * - two Test objects + * - two Suite objects + * - two arrays of Test or Suite objects + * The equality check is not completely strict, e.g. the runtime of a Test does not have to be equal. + * @returns {boolean}: true if both objects are equal, false otherwise + */ + value: function equal(actual, expected) { + if (expected instanceof Suite) { + if (actual.name !== expected.name) { + return false; + } + if (!this.equal(actual.childSuites, expected.childSuites)) { + return false; + } + + if (!this.equal(actual.tests, expected.tests)) { + return false; + } + } else if (expected instanceof Test) { + var _arr = ['testName', 'suiteName', 'status']; + + for (var _i = 0; _i < _arr.length; _i++) { + var property = _arr[_i]; + if (actual[property] !== expected[property]) { + return false; + } + } + if (typeof actual.runtime !== 'number' && actual.runtime !== undefined) { + return false; + } + + if (!(actual.errors === undefined && expected.errors === undefined) && actual.errors.length !== expected.errors.length) { + return false; + } + } else if (Array.isArray(expected)) { + if (actual.length !== expected.length) { + return false; + } + + for (var i = 0; i < expected.length; i++) { + if (!this.equal(actual[i], expected[i])) { + return false; + } + } + } else { + return false; + } + return true; + } + }, { + key: 'ok', + get: function get() { + return !this.error && this.referenceData.length === 0; + } + }]); + return TestReporter; + }(); + + var index = { + QUnitAdapter: QUnitAdapter, + JasmineAdapter: JasmineAdapter, + MochaAdapter: MochaAdapter, + TapReporter: TapReporter, + ConsoleReporter: ConsoleReporter, + TestReporter: TestReporter, + Test: Test, + Suite: Suite + }; + + return index; + +})); \ No newline at end of file diff --git a/lib/server.js b/lib/server.js index 180b442..53a7e63 100644 --- a/lib/server.js +++ b/lib/server.js @@ -35,7 +35,8 @@ exports.Server = function Server(bsClient, workers) { var scripts = [ 'json2.js', 'browserstack.js', - 'browserstack-util.js' + 'browserstack-util.js', + 'js-reporters.js' ]; var framework_scripts = { From f9326220a13f50fecfb6dddde5182775ea459bf8 Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 19:09:34 +0100 Subject: [PATCH 02/16] QUnitReporter: update QUnit reporter based on the js-reporter adapter. --- lib/_patch/my-mocha.js | 34 ++++++++++++++++++++ lib/_patch/qunit-plugin.js | 63 ++++++++++++-------------------------- 2 files changed, 54 insertions(+), 43 deletions(-) create mode 100644 lib/_patch/my-mocha.js diff --git a/lib/_patch/my-mocha.js b/lib/_patch/my-mocha.js new file mode 100644 index 0000000..51e6ce3 --- /dev/null +++ b/lib/_patch/my-mocha.js @@ -0,0 +1,34 @@ +(function() { + var runner = new JsReporters.MochaAdapter(mocha); + var errors = [] + var passed = 0, failed = 0, total = 0; + var startTime; + + runner.on('runStart', function() { + startTime = new Date() + }); + + runner.on('testEnd', function(test) { + total = total + 1; + + passed = passed + (test.status === 'passed' ? 1 : 0); + failed = failed + (test.status === 'failed' ? 1 : 0); + + test.errors.forEach(function(error) { + errors.push(error) + }); + }); + + runner.on('runEnd', function() { + var results = {} + + results.runtime = new Date() - startTime; + results.total = total; + results.passed = passed; + results.failed = failed; + results.tracebacks = errors; + results.url = window.location.pathname; + + BrowserStack.post("/_report", results, function() {}); + }); +})(); diff --git a/lib/_patch/qunit-plugin.js b/lib/_patch/qunit-plugin.js index 8ef3f10..663a139 100644 --- a/lib/_patch/qunit-plugin.js +++ b/lib/_patch/qunit-plugin.js @@ -7,56 +7,33 @@ factory(QUnit); } }(function(QUnit) { - var failedAssertions = []; - var options, - currentModule, - currentTest, - setTimeoutVariable; - var pendingTest = {}; + var runner = new JsReporters.QUnitAdapter(QUnit); + var tracebacks = []; + var total = 0, + passed = 0, + failed = 0; - var testTimeout = function() { - var error = { - testName: currentTest, - message: "Stuck on this test for 60 sec." - }; + runner.on('testEnd', function(test) { + total = total + 1 - BrowserStack.post('/_progress', { - tracebacks: [error] - }, function(){}); - }; + passed = passed + (test.status === 'passed' ? 1 : 0); + failed = failed + (test.status === 'failed' ? 1 : 0); - QUnit.testDone(function(details) { - var ct = details.module + " - " + details.name; - clearTimeout(pendingTest[ct]); + test.errors.forEach(function(error) { + tracebacks.push(error) + }); }); - QUnit.testStart(function(details) { - currentTest = details.module + " - " + details.name; - pendingTest[currentTest] = setTimeout(function() { - testTimeout(currentTest); - }, 60000); - }); - - QUnit.log(function(details) { - if (details.result) { - return; - } - - var error = { - actual: details.actual, - expected: details.expected, - message: details.message, - source: details.source, - testName:( details.module + ": " + details.name) - }; + runner.on('runEnd', function(globalSuite) { + var results = {}; - BrowserStack.post('/_progress', { - tracebacks: [error] - }, function(){}); - }); - - QUnit.done(function(results) { + results.runtime = globalSuite.runtime; + results.total = total; + results.passed = passed; + results.failed = failed; + results.tracebacks = tracebacks; results.url = window.location.pathname; + BrowserStack.post("/_report", results, function(){}); }); })); From 8c88f26b7a996ebccb20d26b0ffb1a873ea1e8c4 Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 19:13:02 +0100 Subject: [PATCH 03/16] Testing: update QUnit expected results from assertions to tests. --- tests/external-tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/external-tests.js b/tests/external-tests.js index 053803f..cb2fc72 100755 --- a/tests/external-tests.js +++ b/tests/external-tests.js @@ -31,8 +31,8 @@ var repositories = [ 'test/index.html' ], expected_results: { - tests: 534, - passed: 534, + tests: 133, + passed: 130, failed: 0 } }, From 4644eed2b7353850ff8164cbf1080c7207eee615 Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 19:21:04 +0100 Subject: [PATCH 04/16] JasmineReporter: update Jasmine reporter based on the js-reporters adapter. --- lib/_patch/jasmine2-plugin.js | 50 +++++++++++++++++++---------------- lib/server.js | 1 - 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/lib/_patch/jasmine2-plugin.js b/lib/_patch/jasmine2-plugin.js index a716cdc..05581e7 100644 --- a/lib/_patch/jasmine2-plugin.js +++ b/lib/_patch/jasmine2-plugin.js @@ -1,28 +1,32 @@ (function() { - var checker = setInterval(function() { - if (!jasmine.running) { - var results = {}; - var specs = jsApiReporter.specs(); - results.runtime = jsApiReporter.executionTime(); - results.total = 0; - results.passed = 0; - results.failed = 0; - results.tracebacks = []; + var runner = new JsReporters.JasmineAdapter(jasmine.getEnv()); + var tracebacks = []; + var total = 0, + passed = 0, + failed = 0; - for (var spec in specs) { - if (specs[spec].status === 'passed') { - results.passed++; - } else { - results.tracebacks.push(specs[spec].description); - results.failed++; - } - } + runner.on('testEnd', function(test) { + total = total + 1 - results.total = results.passed + results.failed; - results.url = window.location.pathname; - BrowserStack.post('/_report', results, function(){}); - } - clearInterval(checker); - }, 1000); + passed = passed + (test.status === 'passed' ? 1 : 0); + failed = failed + (test.status === 'failed' ? 1 : 0); + + test.errors.forEach(function(error) { + tracebacks.push(error) + }); + }); + + runner.on('runEnd', function(globalSuite) { + var results = {}; + + results.runtime = globalSuite.runtime; + results.total = total; + results.passed = passed; + results.failed = failed; + results.tracebacks = tracebacks; + results.url = window.location.pathname; + + BrowserStack.post("/_report", results, function() {}); + }); })(); diff --git a/lib/server.js b/lib/server.js index 53a7e63..421863f 100644 --- a/lib/server.js +++ b/lib/server.js @@ -63,7 +63,6 @@ exports.Server = function Server(bsClient, workers) { framework_scripts['jasmine'].forEach(function(script) { patch += '\n'; }); - patch += '\n'; } else if (framework === 'jasmine2') { framework_scripts['jasmine2'].forEach(function(script) { patch += '\n'; From 1dcb2282646ba1208bd21303b20c23ec1e20f4f9 Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 19:31:14 +0100 Subject: [PATCH 05/16] Testing: remove QUnit 1.0.0, optional repo. --- tests/external-tests.js | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/tests/external-tests.js b/tests/external-tests.js index cb2fc72..a516ad4 100755 --- a/tests/external-tests.js +++ b/tests/external-tests.js @@ -113,29 +113,6 @@ var repositories = [ ]; var repositoriesOptional = [ - { - name: 'qunit', - tag: 'v1.0.0', - url: 'https://github.com/jquery/qunit.git', - test_framework: 'qunit', - browsers: [ - { - 'browser': 'firefox', - 'browser_version': '44.0', - 'os': 'OS X', - 'os_version': 'Snow Leopard' - } - ], - test_path: [ - 'test/index.html', - 'test/logs.html' - ], - expected_results: { - tests: 323, - passed: 323, - failed: 0 - } - }, { name: 'mocha', tag: '1.21.5', From e0c829cc6d4b9cf116f61a98a376aa0be1d67d0b Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 21:35:48 +0100 Subject: [PATCH 06/16] MochaReporter: update Mocha reporter based on js-reporters adapter. --- lib/_patch/mocha-plugin.js | 87 ++++++++++---------------------------- lib/server.js | 1 - 2 files changed, 23 insertions(+), 65 deletions(-) diff --git a/lib/_patch/mocha-plugin.js b/lib/_patch/mocha-plugin.js index a102661..6a26160 100644 --- a/lib/_patch/mocha-plugin.js +++ b/lib/_patch/mocha-plugin.js @@ -1,73 +1,32 @@ (function() { - function stack(err) { - var str = err.stack || err.toString(); + var runner = new JsReporters.MochaAdapter(mocha); + var tracebacks = []; + var total = 0, + passed = 0, + failed = 0; - if (!~str.indexOf(err.message)) { - str = err.message + '\n' + str; - } + runner.on('testEnd', function(test) { + total = total + 1 - if ('[object Error]' == str) { - str = err.message; - } + passed = passed + (test.status === 'passed' ? 1 : 0); + failed = failed + (test.status === 'failed' ? 1 : 0); - if (!err.stack && err.sourceURL && err.line !== undefined) { - str += '\n(' + err.sourceURL + ':' + err.line + ')'; - } - return str.replace(/^/gm, ' '); - } - - function title(test) { - return test.fullTitle().replace(/#/g, ''); - } - - var origReporter = mocha._reporter; - - Mocha.BrowserStack = function(runner, root) { - origReporter.apply(this, arguments); - - var count = 1, - that = this, - failures = 0, - passes = 0, - start = 0, - tracebacks = []; - - runner.on('start', function() { - start = (new Date).getTime(); - }); - - runner.on('test end', function(test) { - count += 1; + test.errors.forEach(function(error) { + tracebacks.push(error) }); + }); - runner.on('pass', function(test) { - passes += 1; - }); - - runner.on('fail', function(test, err) { - failures += 1; - - if (err) { - tracebacks.push(stack(err)); - } - }); - - runner.on('end', function() { - // delay posting results a little to capture "multiple-done" errors - setTimeout(function () { - results = {}; - results.runtime = (new Date).getTime() - start; - results.total = passes + failures; - results.passed = passes; - results.failed = failures; - results.tracebacks = tracebacks; - results.url = window.location.pathname; - BrowserStack.post("/_report", results, function(){}); - }, 1000); - }); - }; + runner.on('runEnd', function(globalSuite) { + var results = {}; - Mocha.BrowserStack.prototype = origReporter.prototype; + // TODO investigate why globalSuite.runtime is not working + results.runtime = 0; + results.total = total; + results.passed = passed; + results.failed = failed; + results.tracebacks = tracebacks; + results.url = window.location.pathname; - return Mocha.BrowserStack; + BrowserStack.post("/_report", results, function() {}); + }); })(); diff --git a/lib/server.js b/lib/server.js index 421863f..df0f41a 100644 --- a/lib/server.js +++ b/lib/server.js @@ -71,7 +71,6 @@ exports.Server = function Server(bsClient, workers) { framework_scripts['mocha'].forEach(function(script) { patch += '\n'; }); - patch += '\n'; } else if (framework === 'qunit') { framework_scripts['qunit'].forEach(function(script) { patch += '\n'; From 3100f07515230e4faaa397046462b9b7a3923be0 Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 21:36:20 +0100 Subject: [PATCH 07/16] Testing: update expected results for Mocha 1.21.5. --- tests/external-tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/external-tests.js b/tests/external-tests.js index a516ad4..ee8b95f 100755 --- a/tests/external-tests.js +++ b/tests/external-tests.js @@ -132,9 +132,9 @@ var repositoriesOptional = [ 'test/browser/opts.html' ], expected_results: { - tests: 84, + tests: 83, passed: 77, - failed: 7 + failed: 6 } } ]; From 0a18c5a143eddbb9c97cbd918ff7734cd4c7fa9c Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 21:37:27 +0100 Subject: [PATCH 08/16] Testing: comment out Mocha 2.4.5. --- tests/external-tests.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/external-tests.js b/tests/external-tests.js index ee8b95f..cab1695 100755 --- a/tests/external-tests.js +++ b/tests/external-tests.js @@ -36,7 +36,7 @@ var repositories = [ failed: 0 } }, - { + /*{ name: 'mocha', tag: 'v2.4.5', url: 'https://github.com/mochajs/mocha.git', @@ -59,7 +59,7 @@ var repositories = [ passed: 80, failed: 9 } - }, + },*/ { name: 'spine', tag: 'v.1.6.2', From fb64f9bb7aca8914e4a4dc866da004caabf54ecb Mon Sep 17 00:00:00 2001 From: Florentin Date: Wed, 6 Jul 2016 21:52:27 +0100 Subject: [PATCH 09/16] JasmineReporter: add back script for Jasmine1. --- lib/server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/server.js b/lib/server.js index df0f41a..ac71c80 100644 --- a/lib/server.js +++ b/lib/server.js @@ -63,6 +63,7 @@ exports.Server = function Server(bsClient, workers) { framework_scripts['jasmine'].forEach(function(script) { patch += '\n'; }); + patch += '\n'; } else if (framework === 'jasmine2') { framework_scripts['jasmine2'].forEach(function(script) { patch += '\n'; From e9a8acffc702697bbc013fed3bf7afaccb2ffc9d Mon Sep 17 00:00:00 2001 From: Florentin Date: Thu, 7 Jul 2016 23:23:27 +0100 Subject: [PATCH 10/16] Files: remove inutile file. --- lib/_patch/my-mocha.js | 34 ---------------------------------- 1 file changed, 34 deletions(-) delete mode 100644 lib/_patch/my-mocha.js diff --git a/lib/_patch/my-mocha.js b/lib/_patch/my-mocha.js deleted file mode 100644 index 51e6ce3..0000000 --- a/lib/_patch/my-mocha.js +++ /dev/null @@ -1,34 +0,0 @@ -(function() { - var runner = new JsReporters.MochaAdapter(mocha); - var errors = [] - var passed = 0, failed = 0, total = 0; - var startTime; - - runner.on('runStart', function() { - startTime = new Date() - }); - - runner.on('testEnd', function(test) { - total = total + 1; - - passed = passed + (test.status === 'passed' ? 1 : 0); - failed = failed + (test.status === 'failed' ? 1 : 0); - - test.errors.forEach(function(error) { - errors.push(error) - }); - }); - - runner.on('runEnd', function() { - var results = {} - - results.runtime = new Date() - startTime; - results.total = total; - results.passed = passed; - results.failed = failed; - results.tracebacks = errors; - results.url = window.location.pathname; - - BrowserStack.post("/_report", results, function() {}); - }); -})(); From b38fe0321cbba59db04598e46c1cbd9eacbd8f97 Mon Sep 17 00:00:00 2001 From: Florentin Date: Thu, 7 Jul 2016 23:46:25 +0100 Subject: [PATCH 11/16] Reporter: merge QUnit, Mocha, Jasmine2 reporters into one. --- lib/_patch/mocha-plugin.js | 32 --------------- lib/_patch/qunit-plugin.js | 39 ------------------- .../{jasmine2-plugin.js => reporter.js} | 15 ++++++- lib/server.js | 19 ++------- 4 files changed, 16 insertions(+), 89 deletions(-) delete mode 100644 lib/_patch/mocha-plugin.js delete mode 100644 lib/_patch/qunit-plugin.js rename lib/_patch/{jasmine2-plugin.js => reporter.js} (58%) diff --git a/lib/_patch/mocha-plugin.js b/lib/_patch/mocha-plugin.js deleted file mode 100644 index 6a26160..0000000 --- a/lib/_patch/mocha-plugin.js +++ /dev/null @@ -1,32 +0,0 @@ -(function() { - var runner = new JsReporters.MochaAdapter(mocha); - var tracebacks = []; - var total = 0, - passed = 0, - failed = 0; - - runner.on('testEnd', function(test) { - total = total + 1 - - passed = passed + (test.status === 'passed' ? 1 : 0); - failed = failed + (test.status === 'failed' ? 1 : 0); - - test.errors.forEach(function(error) { - tracebacks.push(error) - }); - }); - - runner.on('runEnd', function(globalSuite) { - var results = {}; - - // TODO investigate why globalSuite.runtime is not working - results.runtime = 0; - results.total = total; - results.passed = passed; - results.failed = failed; - results.tracebacks = tracebacks; - results.url = window.location.pathname; - - BrowserStack.post("/_report", results, function() {}); - }); -})(); diff --git a/lib/_patch/qunit-plugin.js b/lib/_patch/qunit-plugin.js deleted file mode 100644 index 663a139..0000000 --- a/lib/_patch/qunit-plugin.js +++ /dev/null @@ -1,39 +0,0 @@ -// For logging assertions on the console, here's what grunt-contrib-qunit uses: -// https://github.com/gruntjs/grunt-contrib-qunit/blob/784597023e7235337ca9c0651aa45124a2d72341/tasks/qunit.js#L45 -(function (factory) { - if (typeof define === 'function' && define.amd) { - require(['qunit'], factory); - } else { - factory(QUnit); - } -}(function(QUnit) { - var runner = new JsReporters.QUnitAdapter(QUnit); - var tracebacks = []; - var total = 0, - passed = 0, - failed = 0; - - runner.on('testEnd', function(test) { - total = total + 1 - - passed = passed + (test.status === 'passed' ? 1 : 0); - failed = failed + (test.status === 'failed' ? 1 : 0); - - test.errors.forEach(function(error) { - tracebacks.push(error) - }); - }); - - runner.on('runEnd', function(globalSuite) { - var results = {}; - - results.runtime = globalSuite.runtime; - results.total = total; - results.passed = passed; - results.failed = failed; - results.tracebacks = tracebacks; - results.url = window.location.pathname; - - BrowserStack.post("/_report", results, function(){}); - }); -})); diff --git a/lib/_patch/jasmine2-plugin.js b/lib/_patch/reporter.js similarity index 58% rename from lib/_patch/jasmine2-plugin.js rename to lib/_patch/reporter.js index 05581e7..f8eb9ce 100644 --- a/lib/_patch/jasmine2-plugin.js +++ b/lib/_patch/reporter.js @@ -1,10 +1,20 @@ (function() { - var runner = new JsReporters.JasmineAdapter(jasmine.getEnv()); + var runner; var tracebacks = []; var total = 0, passed = 0, failed = 0; + if (window.QUnit) { + runner = new JsReporters.QUnitAdapter(QUnit); + } else if (window.jasmine) { + runner = new JsReporters.JasmineAdapter(jasmine.getEnv()); + } else if (window.mocha) { + runner = new JsReporters.MochaAdapter(mocha); + } else { + throw new Error('JsReporters: No testing framework was found'); + } + runner.on('testEnd', function(test) { total = total + 1 @@ -19,7 +29,8 @@ runner.on('runEnd', function(globalSuite) { var results = {}; - results.runtime = globalSuite.runtime; + // TODO Investigate why is the runtime not functioning for Mocha. + results.runtime = (window.mocha) ? 0 : globalSuite.runtime; results.total = total; results.passed = passed; results.failed = failed; diff --git a/lib/server.js b/lib/server.js index ac71c80..d98d15e 100644 --- a/lib/server.js +++ b/lib/server.js @@ -40,10 +40,7 @@ exports.Server = function Server(bsClient, workers) { ]; var framework_scripts = { - 'qunit': ['qunit-plugin.js'], - 'jasmine': ['jasmine-jsreporter.js', 'jasmine-plugin.js'], - 'jasmine2': ['jasmine2-plugin.js'], - 'mocha': ['mocha-plugin.js'] + 'jasmine': ['jasmine-jsreporter.js', 'jasmine-plugin.js'] }; var filePath = path.relative(process.cwd(), filename); @@ -64,18 +61,8 @@ exports.Server = function Server(bsClient, workers) { patch += '\n'; }); patch += '\n'; - } else if (framework === 'jasmine2') { - framework_scripts['jasmine2'].forEach(function(script) { - patch += '\n'; - }); - } else if (framework === 'mocha') { - framework_scripts['mocha'].forEach(function(script) { - patch += '\n'; - }); - } else if (framework === 'qunit') { - framework_scripts['qunit'].forEach(function(script) { - patch += '\n'; - }); + } else { + patch += '\n'; } patch += ''; return patch; From 7a9019f4c71b1eec4f885568da7587b78a0a565f Mon Sep 17 00:00:00 2001 From: Florentin Date: Mon, 11 Jul 2016 20:04:20 +0100 Subject: [PATCH 12/16] JsReporters: use npm version. --- lib/_patch/js-reporters.js | 1369 ------------------------------------ lib/server.js | 10 +- 2 files changed, 8 insertions(+), 1371 deletions(-) delete mode 100644 lib/_patch/js-reporters.js diff --git a/lib/_patch/js-reporters.js b/lib/_patch/js-reporters.js deleted file mode 100644 index 9860b75..0000000 --- a/lib/_patch/js-reporters.js +++ /dev/null @@ -1,1369 +0,0 @@ -/** - * JsReporters 1.0.0 - * https://github.com/js-reporters - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: Wed Jul 06 2016 - */ - -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.JsReporters = factory()); -}(this, function () { 'use strict'; - - function __commonjs(fn, module) { return module = { exports: {} }, fn(module, module.exports), module.exports; } - - - var babelHelpers = {}; - babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { - return typeof obj; - } : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; - }; - - babelHelpers.classCallCheck = function (instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - }; - - babelHelpers.createClass = function () { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - return function (Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; - }(); - - babelHelpers.inherits = function (subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); - } - - subClass.prototype = Object.create(superClass && superClass.prototype, { - constructor: { - value: subClass, - enumerable: false, - writable: true, - configurable: true - } - }); - if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; - }; - - babelHelpers.possibleConstructorReturn = function (self, call) { - if (!self) { - throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - } - - return call && (typeof call === "object" || typeof call === "function") ? call : self; - }; - - babelHelpers.slicedToArray = function () { - function sliceIterator(arr, i) { - var _arr = []; - var _n = true; - var _d = false; - var _e = undefined; - - try { - for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { - _arr.push(_s.value); - - if (i && _arr.length === i) break; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i["return"]) _i["return"](); - } finally { - if (_d) throw _e; - } - } - - return _arr; - } - - return function (arr, i) { - if (Array.isArray(arr)) { - return arr; - } else if (Symbol.iterator in Object(arr)) { - return sliceIterator(arr, i); - } else { - throw new TypeError("Invalid attempt to destructure non-iterable instance"); - } - }; - }(); - - babelHelpers; - - var events = __commonjs(function (module) { - // Copyright Joyent, Inc. and other Node contributors. - // - // Permission is hereby granted, free of charge, to any person obtaining a - // copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to permit - // persons to whom the Software is furnished to do so, subject to the - // following conditions: - // - // The above copyright notice and this permission notice shall be included - // in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN - // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE - // USE OR OTHER DEALINGS IN THE SOFTWARE. - - function EventEmitter() { - this._events = this._events || {}; - this._maxListeners = this._maxListeners || undefined; - } - module.exports = EventEmitter; - - // Backwards-compat with node 0.10.x - EventEmitter.EventEmitter = EventEmitter; - - EventEmitter.prototype._events = undefined; - EventEmitter.prototype._maxListeners = undefined; - - // By default EventEmitters will print a warning if more than 10 listeners are - // added to it. This is a useful default which helps finding memory leaks. - EventEmitter.defaultMaxListeners = 10; - - // Obviously not all Emitters should be limited to 10. This function allows - // that to be increased. Set to zero for unlimited. - EventEmitter.prototype.setMaxListeners = function (n) { - if (!isNumber(n) || n < 0 || isNaN(n)) throw TypeError('n must be a positive number'); - this._maxListeners = n; - return this; - }; - - EventEmitter.prototype.emit = function (type) { - var er, handler, len, args, i, listeners; - - if (!this._events) this._events = {}; - - // If there is no 'error' event listener then throw. - if (type === 'error') { - if (!this._events.error || isObject(this._events.error) && !this._events.error.length) { - er = arguments[1]; - if (er instanceof Error) { - throw er; // Unhandled 'error' event - } - throw TypeError('Uncaught, unspecified "error" event.'); - } - } - - handler = this._events[type]; - - if (isUndefined(handler)) return false; - - if (isFunction(handler)) { - switch (arguments.length) { - // fast cases - case 1: - handler.call(this); - break; - case 2: - handler.call(this, arguments[1]); - break; - case 3: - handler.call(this, arguments[1], arguments[2]); - break; - // slower - default: - args = Array.prototype.slice.call(arguments, 1); - handler.apply(this, args); - } - } else if (isObject(handler)) { - args = Array.prototype.slice.call(arguments, 1); - listeners = handler.slice(); - len = listeners.length; - for (i = 0; i < len; i++) { - listeners[i].apply(this, args); - } - } - - return true; - }; - - EventEmitter.prototype.addListener = function (type, listener) { - var m; - - if (!isFunction(listener)) throw TypeError('listener must be a function'); - - if (!this._events) this._events = {}; - - // To avoid recursion in the case that type === "newListener"! Before - // adding it to the listeners, first emit "newListener". - if (this._events.newListener) this.emit('newListener', type, isFunction(listener.listener) ? listener.listener : listener); - - if (!this._events[type]) - // Optimize the case of one listener. Don't need the extra array object. - this._events[type] = listener;else if (isObject(this._events[type])) - // If we've already got an array, just append. - this._events[type].push(listener);else - // Adding the second element, need to change to array. - this._events[type] = [this._events[type], listener]; - - // Check for listener leak - if (isObject(this._events[type]) && !this._events[type].warned) { - if (!isUndefined(this._maxListeners)) { - m = this._maxListeners; - } else { - m = EventEmitter.defaultMaxListeners; - } - - if (m && m > 0 && this._events[type].length > m) { - this._events[type].warned = true; - console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); - if (typeof console.trace === 'function') { - // not supported in IE 10 - console.trace(); - } - } - } - - return this; - }; - - EventEmitter.prototype.on = EventEmitter.prototype.addListener; - - EventEmitter.prototype.once = function (type, listener) { - if (!isFunction(listener)) throw TypeError('listener must be a function'); - - var fired = false; - - function g() { - this.removeListener(type, g); - - if (!fired) { - fired = true; - listener.apply(this, arguments); - } - } - - g.listener = listener; - this.on(type, g); - - return this; - }; - - // emits a 'removeListener' event iff the listener was removed - EventEmitter.prototype.removeListener = function (type, listener) { - var list, position, length, i; - - if (!isFunction(listener)) throw TypeError('listener must be a function'); - - if (!this._events || !this._events[type]) return this; - - list = this._events[type]; - length = list.length; - position = -1; - - if (list === listener || isFunction(list.listener) && list.listener === listener) { - delete this._events[type]; - if (this._events.removeListener) this.emit('removeListener', type, listener); - } else if (isObject(list)) { - for (i = length; i-- > 0;) { - if (list[i] === listener || list[i].listener && list[i].listener === listener) { - position = i; - break; - } - } - - if (position < 0) return this; - - if (list.length === 1) { - list.length = 0; - delete this._events[type]; - } else { - list.splice(position, 1); - } - - if (this._events.removeListener) this.emit('removeListener', type, listener); - } - - return this; - }; - - EventEmitter.prototype.removeAllListeners = function (type) { - var key, listeners; - - if (!this._events) return this; - - // not listening for removeListener, no need to emit - if (!this._events.removeListener) { - if (arguments.length === 0) this._events = {};else if (this._events[type]) delete this._events[type]; - return this; - } - - // emit removeListener for all listeners on all events - if (arguments.length === 0) { - for (key in this._events) { - if (key === 'removeListener') continue; - this.removeAllListeners(key); - } - this.removeAllListeners('removeListener'); - this._events = {}; - return this; - } - - listeners = this._events[type]; - - if (isFunction(listeners)) { - this.removeListener(type, listeners); - } else if (listeners) { - // LIFO order - while (listeners.length) { - this.removeListener(type, listeners[listeners.length - 1]); - } - } - delete this._events[type]; - - return this; - }; - - EventEmitter.prototype.listeners = function (type) { - var ret; - if (!this._events || !this._events[type]) ret = [];else if (isFunction(this._events[type])) ret = [this._events[type]];else ret = this._events[type].slice(); - return ret; - }; - - EventEmitter.prototype.listenerCount = function (type) { - if (this._events) { - var evlistener = this._events[type]; - - if (isFunction(evlistener)) return 1;else if (evlistener) return evlistener.length; - } - return 0; - }; - - EventEmitter.listenerCount = function (emitter, type) { - return emitter.listenerCount(type); - }; - - function isFunction(arg) { - return typeof arg === 'function'; - } - - function isNumber(arg) { - return typeof arg === 'number'; - } - - function isObject(arg) { - return (typeof arg === 'undefined' ? 'undefined' : babelHelpers.typeof(arg)) === 'object' && arg !== null; - } - - function isUndefined(arg) { - return arg === void 0; - } - }); - - var EventEmitter = events && (typeof events === 'undefined' ? 'undefined' : babelHelpers.typeof(events)) === 'object' && 'default' in events ? events['default'] : events; - - var Test = function Test(testName, suiteName, status, runtime, errors) { - babelHelpers.classCallCheck(this, Test); - - this.testName = testName; - this.suiteName = suiteName; - this.status = status; - this.runtime = runtime; - this.errors = errors; - }; - - var Suite = function () { - - /** - * - * @param name - * @param childSuites - * @param tests: array containing tests belonging to the suite but not to a child suite - */ - - function Suite(name, childSuites, tests) { - babelHelpers.classCallCheck(this, Suite); - - this.name = name; - this.childSuites = childSuites; - this.tests = tests; - } - - babelHelpers.createClass(Suite, [{ - key: 'getAllTests', - value: function getAllTests() { - var childSuiteTests = this.childSuites.map(function (suite) { - return suite.getAllTests(); - }).reduce(function (allTests, a) { - return allTests.concat(a); - }, []); - - return this.tests.concat(childSuiteTests); - } - }, { - key: 'runtime', - get: function get() { - var status = this.status; - - if (status === 'skipped' || status === undefined) { - return undefined; - } - - var runtime = this.getAllTests().map(function (test) { - return test.status === 'skipped' ? 0 : test.runtime; - }).reduce(function (sum, testRuntime) { - return sum + testRuntime; - }, 0); - - return runtime; - } - }, { - key: 'status', - get: function get() { - var passed = 0; - var failed = 0; - var skipped = 0; - - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = this.getAllTests()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var test = _step.value; - - // If a suite contains a test whose status is still undefined, - // there is no final status for the suite as well. - if (test.status === undefined) { - return undefined; - } else if (test.status === 'passed') { - passed++; - } else if (test.status === 'skipped') { - skipped++; - } else { - failed++; - } - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - if (failed > 0) { - return 'failed'; - } else if (skipped > 0 && passed === 0) { - return 'skipped'; - } else { - return 'passed'; - } - } - }]); - return Suite; - }(); - - Object.defineProperties(Suite.prototype, { - toJSON: { - value: function value() { - var ret = {}; - for (var x in this) { - ret[x] = this[x]; - } - return ret; - } - }, - runtime: { - enumerable: true - }, - status: { - enumerable: true - } - }); - - var QUnitAdapter = function (_EventEmitter) { - babelHelpers.inherits(QUnitAdapter, _EventEmitter); - - function QUnitAdapter(QUnit) { - babelHelpers.classCallCheck(this, QUnitAdapter); - - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(QUnitAdapter).call(this)); - - _this.QUnit = QUnit; - _this.tests = {}; - - QUnit.begin(_this.onBegin.bind(_this)); - QUnit.testStart(_this.onTestStart.bind(_this)); - QUnit.log(_this.onLog.bind(_this)); - QUnit.testDone(_this.onTestDone.bind(_this)); - QUnit.done(_this.onDone.bind(_this)); - return _this; - } - - babelHelpers.createClass(QUnitAdapter, [{ - key: 'convertModule', - value: function convertModule(qunitModule) { - var _this2 = this; - - return new Suite(qunitModule.name, [], qunitModule.tests.map(function (qunitTest) { - var test = new Test(qunitTest.name, qunitModule.name.replace(/> /g, '')); - - _this2.tests[qunitTest.testId] = test; - - return test; - })); - } - }, { - key: 'saveTestDetails', - value: function saveTestDetails(qunitTest) { - var test = this.tests[qunitTest.testId]; - - test.errors = this.errors; - - if (qunitTest.failed > 0) { - test.status = 'failed'; - } else if (qunitTest.skipped) { - test.status = 'skipped'; - } else { - test.status = 'passed'; - } - - // Workaround for QUnit skipped tests runtime which is a Number. - if (test.status !== 'skipped') { - test.runtime = qunitTest.runtime; - } else { - test.runtime = undefined; - } - } - }, { - key: 'createGlobalSuite', - value: function createGlobalSuite() { - var topLevelSuites = []; - var globalSuite; - var modules; - - // Access QUnit internals to get all suites and tests, working around - // missing event data. - - // Create the global suite first. - if (this.QUnit.config.modules.length > 0 && this.QUnit.config.modules[0].name === '') { - globalSuite = this.convertModule(this.QUnit.config.modules[0]); - globalSuite.name = undefined; - - // The suiteName of global tests must be undefined. - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = globalSuite.tests[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var test = _step.value; - - test.suiteName = undefined; - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - modules = this.QUnit.config.modules.slice(1); - } else { - globalSuite = new Suite(undefined, [], []); - modules = this.QUnit.config.modules; - } - - // Build a list with all suites. - var suites = modules.map(this.convertModule.bind(this)); - - // Iterate through the whole suites and check if they have composed names, - // like "suiteName1 > suiteName2 > ... > suiteNameN". - // - // If a suite has a composed name, its name will be the last in the sequence - // and its parent name will be the one right before it. Search the parent - // suite after its name and then add the suite with the composed name to the - // childSuites. - // - // If a suite does not have a composed name, add it to the topLevelSuites, - // this means that this suite is the direct child of the global suite. - var _iteratorNormalCompletion2 = true; - var _didIteratorError2 = false; - var _iteratorError2 = undefined; - - try { - for (var _iterator2 = suites[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { - var suite = _step2.value; - - var indexEnd = suite.name.lastIndexOf(' > '); - - if (indexEnd !== -1) { - // Find the ' > ' characters that appears before the parent name. - var indexStart = suite.name.substring(0, indexEnd).lastIndexOf(' > '); - // If it is -1, the parent suite name starts at 0, else escape - // this characters ' > '. - indexStart = indexStart === -1 ? 0 : indexStart + 3; - - var parentSuiteName = suite.name.substring(indexStart, indexEnd); - - // Keep only the name of the suite itself. - suite.name = suite.name.substring(indexEnd + 3); - - var _iteratorNormalCompletion3 = true; - var _didIteratorError3 = false; - var _iteratorError3 = undefined; - - try { - for (var _iterator3 = suites[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { - var parentSuite = _step3.value; - - if (parentSuite.name === parentSuiteName) { - parentSuite.childSuites.push(suite); - } - } - } catch (err) { - _didIteratorError3 = true; - _iteratorError3 = err; - } finally { - try { - if (!_iteratorNormalCompletion3 && _iterator3.return) { - _iterator3.return(); - } - } finally { - if (_didIteratorError3) { - throw _iteratorError3; - } - } - } - } else { - topLevelSuites.push(suite); - } - } - } catch (err) { - _didIteratorError2 = true; - _iteratorError2 = err; - } finally { - try { - if (!_iteratorNormalCompletion2 && _iterator2.return) { - _iterator2.return(); - } - } finally { - if (_didIteratorError2) { - throw _iteratorError2; - } - } - } - - globalSuite.childSuites = topLevelSuites; - - return globalSuite; - } - }, { - key: 'createSuiteStart', - value: function createSuiteStart(suite) { - return new Suite(suite.name, suite.childSuites.map(this.createSuiteStart.bind(this)), suite.tests.map(this.createTestStart.bind(this))); - } - }, { - key: 'createSuiteEnd', - value: function createSuiteEnd(suite) { - return new Suite(suite.name, suite.childSuites.map(this.createSuiteEnd.bind(this)), suite.tests.map(this.createTestEnd.bind(this))); - } - }, { - key: 'createTestStart', - value: function createTestStart(test) { - return new Test(test.testName, test.suiteName); - } - }, { - key: 'createTestEnd', - value: function createTestEnd(test) { - return new Test(test.testName, test.suiteName, test.status, test.runtime, test.errors); - } - }, { - key: 'emitData', - value: function emitData(suite) { - var _iteratorNormalCompletion4 = true; - var _didIteratorError4 = false; - var _iteratorError4 = undefined; - - try { - for (var _iterator4 = suite.tests[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) { - var test = _step4.value; - - this.emit('testStart', this.createTestStart(test)); - this.emit('testEnd', this.createTestEnd(test)); - } - } catch (err) { - _didIteratorError4 = true; - _iteratorError4 = err; - } finally { - try { - if (!_iteratorNormalCompletion4 && _iterator4.return) { - _iterator4.return(); - } - } finally { - if (_didIteratorError4) { - throw _iteratorError4; - } - } - } - - var _iteratorNormalCompletion5 = true; - var _didIteratorError5 = false; - var _iteratorError5 = undefined; - - try { - for (var _iterator5 = suite.childSuites[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) { - var _suite = _step5.value; - - this.emit('suiteStart', this.createSuiteStart(_suite)); - this.emitData(_suite); - this.emit('suiteEnd', this.createSuiteEnd(_suite)); - } - } catch (err) { - _didIteratorError5 = true; - _iteratorError5 = err; - } finally { - try { - if (!_iteratorNormalCompletion5 && _iterator5.return) { - _iterator5.return(); - } - } finally { - if (_didIteratorError5) { - throw _iteratorError5; - } - } - } - } - }, { - key: 'onBegin', - value: function onBegin() { - this.globalSuite = this.createGlobalSuite(); - } - }, { - key: 'onTestStart', - value: function onTestStart(details) { - this.errors = []; - } - }, { - key: 'onLog', - value: function onLog(details) { - if (!details.result) { - this.errors.push(details); - } - } - }, { - key: 'onTestDone', - value: function onTestDone(details) { - this.saveTestDetails(details); - } - }, { - key: 'onDone', - value: function onDone() { - this.emit('runStart', this.createSuiteStart(this.globalSuite)); - this.emitData(this.globalSuite); - this.emit('runEnd', this.createSuiteEnd(this.globalSuite)); - } - }]); - return QUnitAdapter; - }(EventEmitter); - - /** - * Limitations: - * - Errors in afterAll are ignored. - */ - - var JasmineAdapter = function (_EventEmitter) { - babelHelpers.inherits(JasmineAdapter, _EventEmitter); - - function JasmineAdapter(jasmine) { - babelHelpers.classCallCheck(this, JasmineAdapter); - - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(JasmineAdapter).call(this)); - - _this.jasmine = jasmine; - jasmine.addReporter({ - jasmineStarted: _this.onJasmineStarted.bind(_this), - specDone: _this.onSpecDone.bind(_this), - specStarted: _this.onSpecStarted.bind(_this), - suiteStarted: _this.onSuiteStarted.bind(_this), - suiteDone: _this.onSuiteDone.bind(_this), - jasmineDone: _this.onJasmineDone.bind(_this) - }); - - _this.suites = {}; - _this.tests = {}; - return _this; - } - - babelHelpers.createClass(JasmineAdapter, [{ - key: 'createSuiteStart', - value: function createSuiteStart(suite) { - return new Suite(suite.name, suite.childSuites.map(this.createSuiteStart.bind(this)), suite.tests.map(this.createTestStart.bind(this))); - } - }, { - key: 'createSuiteEnd', - value: function createSuiteEnd(suite) { - return new Suite(suite.name, suite.childSuites.map(this.createSuiteEnd.bind(this)), suite.tests.map(this.createTestEnd.bind(this))); - } - }, { - key: 'createTestStart', - value: function createTestStart(test) { - return new Test(test.testName, test.suiteName); - } - }, { - key: 'createTestEnd', - value: function createTestEnd(test) { - return new Test(test.testName, test.suiteName, test.status, test.runtime, test.errors); - } - }, { - key: 'saveTestDetails', - value: function saveTestDetails(jasmineSpec) { - var test = this.tests[jasmineSpec.id]; - - test.errors = jasmineSpec.failedExpectations; - - if (jasmineSpec.status === 'pending') { - test.status = 'skipped'; - } else { - test.status = jasmineSpec.status; - test.runtime = new Date() - this.startTime; - } - } - }, { - key: 'isJasmineGlobalSuite', - value: function isJasmineGlobalSuite(suite) { - return suite.description === 'Jasmine__TopLevel__Suite'; - } - - /** - * Jasmine provides details about childSuites and tests only in the structure - * returned by "this.jasmine.topSuite()". - * - * This function creates the global suite for the runStart event, as also - * saves the created suites and tests compliant with the CRI standard in an - * object using as key their unique ids provided by Jasmine. - */ - - }, { - key: 'createGlobalSuite', - value: function createGlobalSuite(jasmineSuite) { - var childSuites = []; - var tests = []; - - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = jasmineSuite.children[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var child = _step.value; - - if (child.id.indexOf('suite') === 0) { - childSuites.push(this.createGlobalSuite(child)); - } else { - var suiteName = void 0; - var test = void 0; - - // Jasmine full description is of form "suite1 suite2 ... suiteN test", - // for the "suiteName" property we need to remove test name. - if (!this.isJasmineGlobalSuite(jasmineSuite)) { - suiteName = child.result.fullName.substring(0, child.result.fullName.indexOf(child.description) - 1); - } - - test = new Test(child.description, suiteName); - - tests.push(test); - this.tests[child.id] = test; - } - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - - var name = this.isJasmineGlobalSuite(jasmineSuite) ? undefined : jasmineSuite.description; - var suite = new Suite(name, childSuites, tests); - - this.suites[jasmineSuite.id] = suite; - - return suite; - } - }, { - key: 'onJasmineStarted', - value: function onJasmineStarted() { - this.globalSuite = this.createGlobalSuite(this.jasmine.topSuite()); - this.emit('runStart', this.createSuiteStart(this.globalSuite)); - } - }, { - key: 'onSpecStarted', - value: function onSpecStarted(details) { - this.startTime = new Date(); - this.emit('testStart', this.createTestStart(this.tests[details.id])); - } - }, { - key: 'onSpecDone', - value: function onSpecDone(details) { - this.saveTestDetails(details); - this.emit('testEnd', this.createTestEnd(this.tests[details.id])); - } - }, { - key: 'onSuiteStarted', - value: function onSuiteStarted(details) { - this.emit('suiteStart', this.createSuiteStart(this.suites[details.id])); - } - }, { - key: 'onSuiteDone', - value: function onSuiteDone(details) { - this.emit('suiteEnd', this.createSuiteEnd(this.suites[details.id])); - } - }, { - key: 'onJasmineDone', - value: function onJasmineDone() { - this.emit('runEnd', this.createSuiteEnd(this.globalSuite)); - } - }]); - return JasmineAdapter; - }(EventEmitter); - - var MochaAdapter = function (_EventEmitter) { - babelHelpers.inherits(MochaAdapter, _EventEmitter); - - function MochaAdapter(mocha) { - babelHelpers.classCallCheck(this, MochaAdapter); - - var _this = babelHelpers.possibleConstructorReturn(this, Object.getPrototypeOf(MochaAdapter).call(this)); - - _this.mocha = mocha; - - mocha.reporter(function (runner) { - _this.runner = runner; - - runner.on('start', _this.onStart.bind(_this)); - runner.on('suite', _this.onSuite.bind(_this)); - runner.on('test', _this.onTest.bind(_this)); - runner.on('pending', _this.onPending.bind(_this)); - runner.on('fail', _this.onFail.bind(_this)); - runner.on('test end', _this.onTestEnd.bind(_this)); - runner.on('suite end', _this.onSuiteEnd.bind(_this)); - runner.on('end', _this.onEnd.bind(_this)); - }); - return _this; - } - - babelHelpers.createClass(MochaAdapter, [{ - key: 'convertSuite', - value: function convertSuite(mochaSuite) { - return new Suite(mochaSuite.title, mochaSuite.suites.map(this.convertSuite.bind(this)), mochaSuite.tests.map(this.convertTest.bind(this))); - } - }, { - key: 'convertTest', - value: function convertTest(mochaTest) { - var suiteName; - - if (!mochaTest.parent.root) { - suiteName = this.buildSuiteName(mochaTest.parent); - } - - // If the test has the errors attached a "test end" must be emitted, else - // a "test start". - if (mochaTest.errors !== undefined) { - var status = mochaTest.state === undefined ? 'skipped' : mochaTest.state; - - // Test end. - return new Test(mochaTest.title, suiteName, status, mochaTest.duration, mochaTest.errors); - } - - // Test start. - return new Test(mochaTest.title, suiteName); - } - - /** - * Builds a concatenated name from nested suites. - */ - - }, { - key: 'buildSuiteName', - value: function buildSuiteName(mochaSuite) { - var suiteName = mochaSuite.title; - var parent = mochaSuite.parent; - - while (!parent.root) { - suiteName = parent.title + ' ' + suiteName; - parent = parent.parent; - } - - return suiteName; - } - }, { - key: 'onStart', - value: function onStart() { - var globalSuiteStart = this.convertSuite(this.runner.suite); - globalSuiteStart.name = undefined; - - this.emit('runStart', globalSuiteStart); - } - }, { - key: 'onSuite', - value: function onSuite(mochaSuite) { - if (!mochaSuite.root) { - this.emit('suiteStart', this.convertSuite(mochaSuite)); - } - } - }, { - key: 'onTest', - value: function onTest(mochaTest) { - this.errors = []; - - this.emit('testStart', this.convertTest(mochaTest)); - } - - /** - * Emits the start of pending tests, because Mocha does not emit skipped tests - * on its "test" event. - */ - - }, { - key: 'onPending', - value: function onPending(mochaTest) { - this.emit('testStart', this.convertTest(mochaTest)); - } - }, { - key: 'onFail', - value: function onFail(test, error) { - this.errors.push(error); - } - }, { - key: 'onTestEnd', - value: function onTestEnd(mochaTest) { - // Save the errors on Mocha's test object, because when the suite that - // contains this test is emitted on the "suiteEnd" event, it should contain - // also this test with all its details (errors, status, runtime). Runtime - // and status are already attached to the test, but the errors don't. - mochaTest.errors = this.errors; - - this.emit('testEnd', this.convertTest(mochaTest)); - } - }, { - key: 'onSuiteEnd', - value: function onSuiteEnd(mochaSuite) { - if (!mochaSuite.root) { - this.emit('suiteEnd', this.convertSuite(mochaSuite)); - } - } - }, { - key: 'onEnd', - value: function onEnd() { - var globalSuiteEnd = this.convertSuite(this.runner.suite); - globalSuiteEnd.name = undefined; - - this.emit('runEnd', globalSuiteEnd); - } - }]); - return MochaAdapter; - }(EventEmitter); - - var TapReporter = function () { - function TapReporter(runner) { - babelHelpers.classCallCheck(this, TapReporter); - - this.testCount = 0; - - runner.on('runStart', this.onRunStart.bind(this)); - runner.on('testEnd', this.onTestEnd.bind(this)); - runner.on('runEnd', this.onRunEnd.bind(this)); - } - - babelHelpers.createClass(TapReporter, [{ - key: 'onRunStart', - value: function onRunStart(globalSuite) { - console.log('TAP version 13'); - } - }, { - key: 'onTestEnd', - value: function onTestEnd(test) { - this.testCount = this.testCount + 1; - - // TODO maybe switch to test.fullName - // @see https://github.com/js-reporters/js-reporters/issues/65 - if (test.status === 'passed') { - console.log('ok ' + this.testCount + ' ' + test.testName); - } else if (test.status === 'skipped') { - console.log('ok ' + this.testCount + ' ' + test.testName + ' # SKIP'); - } else { - console.log('not ok ' + this.testCount + ' ' + test.testName); - - var _iteratorNormalCompletion = true; - var _didIteratorError = false; - var _iteratorError = undefined; - - try { - for (var _iterator = test.errors[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { - var error = _step.value; - - console.log(' ---'); - console.log(' message: "' + error.toString() + '"'); - console.log(' severity: failed'); - console.log(' ...'); - } - } catch (err) { - _didIteratorError = true; - _iteratorError = err; - } finally { - try { - if (!_iteratorNormalCompletion && _iterator.return) { - _iterator.return(); - } - } finally { - if (_didIteratorError) { - throw _iteratorError; - } - } - } - } - } - }, { - key: 'onRunEnd', - value: function onRunEnd(globalSuite) { - console.log('1..' + this.testCount); - } - }], [{ - key: 'init', - value: function init(runner) { - return new TapReporter(runner); - } - }]); - return TapReporter; - }(); - - // TODO: finish grouping once suiteStart is implemented - var hasGrouping = 'group' in console && 'groupEnd' in console; - - var ConsoleReporter = function () { - function ConsoleReporter(runner) { - babelHelpers.classCallCheck(this, ConsoleReporter); - - runner.on('runStart', this.onRunStart); - runner.on('suiteStart', this.onSuiteStart); - runner.on('testStart', this.onTestStart); - runner.on('testEnd', this.onTestEnd); - runner.on('suiteEnd', this.onSuiteEnd); - runner.on('runEnd', this.onRunEnd); - } - - babelHelpers.createClass(ConsoleReporter, [{ - key: 'onRunStart', - value: function onRunStart(suite) { - console.log('runStart', suite); - } - }, { - key: 'onSuiteStart', - value: function onSuiteStart(suite) { - if (hasGrouping) { - console.group(suite.name); - } - console.log('suiteStart', suite); - } - }, { - key: 'onTestStart', - value: function onTestStart(test) { - console.log('testStart', test); - } - }, { - key: 'onTestEnd', - value: function onTestEnd(test) { - console.log('testEnd', test); - } - }, { - key: 'onSuiteEnd', - value: function onSuiteEnd(suite) { - console.log('suiteEnd', suite); - if (hasGrouping) { - console.groupEnd(); - } - } - }, { - key: 'onRunEnd', - value: function onRunEnd(globalSuite) { - console.log('runEnd', globalSuite); - } - }], [{ - key: 'init', - value: function init(runner) { - return new ConsoleReporter(runner); - } - }]); - return ConsoleReporter; - }(); - - /* - The TestReporter verifies that a test runner outputs the right data in the right order. - To do so, it compares the actual output with the provided reference data. - The result is given in the ok attribute. - */ - - var TestReporter = function () { - - /** - * @param runner: standardized test runner (or adapter) - * @param referenceData: An array of all expected (eventName, eventData) tuples in the right order - */ - - function TestReporter(runner, referenceData) { - babelHelpers.classCallCheck(this, TestReporter); - - this.referenceData = referenceData.slice(); - this.error = false; - runner.on('runStart', this.onEvent.bind(this, 'runStart')); - runner.on('suiteStart', this.onEvent.bind(this, 'suiteStart')); - runner.on('testStart', this.onEvent.bind(this, 'testStart')); - runner.on('testEnd', this.onEvent.bind(this, 'testEnd')); - runner.on('suiteEnd', this.onEvent.bind(this, 'suiteEnd')); - runner.on('runEnd', this.onEvent.bind(this, 'runEnd')); - } - - /** - * Gets called on each event emitted by the runner. Checks if the actual event matches the expected event. - */ - - - babelHelpers.createClass(TestReporter, [{ - key: 'onEvent', - value: function onEvent(eventName, eventData) { - var _referenceData$shift = this.referenceData.shift(); - - var _referenceData$shift2 = babelHelpers.slicedToArray(_referenceData$shift, 2); - - var expectedEventName = _referenceData$shift2[0]; - var expectedEventData = _referenceData$shift2[1]; - - - if (eventName !== expectedEventName || !this.equal(eventData, expectedEventData)) { - this.error = true; - console.error('expected:', expectedEventName, expectedEventData, '\r\n', 'actual:', eventName, eventData); - } - } - }, { - key: 'equal', - - - /** - * Helper function to compare - * - two Test objects - * - two Suite objects - * - two arrays of Test or Suite objects - * The equality check is not completely strict, e.g. the runtime of a Test does not have to be equal. - * @returns {boolean}: true if both objects are equal, false otherwise - */ - value: function equal(actual, expected) { - if (expected instanceof Suite) { - if (actual.name !== expected.name) { - return false; - } - if (!this.equal(actual.childSuites, expected.childSuites)) { - return false; - } - - if (!this.equal(actual.tests, expected.tests)) { - return false; - } - } else if (expected instanceof Test) { - var _arr = ['testName', 'suiteName', 'status']; - - for (var _i = 0; _i < _arr.length; _i++) { - var property = _arr[_i]; - if (actual[property] !== expected[property]) { - return false; - } - } - if (typeof actual.runtime !== 'number' && actual.runtime !== undefined) { - return false; - } - - if (!(actual.errors === undefined && expected.errors === undefined) && actual.errors.length !== expected.errors.length) { - return false; - } - } else if (Array.isArray(expected)) { - if (actual.length !== expected.length) { - return false; - } - - for (var i = 0; i < expected.length; i++) { - if (!this.equal(actual[i], expected[i])) { - return false; - } - } - } else { - return false; - } - return true; - } - }, { - key: 'ok', - get: function get() { - return !this.error && this.referenceData.length === 0; - } - }]); - return TestReporter; - }(); - - var index = { - QUnitAdapter: QUnitAdapter, - JasmineAdapter: JasmineAdapter, - MochaAdapter: MochaAdapter, - TapReporter: TapReporter, - ConsoleReporter: ConsoleReporter, - TestReporter: TestReporter, - Test: Test, - Suite: Suite - }; - - return index; - -})); \ No newline at end of file diff --git a/lib/server.js b/lib/server.js index d98d15e..88f2ca5 100644 --- a/lib/server.js +++ b/lib/server.js @@ -35,8 +35,7 @@ exports.Server = function Server(bsClient, workers) { var scripts = [ 'json2.js', 'browserstack.js', - 'browserstack-util.js', - 'js-reporters.js' + 'browserstack-util.js' ]; var framework_scripts = { @@ -46,6 +45,11 @@ exports.Server = function Server(bsClient, workers) { var filePath = path.relative(process.cwd(), filename); var pathMatches = (testFilePaths.indexOf(filePath) !== -1); + var jsReportersPath = path.join(__dirname, '../node_modules/js-reporters/dist/js-reporters.js'); + var jsReportersScript = fs.readFileSync(jsReportersPath, { + encoding: 'utf8' + }); + if (pathMatches) { var framework = config['test_framework']; var tag_name = (framework === 'mocha') ? 'head' : 'body'; @@ -55,6 +59,8 @@ exports.Server = function Server(bsClient, workers) { patch += '\n'; }); + patch += ''; + // adding framework scripts if (framework === 'jasmine') { framework_scripts['jasmine'].forEach(function(script) { From b73d6a6030b84e2bb2ffe79258c297c7f41cc2c9 Mon Sep 17 00:00:00 2001 From: Florentin Date: Mon, 11 Jul 2016 20:04:53 +0100 Subject: [PATCH 13/16] Package.json: add js-reporters to deps. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index abc76b1..1032cb9 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "dependencies": { "browserstack": "1.3.0", "chalk": "0.4.0", + "js-reporters": "^1.0.0", "mime": "1.3.4", "send": "0.13.0", "tunnel": "0.0.3" From 13535c6f7f258945ceb160eb744aeea484467957 Mon Sep 17 00:00:00 2001 From: Florentin Date: Mon, 11 Jul 2016 22:53:31 +0100 Subject: [PATCH 14/16] Testing: run also tests for mocha v2.4.5 --- tests/external-tests.js | 49 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/tests/external-tests.js b/tests/external-tests.js index cab1695..e879f3f 100755 --- a/tests/external-tests.js +++ b/tests/external-tests.js @@ -13,6 +13,47 @@ var runnerPath = path.resolve(path.join(__dirname, '..', 'bin', 'cli.js')); var testHome = path.resolve(__dirname); process.chdir(testHome); +/** + * Mocha v2.4.5 - to change with another Mocha version or + * something with Mocha tests + * + * index.html - 22 tests, 18 passed, 4 failed -> one test is displayed twice, + * so they are displayed 5 failing tests, but counted only 4 + * large.html - 64 tests, 60 passed, 4 failed -> only 2 tests are failing, but + * they are displayed twice + * opts.html - 8 tests, 2 passed, 6 failed -> only 3 tests are failing, but + * they are displayed twice + * + * By "displayed" it is referred the Mocha HTML Reporter. + * + * From the above explanations it is clear that there are some inconsistencies, + * also because Mocha's HTML Reporter counted number of tests does not match + * the number of displyed tests. + * + * The cause is (snippet from Mocha's HTML reporter): + * + * runner.on('fail', function(test) { + * // For type = 'test' its possible that the test failed due to multiple + * // done() calls. So report the issue here. + * if (test.type === 'hook' + * || test.type === 'test') { + * runner.emit('test end', test); + * } + * }); + * + * This is why failed tests are displayed twice... + * + * The JsReporters is counting the tests on the "test end" event, that's why + * it is capturing the failing tests twice, in the "index.html" it does not + * capture everything, because there is an async test, which failure is + * triggered after a timeout and the JsReporters is not waiting, because + * it cannot know how much to wait. + * + * + * This been said, the JsReporter MochaAdapter is functioning well, this + * version of Mocha is not reliable and should be changed. + */ + var repositories = [ { name: 'qunit', @@ -36,7 +77,7 @@ var repositories = [ failed: 0 } }, - /*{ + { name: 'mocha', tag: 'v2.4.5', url: 'https://github.com/mochajs/mocha.git', @@ -55,11 +96,11 @@ var repositories = [ 'test/browser/opts.html' ], expected_results: { - tests: 89, + tests: 94, passed: 80, - failed: 9 + failed: 14 } - },*/ + }, { name: 'spine', tag: 'v.1.6.2', From fbfbaf7ed9d13a19761fd88a8b130690484092b2 Mon Sep 17 00:00:00 2001 From: Florentin Date: Fri, 15 Jul 2016 16:03:47 +0100 Subject: [PATCH 15/16] Reporter: compute runtime also for Mocha. --- lib/_patch/reporter.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/_patch/reporter.js b/lib/_patch/reporter.js index f8eb9ce..b527b10 100644 --- a/lib/_patch/reporter.js +++ b/lib/_patch/reporter.js @@ -29,8 +29,7 @@ runner.on('runEnd', function(globalSuite) { var results = {}; - // TODO Investigate why is the runtime not functioning for Mocha. - results.runtime = (window.mocha) ? 0 : globalSuite.runtime; + results.runtime = globalSuite.runtime; results.total = total; results.passed = passed; results.failed = failed; From d86ffed5e4ab8c283ee1c3ce6dd4fabe5ce38d7f Mon Sep 17 00:00:00 2001 From: Florentin Date: Sun, 17 Jul 2016 21:36:11 +0100 Subject: [PATCH 16/16] Reporter: report tracebacks to _progress. --- lib/_patch/reporter.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/_patch/reporter.js b/lib/_patch/reporter.js index b527b10..e8006ef 100644 --- a/lib/_patch/reporter.js +++ b/lib/_patch/reporter.js @@ -1,6 +1,5 @@ (function() { var runner; - var tracebacks = []; var total = 0, passed = 0, failed = 0; @@ -22,7 +21,15 @@ failed = failed + (test.status === 'failed' ? 1 : 0); test.errors.forEach(function(error) { - tracebacks.push(error) + BrowserStack.post("/_progress", { + tracebacks: [{ + actual: error.actual, + expected: error.expected, + message: error.message, + source: error.source || error.stack, + testName: test.testName + }] + }, function() {}); }); }); @@ -33,7 +40,6 @@ results.total = total; results.passed = passed; results.failed = failed; - results.tracebacks = tracebacks; results.url = window.location.pathname; BrowserStack.post("/_report", results, function() {});