Plato on Github
Report Home
index.js
Maintainability
70.46
Lines of code
316
Difficulty
37.82
Estimated Errors
1.39
Function weight
By Complexity
By SLOC
/*! * * Copyright (c) 2013 Sebastian Golasch * * 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. * */ 'use strict'; // ext. libs var _ = require('lodash'); var fs = require('fs'); var EventEmitter2 = require('eventemitter2').EventEmitter2; // int. libs var test = require('dalek-internal-test'); /** * @constructor * @param {object} options */ var Testsuite = function (options) { this.emitter = new EventEmitter2(); this.initialize(options); this.suite = this.loadTestsuite(options.file); }; /** * Testsuite * * @module DalekJS * @class Testsuite * @namespace Dalek * @part Testsuite * @api */ Testsuite.prototype = { /** * Assigns the initial options * driverEmitter -> the drivers event dispatcher * reporterEmitter -> the reporters event dispatcher * driver -> the driver instance (e.g. native webdriver, selenium, etc.) * name -> the suites filename (default suite name) * * @method initialize * @param {object} options * @chainable */ initialize: function (options) { this.driverEmitter = options.driverEmitter; this.reporterEmitter = options.reporterEmitter; this.driver = options.driver; this.name = options.file; this.numberOfSuites = options.numberOfSuites; this.error = null; return this; }, /** * Loads the testsuite that should be executed * * @method loadTestsuite * @param {string} testfile * @return {object} testsuite */ loadTestsuite: function (testfile) { var suite = {}; // catch any errors, like falsy requires & stuff try { if (fs.existsSync(process.cwd() + '/' + testfile)) { suite = require(process.cwd() + '/' + testfile.replace('.js', '')); } else { this.error = 'Suite "' + testfile + '" does not exist. Skipping!'; return suite; } } catch (e) { this.error = 'Failure loading suite "' + testfile + '". Skipping!'; return suite; } suite._uid = _.uniqueId('Suite'); return suite; }, /** * Checks if all tests from the testsuite are executed. * Runs the next test if not. * Triggers `asyncs` callback if the suite is finished. * Decrements the `testsToBeExecuted` counter * * @method testFinished * @param {function} callback * @param {array} tests * @param {object} test * @param {string} event * @chainable */ testFinished: function (callback, tests) { // run a function after the test, if given if (this.options.afterEach) { this.options.afterEach(); } // check if there are still tests that should be executed in this suite, // if so, run them if (this.decrementTestsToBeExecuted() > 1) { this.executeNextTest(tests); return this; } // run a function after the testsuite, if given if (this.options.teardown) { this.options.teardown(); } // emit the suite started event this.reporterEmitter.emit('report:testsuite:finished', this.name); // move on to the next suite callback(); return this; }, /** * Decrements number of tests that should be executed in this suite * * @method decrementTestsToBeExecuted * @return {integer} numberOfTestsToBeExecuted */ decrementTestsToBeExecuted: function () { return (this.testsToBeExecuted--) -1; }, /** * Returns the name of the testsuite * If the suite has no name, it will return the testsuites filename * * @method getName * @return {string} name */ getName: function () { if (this.suite.name && _.isString(this.suite.name)) { var name = this.suite.name; delete this.suite.name; return name; } return this.name; }, /** * Returns the options of the testsuite * If the suite has no options, it will return an empty object * * @method getOptions * @return {object} options Suite options */ getOptions: function () { if (this.suite.options && _.isObject(this.suite.options)) { var options = this.suite.options; delete this.suite.options; return options; } return {}; }, /** * Returns all names (aka. object keys) the tests that should be executed * * @method getTests * @return {array} test */ getTests: function () { return Object.keys(this.suite); }, /** * Returns the number of tests to be executed * * @method getNumberOfTests * @param {array} tests * @return {integer} numberOfTests */ getNumberOfTests: function (tests) { return tests.length; }, /** * Returns the next test, that should be executed * * @method getNextTest * @return {string} testName */ getNextTest: function (tests) { return tests.shift(); }, /** * Executes the next test in the sequence * * @method executeNextTest * @param {array} tests * @return {mixed} testValue */ executeNextTest: function (tests) { // grab the next test in the queue var testName = this.getNextTest(tests); // get the next test function var testFunction = this.getTest(testName); // run a setup function before the test, if given if (this.options.beforeEach) { this.options.beforeEach(); } // generate an instance of the test & start it return testFunction(this.getTestInstance(testName)); }, /** * Generates a new test instance * * @method getTestInstance * @param {string} name * @return {Dalek.Test} test */ getTestInstance: function (name) { return test({events: this.emitter, driver: this.driver, reporter: this.reporterEmitter, name: name}); }, /** * Returns a test function by its name * * @method getTest * @param {string} name * @return {function} test */ getTest: function (name) { return this.suite[name] && _.isFunction(this.suite[name]) ? this.suite[name] : this.testDoesNotExist; }, /** * Will be executed if a test is started, that does not exist * * @method testDoesNotExist * @param {object} options */ testDoesNotExist: function (options) { if (options.name) { this.reporterEmitter.emit('warning', 'Test "' + options.name + '" does not exist! Skipping.'); } return this; }, /** * Runs any tests from this testsuite in sequence * * @method run * @param {function} callback * @chainable */ run: function (callback) { var tests = []; // check if the suite is if (this.error) { this.reporterEmitter.emit('report:testsuite:started', null); // emit a warning notice this.reporterEmitter.emit('warning', this.error); // emit the suite finished event this.reporterEmitter.emit('report:testsuite:finished', null); // move on to the next suite callback(); } // extract suite name this.name = this.getName(); // extract suite options this.options = this.getOptions(); // extract tests tests = this.getTests(); this.testsToBeExecuted = this.numberOfTests = this.getNumberOfTests(tests); // run a function before the testsuite has been launched, if given if (this.options.setup) { this.options.setup(); console.dir(test); } // kickstart the test execution this.executeNextTest(tests); // emit the suite started event this.reporterEmitter.emit('report:testsuite:started', this.name); // listen to the test:finished event & then start the next test // if there are no tests in this suite left, // run the async callback & mark this suite as finished this.emitter.onAny(this.testFinished.bind(this, callback, tests)); return this; } }; // export the testuite instance module.exports = Testsuite;