UNPKG

@small-tech/tap-monkey

Version:

A tap formatter that’s also a monkey.

165 lines (135 loc) 5.26 kB
#!/usr/bin/env node ////////////////////////////////////////////////////////////////////////////////////////// // // A tap formatter that’s also a monkey. // // Displays test runner status using a static single-line spinner (hint: it’s a monkey) // and only fills your screen with text on failures and with your coverage report. // // Copyright ⓒ 2021 Aral Balkan, Small Technology Foundation // License: ISC // ////////////////////////////////////////////////////////////////////////////////////////// import Ora from 'ora' import chalk from 'chalk' import { performance } from 'perf_hooks' import tapOut from '@small-tech/tap-out' import os from 'os' // The formatter has a --quiet option that stops status updates being // printed until there is a failure or until the aggregate statistics is // being shown. People using screen readers and other assistive technologies // might want to use this if the number of status updates becomes overwhelming. export const context = { quiet: (process.argv.length === 3 && process.argv[2] === '--quiet') } // Due to the 300ms frame duration of the monkey animation, not every // status update we receive about new test suites and test passes will be // reflected in the spinner text and that’s ok. Failures, of course, are // all output in full to the terminal. const indentedMonkey = { "interval": 300, "frames": [ " 🙈 ", " 🙈 ", " 🙉 ", " 🙊 " ] } const spinner = new Ora({ spinner: indentedMonkey, discardStdin: false, text: 'Running tests…' }) const parser = tapOut() process.stdin.pipe(parser) const startTime = performance.now() let printingCoverage = false let coverageBorderCount = 0 let currentTest = '' const passHandler = (assert => { if (!context.quiet) { spinner.text = `${chalk.green('✔')} ${assert.name}` } }) const testHandler = (test => { spinner.start() if (!context.quiet) { currentTest = test.name spinner.text = `Running ${chalk.underline(currentTest)} tests` } }) const failHandler = (assert => { // Stop the spinner and output failures in full. spinner.stop() const e = assert.error console.log(`${chalk.red('✖ FAIL:')} ${assert.name} \n`) if (e.operator !== undefined) console.log(` operator:`, e.operator) if (e.expected !== undefined) console.log(` ${chalk.green(`expected: ${e.expected}`)}`) if (e.actual !== undefined) console.log(` ${chalk.red(`actual : ${e.actual}`)}`) if (e.at !== undefined) console.log(` ${chalk.yellow(`at : ${e.at.file.replace(os.homedir(), '~')}:${e.at.line}:${e.at.character}`)}`) console.log() e.stack.split('\n').forEach(line => { console.log(' ', chalk.red(line)) }) spinner.start() }) const bailOutHandler = (event => { // If the test runner has emitted a bail out event, it has signaled // that it cannot continue. So we notify the person and exit. spinner.stop() console.error(chalk.red(event.raw)) console.error() process.exit(1) }) const commentHandler = (comment => { spinner.stop() let commentText = comment.raw const isCoverageBorder = commentText.startsWith('----') if (isCoverageBorder) { printingCoverage = true } if (printingCoverage) { if (isCoverageBorder) { coverageBorderCount++ switch(coverageBorderCount) { case 1: commentText = `╭─${commentText.replace(/\-\|\-/g, '─┬─')}─╮`; break case 2: commentText = `├─${commentText.replace(/\-\|\-/g, '─┼─')}─┤`; break case 3: commentText = `╰─${commentText.replace(/\-\|\-/g, '─┴─')}─╯\n`; break default: throw new Error('Too many borders found in coverage. Panic!') } } else { // Printing coverage but this line isn’t a border, just surround it with vertical borders. commentText = `│ ${commentText} │` } // Replace any inner borders that there might be with proper box-drawing characters. console.log(commentText.replace(/\|/g, '│').replace(/\-/g, '─')) } else { // We aren’t printing coverage yet so this must be a regular TAP comment. // Display it fully. console.log(chalk.yellow(' 🢂 '), commentText.trim()) spinner.start() } }) const outputHandler = (results => { const duration = ((performance.now() - startTime)/1000).toFixed(2) spinner.stop() const total = results.asserts.length const passing = results.pass.length const failing = results.fail.length // TODO: Handle edge case of zero total tests. if (failing > 0) { console.log(` 🙊️ ${chalk.magenta('There are failed tests.')}`) } else { console.log(` 🍌️ ${chalk.green('All tests passing!')}`) } console.log() console.log( ` Total ${total}`) console.log(chalk.green(` Passing ${passing}`)) console.log(chalk.red( ` Failing ${failing}`)) console.log(chalk.gray( ` Duration ${duration} secs`)) }) parser.on('test', testHandler) parser.on('pass', passHandler) parser.on('fail', failHandler) parser.on('bailOut', bailOutHandler) parser.on('comment', commentHandler) parser.on('output', outputHandler) export default { testHandler, passHandler, failHandler, bailOutHandler, commentHandler, outputHandler, parser, spinner }