Skip to content

Commit 9d38c8f

Browse files
author
vdemedes
committed
add programmatic API
1 parent 876a579 commit 9d38c8f

File tree

9 files changed

+316
-201
lines changed

9 files changed

+316
-201
lines changed

api.js

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
'use strict';
2+
var EventEmitter = require('events').EventEmitter;
3+
var path = require('path');
4+
var util = require('util');
5+
var fs = require('fs');
6+
var flatten = require('arr-flatten');
7+
var Promise = require('bluebird');
8+
var figures = require('figures');
9+
var assign = require('object-assign');
10+
var globby = require('globby');
11+
var chalk = require('chalk');
12+
var fork = require('./lib/fork');
13+
14+
function Api(files, options) {
15+
if (!(this instanceof Api)) {
16+
return new Api(files, options);
17+
}
18+
19+
EventEmitter.call(this);
20+
21+
assign(this, options);
22+
23+
this.rejectionCount = 0;
24+
this.exceptionCount = 0;
25+
this.passCount = 0;
26+
this.failCount = 0;
27+
this.fileCount = 0;
28+
this.testCount = 0;
29+
this.errors = [];
30+
this.stats = [];
31+
this.tests = [];
32+
this.files = files || [];
33+
34+
Object.keys(Api.prototype).forEach(function (key) {
35+
this[key] = this[key].bind(this);
36+
}, this);
37+
}
38+
39+
util.inherits(Api, EventEmitter);
40+
module.exports = Api;
41+
42+
Api.prototype._runFile = function (file) {
43+
var args = [file];
44+
45+
if (this.failFast) {
46+
args.push('--fail-fast');
47+
}
48+
49+
if (this.serial) {
50+
args.push('--serial');
51+
}
52+
53+
// Forward the `time-require` `--sorted` flag.
54+
// Intended for internal optimization tests only.
55+
if (this._sorted) {
56+
args.push('--sorted');
57+
}
58+
59+
return fork(args)
60+
.on('stats', this._handleStats)
61+
.on('test', this._handleTest)
62+
.on('unhandledRejections', this._handleRejections)
63+
.on('uncaughtException', this._handleExceptions);
64+
};
65+
66+
Api.prototype._handleRejections = function (data) {
67+
this.rejectionCount += data.rejections.length;
68+
69+
data.rejections.forEach(function (err) {
70+
err.type = 'rejection';
71+
err.file = data.file;
72+
this.emit('error', err);
73+
this.errors.push(err);
74+
}, this);
75+
};
76+
77+
Api.prototype._handleExceptions = function (err) {
78+
this.exceptionCount++;
79+
err.type = 'exception';
80+
this.emit('error', err);
81+
this.errors.push(err);
82+
};
83+
84+
Api.prototype._handleStats = function (stats) {
85+
this.testCount += stats.testCount;
86+
};
87+
88+
Api.prototype._handleTest = function (test) {
89+
test.title = this._prefixTitle(test.file) + test.title;
90+
91+
var isError = test.error.message;
92+
93+
if (isError) {
94+
this.errors.push(test);
95+
} else {
96+
test.error = null;
97+
}
98+
99+
this.emit('test', test);
100+
};
101+
102+
Api.prototype._prefixTitle = function (file) {
103+
if (this.fileCount === 1) {
104+
return '';
105+
}
106+
107+
var separator = ' ' + chalk.gray.dim(figures.pointerSmall) + ' ';
108+
109+
var base = path.dirname(this.files[0]);
110+
111+
if (base === '.') {
112+
base = this.files[0] || 'test';
113+
}
114+
115+
base += path.sep;
116+
117+
var prefix = path.relative('.', file)
118+
.replace(base, '')
119+
.replace(/\.spec/, '')
120+
.replace(/test\-/g, '')
121+
.replace(/\.js$/, '')
122+
.split(path.sep)
123+
.join(separator);
124+
125+
if (prefix.length > 0) {
126+
prefix += separator;
127+
}
128+
129+
return prefix;
130+
};
131+
132+
Api.prototype.run = function () {
133+
var self = this;
134+
135+
return handlePaths(this.files)
136+
.map(function (file) {
137+
return path.resolve(file);
138+
})
139+
.then(function (files) {
140+
if (files.length === 0) {
141+
return Promise.reject(new Error('Couldn\'t find any files to test'));
142+
}
143+
144+
self.fileCount = files.length;
145+
146+
var tests = files.map(self._runFile);
147+
148+
// receive test count from all files and then run the tests
149+
var statsCount = 0;
150+
var deferred = Promise.pending();
151+
152+
tests.forEach(function (test) {
153+
var counted = false;
154+
155+
function tryRun() {
156+
if (counted) {
157+
return;
158+
}
159+
160+
if (++statsCount === self.fileCount) {
161+
self.emit('ready');
162+
163+
var method = self.serial ? 'mapSeries' : 'map';
164+
165+
deferred.resolve(Promise[method](files, function (file, index) {
166+
return tests[index].run();
167+
}));
168+
}
169+
}
170+
171+
test.on('stats', tryRun);
172+
test.catch(tryRun);
173+
});
174+
175+
return deferred.promise;
176+
})
177+
.then(function (results) {
178+
// assemble stats from all tests
179+
self.stats = results.map(function (result) {
180+
return result.stats;
181+
});
182+
183+
self.tests = results.map(function (result) {
184+
return result.tests;
185+
});
186+
187+
self.tests = flatten(self.tests);
188+
189+
self.passCount = sum(self.stats, 'passCount');
190+
self.failCount = sum(self.stats, 'failCount');
191+
});
192+
};
193+
194+
function handlePaths(files) {
195+
if (files.length === 0) {
196+
files = [
197+
'test.js',
198+
'test-*.js',
199+
'test/*.js'
200+
];
201+
}
202+
203+
files.push('!**/node_modules/**');
204+
205+
// convert pinkie-promise to Bluebird promise
206+
files = Promise.resolve(globby(files));
207+
208+
return files
209+
.map(function (file) {
210+
if (fs.statSync(file).isDirectory()) {
211+
return handlePaths([path.join(file, '*.js')]);
212+
}
213+
214+
return file;
215+
})
216+
.then(flatten)
217+
.filter(function (file) {
218+
return path.extname(file) === '.js' && path.basename(file)[0] !== '_';
219+
});
220+
}
221+
222+
function sum(arr, key) {
223+
var result = 0;
224+
225+
arr.forEach(function (item) {
226+
result += item[key];
227+
});
228+
229+
return result;
230+
}

0 commit comments

Comments
 (0)