diff --git a/index.js b/index.js index 46c5663..5bd80dc 100755 --- a/index.js +++ b/index.js @@ -25,7 +25,15 @@ function Formatter (type, options) { if (!(this instanceof Formatter)) { return new Formatter(type, options) } - if (!reporters[type]) { + var _reporter = reporters[type]; + if (!_reporter) { + try { + _reporter = require(type); + } catch (err) { + console.warn(err); + } + } + if (!_reporter) { console.error('Unknown format type: %s\n\n%s', type, avail()) type = 'silent' } @@ -50,12 +58,16 @@ function Formatter (type, options) { } var runner = this.runner = new Runner(options) - this.reporter = new reporters[type](this.runner, {}) + var reporter = this.reporter = new _reporter(this.runner, {}) Writable.call(this, options) runner.on('end', function () { if (!runner.parser.ok) exitCode = 1 + + if (reporter.done) { + reporter.done(runner.stats.failures, process.exit) + } }) } diff --git a/lib/runner.js b/lib/runner.js index 4b5ea0f..86e855f 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -39,7 +39,8 @@ module.exports = Runner // // test end(test) // Emitted immediately after the "test" event because test points are -// not async in TAP. +// not async in TAP. This event is only emitted for passes and +// failures; it is not emitted for skips or TODO tests. var util = require('util') var Test = require('./test.js') @@ -76,6 +77,9 @@ Runner.prototype.write = function () { if (!this.emittedStart) { this.emittedStart = true this.emit('start') + this.suite = new Suite('root') + this.parser.parent = this + this.emit('suite', this.suite) } return this.parser.write.apply(this.parser, arguments) @@ -85,13 +89,6 @@ Runner.prototype.end = function () { return this.parser.end.apply(this.parser, arguments) } -Parser.prototype.fullTitle = function () { - if (!this.parent) - return this.name || '' - else - return (this.parent.fullTitle() + ' ' + (this.name || '')).trim() -} - function attachEvents (runner, parser, level) { parser.runner = runner @@ -103,6 +100,7 @@ function attachEvents (runner, parser, level) { runner.emit('version', v) }) parser.on('complete', function (res) { + runner.emit('suite end', runner.suite) runner.emit('end') }) parser.on('comment', function (c) { @@ -256,9 +254,11 @@ function attachEvents (runner, parser, level) { function emitSuite (parser) { if (!parser.emittedSuite && parser.name) { parser.emittedSuite = true - var suite = parser.suite = new Suite(parser) - if (parser.parent && parser.parent.suite) - parser.parent.suite.suites.push(suite) + var ancestor = parser + while (ancestor && !ancestor.suite) + ancestor = ancestor.parent + var suite = parser.suite = new Suite(parser.name, ancestor.suite) + if (parser.runner.stats) parser.runner.stats.suites ++ @@ -268,28 +268,19 @@ function emitSuite (parser) { function emitTest (parser, result) { var runner = parser.runner - var test = new Test(result, parser) - - if (parser.suite) { - parser.suite.tests.push(test) - if (!result.ok) { - for (var p = parser; p && p.suite; p = p.parent) { - p.suite.ok = false - } - } - parser.suite.ok = parser.suite.ok && result.ok - } - + var test = new Test(result, parser.suite) runner.emit('test', test) - if (result.skip || result.todo) { + if (test.pending) { runner.emit('pending', test) - } else if (result.ok) { + } else if (test.state === 'passed') { runner.emit('pass', test) - } else { + } else if (test.state === 'failed') { var error = getError(result) runner.emit('fail', test, error) } - runner.emit('test end', test) + if (!test.pending && !result.skip) { + runner.emit('test end', test) + } } function getError (result) { diff --git a/lib/suite.js b/lib/suite.js index d1ed52c..d8effc9 100644 --- a/lib/suite.js +++ b/lib/suite.js @@ -2,21 +2,36 @@ module.exports = Suite -function Suite (parent) { - if (!parent.parent || !parent.parent.emittedSuite) - this.root = true - else - this.root = false - - this.title = parent.name || '' +function Suite (title, parent) { + this.root = !parent + this.title = title this.suites = [] this.tests = [] this.ok = true + this._beforeEach = [] + this._beforeAll = [] + this._afterEach = [] + this._afterAll = [] + + Object.defineProperty(this, 'parent', { + value: parent, + writable: true, + configurable: true, + enumerable: false + }) + + if (parent) { + parent.suites.push(this) + } } Suite.prototype.fullTitle = function () { - if (!this.parent) - return (this.title || '').trim() - else - return (this.parent.fullTitle() + ' ' + (this.title || '')).trim() + return this.titlePath().join(' ').trim() +} + +Suite.prototype.titlePath = function () { + var title = [(this.title || '').trim()] + if (this.parent && this.parent.titlePath) + return this.parent.titlePath().concat(title) + return title } diff --git a/lib/test.js b/lib/test.js index a641b14..a5e0f56 100644 --- a/lib/test.js +++ b/lib/test.js @@ -2,32 +2,59 @@ module.exports = Test -function Test (result, parent) { +function Test (result, suite) { this.result = result this._slow = 75 this.duration = result.time - this.title = result.name - this.state = result.ok ? 'pass' : 'failed' - this.pending = result.todo || result.skip || false - if (result.diag && result.diag.source) { - var source = result.diag.source - this.fn = { - toString: function () { - return 'function(){' + source + '\n}' + this.title = result.name || result.skip || '' + this.pending = result.todo || false + if (result.ok) { + this.state = result.skip ? 'skipped' : 'passed' + } else { + this.state = 'failed' + } + if (result.diag) { + if (result.diag.source) { + var source = result.diag.source + this.fn = { + toString: function () { + return 'function(){' + source + '\n}' + } + } + } else { + this.context = { + title: 'diagnostic', + value: result.diag, } } } - Object.defineProperty(this, 'parent', { - value: parent, + Object.defineProperty(this, 'suite', { + value: suite, writable: true, configurable: true, enumerable: false }) + + if (suite) { + suite.tests.push(this) + if (!result.ok) { + for (var ancestor = suite; ancestor; ancestor = ancestor.parent) { + ancestor.ok = false + } + } + } } Test.prototype.fullTitle = function () { - return (this.parent.fullTitle() + ' ' + (this.title || '')).trim() + return this.titlePath().join(' ').trim() +} + +Test.prototype.titlePath = function () { + var title = [(this.title || '').trim()] + if (this.suite && this.suite.titlePath) + return this.suite.titlePath().concat(title) + return title } Test.prototype.slow = function (ms){