|
|
@ -1,4 +1,11 @@ |
|
|
|
'use strict'; |
|
|
|
var EventEmitter = require('events').EventEmitter; |
|
|
|
var util = require('util'); |
|
|
|
var fnName = require('fn-name'); |
|
|
|
var Concurrent = require('./concurrent'); |
|
|
|
var Sequence = require('./sequence'); |
|
|
|
var Test = require('./test'); |
|
|
|
|
|
|
|
module.exports = TestCollection; |
|
|
|
|
|
|
|
function TestCollection() { |
|
|
@ -6,158 +13,168 @@ function TestCollection() { |
|
|
|
throw new TypeError('Class constructor TestCollection cannot be invoked without \'new\''); |
|
|
|
} |
|
|
|
|
|
|
|
this.serial = []; |
|
|
|
this.concurrent = []; |
|
|
|
EventEmitter.call(this); |
|
|
|
|
|
|
|
this.hasExclusive = false; |
|
|
|
this.tests = { |
|
|
|
concurrent: [], |
|
|
|
serial: [] |
|
|
|
}; |
|
|
|
|
|
|
|
this.hooks = { |
|
|
|
before: [], |
|
|
|
beforeEach: [], |
|
|
|
after: [], |
|
|
|
afterEach: [] |
|
|
|
}; |
|
|
|
this.hasExclusive = false; |
|
|
|
|
|
|
|
this._emitTestResult = this._emitTestResult.bind(this); |
|
|
|
} |
|
|
|
|
|
|
|
util.inherits(TestCollection, EventEmitter); |
|
|
|
|
|
|
|
TestCollection.prototype.add = function (test) { |
|
|
|
var metadata = test.metadata; |
|
|
|
var type = metadata.type; |
|
|
|
|
|
|
|
if (!type) { |
|
|
|
throw new Error('test type must be specified'); |
|
|
|
throw new Error('Test type must be specified'); |
|
|
|
} |
|
|
|
|
|
|
|
if (!test.title && test.fn) { |
|
|
|
test.title = fnName(test.fn); |
|
|
|
} |
|
|
|
|
|
|
|
// workaround for Babel giving anonymous functions a name
|
|
|
|
if (test.title === 'callee$0$0') { |
|
|
|
test.title = null; |
|
|
|
} |
|
|
|
|
|
|
|
if (type === 'test') { |
|
|
|
if (this.hasExclusive && !metadata.exclusive) { |
|
|
|
return; |
|
|
|
if (!test.title) { |
|
|
|
if (type === 'test') { |
|
|
|
test.title = '[anonymous]'; |
|
|
|
} else { |
|
|
|
test.title = type; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (metadata.exclusive && !this.hasExclusive) { |
|
|
|
this.hasExclusive = true; |
|
|
|
this.serial = []; |
|
|
|
this.concurrent = []; |
|
|
|
// add a hook
|
|
|
|
if (type !== 'test') { |
|
|
|
if (metadata.exclusive) { |
|
|
|
throw new Error('"only" cannot be used with a ' + type + ' test'); |
|
|
|
} |
|
|
|
|
|
|
|
(metadata.serial ? this.serial : this.concurrent).push(test); |
|
|
|
this.hooks[type].push(test); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// add .only() tests if .only() was used previously
|
|
|
|
if (this.hasExclusive && !metadata.exclusive) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (metadata.exclusive) { |
|
|
|
throw new Error('you can\'t use "only" with a ' + type + ' test'); |
|
|
|
if (metadata.exclusive && !this.hasExclusive) { |
|
|
|
this.tests.concurrent = []; |
|
|
|
this.tests.serial = []; |
|
|
|
this.hasExclusive = true; |
|
|
|
} |
|
|
|
|
|
|
|
this.tests[type].push(test); |
|
|
|
if (metadata.serial) { |
|
|
|
this.tests.serial.push(test); |
|
|
|
} else { |
|
|
|
this.tests.concurrent.push(test); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
var fnName = require('fn-name'); |
|
|
|
var Sequence = require('./sequence'); |
|
|
|
var Concurrent = require('./concurrent'); |
|
|
|
var Test = require('./test'); |
|
|
|
TestCollection.prototype._skippedTest = function (test) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
function computeTitle(entry) { |
|
|
|
entry.title = entry.title || fnName(entry.fn); |
|
|
|
return { |
|
|
|
run: function () { |
|
|
|
var result = { |
|
|
|
passed: true, |
|
|
|
result: test |
|
|
|
}; |
|
|
|
|
|
|
|
// workaround for Babel giving anonymous functions a name
|
|
|
|
if (entry.title === 'callee$0$0') { |
|
|
|
entry.title = null; |
|
|
|
} |
|
|
|
self._emitTestResult(result); |
|
|
|
|
|
|
|
if (!entry.title) { |
|
|
|
if (entry.metadata.type === 'test') { |
|
|
|
entry.title = '[anonymous]'; |
|
|
|
} else { |
|
|
|
entry.title = entry.metadata.type; |
|
|
|
return result; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
TestCollection.prototype._emitTestResult = function (test) { |
|
|
|
this.emit('test', test); |
|
|
|
}; |
|
|
|
|
|
|
|
function buildHooks(hookArray, title, contextRef, report) { |
|
|
|
return hookArray.map(function (hook) { |
|
|
|
var test = buildHook(hook, title, contextRef, report); |
|
|
|
TestCollection.prototype._buildHooks = function (hooks, testTitle, context) { |
|
|
|
return hooks.map(function (hook) { |
|
|
|
var test = this._buildHook(hook, testTitle, context); |
|
|
|
|
|
|
|
if (hook.metadata.skipped) { |
|
|
|
return skipRunner(test, report); |
|
|
|
return this._skippedTest(test); |
|
|
|
} |
|
|
|
|
|
|
|
return test; |
|
|
|
}); |
|
|
|
} |
|
|
|
}, this); |
|
|
|
}; |
|
|
|
|
|
|
|
TestCollection.prototype._buildHook = function (hook, testTitle, context) { |
|
|
|
var title = hook.title; |
|
|
|
|
|
|
|
if (testTitle) { |
|
|
|
title += ' for ' + testTitle; |
|
|
|
} |
|
|
|
|
|
|
|
if (!context) { |
|
|
|
context = null; |
|
|
|
} |
|
|
|
|
|
|
|
function buildHook(hook, title, contextRef, report) { |
|
|
|
var test = new Test(hook.title + ' for ' + title, hook.fn, contextRef, report); |
|
|
|
var test = new Test(title, hook.fn, context, this._emitTestResult); |
|
|
|
test.metadata = hook.metadata; |
|
|
|
return test; |
|
|
|
} |
|
|
|
|
|
|
|
function buildTest(entry, ctxRef, report) { |
|
|
|
var test = new Test(entry.title, entry.fn, ctxRef, report); |
|
|
|
test.metadata = entry.metadata; |
|
|
|
return test; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
function skipRunner(test, report) { |
|
|
|
return { |
|
|
|
run: function () { |
|
|
|
var result = { |
|
|
|
passed: true, |
|
|
|
result: test |
|
|
|
}; |
|
|
|
if (report) { |
|
|
|
report(result); |
|
|
|
} |
|
|
|
return result; |
|
|
|
} |
|
|
|
}; |
|
|
|
} |
|
|
|
TestCollection.prototype._buildTest = function (test, context) { |
|
|
|
if (!context) { |
|
|
|
context = null; |
|
|
|
} |
|
|
|
|
|
|
|
var metadata = test.metadata; |
|
|
|
|
|
|
|
function buildTestWithHooks(entry, beforeEach, afterEach, report) { |
|
|
|
if (entry.metadata.skipped) { |
|
|
|
return [skipRunner(buildTest(entry), report)]; |
|
|
|
test = new Test(test.title, test.fn, context, this._emitTestResult); |
|
|
|
test.metadata = metadata; |
|
|
|
|
|
|
|
return test; |
|
|
|
}; |
|
|
|
|
|
|
|
TestCollection.prototype._buildTestWithHooks = function (test) { |
|
|
|
if (test.metadata.skipped) { |
|
|
|
return [this._skippedTest(this._buildTest(test))]; |
|
|
|
} |
|
|
|
|
|
|
|
var contextRef = {context: {}}; |
|
|
|
var arr = buildHooks(beforeEach, entry.title, contextRef, report); |
|
|
|
arr.push(buildTest(entry, contextRef, report)); |
|
|
|
var context = {context: {}}; |
|
|
|
|
|
|
|
return arr.concat(buildHooks(afterEach, entry.title, contextRef, report)); |
|
|
|
} |
|
|
|
var beforeHooks = this._buildHooks(this.hooks.beforeEach, test.title, context); |
|
|
|
var afterHooks = this._buildHooks(this.hooks.afterEach, test.title, context); |
|
|
|
|
|
|
|
return [].concat(beforeHooks, this._buildTest(test, context), afterHooks); |
|
|
|
}; |
|
|
|
|
|
|
|
TestCollection.prototype._buildTests = function (tests) { |
|
|
|
return tests.map(function (test) { |
|
|
|
return new Sequence(this._buildTestWithHooks(test), true); |
|
|
|
}, this); |
|
|
|
}; |
|
|
|
|
|
|
|
TestCollection.prototype.build = function (bail) { |
|
|
|
var beforeHooks = new Sequence(this._buildHooks(this.hooks.before)); |
|
|
|
var afterHooks = new Sequence(this._buildHooks(this.hooks.after)); |
|
|
|
|
|
|
|
var serialTests = new Sequence(this._buildTests(this.tests.serial), bail); |
|
|
|
var concurrentTests = new Concurrent(this._buildTests(this.tests.concurrent), bail); |
|
|
|
var allTests = new Sequence([serialTests, concurrentTests]); |
|
|
|
|
|
|
|
TestCollection.prototype.buildPhases = function (report, bail) { |
|
|
|
[ |
|
|
|
this.serial, |
|
|
|
this.concurrent, |
|
|
|
this.tests.before, |
|
|
|
this.tests.beforeEach, |
|
|
|
this.tests.after, |
|
|
|
this.tests.afterEach |
|
|
|
].forEach(function (arr) { |
|
|
|
arr.forEach(computeTitle); |
|
|
|
}); |
|
|
|
|
|
|
|
return new Sequence( |
|
|
|
[ |
|
|
|
new Sequence(this.tests.before.map(function (entry) { |
|
|
|
if (entry.metadata.skipped) { |
|
|
|
return skipRunner(entry, report); |
|
|
|
} |
|
|
|
|
|
|
|
return buildTest(entry, null, report); |
|
|
|
})), |
|
|
|
new Sequence([ |
|
|
|
new Sequence(this.serial.map(function (entry) { |
|
|
|
return new Sequence(buildTestWithHooks(entry, this.tests.beforeEach, this.tests.afterEach, report), true); |
|
|
|
}, this), bail), |
|
|
|
new Concurrent(this.concurrent.map(function (entry) { |
|
|
|
return new Sequence(buildTestWithHooks(entry, this.tests.beforeEach, this.tests.afterEach, report), true); |
|
|
|
}, this), bail) |
|
|
|
], bail), |
|
|
|
new Sequence(this.tests.after.map(function (entry) { |
|
|
|
if (entry.metadata.skipped) { |
|
|
|
return skipRunner(entry, report); |
|
|
|
} |
|
|
|
|
|
|
|
return buildTest(entry, null, report); |
|
|
|
})) |
|
|
|
], |
|
|
|
true |
|
|
|
); |
|
|
|
return new Sequence([beforeHooks, allTests, afterHooks], true); |
|
|
|
}; |
|
|
|