Plato on Github
Report Home
index.js
Maintainability
72.86
Lines of code
398
Difficulty
38.77
Estimated Errors
2.21
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 Q = require('q'); // Int. libs var actions = require('dalek-internal-actions'); var assertions = require('dalek-internal-assertions'); /** * Prepares the test instance values * * @param {object} opts Options like the tests name, etc. * @constructor */ var Test = function (opts) { // prepare meta queue data this.actionPromiseQueue = []; // prepare assertion data this.expactation = null; this.runnedExpactations = 0; this.failedAssertions = 0; // prepare test specific data this.name = opts.name; this.lastChain = []; this.uuids = {}; this.contextVars = {}; if (this.name) { this.timeoutForDone = setTimeout(function () { this.done(); this.reporter.emit('warning', 'done not called!'); }.bind(this), 2000); } }; /** * Generates an test instance * * @module DalekJS * @class Test * @namespace Dalek * @part test * @api */ Test.prototype = { /** * Specify how many assertions are expected to run within a test. * Very useful for ensuring that all your callbacks and assertions are run. * * @method expect * @param {Integer} expecatation Number of assertions that should be run * @chainable */ expect: function (expectation) { this.expectation = parseInt(expectation, 10); return this; }, /** * Global data store (works between the node & browser envs) * * @method expect * @param {string|number} key Key to store or fetch data * @param {mixed} value *optional* Data that should be stored * @return {mixed} Data that has been stored * @chainable */ data: function (key, value) { if (value) { this.contextVars[key] = value; return this; } return this.contextVars[key]; }, /** * Increment the number of executed assertions * * @method incrementExpectations * @chainable */ incrementExpectations: function () { this.runnedExpactations++; return this; }, /** * Increment the number of failed assertions * * @method incrementFailedAssertions * @chainable */ incrementFailedAssertions: function () { this.failedAssertions++; return this; }, /** * Checks if the runned tests fullfill the set expectations * or if no expectations were raised * * @method checkExpectations * @return {bool} checkedExpectations Expectations match */ checkExpectations: function () { return (this.expectation === null || !this.expectation || (this.runnedExpactations === this.expectation)); }, /** * Checks if all runned assertions passed * * @method checkAssertions * @return {bool} assertiosnFailed Any expectation failed */ checkAssertions: function () { return this.failedAssertions === 0; }, /** * Sets up all the bindings needed for a test to run * * @method done * @return {object} result A promise * @private */ done: function () { var result = Q.resolve(); // clear the done error timeout clearTimeout(this.timeoutForDone); // remove all previously attached event listeners to clear the message queue this.driver.events.removeAllListeners('driver:message'); // resolve the deferred when the test is finished Test.testStarted.fin(this._testFinished.bind(this, result)); return result; }, /** * Emits the test finished events & resolves all promises * when its done * * @method _testFinished * @param {object} result Promised result var * @return {object} result Promised result var * @private */ _testFinished: function (result) { // add a last deferred function on the end of the action queue, // to tell that this test is finished this.actionPromiseQueue.push(this._testFin.bind(this)); // initialize all of the event receiver functions, // that later take the driver result this.actionPromiseQueue.forEach(function (f) { result = result.then(f).fail(function () { console.error(arguments); process.exit(0); }); }.bind(this)); // run the driver when all actions are stored in the queue Q.allSettled(this.actionPromiseQueue) .then(this.driver.run.bind(this.driver)); return result; }, /** * Emits the test started event * * @method _reportTestStarted * @param {string} name Name of the test * @chainable * @private */ _reportTestStarted: function (name) { this.reporter.emit('report:test:started', {name: name}); return this; }, /** * Checks if the test run is complete & emits/resolves * all the needed events/promises when the run is complete * * @method _onDriverMessage * @param {object} data Data that is returned by the driver:message event * @chainable * @private */ _onDriverMessage: function (data) { // check if the test run is complete if (data && data.key === 'run.complete') { // emit the test finish events & resolve the deferred this._emitConcreteTestFinished(); this._emitAssertionStatus(); this._emitTestFinished(); this.deferred.resolve(); } return this; }, /** * Emits an event, that the current test run has been finished * * @method _emitConcreteTestFinished * @chainable * @private */ _emitConcreteTestFinished: function () { this.events.emit('test:' + this._uid + ':finished', 'test:finished', this); return this; }, /** * Emits an event that describes the current state of all assertions * * @method _emitAssertionStatus * @chainable * @private */ _emitAssertionStatus: function () { this.reporter.emit('report:assertion:status', { expected: (this.expectation ? this.expectation : this.runnedExpactations), run: this.runnedExpactations, status: this._testStatus() }); return this; }, /** * Get the overall test status (assertions & expectation) * * @method _testStatus * @return {bool} status The test status * @chainable * @private */ _testStatus: function () { return this.checkExpectations() && this.checkAssertions(); }, /** * Emits an event that describes the current state of all assertions. * The event should be fired when a test is finished * * @method _emitTestFinished * @chainable * @private */ _emitTestFinished: function () { this.reporter.emit('report:test:finished', { name: this.name, id: this._uid, passedAssertions: this.runnedExpactations - this.failedAssertions, failedAssertions: this.failedAssertions, runnedExpactations: this.runnedExpactations, status: this._testStatus(), nl: true }); return this; }, /** * Kicks off the test & binds all promises/events * * @method _testFin * @return {object} promise A promise * @private */ _testFin: function () { this.deferred = Q.defer(); if (_.isFunction(this.driver.end)) { this.driver.end(); } // emit report startet event this._reportTestStarted(this.name); // listen to all the messages from the driver this.driver.events.on('driver:message', this._onDriverMessage.bind(this)); return this.deferred.promise; }, /** * Copies assertion methods * * @method _inheritAssertions * @param {Test} test Instacne of test * @chainable * @private */ _inheritAssertions: function (test) { ['is'].forEach(function (method) { test[method] = test.assert[method].bind(test.assert); }); return test; }, /** * Copies assertion helper methods * * @method _inheritAssertions * @param {Test} test Instacne of test * @chainable * @private */ _inheritAssertionHelpers: function (test) { ['not', 'between', 'gt', 'gte', 'lt', 'lte'].forEach(function (method) { test.is[method] = test.assert[method].bind(test.assert); test.assert.is[method] = test.assert[method].bind(test.assert); }); ['contain', 'match'].forEach(function (method) { test.to = test.to || {}; test.assert.to = test.assert.to || {}; test.to[method] = test.assert[method].bind(test.assert); test.assert.to[method] = test.assert[method].bind(test.assert); }); return test; }, /** * Set up the instance * * @method _inheritAssertions * @param {Test} test Instacne of test * @param {object} opts Options * @chainable * @private */ _initialize: function (test, opts) { test._uid = _.uniqueId('test'); test.events = opts.events; test.driver = opts.driver; test.reporter = opts.reporter; return test; } }; // export a function that generates a new test instance module.exports = function (opts) { // mixin assertions, actions & getters Test.prototype = _.extend(Test.prototype, actions({reporter: opts.reporter}).prototype); var test = new Test(opts); test.assert = new (assertions())({test: test}); test.assert.done = test.done.bind(this); test.assert.query = test.query.bind(test.assert); test.assert.$ = test.query.bind(test.assert); test.end = test.assert.end.bind(test.assert); // copy log methods test.log = {}; test.log.dom = test.logger.dom.bind(test); test.log.message = test.logger.message.bind(test); // copy assertions methods test = test._inheritAssertions(test); // copy assertion helper methods test = test._inheritAssertionHelpers(test); // initialize the instance test = test._initialize(test, opts); // TODO: Promise driver start // so that we can reexecute them and clean the env between tests Test.testStarted = test.driver.start(Q); return test; };