UNPKG

@testim/testim-cli

Version:

Command line interface for running Testing on you CI

253 lines (213 loc) 6.17 kB
/** * Module dependencies. */ var util = require('util'), Base = require('./base'); /** * Initialize a new `Spec` matrix test reporter. * * @param {Runner} runner * @api public */ var Spec = function Spec() { Base.call(this); var cntError = 0; this.testsBy = {}; this.indents = 0; this.on('start', function(){ console.log(); }); /** * remember which tests got executed by runner */ this.on('runner:start', function(runner) { this.testsBy[runner.pid] = []; }); this.on('suite:start', function(suite){ /** * mark state for runner as "reached" */ this.testsBy[suite.pid].push(true); /** * only continue if all runner have reached that state * otherwise show spinner ascii gimmick */ if(!this.gotExecutedByAllRunner(suite.pid)) { return this.runSpinner(suite, 'suite'); } ++this.indents; this.clearSpinner(); console.log(this.color('suite', '%s%s'), this.indent(), suite.title); }); this.on('suite:end', function(suite){ /** * mark state for runner as "reached" */ this.testsBy[suite.pid].push(true); /** * only continue if all runner have reached that state */ if(!this.gotExecutedByAllRunner(suite.pid)) { return; } --this.indents; if(1 === this.indents) { console.log(); } }); this.on('test:start', function(test) { if(this.spinner) { return; } this.runSpinner(test, 'pass'); }); this.on('test:pending', function(test) { /** * mark state for runner as "reached" */ this.testsBy[test.pid].push(true); /** * only continue if all runner have reached that state * otherwise show spinner ascii gimmick */ if(!this.gotExecutedByAllRunner(test.pid)) { return; } var fmt = this.indent() + this.color('pending', ' - %s'); this.clearSpinner(); this.cursor.CR(); console.log(fmt, test.title); }); this.on('test:pass', function(test) { /** * mark state for runner as "reached" */ this.testsBy[test.pid].push(true); /** * only continue if all runner have reached that state */ if(!this.gotExecutedByAllRunner(test.pid)) { return; } var fmt = this.indent() + this.color('checkmark', ' ' + this.symbols.ok) + this.color('pass', ' %s') + this.color('medium', ' (%dms)'); this.clearSpinner(); this.cursor.CR(); console.log(fmt, test.title, test.duration); }); this.on('test:fail', function(test) { /** * mark state for runner as "reached" */ this.testsBy[test.pid].push(true); /** * only continue if all runner have reached that state */ if(!this.gotExecutedByAllRunner(test.pid)) { return; } this.clearSpinner(); this.cursor.CR(); console.log(this.indent() + this.color('fail', ' %d) %s'), ++cntError, test.title); }); this.on('end', function() { this.clearSpinner(); this.epilogue(); console.log(); }); }; /** * Inherit from Base */ util.inherits(Spec, Base); /** * returns true if test got executed by all runner */ Spec.prototype.gotExecutedByAllRunner = function(pid) { /** * always true when there is only one runner */ if(Object.keys(this.testsBy).length === 1) { return true; } var pos = this.testsBy[pid].length - 1; return this.gotExecutedBy(pos) === Object.keys(this.stats.runner).length; }; /** * returns number of how many runners have executed the test */ Spec.prototype.gotExecutedBy = function(pos) { var self = this, gotExecutedBy = 0; Object.keys(this.testsBy).forEach(function(pid) { /** * only increase variable if runner has executed the tes */ !!self.testsBy[pid][pos] && gotExecutedBy++; }); return gotExecutedBy; }; Spec.prototype.indent = function() { return this.indents < 0 ? '' : Array(this.indents).join(' '); }; /** * starts little ascii spinner gimick */ Spec.prototype.runSpinner = function(test, color) { var spinStates = ['◴','◷','◶','◵'], testsBy = this.testsBy, inSpinState = 0; /** * no need for a spinner if one is already spinning or if we only have one runner */ if(this.spinner || Object.keys(this.testsBy).length === 1) { return; } /** * no fancy spinner without tty */ if(!this.cursor.isatty) { this.spinner = true; return; } this.spinner = setInterval(function() { this.cursor.beginningOfLine(); /** * no special spinner for suite label */ if(color === 'suite') { return process.stdout.write(this.color(color, test.title)); } /** * get position of slowest runner */ var pos = null; Object.keys(testsBy).forEach(function(pid) { if(pos === null) { pos = testsBy[pid].length; } pos = Math.min(pos, testsBy[pid].length); }); /** * need util.print here as it prints with right encoding */ process.stdout.write(' ' + this.color('medium', spinStates[inSpinState % 4]) + ' ' + this.color(color, test.title)); process.stdout.write(this.color('medium', ' (' + this.gotExecutedBy(pos) + '/' + Object.keys(this.stats.runner).length) + ')'); inSpinState++; }.bind(this), 100); }; /** * remove and clear spinner */ Spec.prototype.clearSpinner = function() { clearInterval(this.spinner); delete this.spinner; this.cursor.deleteLine(); this.cursor.beginningOfLine(); }; /** * Expose Spec */ exports = module.exports = Spec;