Skip to content

Commit

Permalink
Close #279 PR: Add programmatic API. Fixes #83, Fixes #282, Fixes #27
Browse files Browse the repository at this point in the history
  • Loading branch information
vdemedes authored and sindresorhus committed Nov 30, 2015
1 parent 2f05aca commit 766cfee
Show file tree
Hide file tree
Showing 9 changed files with 316 additions and 201 deletions.
230 changes: 230 additions & 0 deletions api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
'use strict';
var EventEmitter = require('events').EventEmitter;
var path = require('path');
var util = require('util');
var fs = require('fs');
var flatten = require('arr-flatten');
var Promise = require('bluebird');
var figures = require('figures');
var assign = require('object-assign');
var globby = require('globby');
var chalk = require('chalk');
var fork = require('./lib/fork');

function Api(files, options) {
if (!(this instanceof Api)) {
return new Api(files, options);
}

EventEmitter.call(this);

assign(this, options);

this.rejectionCount = 0;
this.exceptionCount = 0;
this.passCount = 0;
this.failCount = 0;
this.fileCount = 0;
this.testCount = 0;
this.errors = [];
this.stats = [];
this.tests = [];
this.files = files || [];

Object.keys(Api.prototype).forEach(function (key) {
this[key] = this[key].bind(this);
}, this);
}

util.inherits(Api, EventEmitter);
module.exports = Api;

Api.prototype._runFile = function (file) {
var args = [file];

if (this.failFast) {
args.push('--fail-fast');
}

if (this.serial) {
args.push('--serial');
}

// Forward the `time-require` `--sorted` flag.
// Intended for internal optimization tests only.
if (this._sorted) {
args.push('--sorted');
}

return fork(args)
.on('stats', this._handleStats)
.on('test', this._handleTest)
.on('unhandledRejections', this._handleRejections)
.on('uncaughtException', this._handleExceptions);
};

Api.prototype._handleRejections = function (data) {
this.rejectionCount += data.rejections.length;

data.rejections.forEach(function (err) {
err.type = 'rejection';
err.file = data.file;
this.emit('error', err);
this.errors.push(err);
}, this);
};

Api.prototype._handleExceptions = function (err) {
this.exceptionCount++;
err.type = 'exception';
this.emit('error', err);
this.errors.push(err);
};

Api.prototype._handleStats = function (stats) {
this.testCount += stats.testCount;
};

Api.prototype._handleTest = function (test) {
test.title = this._prefixTitle(test.file) + test.title;

var isError = test.error.message;

if (isError) {
this.errors.push(test);
} else {
test.error = null;
}

this.emit('test', test);
};

Api.prototype._prefixTitle = function (file) {
if (this.fileCount === 1) {
return '';
}

var separator = ' ' + chalk.gray.dim(figures.pointerSmall) + ' ';

var base = path.dirname(this.files[0]);

if (base === '.') {
base = this.files[0] || 'test';
}

base += path.sep;

var prefix = path.relative('.', file)
.replace(base, '')
.replace(/\.spec/, '')
.replace(/test\-/g, '')
.replace(/\.js$/, '')
.split(path.sep)
.join(separator);

if (prefix.length > 0) {
prefix += separator;
}

return prefix;
};

Api.prototype.run = function () {
var self = this;

return handlePaths(this.files)
.map(function (file) {
return path.resolve(file);
})
.then(function (files) {
if (files.length === 0) {
return Promise.reject(new Error('Couldn\'t find any files to test'));
}

self.fileCount = files.length;

var tests = files.map(self._runFile);

// receive test count from all files and then run the tests
var statsCount = 0;
var deferred = Promise.pending();

tests.forEach(function (test) {
var counted = false;

function tryRun() {
if (counted) {
return;
}

if (++statsCount === self.fileCount) {
self.emit('ready');

var method = self.serial ? 'mapSeries' : 'map';

deferred.resolve(Promise[method](files, function (file, index) {
return tests[index].run();
}));
}
}

test.on('stats', tryRun);
test.catch(tryRun);
});

return deferred.promise;
})
.then(function (results) {
// assemble stats from all tests
self.stats = results.map(function (result) {
return result.stats;
});

self.tests = results.map(function (result) {
return result.tests;
});

self.tests = flatten(self.tests);

self.passCount = sum(self.stats, 'passCount');
self.failCount = sum(self.stats, 'failCount');
});
};

function handlePaths(files) {
if (files.length === 0) {
files = [
'test.js',
'test-*.js',
'test/*.js'
];
}

files.push('!**/node_modules/**');

// convert pinkie-promise to Bluebird promise
files = Promise.resolve(globby(files));

return files
.map(function (file) {
if (fs.statSync(file).isDirectory()) {
return handlePaths([path.join(file, '*.js')]);
}

return file;
})
.then(flatten)
.filter(function (file) {
return path.extname(file) === '.js' && path.basename(file)[0] !== '_';
});
}

function sum(arr, key) {
var result = 0;

arr.forEach(function (item) {
result += item[key];
});

return result;
}
Loading

0 comments on commit 766cfee

Please sign in to comment.