Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,4 @@ setImmediate(function () {
runner.run().then(exit);
});

module.exports = runner.addTest.bind(runner);
module.exports.serial = runner.addSerialTest.bind(runner);
module.exports.before = runner.addBeforeHook.bind(runner);
module.exports.after = runner.addAfterHook.bind(runner);
module.exports.beforeEach = runner.addBeforeEachHook.bind(runner);
module.exports.afterEach = runner.addAfterEachHook.bind(runner);
module.exports.skip = runner.addSkippedTest.bind(runner);
module.exports.only = runner.addOnlyTest.bind(runner);
module.exports = runner.test;
24 changes: 24 additions & 0 deletions lib/hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
var Test = require('./test');

module.exports = Hook;

function Hook(title, fn) {
if (!(this instanceof Hook)) {
return new Hook(title, fn);
}

if (typeof title === 'function') {
fn = title;
title = null;
}

this.title = title;
this.fn = fn;
}

Hook.prototype.test = function (testTitle) {
var title = this.title || (this.metadata.type + ' for "' + testTitle + '"');
var test = new Test(title, this.fn);
test.metadata = this.metadata;
return test;
};
174 changes: 73 additions & 101 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ var util = require('util');
var Promise = require('bluebird');
var hasFlag = require('has-flag');
var Test = require('./test');
var Hook = require('./hook');
var send = require('./send');
var objectAssign = require('object-assign');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: We probably should look in to replacing some of these polyfills with core-js equivalents. Hopefully cutting down on duplicate downloads for users.


function noop() {}

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

function eachSeries(items, fn, context) {
return Promise.resolve(items).each(fn.bind(context));
return Promise.each(items, fn.bind(context));
}

function Runner(opts) {
Expand All @@ -25,112 +27,77 @@ function Runner(opts) {

this.results = [];

this.stats = {
failCount: 0,
passCount: 0,
testCount: 0
};
this.tests = [];

this.tests = {
concurrent: [],
serial: [],
only: [],
before: [],
after: [],
beforeEach: [],
afterEach: []
};
this.test = makeChain({
type: 'test',
serial: false,
exclusive: false,
skipped: false
}, this._addFn.bind(this));
}

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

Runner.prototype.addTest = function (title, cb) {
this.stats.testCount++;
this.tests.concurrent.push(new Test(title, cb));
};

Runner.prototype.addSerialTest = function (title, cb) {
this.stats.testCount++;
this.tests.serial.push(new Test(title, cb));
};

Runner.prototype.addBeforeHook = function (title, cb) {
var test = new Test(title, cb);
test.type = 'hook';

this.tests.before.push(test);
};

Runner.prototype.addAfterHook = function (title, cb) {
var test = new Test(title, cb);
test.type = 'hook';

this.tests.after.push(test);
var chainableFunctions = {
serial: {serial: true},
before: {type: 'before'},
after: {type: 'after'},
skip: {skipped: true},
only: {exclusive: true},
beforeEach: {type: 'beforeEach'},
afterEach: {type: 'afterEach'}
};

Runner.prototype.addBeforeEachHook = function (title, cb) {
if (!cb) {
cb = title;
title = undefined;
function makeChain(defaults, parentAdd) {
function fn(title, fn) {
parentAdd(defaults, title, fn);
}

this.tests.beforeEach.push({
title: title,
fn: cb
});
};

Runner.prototype.addAfterEachHook = function (title, cb) {
if (!cb) {
cb = title;
title = undefined;
function add(opts, title, fn) {
opts = objectAssign({}, defaults, opts);
parentAdd(objectAssign({}, defaults, opts), title, fn);
}

this.tests.afterEach.push({
title: title,
fn: cb
Object.keys(chainableFunctions).forEach(function (key) {
Object.defineProperty(fn, key, {
get: function () {
return makeChain(objectAssign({}, defaults, chainableFunctions[key]), add);
}
});
});
};

Runner.prototype.addSkippedTest = function (title, cb) {
var test = new Test(title, cb);
test.skip = true;
return fn;
}

this.tests.concurrent.push(test);
};
Object.keys(chainableFunctions).forEach(function (key) {
Object.defineProperty(Runner.prototype, key, {
get: function () {
return this.test[key];
}
});
});

Runner.prototype.addOnlyTest = function (title, cb) {
this.stats.testCount++;
this.tests.only.push(new Test(title, cb));
Runner.prototype._addFn = function (opts, title, fn) {
var Constructor = (opts && /Each/.test(opts.type)) ? Hook : Test;
var test = new Constructor(title, fn);
test.metadata = objectAssign({}, opts);
this.tests.push(test);
};

Runner.prototype._runTestWithHooks = function (test) {
if (test.skip) {
return this._addTestResult(test);
}

var beforeHooks = this.tests.beforeEach.map(function (hook) {
var title = hook.title || 'beforeEach for "' + test.title + '"';
hook = new Test(title, hook.fn);
hook.type = 'eachHook';

return hook;
});

var afterHooks = this.tests.afterEach.map(function (hook) {
var title = hook.title || 'afterEach for "' + test.title + '"';
hook = new Test(title, hook.fn);
hook.type = 'eachHook';

return hook;
});

var tests = [];
function hookToTest(hook) {
return hook.test(test.title);
}

tests.push.apply(tests, beforeHooks);
var tests = this.select({type: 'beforeEach', skipped: false}).map(hookToTest);
tests.push(test);
tests.push.apply(tests, afterHooks);
tests.push.apply(tests, this.select({type: 'afterEach', skipped: false}).map(hookToTest));

var context = {};

Expand Down Expand Up @@ -158,15 +125,15 @@ Runner.prototype._runTest = function (test) {
});
};

Runner.prototype.concurrent = function (tests) {
Runner.prototype._runConcurrent = function (tests) {
if (hasFlag('serial')) {
return this.serial(tests);
return this._runSerial(tests);
}

return each(tests, this._runTestWithHooks, this);
};

Runner.prototype.serial = function (tests) {
Runner.prototype._runSerial = function (tests) {
return eachSeries(tests, this._runTestWithHooks, this);
};

Expand All @@ -179,7 +146,7 @@ Runner.prototype._addTestResult = function (test) {
duration: test.duration,
title: test.title,
error: test.assertError,
type: test.type,
type: test.metadata.type,
skip: test.skip
};

Expand All @@ -188,43 +155,48 @@ Runner.prototype._addTestResult = function (test) {
};

Runner.prototype.run = function () {
var tests = this.tests;
var stats = this.stats;
var self = this;
var hasExclusive = Boolean(this.select({exclusive: true, skipped: false, type: 'test'}).length);
var serial = this.select({exclusive: hasExclusive, skipped: false, serial: true, type: 'test'});
var concurrent = this.select({exclusive: hasExclusive, skipped: false, serial: false, type: 'test'});

var hasOnlyTests = tests.only.length > 0;
var stats = this.stats = {
failCount: 0,
passCount: 0,
testCount: serial.length + concurrent.length
};

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

return eachSeries(tests.before, this._runTest, this)
return eachSeries(this.select({type: 'before', skipped: false}), this._runTest, this)
.catch(noop)
.then(function () {
if (stats.failCount > 0) {
return Promise.reject();
}
})
.then(function () {
return self.concurrent(tests.only);
})
.then(function () {
if (!hasOnlyTests) {
return self.serial(tests.serial);
}
return self._runSerial(serial);
})
.then(function () {
if (!hasOnlyTests) {
return self.concurrent(tests.concurrent);
}
return self._runConcurrent(concurrent);
})
.then(function () {
return eachSeries(tests.after, self._runTest, self);
return eachSeries(self.select({type: 'after', skipped: false}), self._runTest, self);
})
.catch(noop)
.then(function () {
stats.testCount = tests.only.length || stats.testCount;
stats.passCount = stats.testCount - stats.failCount;
});
};

Runner.prototype.select = function (filter) {
return this.tests.filter(function (test) {
return Object.keys(filter).every(function (key) {
return filter[key] === test.metadata[key];
});
});
};
3 changes: 0 additions & 3 deletions lib/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ function Test(title, fn) {
this.duration = null;
this.assertError = undefined;

// test type, can be: test, hook, eachHook
this.type = 'test';

// store the time point before test execution
// to calculate the total time spent in test
this._timeStart = null;
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"loud-rejection": "^1.2.0",
"max-timeout": "^1.0.0",
"meow": "^3.6.0",
"object-assign": "^4.0.1",
"observable-to-promise": "^0.1.0",
"plur": "^2.0.0",
"power-assert-formatter": "^1.3.0",
Expand Down
14 changes: 14 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,20 @@ test(t => {
});
```

### Chaining test modifiers

You can chain test modifiers together in the following ways:

```js
test.before.skip([title], testFn);
test.skip.after(....);
test.serial.only(...);
test.only.serial(...);
```

This is especially helpful temporarily using `skip` or `only` on a test, without losing the information
and behavior the other modifiers provide.

### Custom assertion module

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).
Expand Down
Loading