UNPKG

@jhermsmeier/control

Version:

Minimal, speedy test framework

183 lines (145 loc) 5.32 kB
import color from './color.mjs' // TODO: // - [ ] Handle before/after lifecycle errors export default class Reporter { constructor( control, options ) { this.control = control this.depth = 0 this.indent = 2 this.timing = { test: 0, total: 0, } this.testCount = 0 this.okTestCount = 0 this.failTestCount = 0 this.skipTestCount = 0 this.failedTests = [] this.libraryPath = options.libraryPath this.symbol = { ok: '·', skip: '-', fail: '⤫', } this.control.on( 'context', this ) this.control.on( 'setup', this ) this.control.on( 'test', this ) this.control.on( 'teardown', this ) this.control.on( 'context:end', this ) this.control.on( 'end', this ) } log( msg ) { return process.stdout.write( msg ) } handleEvent( eventType, ...argv ) { switch( eventType ) { case 'start': this.onStart(); break case 'context': this.onContext( ...argv ); break case 'setup': this.onContextLifeCycle( eventType, ...argv ); break case 'teardown': this.onContextLifeCycle( eventType, ...argv ); break case 'context:end': this.onContextEnd( ...argv ); break case 'test': this.onTest( ...argv ); break case 'end': this.onEnd(); break } } formatTime( ms ) { return ms.toFixed( 2 ) + 'ms' } formatError( error, indent = '' ) { var stack = error.stack var match = /^\s+at\s/m.exec( stack ) var stackFrameStart = match ? match.index : 0 var stackIndent = indent + ' ' if( stackFrameStart > 0 ) { stack = stack.slice( stackFrameStart ) stack = stack.split( '\n' ) if( this.libraryPath ) { stack = stack.filter( line => { return !line.includes( this.libraryPath ) }) } stack = stack.reduce(( stack, line ) => { return `${ stack }${ stackIndent }${ line.trim() }\n` }, '') } var title = color.brightRed( `${ error.name }: ${ error.message }` ) return `${ indent }${ title }\n${ color.gray( stack ) }\n` } formatInlineError( error ) { var message = ( error.message || 'Unknown error' ) // Truncate error message to first line, // and append an ellipsis var eol = message.indexOf( '\n' ) if( eol != -1 ) { message = message.substring( 0, eol ) .replace( /[.,:;]$|$/, ' […]' ) } return color.brightRed( message ) } onStart() { this.timing.total = performance.now() } onContext( context ) { this.depth = Math.max( 0, context.parents.length - 1 ) + 1 if( context.label ) { var indent = ' '.repeat( this.indent * this.depth ) this.log( `\n${ indent }${ color.brightWhite( context.label ) }\n` ) } else { this.log( '\n' ) } this.depth++ } onContextLifeCycle( type, task ) { if( task.error ) { var indent = ' '.repeat( this.indent * this.depth ) this.failedTests.push( task ) this.log( `${ indent }${ color.brightRed( this.symbol.fail ) } ${ task.label || type } ${ color.gray( '–' ) } ${ this.formatInlineError( task.error ) }\n` ) } } onContextEnd( context ) { // ... } onTest( task ) { var indent = ' '.repeat( this.indent * this.depth ) this.testCount++ this.timing.test += task.timing.total if( task.error != null ) { this.failTestCount++ this.failedTests.push( task ) this.log( `${ indent }${ color.brightRed( this.symbol.fail ) } ${ task.label } ${ color.gray( '–' ) } ${ this.formatInlineError( task.error ) } ${ color.gray( `… ${ this.formatTime( task.timing.task ) }` ) }\n` ) } else if( task.skip ) { this.skipTestCount++ this.log( `${ indent }${ color.cyan( this.symbol.skip ) } ${ color.gray( task.label ) }\n` ) } else { this.okTestCount++ this.log( `${ indent }${ color.brightGreen( this.symbol.ok ) } ${ task.label } ${ color.gray( `… ${ this.formatTime( task.timing.task ) }` ) }\n` ) } } onEnd() { this.timing.total = performance.now() - this.timing.total if( this.failedTests.length ) { let separator = color.gray( '╌'.repeat( Math.min( process.stdout.columns, 120 ) ) ) let indent = ' ' this.log( `\n${separator}\n` ) for( let task of this.failedTests ) { let taskPath = [].concat( task.context.parents, task.context, task ) .filter( p => p.label ).map( p => p.label ).join( color.gray( ' / ' ) ) this.log( `\n${ indent }${ taskPath }${ color.gray(':') }\n` ) this.log( `${ this.formatError( task.error, indent ) }` ) } this.log( `${separator}\n` ) } var padLength = this.testCount.toString().length var pad = ( num ) => num.toString().padStart( padLength, ' ' ) this.log( `\n ${ pad( this.testCount ) } tests ${ color.gray( `… ${ this.formatTime( this.timing.test ) }` ) }\n` ) if( this.okTestCount ) { this.log( color.brightGreen( `${ this.symbol.ok } ${ pad( this.okTestCount ) } pass\n` ) ) } if( this.skipTestCount ) { this.log( color.brightCyan( `${ this.symbol.skip } ${ pad( this.skipTestCount ) } skip\n` ) ) } if( this.failTestCount ) { this.log( color.brightRed( `${ this.symbol.fail } ${ pad( this.failTestCount ) } fail\n` ) ) } } }