Skip to content

Commit ff736c4

Browse files
committed
squash of PR #243
runner.js: simplify test data structure split hooks to their own file auto-generate runner.addXXX functions add chaining drop `map-obj` as a dependency (I did not end up needing it). refactor tests to use chained methods simplify chain builder add chainable tests make all hooks skippable test title of skipped test move test flags to metadata object, instead of right on test remove unused property
1 parent 60b2337 commit ff736c4

File tree

7 files changed

+293
-202
lines changed

7 files changed

+293
-202
lines changed

index.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,4 @@ setImmediate(function () {
7070
runner.run().then(exit);
7171
});
7272

73-
module.exports = runner.addTest.bind(runner);
74-
module.exports.serial = runner.addSerialTest.bind(runner);
75-
module.exports.before = runner.addBeforeHook.bind(runner);
76-
module.exports.after = runner.addAfterHook.bind(runner);
77-
module.exports.beforeEach = runner.addBeforeEachHook.bind(runner);
78-
module.exports.afterEach = runner.addAfterEachHook.bind(runner);
79-
module.exports.skip = runner.addSkippedTest.bind(runner);
80-
module.exports.only = runner.addOnlyTest.bind(runner);
73+
module.exports = runner.test;

lib/hook.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
var Test = require('./test');
2+
3+
module.exports = Hook;
4+
5+
function Hook(title, fn) {
6+
if (!(this instanceof Hook)) {
7+
return new Hook(title, fn);
8+
}
9+
10+
if (typeof title === 'function') {
11+
fn = title;
12+
title = null;
13+
}
14+
15+
this.title = title;
16+
this.fn = fn;
17+
}
18+
19+
Hook.prototype.test = function (testTitle) {
20+
var title = this.title || (this.metadata.type + ' for "' + testTitle + '"');
21+
var test = new Test(title, this.fn);
22+
test.metadata = this.metadata;
23+
return test;
24+
};

lib/runner.js

Lines changed: 74 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ var util = require('util');
44
var Promise = require('bluebird');
55
var hasFlag = require('has-flag');
66
var Test = require('./test');
7+
var Hook = require('./hook');
78
var send = require('./send');
9+
var objectAssign = require('object-assign');
810

911
function noop() {}
1012

@@ -13,7 +15,7 @@ function each(items, fn, context) {
1315
}
1416

1517
function eachSeries(items, fn, context) {
16-
return Promise.resolve(items).each(fn.bind(context));
18+
return Promise.each(items, fn.bind(context));
1719
}
1820

1921
function Runner(opts) {
@@ -25,112 +27,77 @@ function Runner(opts) {
2527

2628
this.results = [];
2729

28-
this.stats = {
29-
failCount: 0,
30-
passCount: 0,
31-
testCount: 0
32-
};
30+
this.tests = [];
3331

34-
this.tests = {
35-
concurrent: [],
36-
serial: [],
37-
only: [],
38-
before: [],
39-
after: [],
40-
beforeEach: [],
41-
afterEach: []
42-
};
32+
this.test = makeChain({
33+
type: 'test',
34+
serial: false,
35+
exclusive: false,
36+
skipped: false
37+
}, this._addFn.bind(this));
4338
}
4439

4540
util.inherits(Runner, EventEmitter);
4641
module.exports = Runner;
4742

48-
Runner.prototype.addTest = function (title, cb) {
49-
this.stats.testCount++;
50-
this.tests.concurrent.push(new Test(title, cb));
51-
};
52-
53-
Runner.prototype.addSerialTest = function (title, cb) {
54-
this.stats.testCount++;
55-
this.tests.serial.push(new Test(title, cb));
56-
};
57-
58-
Runner.prototype.addBeforeHook = function (title, cb) {
59-
var test = new Test(title, cb);
60-
test.type = 'hook';
61-
62-
this.tests.before.push(test);
63-
};
64-
65-
Runner.prototype.addAfterHook = function (title, cb) {
66-
var test = new Test(title, cb);
67-
test.type = 'hook';
68-
69-
this.tests.after.push(test);
43+
var chainableFunctions = {
44+
serial: {serial: true},
45+
before: {type: 'before'},
46+
after: {type: 'after'},
47+
skip: {skipped: true},
48+
only: {exclusive: true},
49+
beforeEach: {type: 'beforeEach'},
50+
afterEach: {type: 'afterEach'}
7051
};
7152

72-
Runner.prototype.addBeforeEachHook = function (title, cb) {
73-
if (!cb) {
74-
cb = title;
75-
title = undefined;
53+
function makeChain(defaults, parentAdd) {
54+
function fn(title, fn) {
55+
parentAdd(defaults, title, fn);
7656
}
7757

78-
this.tests.beforeEach.push({
79-
title: title,
80-
fn: cb
81-
});
82-
};
83-
84-
Runner.prototype.addAfterEachHook = function (title, cb) {
85-
if (!cb) {
86-
cb = title;
87-
title = undefined;
58+
function add(opts, title, fn) {
59+
opts = objectAssign({}, defaults, opts);
60+
parentAdd(objectAssign({}, defaults, opts), title, fn);
8861
}
8962

90-
this.tests.afterEach.push({
91-
title: title,
92-
fn: cb
63+
Object.keys(chainableFunctions).forEach(function (key) {
64+
Object.defineProperty(fn, key, {
65+
get: function () {
66+
return makeChain(objectAssign({}, defaults, chainableFunctions[key]), add);
67+
}
68+
});
9369
});
94-
};
9570

96-
Runner.prototype.addSkippedTest = function (title, cb) {
97-
var test = new Test(title, cb);
98-
test.skip = true;
71+
return fn;
72+
}
9973

100-
this.tests.concurrent.push(test);
101-
};
74+
Object.keys(chainableFunctions).forEach(function (key) {
75+
Object.defineProperty(Runner.prototype, key, {
76+
get: function () {
77+
return this.test[key];
78+
}
79+
});
80+
});
10281

103-
Runner.prototype.addOnlyTest = function (title, cb) {
104-
this.stats.testCount++;
105-
this.tests.only.push(new Test(title, cb));
82+
Runner.prototype._addFn = function (opts, title, fn) {
83+
var Constructor = (opts && /Each/.test(opts.type)) ? Hook : Test;
84+
var test = new Constructor(title, fn);
85+
test.metadata = objectAssign({}, opts);
86+
this.tests.push(test);
10687
};
10788

10889
Runner.prototype._runTestWithHooks = function (test) {
10990
if (test.skip) {
11091
return this._addTestResult(test);
11192
}
11293

113-
var beforeHooks = this.tests.beforeEach.map(function (hook) {
114-
var title = hook.title || 'beforeEach for "' + test.title + '"';
115-
hook = new Test(title, hook.fn);
116-
hook.type = 'eachHook';
117-
118-
return hook;
119-
});
120-
121-
var afterHooks = this.tests.afterEach.map(function (hook) {
122-
var title = hook.title || 'afterEach for "' + test.title + '"';
123-
hook = new Test(title, hook.fn);
124-
hook.type = 'eachHook';
125-
126-
return hook;
127-
});
128-
129-
var tests = [];
94+
function hookToTest(hook) {
95+
return hook.test(test.title);
96+
}
13097

131-
tests.push.apply(tests, beforeHooks);
98+
var tests = this.select({type: 'beforeEach', skipped: false}).map(hookToTest);
13299
tests.push(test);
133-
tests.push.apply(tests, afterHooks);
100+
tests.push.apply(tests, this.select({type: 'afterEach', skipped: false}).map(hookToTest));
134101

135102
var context = {};
136103

@@ -158,15 +125,15 @@ Runner.prototype._runTest = function (test) {
158125
});
159126
};
160127

161-
Runner.prototype.concurrent = function (tests) {
128+
Runner.prototype._runConcurrent = function (tests) {
162129
if (hasFlag('serial')) {
163-
return this.serial(tests);
130+
return this._runSerial(tests);
164131
}
165132

166133
return each(tests, this._runTestWithHooks, this);
167134
};
168135

169-
Runner.prototype.serial = function (tests) {
136+
Runner.prototype._runSerial = function (tests) {
170137
return eachSeries(tests, this._runTestWithHooks, this);
171138
};
172139

@@ -179,7 +146,7 @@ Runner.prototype._addTestResult = function (test) {
179146
duration: test.duration,
180147
title: test.title,
181148
error: test.assertError,
182-
type: test.type,
149+
type: test.metadata.type,
183150
skip: test.skip
184151
};
185152

@@ -188,43 +155,49 @@ Runner.prototype._addTestResult = function (test) {
188155
};
189156

190157
Runner.prototype.run = function () {
191-
var tests = this.tests;
192-
var stats = this.stats;
193158
var self = this;
159+
var hasExclusive = Boolean(this.select({exclusive: true, skipped: false, type: 'test'}).length);
160+
var serial = this.select({exclusive: hasExclusive, skipped: false, serial: true, type: 'test'});
161+
var concurrent = this.select({exclusive: hasExclusive, skipped: false, serial: false, type: 'test'});
194162

195-
var hasOnlyTests = tests.only.length > 0;
163+
var stats = this.stats = {
164+
failCount: 0,
165+
passCount: 0,
166+
testCount: serial.length + concurrent.length
167+
};
196168

197169
// Runner is executed directly in tests, in that case process.send() == undefined
198170
if (process.send) {
199171
send('stats', stats);
200172
}
201173

202-
return eachSeries(tests.before, this._runTest, this)
174+
return eachSeries(this.select({type: 'before', skipped: false}), this._runTest, this)
203175
.catch(noop)
204176
.then(function () {
205177
if (stats.failCount > 0) {
206178
return Promise.reject();
207179
}
208180
})
209181
.then(function () {
210-
return self.concurrent(tests.only);
211-
})
212-
.then(function () {
213-
if (!hasOnlyTests) {
214-
return self.serial(tests.serial);
215-
}
182+
return self._runSerial(serial);
216183
})
217184
.then(function () {
218-
if (!hasOnlyTests) {
219-
return self.concurrent(tests.concurrent);
220-
}
185+
return self._runConcurrent(concurrent);
221186
})
222187
.then(function () {
223-
return eachSeries(tests.after, self._runTest, self);
188+
return eachSeries(self.select({type: 'after', skipped: false}), self._runTest, self);
224189
})
225190
.catch(noop)
226191
.then(function () {
227-
stats.testCount = tests.only.length || stats.testCount;
228192
stats.passCount = stats.testCount - stats.failCount;
229193
});
230194
};
195+
196+
Runner.prototype.select = function (filter) {
197+
function filterFn(test) {
198+
return Object.keys(filter).every(function (key) {
199+
return filter[key] === test.metadata[key];
200+
});
201+
}
202+
return this.tests.filter(filterFn);
203+
};

lib/test.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ function Test(title, fn) {
2828
this.duration = null;
2929
this.context = {};
3030

31-
// test type, can be: test, hook, eachHook
32-
this.type = 'test';
33-
3431
// store the time point before test execution
3532
// to calculate the total time spent in test
3633
this._timeStart = null;

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@
9393
"loud-rejection": "^1.2.0",
9494
"max-timeout": "^1.0.0",
9595
"meow": "^3.6.0",
96+
"object-assign": "^4.0.1",
9697
"observable-to-promise": "^0.1.0",
9798
"plur": "^2.0.0",
9899
"power-assert-formatter": "^1.3.0",

0 commit comments

Comments
 (0)