diff --git a/lib/reporters/tap.js b/lib/reporters/tap.js index feaf7ea21a..ccff657e6a 100644 --- a/lib/reporters/tap.js +++ b/lib/reporters/tap.js @@ -6,7 +6,10 @@ * Module dependencies. */ +var util = require('util'); var Base = require('./base'); +var inherits = require('../utils').inherits; +var sprintf = util.format; /** * Expose `TAP`. @@ -15,25 +18,34 @@ var Base = require('./base'); exports = module.exports = TAP; /** - * Initialize a new `TAP` reporter. + * Constructs a new TAP reporter with runner instance and reporter options. * * @public * @class - * @memberof Mocha.reporters * @extends Mocha.reporters.Base - * @api public - * @param {Runner} runner + * @memberof Mocha.reporters + * @param {Runner} runner - Instance triggers reporter actions. + * @param {Object} [options] - runner options */ -function TAP(runner) { - Base.call(this, runner); +function TAP(runner, options) { + Base.call(this, runner, options); + var self = this; var n = 1; - var passes = 0; - var failures = 0; - runner.on('start', function() { - var total = runner.grepTotal(runner.suite); - console.log('%d..%d', 1, total); + var tapVersion = '12'; + if (options && options.reporterOptions) { + if (options.reporterOptions.tapVersion) { + tapVersion = options.reporterOptions.tapVersion.toString(); + } + } + + this._producer = createProducer(tapVersion); + + runner.once('start', function() { + var ntests = runner.grepTotal(runner.suite); + self._producer.writeVersion(); + self._producer.writePlan(ntests); }); runner.on('test end', function() { @@ -41,39 +53,233 @@ function TAP(runner) { }); runner.on('pending', function(test) { - console.log('ok %d %s # SKIP -', n, title(test)); + self._producer.writePending(n, test); }); runner.on('pass', function(test) { - passes++; - console.log('ok %d %s', n, title(test)); + self._producer.writePass(n, test); }); runner.on('fail', function(test, err) { - failures++; - console.log('not ok %d %s', n, title(test)); - if (err.message) { - console.log(err.message.replace(/^/gm, ' ')); - } - if (err.stack) { - console.log(err.stack.replace(/^/gm, ' ')); - } + self._producer.writeFail(n, test, err); }); runner.once('end', function() { - console.log('# tests ' + (passes + failures)); - console.log('# pass ' + passes); - console.log('# fail ' + failures); + self._producer.writeEpilogue(runner.stats); }); } /** - * Return a TAP-safe title of `test` + * Inherit from `Base.prototype`. + */ +inherits(TAP, Base); + +/** + * Returns a TAP-safe title of `test`. * - * @api private - * @param {Object} test - * @return {String} + * @private + * @param {Test} test - Test instance. + * @return {String} title with any hash character removed */ function title(test) { return test.fullTitle().replace(/#/g, ''); } + +/** + * Writes newline-terminated formatted string to reporter output stream. + * + * @private + * @param {string} format - `printf`-like format string + * @param {...*} [varArgs] - Format string arguments + */ +function println(format, varArgs) { + var vargs = Array.from(arguments); + vargs[0] += '\n'; + process.stdout.write(sprintf.apply(null, vargs)); +} + +/** + * Returns a `tapVersion`-appropriate TAP producer instance, if possible. + * + * @private + * @param {string} tapVersion - Version of TAP specification to produce. + * @returns {TAPProducer} specification-appropriate instance + * @throws {Error} if specification version has no associated producer. + */ +function createProducer(tapVersion) { + var producers = { + '12': new TAP12Producer(), + '13': new TAP13Producer() + }; + var producer = producers[tapVersion]; + + if (!producer) { + throw new Error( + 'invalid or unsupported TAP version: ' + JSON.stringify(tapVersion) + ); + } + + return producer; +} + +/** + * @summary + * Constructs a new TAPProducer. + * + * @description + * Only to be used as an abstract base class. + * + * @private + * @constructor + */ +function TAPProducer() {} + +/** + * Writes the TAP version to reporter output stream. + * + * @abstract + */ +TAPProducer.prototype.writeVersion = function() {}; + +/** + * Writes the plan to reporter output stream. + * + * @abstract + * @param {number} ntests - Number of tests that are planned to run. + */ +TAPProducer.prototype.writePlan = function(ntests) { + println('%d..%d', 1, ntests); +}; + +/** + * Writes that test passed to reporter output stream. + * + * @abstract + * @param {number} n - Index of test that passed. + * @param {Test} test - Instance containing test information. + */ +TAPProducer.prototype.writePass = function(n, test) { + println('ok %d %s', n, title(test)); +}; + +/** + * Writes that test was skipped to reporter output stream. + * + * @abstract + * @param {number} n - Index of test that was skipped. + * @param {Test} test - Instance containing test information. + */ +TAPProducer.prototype.writePending = function(n, test) { + println('ok %d %s # SKIP -', n, title(test)); +}; + +/** + * Writes that test failed to reporter output stream. + * + * @abstract + * @param {number} n - Index of test that failed. + * @param {Test} test - Instance containing test information. + * @param {Error} err - Reason the test failed. + */ +TAPProducer.prototype.writeFail = function(n, test, err) { + println('not ok %d %s', n, title(test)); +}; + +/** + * Writes the summary epilogue to reporter output stream. + * + * @abstract + * @param {Object} stats - Object containing run statistics. + */ +TAPProducer.prototype.writeEpilogue = function(stats) { + // :TBD: Why is this not counting pending tests? + println('# tests ' + (stats.passes + stats.failures)); + println('# pass ' + stats.passes); + // :TBD: Why are we not showing pending results? + println('# fail ' + stats.failures); +}; + +/** + * @summary + * Constructs a new TAP12Producer. + * + * @description + * Produces output conforming to the TAP12 specification. + * + * @private + * @constructor + * @extends TAPProducer + * @see {@link https://testanything.org/tap-specification.html|Specification} + */ +function TAP12Producer() { + /** + * Writes that test failed to reporter output stream, with error formatting. + * @override + */ + this.writeFail = function(n, test, err) { + TAPProducer.prototype.writeFail.call(this, n, test, err); + if (err.message) { + println(err.message.replace(/^/gm, ' ')); + } + if (err.stack) { + println(err.stack.replace(/^/gm, ' ')); + } + }; +} + +/** + * Inherit from `TAPProducer.prototype`. + */ +inherits(TAP12Producer, TAPProducer); + +/** + * @summary + * Constructs a new TAP13Producer. + * + * @description + * Produces output conforming to the TAP13 specification. + * + * @private + * @constructor + * @extends TAPProducer + * @see {@link https://testanything.org/tap-version-13-specification.html|Specification} + */ +function TAP13Producer() { + /** + * Writes the TAP version to reporter output stream. + * @override + */ + this.writeVersion = function() { + println('TAP version 13'); + }; + + /** + * Writes that test failed to reporter output stream, with error formatting. + * @override + */ + this.writeFail = function(n, test, err) { + TAPProducer.prototype.writeFail.call(this, n, test, err); + var emitYamlBlock = err.message != null || err.stack != null; + if (emitYamlBlock) { + println(indent(1) + '---'); + if (err.message) { + println(indent(2) + 'message: |-'); + println(err.message.replace(/^/gm, indent(3))); + } + if (err.stack) { + println(indent(2) + 'stack: |-'); + println(err.stack.replace(/^/gm, indent(3))); + } + println(indent(1) + '...'); + } + }; + + function indent(level) { + return Array(level + 1).join(' '); + } +} + +/** + * Inherit from `TAPProducer.prototype`. + */ +inherits(TAP13Producer, TAPProducer); diff --git a/test/integration/fixtures/reporters.fixture.js b/test/integration/fixtures/reporters.fixture.js new file mode 100644 index 0000000000..ab16bc0eed --- /dev/null +++ b/test/integration/fixtures/reporters.fixture.js @@ -0,0 +1,63 @@ +'use strict'; + +/** + * This file generates a wide range of output to test reporter functionality. + */ + +describe('Animals', function() { + + it('should consume organic material', function(done) { done(); }); + it('should breathe oxygen', function(done) { + // we're a jellyfish + var actualBreathe = 'nothing'; + var expectedBreathe = 'oxygen'; + expect(actualBreathe, 'to equal', expectedBreathe); + done(); + }); + it('should be able to move', function(done) { done(); }); + it('should reproduce sexually', function(done) { done(); }); + it('should grow from a hollow sphere of cells', function(done) { done(); }); + + describe('Vertebrates', function() { + describe('Mammals', function() { + it('should give birth to live young', function(done) { + var expectedMammal = { + consumesMaterial: 'organic', + breathe: 'oxygen', + reproduction: { + type: 'sexually', + spawnType: 'live', + } + }; + var platypus = JSON.parse(JSON.stringify(expectedMammal)); + platypus['reproduction']['spawnType'] = 'hard-shelled egg'; + + expect(platypus, 'to equal', expectedMammal); + done(); + }); + + describe('Blue Whale', function() { + it('should be the largest of all mammals', function(done) { done(); }); + it('should have a body in some shade of blue', function(done) { + var bodyColor = 'blueish_grey'; + var shadesOfBlue = ['cyan', 'light_blue', 'blue', 'indigo']; + expect(bodyColor, 'to be one of', shadesOfBlue); + + done(); + }); + }); + }); + describe('Birds', function() { + it('should have feathers', function(done) { done(); }); + it('should lay hard-shelled eggs', function(done) { done(); }); + }); + }); + + describe('Tardigrades', function() { + it('should answer to "water bear"', function(done) { done(); }); + it('should be able to survive global mass extinction events', function(done) { + throw new Error("How do we even test for this without causing one?") + done(); + }); + }); +}); diff --git a/test/integration/reporters.spec.js b/test/integration/reporters.spec.js index 4e95622dee..c78da8ab6b 100644 --- a/test/integration/reporters.spec.js +++ b/test/integration/reporters.spec.js @@ -99,4 +99,137 @@ describe('reporters', function() { }); }); }); + + describe('tap', function() { + var not = function(predicate) { + return function() { + return !predicate.apply(this, arguments); + }; + }; + var versionPredicate = function(line) { + return line.match(/^TAP version \d+$/) != null; + }; + var planPredicate = function(line) { + return line.match(/^1\.\.\d+$/) != null; + }; + var testLinePredicate = function(line) { + return line.match(/^not ok/) != null || line.match(/^ok/) != null; + }; + var diagnosticPredicate = function(line) { + return line.match(/^#/) != null; + }; + var bailOutPredicate = function(line) { + return line.match(/^Bail out!/) != null; + }; + var anythingElsePredicate = function(line) { + return ( + versionPredicate(line) === false && + planPredicate(line) === false && + testLinePredicate(line) === false && + diagnosticPredicate(line) === false && + bailOutPredicate(line) === false + ); + }; + + describe('produces valid TAP v13 output', function() { + var runFixtureAndValidateOutput = function(fixture, expected) { + it('for ' + fixture, function(done) { + var args = ['--reporter=tap', '--reporter-options', 'tapVersion=13']; + + run(fixture, args, function(err, res) { + if (err) { + done(err); + return; + } + + var expectedVersion = 13; + var expectedPlan = '1..' + expected.numTests; + + var outputLines = res.output.split('\n'); + + // first line must be version line + expect( + outputLines[0], + 'to equal', + 'TAP version ' + expectedVersion + ); + + // plan must appear once + expect(outputLines, 'to contain', expectedPlan); + expect( + outputLines.filter(function(l) { + return l === expectedPlan; + }), + 'to have length', + 1 + ); + // plan cannot appear in middle of the output + var firstTestLine = outputLines.findIndex(testLinePredicate); + // there must be at least one test line + expect(firstTestLine, 'to be greater than', -1); + var lastTestLine = + outputLines.length - + 1 - + outputLines + .slice() + .reverse() + .findIndex(testLinePredicate); + var planLine = outputLines.findIndex(function(line) { + return line === expectedPlan; + }); + expect( + planLine < firstTestLine || planLine > lastTestLine, + 'to equal', + true + ); + + done(); + }); + }); + }; + + runFixtureAndValidateOutput('passing.fixture.js', { + numTests: 2 + }); + runFixtureAndValidateOutput('reporters.fixture.js', { + numTests: 12 + }); + }); + + it('places exceptions correctly in YAML blocks', function(done) { + var args = ['--reporter=tap', '--reporter-options', 'tapVersion=13']; + + run('reporters.fixture.js', args, function(err, res) { + if (err) { + done(err); + return; + } + + var outputLines = res.output.split('\n'); + + for (var i = 0; i + 1 < outputLines.length; i++) { + if ( + testLinePredicate(outputLines[i]) && + testLinePredicate(outputLines[i + 1]) === false + ) { + var blockLinesStart = i + 1; + var blockLinesEnd = + i + + 1 + + outputLines.slice(i + 1).findIndex(not(anythingElsePredicate)); + var blockLines = + blockLinesEnd > blockLinesStart + ? outputLines.slice(blockLinesStart, blockLinesEnd) + : outputLines.slice(blockLinesStart); + i += blockLines.length; + + expect(blockLines[0], 'to match', /^\s+---/); + expect(blockLines[blockLines.length - 1], 'to match', /^\s+\.\.\./); + } + } + + done(); + }); + }); + }); }); diff --git a/test/reporters/tap.spec.js b/test/reporters/tap.spec.js index 208a88b904..c0a5419d37 100644 --- a/test/reporters/tap.spec.js +++ b/test/reporters/tap.spec.js @@ -8,7 +8,6 @@ var makeRunReporter = require('./helpers.js').createRunReporterFunction; describe('TAP reporter', function() { var runner; - var options = {}; var runReporter = makeRunReporter(TAP); var expectedTitle = 'some title'; var countAfterTestEnd = 2; @@ -25,200 +24,455 @@ describe('TAP reporter', function() { afterEach(function() { runner = undefined; + test = undefined; }); - describe('on start', function() { - it('should hand runners suite into grepTotal and log the total', function() { + describe('TAP12 spec', function() { + var options = {}; + + describe('on start', function() { var expectedSuite = 'some suite'; var expectedTotal = 10; var expectedString; - runner = createMockRunner('start', 'start'); - runner.suite = expectedSuite; - runner.grepTotal = function(string) { - expectedString = string; - return expectedTotal; - }; - var stdout = runReporter({}, runner, options); - - var expectedArray = ['1..' + expectedTotal + '\n']; - - expect(stdout, 'to equal', expectedArray); - expect(expectedString, 'to be', expectedSuite); + var stdout; + + before(function() { + runner = createMockRunner('start', 'start'); + runner.suite = expectedSuite; + runner.grepTotal = function(string) { + expectedString = string; + return expectedTotal; + }; + stdout = runReporter({}, runner, options); + }); + + it('should not write the TAP specification version', function() { + expect(stdout, 'not to contain', 'TAP version'); + }); + it('should write the number of tests that it plans to run', function() { + var expectedArray = ['1..' + expectedTotal + '\n']; + expect(stdout, 'to equal', expectedArray); + expect(expectedString, 'to be', expectedSuite); + }); }); - }); - describe('on pending', function() { - it('should write expected message including count and title', function() { - runner = createMockRunner( - 'start test', - 'test end', - 'pending', - null, - test - ); - runner.suite = ''; - runner.grepTotal = function() {}; - var stdout = runReporter({}, runner, options); - - var expectedMessage = - 'ok ' + countAfterTestEnd + ' ' + expectedTitle + ' # SKIP -\n'; - expect(stdout[0], 'to equal', expectedMessage); + describe('on pending', function() { + it('should write expected message including count and title', function() { + runner = createMockRunner( + 'start test', + 'test end', + 'pending', + null, + test + ); + runner.suite = ''; + runner.grepTotal = function() {}; + + var stdout = runReporter({}, runner, options); + + var expectedMessage = + 'ok ' + countAfterTestEnd + ' ' + expectedTitle + ' # SKIP -\n'; + expect(stdout[0], 'to equal', expectedMessage); + }); }); - }); - describe('on pass', function() { - it('should write expected message including count and title', function() { - runner = createMockRunner('start test', 'test end', 'pass', null, test); + describe('on pass', function() { + it('should write expected message including count and title', function() { + runner = createMockRunner('start test', 'test end', 'pass', null, test); + runner.suite = ''; + runner.grepTotal = function() {}; + + var stdout = runReporter({}, runner, options); + + var expectedMessage = + 'ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n'; + expect(stdout[0], 'to equal', expectedMessage); + }); + }); + + describe('on fail', function() { + describe('if there is an error message', function() { + it('should write expected message and error message', function() { + var expectedErrorMessage = 'some error'; + var error = { + message: expectedErrorMessage + }; + runner = createMockRunner( + 'test end fail', + 'test end', + 'fail', + null, + test, + error + ); + runner.on = function(event, callback) { + if (event === 'test end') { + callback(); + } else if (event === 'fail') { + callback(test, error); + } + }; + runner.suite = ''; + runner.grepTotal = function() {}; + + var stdout = runReporter({}, runner, options); + + var expectedArray = [ + 'not ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n', + ' ' + expectedErrorMessage + '\n' + ]; + expect(stdout, 'to equal', expectedArray); + }); + }); + + describe('if there is an error stack', function() { + it('should write expected message and stack', function() { + var expectedStack = 'some stack'; + var error = { + stack: expectedStack + }; + runner = createMockRunner( + 'test end fail', + 'test end', + 'fail', + null, + test, + error + ); + runner.suite = ''; + runner.grepTotal = function() {}; + + var stdout = runReporter({}, runner, options); + + var expectedArray = [ + 'not ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n', + ' ' + expectedStack + '\n' + ]; + expect(stdout, 'to equal', expectedArray); + }); + }); + + describe('if there is an error stack and error message', function() { + it('should write expected message and stack', function() { + var expectedStack = 'some stack'; + var expectedErrorMessage = 'some error'; + var error = { + stack: expectedStack, + message: expectedErrorMessage + }; + runner = createMockRunner( + 'test end fail', + 'test end', + 'fail', + null, + test, + error + ); + runner.on = function(event, callback) { + if (event === 'test end') { + callback(); + } else if (event === 'fail') { + callback(test, error); + } + }; + runner.suite = ''; + runner.grepTotal = function() {}; - runner.suite = ''; - runner.grepTotal = function() {}; - var stdout = runReporter({}, runner, options); + var stdout = runReporter({}, runner, options); - var expectedMessage = - 'ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n'; - expect(stdout[0], 'to equal', expectedMessage); + var expectedArray = [ + 'not ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n', + ' ' + expectedErrorMessage + '\n', + ' ' + expectedStack + '\n' + ]; + expect(stdout, 'to equal', expectedArray); + }); + }); + + describe('if there is no error stack or error message', function() { + it('should write expected message only', function() { + var error = {}; + runner = createMockRunner( + 'test end fail', + 'test end', + 'fail', + null, + test, + error + ); + runner.on = runner.once = function(event, callback) { + if (event === 'test end') { + callback(); + } else if (event === 'fail') { + callback(test, error); + } + }; + runner.suite = ''; + runner.grepTotal = function() {}; + + var stdout = runReporter({}, runner, options); + + var expectedArray = [ + 'not ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n' + ]; + expect(stdout, 'to equal', expectedArray); + }); + }); }); - }); - describe('on fail', function() { - describe('if there is an error message', function() { - it('should write expected message and error message', function() { - var expectedTitle = 'some title'; - var countAfterTestEnd = 2; - var expectedErrorMessage = 'some error'; - var test = { - fullTitle: function() { - return expectedTitle; - }, - slow: function() {} - }; - var error = { - message: expectedErrorMessage - }; - runner = createMockRunner('test end fail', 'test end', 'fail'); - runner.on = function(event, callback) { - if (event === 'test end') { - callback(); - } - if (event === 'fail') { - callback(test, error); - } - }; + describe('on end', function() { + it('should write total tests, passes and failures', function() { + var numberOfPasses = 1; + var numberOfFails = 1; + runner = createMockRunner('fail end pass', 'fail', 'end', 'pass', test); runner.suite = ''; runner.grepTotal = function() {}; + var stdout = runReporter({}, runner, options); + var totalTests = numberOfPasses + numberOfFails; var expectedArray = [ - 'not ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n', - ' ' + expectedErrorMessage + '\n' + 'ok ' + numberOfPasses + ' ' + expectedTitle + '\n', + 'not ok ' + numberOfFails + ' ' + expectedTitle + '\n', + '# tests ' + totalTests + '\n', + '# pass ' + numberOfPasses + '\n', + '# fail ' + numberOfFails + '\n' ]; expect(stdout, 'to equal', expectedArray); }); }); - describe('if there is an error stack', function() { - it('should write expected message and stack', function() { - var expectedStack = 'some stack'; - var error = { - stack: expectedStack + }); + + describe('TAP13 spec', function() { + var options = { + reporterOptions: { + tapVersion: '13' + } + }; + + describe('on start', function() { + var expectedSuite = 'some suite'; + var expectedTotal = 10; + var expectedString; + var stdout; + + before(function() { + runner = createMockRunner('start', 'start'); + runner.suite = expectedSuite; + runner.grepTotal = function(string) { + expectedString = string; + return expectedTotal; }; + + stdout = runReporter({}, runner, options); + }); + + it('should write the TAP specification version', function() { + var tapVersion = options.reporterOptions.tapVersion; + var expectedFirstLine = 'TAP version ' + tapVersion + '\n'; + expect(stdout[0], 'to equal', expectedFirstLine); + }); + it('should write the number of tests that it plans to run', function() { + var expectedSecondLine = '1..' + expectedTotal + '\n'; + expect(stdout[1], 'to equal', expectedSecondLine); + expect(expectedString, 'to be', expectedSuite); + }); + }); + + describe('on pending', function() { + it('should write expected message including count and title', function() { runner = createMockRunner( - 'test end fail', + 'start test', 'test end', - 'fail', + 'pending', null, - test, - error + test ); runner.suite = ''; runner.grepTotal = function() {}; + var stdout = runReporter({}, runner, options); - var expectedArray = [ - 'not ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n', - ' ' + expectedStack + '\n' - ]; - expect(stdout, 'to equal', expectedArray); + var expectedMessage = + 'ok ' + countAfterTestEnd + ' ' + expectedTitle + ' # SKIP -\n'; + expect(stdout[0], 'to equal', expectedMessage); }); }); - describe('if there is an error stack and error message', function() { - it('should write expected message and stack', function() { - var expectedTitle = 'some title'; - var countAfterTestEnd = 2; - var expectedStack = 'some stack'; - var expectedErrorMessage = 'some error'; - var test = { - fullTitle: function() { - return expectedTitle; - }, - slow: function() {} - }; - var error = { - stack: expectedStack, - message: expectedErrorMessage - }; - runner = createMockRunner('test end fail', 'test end', 'fail'); - runner.on = function(event, callback) { - if (event === 'test end') { - callback(); - } - if (event === 'fail') { - callback(test, error); - } - }; + + describe('on pass', function() { + it('should write expected message including count and title', function() { + runner = createMockRunner('start test', 'test end', 'pass', null, test); runner.suite = ''; runner.grepTotal = function() {}; + var stdout = runReporter({}, runner, options); - var expectedArray = [ - 'not ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n', - ' ' + expectedErrorMessage + '\n', - ' ' + expectedStack + '\n' - ]; - expect(stdout, 'to equal', expectedArray); + var expectedMessage = + 'ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n'; + expect(stdout[0], 'to equal', expectedMessage); }); }); - describe('if there is no error stack or error message', function() { - it('should write expected message only', function() { - runner = createMockRunner('test end fail', 'test end', 'fail'); - var error = {}; - runner.on = runner.once = function(event, callback) { - if (event === 'test end') { - callback(); - } - if (event === 'fail') { - callback(test, error); - } - }; + + describe('on fail', function() { + describe('if there is an error message', function() { + it('should write expected message and error message', function() { + var expectedErrorMessage = 'some error'; + var error = { + message: expectedErrorMessage + }; + runner = createMockRunner( + 'test end fail', + 'test end', + 'fail', + null, + test, + error + ); + runner.on = function(event, callback) { + if (event === 'test end') { + callback(); + } else if (event === 'fail') { + callback(test, error); + } + }; + runner.suite = ''; + runner.grepTotal = function() {}; + + var stdout = runReporter({}, runner, options); + + var expectedArray = [ + 'not ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n', + ' ---\n', + ' message: |-\n', + ' ' + expectedErrorMessage + '\n', + ' ...\n' + ]; + expect(stdout, 'to equal', expectedArray); + }); + }); + + describe('if there is an error stack', function() { + it('should write expected message and stack', function() { + var expectedStack = 'some stack'; + var error = { + stack: expectedStack + }; + runner = createMockRunner( + 'test end fail', + 'test end', + 'fail', + null, + test, + error + ); + runner.suite = ''; + runner.grepTotal = function() {}; + + var stdout = runReporter({}, runner, options); + + var expectedArray = [ + 'not ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n', + ' ---\n', + ' stack: |-\n', + ' ' + expectedStack + '\n', + ' ...\n' + ]; + expect(stdout, 'to equal', expectedArray); + }); + }); + + describe('if there is an error stack and error message', function() { + it('should write expected message and stack', function() { + var expectedStack = 'some stack'; + var expectedErrorMessage = 'some error'; + var error = { + stack: expectedStack, + message: expectedErrorMessage + }; + runner = createMockRunner( + 'test end fail', + 'test end', + 'fail', + null, + test, + error + ); + runner.on = function(event, callback) { + if (event === 'test end') { + callback(); + } else if (event === 'fail') { + callback(test, error); + } + }; + runner.suite = ''; + runner.grepTotal = function() {}; + + var stdout = runReporter({}, runner, options); + + var expectedArray = [ + 'not ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n', + ' ---\n', + ' message: |-\n', + ' ' + expectedErrorMessage + '\n', + ' stack: |-\n', + ' ' + expectedStack + '\n', + ' ...\n' + ]; + expect(stdout, 'to equal', expectedArray); + }); + }); + + describe('if there is no error stack or error message', function() { + it('should write expected message only', function() { + var error = {}; + runner = createMockRunner( + 'test end fail', + 'test end', + 'fail', + null, + test, + error + ); + runner.on = runner.once = function(event, callback) { + if (event === 'test end') { + callback(); + } else if (event === 'fail') { + callback(test, error); + } + }; + runner.suite = ''; + runner.grepTotal = function() {}; + + var stdout = runReporter({}, runner, options); + + var expectedArray = [ + 'not ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n' + ]; + expect(stdout, 'to equal', expectedArray); + }); + }); + }); + + describe('on end', function() { + it('should write total tests, passes and failures', function() { + var numberOfPasses = 1; + var numberOfFails = 1; + runner = createMockRunner('fail end pass', 'fail', 'end', 'pass', test); runner.suite = ''; runner.grepTotal = function() {}; + var stdout = runReporter({}, runner, options); + var totalTests = numberOfPasses + numberOfFails; var expectedArray = [ - 'not ok ' + countAfterTestEnd + ' ' + expectedTitle + '\n' + 'ok ' + numberOfPasses + ' ' + expectedTitle + '\n', + 'not ok ' + numberOfFails + ' ' + expectedTitle + '\n', + '# tests ' + totalTests + '\n', + '# pass ' + numberOfPasses + '\n', + '# fail ' + numberOfFails + '\n' ]; expect(stdout, 'to equal', expectedArray); }); }); }); - - describe('on end', function() { - it('should write total tests, passes and failures', function() { - var numberOfPasses = 1; - var numberOfFails = 1; - runner = createMockRunner('fail end pass', 'fail', 'end', 'pass', test); - runner.suite = ''; - runner.grepTotal = function() {}; - var stdout = runReporter({}, runner, options); - - var totalTests = numberOfPasses + numberOfFails; - var expectedArray = [ - 'ok ' + numberOfPasses + ' ' + expectedTitle + '\n', - 'not ok ' + numberOfFails + ' ' + expectedTitle + '\n', - '# tests ' + totalTests + '\n', - '# pass ' + numberOfPasses + '\n', - '# fail ' + numberOfFails + '\n' - ]; - expect(stdout, 'to equal', expectedArray); - }); - }); });