diff --git a/cli.js b/cli.js index 6365908..20c21b1 100755 --- a/cli.js +++ b/cli.js @@ -17,13 +17,15 @@ if (debug.enabled) { require('time-require'); } +var updateNotifier = require('update-notifier'); +var figures = require('figures'); var arrify = require('arrify'); var meow = require('meow'); -var updateNotifier = require('update-notifier'); var chalk = require('chalk'); var Promise = require('bluebird'); -var log = require('./lib/logger'); -var tap = require('./lib/tap'); +var verboseReporter = require('./lib/reporters/verbose'); +var tapReporter = require('./lib/reporters/tap'); +var Logger = require('./lib/logger'); var Api = require('./api'); // Bluebird specific @@ -68,75 +70,37 @@ if (cli.flags.init) { return; } -if (cli.flags.tap) { - console.log(tap.start()); -} else { - log.write(); -} - var api = new Api(cli.input, { failFast: cli.flags.failFast, serial: cli.flags.serial, require: arrify(cli.flags.require) }); -api.on('test', function (test) { - if (cli.flags.tap) { - console.log(tap.test(test)); - return; - } - - if (test.error) { - log.error(test.title, chalk.red(test.error.message)); - } else { - // don't log it if there's only one file and one anonymous test - if (api.fileCount === 1 && api.testCount === 1 && test.title === '[anonymous]') { - return; - } +var logger = new Logger(); +logger.api = api; - log.test(test); - } -}); +if (cli.flags.tap) { + logger.use(tapReporter()); +} else { + logger.use(verboseReporter()); +} -api.on('error', function (data) { - if (cli.flags.tap) { - console.log(tap.unhandledError(data)); - return; - } +logger.start(); - log.unhandledError(data.type, data.file, data); -}); +api.on('test', logger.test); +api.on('error', logger.unhandledError); api.run() .then(function () { - if (cli.flags.tap) { - console.log(tap.finish(api.passCount, api.failCount, api.rejectionCount, api.exceptionCount)); + logger.finish(); + logger.exit(api.failCount > 0 || api.rejectionCount > 0 || api.exceptionCount > 0 ? 1 : 0); + }) + .catch(function (err) { + if (err instanceof Error) { + console.log(' ' + chalk.red(figures.cross) + ' ' + err.message); } else { - log.write(); - log.report(api.passCount, api.failCount, api.rejectionCount, api.exceptionCount); - log.write(); - - if (api.failCount > 0) { - log.errors(api.errors); - } + console.error(err.stack); } - process.stdout.write(''); - flushIoAndExit(api.failCount > 0 || api.rejectionCount > 0 || api.exceptionCount > 0 ? 1 : 0); - }) - .catch(function (err) { - log.error(err.message); - flushIoAndExit(1); + logger.exit(1); }); - -function flushIoAndExit(code) { - // TODO: figure out why this needs to be here to - // correctly flush the output when multiple test files - process.stdout.write(''); - process.stderr.write(''); - - // timeout required to correctly flush IO on Node.js 0.10 on Windows - setTimeout(function () { - process.exit(code); - }, process.env.AVA_APPVEYOR ? 500 : 0); -} diff --git a/lib/logger.js b/lib/logger.js index eb3bc5b..00d40a8 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,98 +1,66 @@ 'use strict'; -var prettyMs = require('pretty-ms'); -var figures = require('figures'); -var Squeak = require('squeak'); -var chalk = require('chalk'); -var plur = require('plur'); -var log = new Squeak({separator: ' '}); -var x = module.exports; -function beautifyStack(stack) { - var re = /(?:^(?! {4}at\b).{6})|(?:\((?:[A-Z]:)?(?:[\\\/](?:(?!node_modules[\\\/]ava[\\\/])[^:\\\/])+)+:\d+:\d+\))/; - var found = false; +function Logger() { + if (!(this instanceof Logger)) { + return new Logger(); + } - return stack.split('\n').filter(function (line) { - var relevant = re.test(line); - found = found || relevant; - return !found || relevant; - }).join('\n'); + Object.keys(Logger.prototype).forEach(function (key) { + this[key] = this[key].bind(this); + }, this); } -x._beautifyStack = beautifyStack; - -log.type('success', { - color: 'green', - prefix: figures.tick -}); - -log.type('error', { - color: 'red', - prefix: figures.cross -}); +module.exports = Logger; -x.write = log.write.bind(log); -x.writelpad = log.writelpad.bind(log); -x.success = log.success.bind(log); -x.error = log.error.bind(log); - -x.test = function (props) { - if (props.err) { - log.error(props.title, chalk.red(props.err.message)); - return; - } +Logger.prototype.use = function (reporter) { + this.reporter = reporter; + this.reporter.api = this.api; +}; - if (props.skip) { - log.write(' ' + chalk.cyan('- ' + props.title)); +Logger.prototype.start = function () { + if (!this.reporter.start) { return; } - // display duration only over a threshold - var threshold = 100; - var dur = props.duration > threshold ? chalk.gray.dim(' (' + prettyMs(props.duration) + ')') : ''; - log.success(props.title + dur); + this.write(this.reporter.start()); }; -x.errors = function (tests) { - var i = 0; +Logger.prototype.test = function (test) { + this.write(this.reporter.test(test)); +}; - tests.forEach(function (test) { - i++; +Logger.prototype.unhandledError = function (err) { + if (!this.reporter.unhandledError) { + return; + } - log.writelpad(chalk.red(i + '.', test.title)); - log.writelpad(chalk.red(beautifyStack(test.error.stack))); - log.write(); - }); + this.write(this.reporter.unhandledError(err)); }; -x.report = function (passed, failed, unhandled, uncaught) { - if (failed > 0) { - log.writelpad(chalk.red(failed, plur('test', failed), 'failed')); - } else { - log.writelpad(chalk.green(passed, plur('test', passed), 'passed')); +Logger.prototype.finish = function () { + if (!this.reporter.finish) { + return; } - if (unhandled > 0) { - log.writelpad(chalk.red(unhandled, 'unhandled', plur('rejection', unhandled))); - } + this.write(this.reporter.finish()); +}; - if (uncaught > 0) { - log.writelpad(chalk.red(uncaught, 'uncaught', plur('exception', uncaught))); +Logger.prototype.write = function (str) { + if (typeof str === 'undefined') { + return; } -}; -var types = { - rejection: 'Unhandled Rejection', - exception: 'Uncaught Exception' + this.reporter.write(str); }; -x.unhandledError = function (type, file, error) { - log.write(chalk.red(types[type] + ':', file)); - - if (error.stack) { - log.writelpad(chalk.red(beautifyStack(error.stack))); - } else { - log.writelpad(chalk.red(JSON.stringify(error))); - } +Logger.prototype.exit = function (code) { + // TODO: figure out why this needs to be here to + // correctly flush the output when multiple test files + process.stdout.write(''); + process.stderr.write(''); - log.write(); + // timeout required to correctly flush IO on Node.js 0.10 on Windows + setTimeout(function () { + process.exit(code); + }, process.env.AVA_APPVEYOR ? 500 : 0); }; diff --git a/lib/tap.js b/lib/reporters/tap.js similarity index 53% rename from lib/tap.js rename to lib/reporters/tap.js index c4de30b..831dada 100644 --- a/lib/tap.js +++ b/lib/reporters/tap.js @@ -10,19 +10,27 @@ function getSourceFromStack(stack, index) { .replace(/^\s+at /, ''); } -exports.start = function () { +function TapReporter() { + if (!(this instanceof TapReporter)) { + return new TapReporter(); + } + + this.i = 0; +} + +module.exports = TapReporter; + +TapReporter.prototype.start = function () { return 'TAP version 13'; }; -var i = 0; - -exports.test = function (test) { +TapReporter.prototype.test = function (test) { var output; if (test.error) { output = [ '# ' + test.title, - format('not ok %d - %s', ++i, test.error.message), + format('not ok %d - %s', ++this.i, test.error.message), ' ---', ' operator: ' + test.error.operator, ' expected: ' + test.error.expected, @@ -33,17 +41,17 @@ exports.test = function (test) { } else { output = [ '# ' + test.title, - format('ok %d - %s', ++i, test.title) + format('ok %d - %s', ++this.i, test.title) ]; } return output.join('\n'); }; -exports.unhandledError = function (err) { +TapReporter.prototype.unhandledError = function (err) { var output = [ '# ' + err.message, - format('not ok %d - %s', ++i, err.message), + format('not ok %d - %s', ++this.i, err.message), ' ---', ' name: ' + err.name, ' at: ' + getSourceFromStack(err.stack, 1), @@ -53,15 +61,19 @@ exports.unhandledError = function (err) { return output.join('\n'); }; -exports.finish = function (passCount, failCount, rejectionCount, exceptionCount) { +TapReporter.prototype.finish = function () { var output = [ '', - '1..' + (passCount + failCount), - '# tests ' + (passCount + failCount), - '# pass ' + passCount, - '# fail ' + (failCount + rejectionCount + exceptionCount), + '1..' + (this.api.passCount + this.api.failCount), + '# tests ' + (this.api.passCount + this.api.failCount), + '# pass ' + this.api.passCount, + '# fail ' + (this.api.failCount + this.api.rejectionCount + this.api.exceptionCount), '' ]; return output.join('\n'); }; + +TapReporter.prototype.write = function (str) { + console.log(str); +}; diff --git a/lib/reporters/verbose.js b/lib/reporters/verbose.js new file mode 100644 index 0000000..17d46b3 --- /dev/null +++ b/lib/reporters/verbose.js @@ -0,0 +1,109 @@ +'use strict'; +var prettyMs = require('pretty-ms'); +var figures = require('figures'); +var chalk = require('chalk'); +var plur = require('plur'); + +function beautifyStack(stack) { + var re = /(?:^(?! {4}at\b).{6})|(?:\((?:[A-Z]:)?(?:[\\\/](?:(?!node_modules[\\\/]ava[\\\/])[^:\\\/])+)+:\d+:\d+\))/; + var found = false; + + return stack.split('\n').filter(function (line) { + var relevant = re.test(line); + found = found || relevant; + return !found || relevant; + }).join('\n'); +} + +function VerboseReporter() { + if (!(this instanceof VerboseReporter)) { + return new VerboseReporter(); + } +} + +module.exports = VerboseReporter; +module.exports._beautifyStack = beautifyStack; + +VerboseReporter.prototype.start = function () { + return ''; +}; + +VerboseReporter.prototype.test = function (test) { + if (test.error) { + return ' ' + chalk.red(figures.cross) + ' ' + test.title + ' ' + chalk.red(test.error.message); + } + + if (test.skip) { + return ' ' + chalk.cyan('- ' + test.title); + } + + if (this.api.fileCount === 1 && this.api.testCount === 1 && test.title === '[anonymous]') { + return null; + } + + // display duration only over a threshold + var threshold = 100; + var duration = test.duration > threshold ? chalk.gray.dim(' (' + prettyMs(test.duration) + ')') : ''; + + return ' ' + chalk.green(figures.tick) + ' ' + test.title + duration; +}; + +VerboseReporter.prototype.unhandledError = function (err) { + var types = { + rejection: 'Unhandled Rejection', + exception: 'Uncaught Exception' + }; + + var output = chalk.red(types[err.type] + ':', err.file) + '\n'; + + if (err.stack) { + output += ' ' + chalk.red(beautifyStack(err.stack)) + '\n'; + } else { + output += ' ' + chalk.red(JSON.stringify(err)) + '\n'; + } + + output += '\n'; + + return output; +}; + +VerboseReporter.prototype.finish = function () { + var output = '\n'; + + if (this.api.failCount > 0) { + output += ' ' + chalk.red(this.api.failCount, plur('test', this.api.failCount), 'failed') + '\n'; + } else { + output += ' ' + chalk.green(this.api.passCount, plur('test', this.api.passCount), 'passed') + '\n'; + } + + if (this.api.rejectionCount > 0) { + output += ' ' + chalk.red(this.api.rejectionCount, 'unhandled', plur('rejection', this.api.rejectionCount)) + '\n'; + } + + if (this.api.exceptionCount > 0) { + output += ' ' + chalk.red(this.api.exceptionCount, 'uncaught', plur('exception', this.api.exceptionCount)) + '\n'; + } + + if (this.api.failCount > 0) { + output += '\n'; + + var i = 0; + + this.api.tests.forEach(function (test) { + if (!test.error.message) { + return; + } + + i++; + + output += ' ' + chalk.red(i + '.', test.title) + '\n'; + output += ' ' + chalk.red(beautifyStack(test.error.stack)) + '\n'; + }); + } + + return output; +}; + +VerboseReporter.prototype.write = function (str) { + console.error(str); +}; diff --git a/test/cli.js b/test/cli.js index 948179b..a54e073 100644 --- a/test/cli.js +++ b/test/cli.js @@ -19,16 +19,6 @@ function execCli(args, cb) { }, cb); } -test('don\'t display test title if there is only one anonymous test', function (t) { - t.plan(2); - - execCli(['fixture/es2015.js'], function (err, stdout, stderr) { - t.ifError(err); - t.is(stderr.trim(), '1 test passed'); - t.end(); - }); -}); - test('throwing a named function will report the to the console', function (t) { execCli('fixture/throw-named-function.js', function (err, stdout, stderr) { t.ok(err); diff --git a/test/logger.js b/test/logger.js deleted file mode 100644 index fee554e..0000000 --- a/test/logger.js +++ /dev/null @@ -1,261 +0,0 @@ -'use strict'; -var test = require('tap').test; -var logger = require('../lib/logger'); -var figures = require('figures'); -var hookStd = require('hook-std'); - -test('beautify stack - removes uninteresting lines', function (t) { - try { - fooFunc(); - } catch (err) { - var stack = logger._beautifyStack(err.stack); - t.match(stack, /fooFunc/); - t.match(stack, /barFunc/); - t.match(err.stack, /Module._compile/); - t.notMatch(stack, /Module\._compile/); - t.end(); - } -}); - -test('logger.write', function (t) { - t.plan(1); - - var unhook = hookStd.stderr({silent: true}, function (output) { - unhook(); - - t.is(output.toString(), 'Test'); - t.end(); - }); - - logger.write('Test'); -}); - -test('logger.writelpad', function (t) { - t.plan(1); - - var unhook = hookStd.stderr({silent: true}, function (output) { - unhook(); - - t.is(output.toString(), ' Test'); - t.end(); - }); - - logger.writelpad('Test'); -}); - -test('logger.success', function (t) { - t.plan(1); - - var unhook = hookStd.stderr({silent: true}, function (output) { - unhook(); - - t.is(output.toString(), ' ' + figures.tick + ' Test'); - t.end(); - }); - - logger.success('Test'); -}); - -test('logger.error', function (t) { - t.plan(1); - - var unhook = hookStd.stderr({silent: true}, function (output) { - unhook(); - - t.is(output.toString(), ' ' + figures.cross + ' Test'); - t.end(); - }); - - logger.error('Test'); -}); - -test('logger.test with passing test and duration less than threshold', function (t) { - t.plan(1); - - var passingTest = { - title: 'Passed', - duration: 90 - }; - - var unhook = hookStd.stderr({silent: true}, function (output) { - unhook(); - - t.is(output.toString(), ' ' + figures.tick + ' Passed'); - t.end(); - }); - - logger.test(passingTest); -}); - -test('logger.test with passing test and duration greater than threshold', function (t) { - t.plan(1); - - var passingTest = { - title: 'Passed', - duration: 150 - }; - - var unhook = hookStd.stderr({silent: true}, function (output) { - unhook(); - - t.is(output.toString(), ' ' + figures.tick + ' Passed (150ms)'); - t.end(); - }); - - logger.test(passingTest); -}); - -test('logger.test with failing test', function (t) { - t.plan(1); - - var passingTest = { - title: 'Failed', - err: { - message: 'Assertion failed' - } - }; - - var unhook = hookStd.stderr({silent: true}, function (output) { - unhook(); - - t.is(output.toString(), ' ' + figures.cross + ' Failed Assertion failed'); - t.end(); - }); - - logger.test(passingTest); -}); - -test('logger.test with skipped test', function (t) { - t.plan(1); - - var skippedTest = { - title: 'Skipped', - skipped: true - }; - - var unhook = hookStd.stderr({silent: true}, function (output) { - unhook(); - - t.is(output.toString(), ' ' + figures.tick + ' Skipped'); - t.end(); - }); - - logger.test(skippedTest); -}); - -test('logger.errors', function (t) { - t.plan(1); - - var lines = []; - var failedTest = { - title: 'Failed', - error: { - stack: 'Unexpected token' - } - }; - - hookStd.stderr({silent: true}, function (output) { - onLine(lines, output); - }); - - logger.errors([failedTest]); - - t.is(lines.join(''), '1. Failed\nUnexpected token\n'); -}); - -test('logger.report', function (t) { - t.plan(1); - - var lines = []; - - hookStd.stderr({silent: true}, function (output) { - onLine(lines, output); - }); - - logger.report(1, 2, 1, 2); - - t.is(lines.join(''), '2 tests failed\n1 unhandled rejection\n2 uncaught exceptions\n'); -}); - -test('logger.unhandledError with exception with stack', function (t) { - t.plan(3); - - var lines = []; - - hookStd.stderr({silent: true}, function (output) { - onLine(lines, output); - }); - - logger.unhandledError('exception', 'test.js', new Error('Unexpected token')); - - t.is(lines[0], 'Uncaught Exception: test.js\n'); - t.match(lines[1], /Error: Unexpected token\n/); - t.match(lines[1], /at Test.test/); -}); - -test('logger.unhandledError with exception without stack', function (t) { - t.plan(2); - - var lines = []; - var error = { - message: 'Unexpected token' - }; - - hookStd.stderr({silent: true}, function (output) { - onLine(lines, output); - }); - - logger.unhandledError('exception', 'test.js', error); - - t.is(lines[0], 'Uncaught Exception: test.js\n'); - t.is(lines[1], '{"message":"Unexpected token"}\n'); -}); - -test('logger.unhandledError rejection with stack', function (t) { - t.plan(3); - - var lines = []; - - hookStd.stderr({silent: true}, function (output) { - onLine(lines, output); - }); - - logger.unhandledError('rejection', 'test.js', new Error('I have been rejected')); - - t.is(lines[0], 'Unhandled Rejection: test.js\n'); - t.match(lines[1], /Error: I have been rejected\n/); - t.match(lines[1], /at Test.test/); -}); - -test('logger.unhandledError rejection without stack', function (t) { - t.plan(2); - - var lines = []; - var error = { - message: 'I have been rejected' - }; - - hookStd.stderr({silent: true}, function (output) { - onLine(lines, output); - }); - - logger.unhandledError('rejection', 'test.js', error); - - t.is(lines[0], 'Unhandled Rejection: test.js\n'); - t.is(lines[1], '{"message":"I have been rejected"}\n'); -}); - -function fooFunc() { - barFunc(); -} - -function barFunc() { - throw new Error(); -} - -function onLine(lines, line) { - var trimmed = line.trim(); - if (trimmed.length) { - lines.push(line.trim() + '\n'); - } -} diff --git a/test/tap.js b/test/reporters/tap.js similarity index 60% rename from test/tap.js rename to test/reporters/tap.js index 577bc98..3b14bde 100644 --- a/test/tap.js +++ b/test/reporters/tap.js @@ -1,14 +1,18 @@ 'use strict'; var test = require('tap').test; -var tap = require('../lib/tap'); +var tapReporter = require('../../lib/reporters/tap'); test('start', function (t) { - t.is(tap.start(), 'TAP version 13'); + var reporter = tapReporter(); + + t.is(reporter.start(), 'TAP version 13'); t.end(); }); test('passing test', function (t) { - var actualOutput = tap.test({ + var reporter = tapReporter(); + + var actualOutput = reporter.test({ title: 'passing' }); @@ -22,7 +26,9 @@ test('passing test', function (t) { }); test('failing test', function (t) { - var actualOutput = tap.test({ + var reporter = tapReporter(); + + var actualOutput = reporter.test({ title: 'failing', error: { message: 'false == true', @@ -35,7 +41,7 @@ test('failing test', function (t) { var expectedOutput = [ '# failing', - 'not ok 2 - false == true', + 'not ok 1 - false == true', ' ---', ' operator: ==', ' expected: true', @@ -49,7 +55,9 @@ test('failing test', function (t) { }); test('unhandled error', function (t) { - var actualOutput = tap.unhandledError({ + var reporter = tapReporter(); + + var actualOutput = reporter.unhandledError({ message: 'unhandled', name: 'TypeError', stack: ['', ' at Test.fn (test.js:1:2)'].join('\n') @@ -57,7 +65,7 @@ test('unhandled error', function (t) { var expectedOutput = [ '# unhandled', - 'not ok 3 - unhandled', + 'not ok 1 - unhandled', ' ---', ' name: TypeError', ' at: Test.fn (test.js:1:2)', @@ -69,18 +77,23 @@ test('unhandled error', function (t) { }); test('results', function (t) { - var passCount = 1; - var failCount = 2; - var rejectionCount = 3; - var exceptionCount = 4; + var reporter = tapReporter(); + var api = { + passCount: 1, + failCount: 2, + rejectionCount: 3, + exceptionCount: 4 + }; + + reporter.api = api; - var actualOutput = tap.finish(passCount, failCount, rejectionCount, exceptionCount); + var actualOutput = reporter.finish(); var expectedOutput = [ '', - '1..' + (passCount + failCount), - '# tests ' + (passCount + failCount), - '# pass ' + passCount, - '# fail ' + (failCount + rejectionCount + exceptionCount), + '1..' + (api.passCount + api.failCount), + '# tests ' + (api.passCount + api.failCount), + '# pass ' + api.passCount, + '# fail ' + (api.failCount + api.rejectionCount + api.exceptionCount), '' ].join('\n'); diff --git a/test/reporters/verbose.js b/test/reporters/verbose.js new file mode 100644 index 0000000..4992d55 --- /dev/null +++ b/test/reporters/verbose.js @@ -0,0 +1,243 @@ +'use strict'; +var figures = require('figures'); +var chalk = require('chalk'); +var test = require('tap').test; +var verboseReporter = require('../../lib/reporters/verbose'); + +function createReporter() { + var reporter = verboseReporter(); + reporter.api = { + fileCount: 1, + testCount: 1 + }; + + return reporter; +} + +test('beautify stack - removes uninteresting lines', function (t) { + try { + fooFunc(); + } catch (err) { + var stack = verboseReporter._beautifyStack(err.stack); + t.match(stack, /fooFunc/); + t.match(stack, /barFunc/); + t.match(err.stack, /Module._compile/); + t.notMatch(stack, /Module\._compile/); + t.end(); + } +}); + +test('start', function (t) { + var reporter = createReporter(); + + t.is(reporter.start(), ''); + t.end(); +}); + +test('passing test and duration less than threshold', function (t) { + var reporter = createReporter(); + + var actualOutput = reporter.test({ + title: 'passed', + duration: 90 + }); + + var expectedOutput = ' ' + chalk.green(figures.tick) + ' passed'; + + t.is(actualOutput, expectedOutput); + t.end(); +}); + +test('passing test and duration greater than threshold', function (t) { + var reporter = createReporter(); + + var actualOutput = reporter.test({ + title: 'passed', + duration: 150 + }); + + var expectedOutput = ' ' + chalk.green(figures.tick) + ' passed' + chalk.grey.dim(' (150ms)'); + + t.is(actualOutput, expectedOutput); + t.end(); +}); + +test('don\'t display test title if there is only one anonymous test', function (t) { + var reporter = createReporter(); + + var output = reporter.test({ + title: '[anonymous]' + }); + + t.is(output, undefined); + t.end(); +}); + +test('failing test', function (t) { + var reporter = createReporter(); + + var actualOutput = reporter.test({ + title: 'failed', + err: { + message: 'assertion failed' + } + }); + + var expectedOutput = ' ' + chalk.red(figures.cross) + ' failed ' + chalk.red('assertion failed'); + + t.is(actualOutput, expectedOutput); + t.end(); +}); + +test('skipped test', function (t) { + var reporter = createReporter(); + + var actualOutput = reporter.test({ + title: 'skipped', + skip: true + }); + + var expectedOutput = ' ' + chalk.cyan('- skipped'); + + t.is(actualOutput, expectedOutput); + t.end(); +}); + +test('uncaught exception', function (t) { + var reporter = createReporter(); + + var output = reporter.unhandledError({ + type: 'exception', + file: 'test.js', + stack: new Error('Unexpected token').stack + }).split('\n'); + + t.is(output[0], chalk.red('Uncaught Exception: test.js')); + t.match(output[1], /Error: Unexpected token/); + t.match(output[2], /at Test\.test/); + t.end(); +}); + +test('unhandled rejection', function (t) { + var reporter = createReporter(); + + var output = reporter.unhandledError({ + type: 'rejection', + file: 'test.js', + stack: new Error('Unexpected token').stack + }).split('\n'); + + t.is(output[0], chalk.red('Unhandled Rejection: test.js')); + t.match(output[1], /Error: Unexpected token/); + t.match(output[2], /at Test\.test/); + t.end(); +}); + +test('unhandled error without stack', function (t) { + var reporter = createReporter(); + + var err = { + type: 'exception', + file: 'test.js', + message: 'test' + }; + + var output = reporter.unhandledError(err).split('\n'); + + t.is(output[0], chalk.red('Uncaught Exception: test.js')); + t.is(output[1], ' ' + chalk.red(JSON.stringify(err))); + t.end(); +}); + +test('results with passing tests', function (t) { + var reporter = createReporter(); + reporter.api.passCount = 1; + + var actualOutput = reporter.finish(); + var expectedOutput = [ + '', + ' ' + chalk.green('1 test passed'), + '' + ].join('\n'); + + t.is(actualOutput, expectedOutput); + t.end(); +}); + +test('results with passing tests and rejections', function (t) { + var reporter = createReporter(); + reporter.api.passCount = 1; + reporter.api.rejectionCount = 1; + + var actualOutput = reporter.finish(); + var expectedOutput = [ + '', + ' ' + chalk.green('1 test passed'), + ' ' + chalk.red('1 unhandled rejection'), + '' + ].join('\n'); + + t.is(actualOutput, expectedOutput); + t.end(); +}); + +test('results with passing tests and exceptions', function (t) { + var reporter = createReporter(); + reporter.api.passCount = 1; + reporter.api.exceptionCount = 1; + + var actualOutput = reporter.finish(); + var expectedOutput = [ + '', + ' ' + chalk.green('1 test passed'), + ' ' + chalk.red('1 uncaught exception'), + '' + ].join('\n'); + + t.is(actualOutput, expectedOutput); + t.end(); +}); + +test('results with passing tests, rejections and exceptions', function (t) { + var reporter = createReporter(); + reporter.api.passCount = 1; + reporter.api.exceptionCount = 1; + reporter.api.rejectionCount = 1; + + var actualOutput = reporter.finish(); + var expectedOutput = [ + '', + ' ' + chalk.green('1 test passed'), + ' ' + chalk.red('1 unhandled rejection'), + ' ' + chalk.red('1 uncaught exception'), + '' + ].join('\n'); + + t.is(actualOutput, expectedOutput); + t.end(); +}); + +test('results with errors', function (t) { + var reporter = createReporter(); + reporter.api.failCount = 1; + reporter.api.tests = [{ + title: 'fail', + error: new Error('error message') + }]; + + var output = reporter.finish().split('\n'); + + t.is(output[1], ' ' + chalk.red('1 test failed')); + t.is(output[3], ' ' + chalk.red('1. fail')); + t.match(output[4], /Error: error message/); + t.match(output[5], /Test\.test/); + t.end(); +}); + +function fooFunc() { + barFunc(); +} + +function barFunc() { + throw new Error(); +}