diff options
author | Florian Dold <florian.dold@gmail.com> | 2017-05-28 00:38:50 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2017-05-28 00:40:43 +0200 |
commit | 7fff4499fd915bcea3fa93b1aa8b35f4fe7a6027 (patch) | |
tree | 6de9a1aebd150a23b7f8c273ec657a5d0a18fe3e /node_modules/ava/lib/reporters | |
parent | 963b7a41feb29cc4be090a2446bdfe0c1f1bcd81 (diff) |
add linting (and some initial fixes)
Diffstat (limited to 'node_modules/ava/lib/reporters')
-rw-r--r-- | node_modules/ava/lib/reporters/improper-usage-messages.js | 21 | ||||
-rw-r--r-- | node_modules/ava/lib/reporters/mini.js | 318 | ||||
-rw-r--r-- | node_modules/ava/lib/reporters/tap.js | 119 | ||||
-rw-r--r-- | node_modules/ava/lib/reporters/verbose.js | 165 |
4 files changed, 623 insertions, 0 deletions
diff --git a/node_modules/ava/lib/reporters/improper-usage-messages.js b/node_modules/ava/lib/reporters/improper-usage-messages.js new file mode 100644 index 000000000..0a2626638 --- /dev/null +++ b/node_modules/ava/lib/reporters/improper-usage-messages.js @@ -0,0 +1,21 @@ +'use strict'; +const chalk = require('chalk'); + +exports.forError = error => { + if (!error.improperUsage) { + return null; + } + + const assertion = error.assertion; + if (assertion !== 'throws' || !assertion === 'notThrows') { + return null; + } + + return `Try wrapping the first argument to \`t.${assertion}()\` in a function: + + ${chalk.cyan(`t.${assertion}(() => { `)}${chalk.grey('/* your code here */')}${chalk.cyan(' })')} + +Visit the following URL for more details: + + ${chalk.blue.underline('https://github.com/avajs/ava#throwsfunctionpromise-error-message')}`; +}; diff --git a/node_modules/ava/lib/reporters/mini.js b/node_modules/ava/lib/reporters/mini.js new file mode 100644 index 000000000..df481a76a --- /dev/null +++ b/node_modules/ava/lib/reporters/mini.js @@ -0,0 +1,318 @@ +'use strict'; +const StringDecoder = require('string_decoder').StringDecoder; +const cliCursor = require('cli-cursor'); +const lastLineTracker = require('last-line-stream/tracker'); +const plur = require('plur'); +const spinners = require('cli-spinners'); +const chalk = require('chalk'); +const cliTruncate = require('cli-truncate'); +const cross = require('figures').cross; +const indentString = require('indent-string'); +const formatAssertError = require('../format-assert-error'); +const extractStack = require('../extract-stack'); +const codeExcerpt = require('../code-excerpt'); +const colors = require('../colors'); +const improperUsageMessages = require('./improper-usage-messages'); + +// TODO(@jamestalamge): This should be fixed in log-update and ansi-escapes once we are confident it's a good solution. +const CSI = '\u001B['; +const ERASE_LINE = CSI + '2K'; +const CURSOR_TO_COLUMN_0 = CSI + '0G'; +const CURSOR_UP = CSI + '1A'; + +// Returns a string that will erase `count` lines from the end of the terminal. +function eraseLines(count) { + let clear = ''; + + for (let i = 0; i < count; i++) { + clear += ERASE_LINE + (i < count - 1 ? CURSOR_UP : ''); + } + + if (count) { + clear += CURSOR_TO_COLUMN_0; + } + + return clear; +} + +class MiniReporter { + constructor(options) { + this.options = Object.assign({}, options); + + chalk.enabled = this.options.color; + for (const key of Object.keys(colors)) { + colors[key].enabled = this.options.color; + } + + const spinnerDef = spinners[process.platform === 'win32' ? 'line' : 'dots']; + this.spinnerFrames = spinnerDef.frames.map(c => chalk.gray.dim(c)); + this.spinnerInterval = spinnerDef.interval; + + this.reset(); + this.stream = process.stderr; + this.stringDecoder = new StringDecoder(); + } + start() { + this.interval = setInterval(() => { + this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length; + this.write(this.prefix()); + }, this.spinnerInterval); + + return this.prefix(''); + } + reset() { + this.clearInterval(); + this.passCount = 0; + this.knownFailureCount = 0; + this.failCount = 0; + this.skipCount = 0; + this.todoCount = 0; + this.rejectionCount = 0; + this.exceptionCount = 0; + this.currentStatus = ''; + this.currentTest = ''; + this.statusLineCount = 0; + this.spinnerIndex = 0; + this.lastLineTracker = lastLineTracker(); + } + spinnerChar() { + return this.spinnerFrames[this.spinnerIndex]; + } + clearInterval() { + clearInterval(this.interval); + this.interval = null; + } + test(test) { + if (test.todo) { + this.todoCount++; + } else if (test.skip) { + this.skipCount++; + } else if (test.error) { + this.failCount++; + } else { + this.passCount++; + if (test.failing) { + this.knownFailureCount++; + } + } + + if (test.todo || test.skip) { + return; + } + + return this.prefix(this._test(test)); + } + prefix(str) { + str = str || this.currentTest; + this.currentTest = str; + + // The space before the newline is required for proper formatting + // TODO(jamestalmage): Figure out why it's needed and document it here + return ` \n ${this.spinnerChar()} ${str}`; + } + _test(test) { + const SPINNER_WIDTH = 3; + const PADDING = 1; + let title = cliTruncate(test.title, process.stdout.columns - SPINNER_WIDTH - PADDING); + + if (test.error || test.failing) { + title = colors.error(test.title); + } + + return title + '\n' + this.reportCounts(); + } + unhandledError(err) { + if (err.type === 'exception') { + this.exceptionCount++; + } else { + this.rejectionCount++; + } + } + reportCounts(time) { + const lines = [ + this.passCount > 0 ? '\n ' + colors.pass(this.passCount, 'passed') : '', + this.knownFailureCount > 0 ? '\n ' + colors.error(this.knownFailureCount, plur('known failure', this.knownFailureCount)) : '', + this.failCount > 0 ? '\n ' + colors.error(this.failCount, 'failed') : '', + this.skipCount > 0 ? '\n ' + colors.skip(this.skipCount, 'skipped') : '', + this.todoCount > 0 ? '\n ' + colors.todo(this.todoCount, 'todo') : '' + ].filter(Boolean); + + if (time && lines.length > 0) { + lines[0] += ' ' + time; + } + + return lines.join(''); + } + finish(runStatus) { + this.clearInterval(); + let time; + + if (this.options.watching) { + time = chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']'); + } + + let status = this.reportCounts(time); + + if (this.rejectionCount > 0) { + status += '\n ' + colors.error(this.rejectionCount, plur('rejection', this.rejectionCount)); + } + + if (this.exceptionCount > 0) { + status += '\n ' + colors.error(this.exceptionCount, plur('exception', this.exceptionCount)); + } + + if (runStatus.previousFailCount > 0) { + status += '\n ' + colors.error(runStatus.previousFailCount, 'previous', plur('failure', runStatus.previousFailCount), 'in test files that were not rerun'); + } + + if (this.knownFailureCount > 0) { + for (const test of runStatus.knownFailures) { + const title = test.title; + status += '\n\n ' + colors.title(title); + // TODO: Output description with link + // status += colors.stack(description); + } + } + + if (this.failCount > 0) { + runStatus.errors.forEach((test, index) => { + if (!test.error) { + return; + } + + const beforeSpacing = index === 0 ? '\n\n' : '\n\n\n\n'; + + status += beforeSpacing + ' ' + colors.title(test.title) + '\n'; + if (test.error.source) { + status += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; + + const excerpt = codeExcerpt(test.error.source, {maxWidth: process.stdout.columns}); + if (excerpt) { + status += '\n' + indentString(excerpt, 2) + '\n'; + } + } + + if (test.error.message) { + status += '\n' + indentString(test.error.message, 2) + '\n'; + } + + if (test.error.avaAssertionError) { + const formatted = formatAssertError.formatSerializedError(test.error); + if (formatted) { + status += '\n' + indentString(formatted, 2); + } + + const message = improperUsageMessages.forError(test.error); + if (message) { + status += '\n' + indentString(message, 2) + '\n'; + } + } + + if (test.error.stack) { + const extracted = extractStack(test.error.stack); + if (extracted.includes('\n')) { + status += '\n' + indentString(colors.errorStack(extracted), 2); + } + } + }); + } + + if (this.rejectionCount > 0 || this.exceptionCount > 0) { + // TODO(sindresorhus): Figure out why this causes a test failure when switched to a for-of loop + runStatus.errors.forEach(err => { + if (err.title) { + return; + } + + if (err.type === 'exception' && err.name === 'AvaError') { + status += '\n\n ' + colors.error(cross + ' ' + err.message); + } else { + const title = err.type === 'rejection' ? 'Unhandled Rejection' : 'Uncaught Exception'; + let description = err.stack ? err.stack.trimRight() : JSON.stringify(err); + description = description.split('\n'); + const errorTitle = err.name ? description[0] : 'Threw non-error: ' + description[0]; + const errorStack = description.slice(1).join('\n'); + + status += '\n\n ' + colors.title(title) + '\n'; + status += ' ' + colors.stack(errorTitle) + '\n'; + status += colors.errorStack(errorStack); + } + }); + } + + if (runStatus.failFastEnabled === true && runStatus.remainingCount > 0 && runStatus.failCount > 0) { + const remaining = 'At least ' + runStatus.remainingCount + ' ' + plur('test was', 'tests were', runStatus.remainingCount) + ' skipped.'; + status += '\n\n ' + colors.information('`--fail-fast` is on. ' + remaining); + } + + if (runStatus.hasExclusive === true && runStatus.remainingCount > 0) { + status += '\n\n ' + colors.information('The .only() modifier is used in some tests.', runStatus.remainingCount, plur('test', runStatus.remainingCount), plur('was', 'were', runStatus.remainingCount), 'not run'); + } + + return status + '\n\n'; + } + section() { + return '\n' + chalk.gray.dim('\u2500'.repeat(process.stdout.columns || 80)); + } + clear() { + return ''; + } + write(str) { + cliCursor.hide(); + this.currentStatus = str; + this._update(); + this.statusLineCount = this.currentStatus.split('\n').length; + } + stdout(data) { + this._update(data); + } + stderr(data) { + this._update(data); + } + _update(data) { + let str = ''; + let ct = this.statusLineCount; + const columns = process.stdout.columns; + let lastLine = this.lastLineTracker.lastLine(); + + // Terminals automatically wrap text. We only need the last log line as seen on the screen. + lastLine = lastLine.substring(lastLine.length - (lastLine.length % columns)); + + // Don't delete the last log line if it's completely empty. + if (lastLine.length > 0) { + ct++; + } + + // Erase the existing status message, plus the last log line. + str += eraseLines(ct); + + // Rewrite the last log line. + str += lastLine; + + if (str.length > 0) { + this.stream.write(str); + } + + if (data) { + // Send new log data to the terminal, and update the last line status. + this.lastLineTracker.update(this.stringDecoder.write(data)); + this.stream.write(data); + } + + let currentStatus = this.currentStatus; + + if (currentStatus.length > 0) { + lastLine = this.lastLineTracker.lastLine(); + // We need a newline at the end of the last log line, before the status message. + // However, if the last log line is the exact width of the terminal a newline is implied, + // and adding a second will cause problems. + if (lastLine.length % columns) { + currentStatus = '\n' + currentStatus; + } + // Rewrite the status message. + this.stream.write(currentStatus); + } + } +} + +module.exports = MiniReporter; diff --git a/node_modules/ava/lib/reporters/tap.js b/node_modules/ava/lib/reporters/tap.js new file mode 100644 index 000000000..37c2cfd95 --- /dev/null +++ b/node_modules/ava/lib/reporters/tap.js @@ -0,0 +1,119 @@ +'use strict'; +const format = require('util').format; +const indentString = require('indent-string'); +const stripAnsi = require('strip-ansi'); +const yaml = require('js-yaml'); +const extractStack = require('../extract-stack'); + +// Parses stack trace and extracts original function name, file name and line +function getSourceFromStack(stack) { + return extractStack(stack).split('\n')[0]; +} + +function dumpError(error, includeMessage) { + const obj = Object.assign({}, error.object); + if (error.name) { + obj.name = error.name; + } + if (includeMessage && error.message) { + obj.message = error.message; + } + + if (error.avaAssertionError) { + if (error.assertion) { + obj.assertion = error.assertion; + } + if (error.operator) { + obj.operator = error.operator; + } + if (error.values.length > 0) { + obj.values = error.values.reduce((acc, value) => { + acc[value.label] = stripAnsi(value.formatted); + return acc; + }, {}); + } + } + + if (error.stack) { + obj.at = getSourceFromStack(error.stack); + } + + return ` ---\n${indentString(yaml.safeDump(obj).trim(), 4)}\n ...`; +} + +class TapReporter { + constructor() { + this.i = 0; + } + start() { + return 'TAP version 13'; + } + test(test) { + let output; + + let directive = ''; + const passed = test.todo ? 'not ok' : 'ok'; + + if (test.todo) { + directive = '# TODO'; + } else if (test.skip) { + directive = '# SKIP'; + } + + const title = stripAnsi(test.title); + + if (test.error) { + output = [ + '# ' + title, + format('not ok %d - %s', ++this.i, title), + dumpError(test.error, true) + ]; + } else { + output = [ + `# ${title}`, + format('%s %d - %s %s', passed, ++this.i, title, directive).trim() + ]; + } + + return output.join('\n'); + } + unhandledError(err) { + const output = [ + `# ${err.message}`, + format('not ok %d - %s', ++this.i, err.message) + ]; + // AvaErrors don't have stack traces + if (err.type !== 'exception' || err.name !== 'AvaError') { + output.push(dumpError(err, false)); + } + + return output.join('\n'); + } + finish(runStatus) { + const output = [ + '', + '1..' + (runStatus.passCount + runStatus.failCount + runStatus.skipCount), + '# tests ' + (runStatus.passCount + runStatus.failCount + runStatus.skipCount), + '# pass ' + runStatus.passCount + ]; + + if (runStatus.skipCount > 0) { + output.push(`# skip ${runStatus.skipCount}`); + } + + output.push('# fail ' + (runStatus.failCount + runStatus.rejectionCount + runStatus.exceptionCount), ''); + + return output.join('\n'); + } + write(str) { + console.log(str); + } + stdout(data) { + process.stderr.write(data); + } + stderr(data) { + this.stdout(data); + } +} + +module.exports = TapReporter; diff --git a/node_modules/ava/lib/reporters/verbose.js b/node_modules/ava/lib/reporters/verbose.js new file mode 100644 index 000000000..1be43ce5e --- /dev/null +++ b/node_modules/ava/lib/reporters/verbose.js @@ -0,0 +1,165 @@ +'use strict'; +const indentString = require('indent-string'); +const prettyMs = require('pretty-ms'); +const figures = require('figures'); +const chalk = require('chalk'); +const plur = require('plur'); +const formatAssertError = require('../format-assert-error'); +const extractStack = require('../extract-stack'); +const codeExcerpt = require('../code-excerpt'); +const colors = require('../colors'); +const improperUsageMessages = require('./improper-usage-messages'); + +class VerboseReporter { + constructor(options) { + this.options = Object.assign({}, options); + + chalk.enabled = this.options.color; + for (const key of Object.keys(colors)) { + colors[key].enabled = this.options.color; + } + } + start() { + return ''; + } + test(test, runStatus) { + if (test.error) { + return ' ' + colors.error(figures.cross) + ' ' + test.title + ' ' + colors.error(test.error.message); + } + + if (test.todo) { + return ' ' + colors.todo('- ' + test.title); + } else if (test.skip) { + return ' ' + colors.skip('- ' + test.title); + } + + if (test.failing) { + return ' ' + colors.error(figures.tick) + ' ' + colors.error(test.title); + } + + if (runStatus.fileCount === 1 && runStatus.testCount === 1 && test.title === '[anonymous]') { + return undefined; + } + + // Display duration only over a threshold + const threshold = 100; + const duration = test.duration > threshold ? colors.duration(' (' + prettyMs(test.duration) + ')') : ''; + + return ' ' + colors.pass(figures.tick) + ' ' + test.title + duration; + } + unhandledError(err) { + if (err.type === 'exception' && err.name === 'AvaError') { + return colors.error(' ' + figures.cross + ' ' + err.message); + } + + const types = { + rejection: 'Unhandled Rejection', + exception: 'Uncaught Exception' + }; + + let output = colors.error(types[err.type] + ':', err.file) + '\n'; + + if (err.stack) { + output += ' ' + colors.stack(err.stack) + '\n'; + } else { + output += ' ' + colors.stack(JSON.stringify(err)) + '\n'; + } + + output += '\n'; + + return output; + } + finish(runStatus) { + let output = '\n'; + + const lines = [ + runStatus.failCount > 0 ? + ' ' + colors.error(runStatus.failCount, plur('test', runStatus.failCount), 'failed') : + ' ' + colors.pass(runStatus.passCount, plur('test', runStatus.passCount), 'passed'), + runStatus.knownFailureCount > 0 ? ' ' + colors.error(runStatus.knownFailureCount, plur('known failure', runStatus.knownFailureCount)) : '', + runStatus.skipCount > 0 ? ' ' + colors.skip(runStatus.skipCount, plur('test', runStatus.skipCount), 'skipped') : '', + runStatus.todoCount > 0 ? ' ' + colors.todo(runStatus.todoCount, plur('test', runStatus.todoCount), 'todo') : '', + runStatus.rejectionCount > 0 ? ' ' + colors.error(runStatus.rejectionCount, 'unhandled', plur('rejection', runStatus.rejectionCount)) : '', + runStatus.exceptionCount > 0 ? ' ' + colors.error(runStatus.exceptionCount, 'uncaught', plur('exception', runStatus.exceptionCount)) : '', + runStatus.previousFailCount > 0 ? ' ' + colors.error(runStatus.previousFailCount, 'previous', plur('failure', runStatus.previousFailCount), 'in test files that were not rerun') : '' + ].filter(Boolean); + + if (lines.length > 0) { + lines[0] += ' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']'); + output += lines.join('\n'); + } + + if (runStatus.knownFailureCount > 0) { + runStatus.knownFailures.forEach(test => { + output += '\n\n\n ' + colors.error(test.title); + }); + } + + if (runStatus.failCount > 0) { + runStatus.tests.forEach((test, index) => { + if (!test.error) { + return; + } + + const beforeSpacing = index === 0 ? '\n\n' : '\n\n\n\n'; + output += beforeSpacing + ' ' + colors.title(test.title) + '\n'; + if (test.error.source) { + output += ' ' + colors.errorSource(test.error.source.file + ':' + test.error.source.line) + '\n'; + + const excerpt = codeExcerpt(test.error.source, {maxWidth: process.stdout.columns}); + if (excerpt) { + output += '\n' + indentString(excerpt, 2) + '\n'; + } + } + + if (test.error.message) { + output += '\n' + indentString(test.error.message, 2) + '\n'; + } + + if (test.error.avaAssertionError) { + const formatted = formatAssertError.formatSerializedError(test.error); + if (formatted) { + output += '\n' + indentString(formatted, 2); + } + + const message = improperUsageMessages.forError(test.error); + if (message) { + output += '\n' + indentString(message, 2) + '\n'; + } + } + + if (test.error.stack) { + const extracted = extractStack(test.error.stack); + if (extracted.includes('\n')) { + output += '\n' + indentString(colors.errorStack(extracted), 2); + } + } + }); + } + + if (runStatus.failFastEnabled === true && runStatus.remainingCount > 0 && runStatus.failCount > 0) { + const remaining = 'At least ' + runStatus.remainingCount + ' ' + plur('test was', 'tests were', runStatus.remainingCount) + ' skipped.'; + output += '\n\n\n ' + colors.information('`--fail-fast` is on. ' + remaining); + } + + if (runStatus.hasExclusive === true && runStatus.remainingCount > 0) { + output += '\n\n\n ' + colors.information('The .only() modifier is used in some tests.', runStatus.remainingCount, plur('test', runStatus.remainingCount), plur('was', 'were', runStatus.remainingCount), 'not run'); + } + + return output + '\n'; + } + section() { + return chalk.gray.dim('\u2500'.repeat(process.stdout.columns || 80)); + } + write(str) { + console.error(str); + } + stdout(data) { + process.stderr.write(data); + } + stderr(data) { + process.stderr.write(data); + } +} + +module.exports = VerboseReporter; |