Skip to content

Commit d874ec9

Browse files
jamestalmagesindresorhus
authored andcommitted
Close #243 PR: Make runner methods chainable.
1 parent 4686080 commit d874ec9

File tree

8 files changed

+305
-202
lines changed

8 files changed

+305
-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: 73 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,48 @@ 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+
return this.tests.filter(function (test) {
198+
return Object.keys(filter).every(function (key) {
199+
return filter[key] === test.metadata[key];
200+
});
201+
});
202+
};

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.assertError = undefined;
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
@@ -98,6 +98,7 @@
9898
"loud-rejection": "^1.2.0",
9999
"max-timeout": "^1.0.0",
100100
"meow": "^3.6.0",
101+
"object-assign": "^4.0.1",
101102
"observable-to-promise": "^0.1.0",
102103
"plur": "^2.0.0",
103104
"power-assert-formatter": "^1.3.0",

readme.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,19 @@ test(t => {
274274
});
275275
```
276276

277+
### Chaining test modifiers
278+
279+
You can chain test modifiers together in the following ways:
280+
281+
```js
282+
test.before.skip([title], testFn);
283+
test.skip.after(....);
284+
test.serial.only(...);
285+
test.only.serial(...);
286+
```
287+
288+
This is especially helpful temporarily using `skip` or `only` on a test, without losing the information and behavior the other modifiers provide.
289+
277290
### Custom assertion module
278291

279292
You can use any assertion module instead or in addition to the one that comes with AVA, but you won't be able to use the `.plan()` method, [yet](https://github.com/sindresorhus/ava/issues/25).

0 commit comments

Comments
 (0)