UNPKG

tap

Version:

A Test-Anything-Protocol library for JavaScript

300 lines (265 loc) 7.7 kB
const React = require('react') const ms = require('ms') const importJSX = require('import-jsx') const {useStdout, Box, Text} = require('ink') const Parser = require('tap-parser') const chalk = require('chalk') // Pull in all the tags here so we can re-export them const AssertCounts = importJSX('./assert-counts.js') const AssertName = importJSX('./assert-name.js') const Counts = importJSX('./counts.js') const Footer = importJSX('./footer.js') const Log = importJSX('./log.js') const PassFail = importJSX('./pass-fail.js') const Result = importJSX('./result.js') const Runs = importJSX('./runs.js') const StatusMark = importJSX('./status-mark.js') const SuiteCounts = importJSX('./suite-counts.js') const Summary = importJSX('./summary.js') const TestPoint = importJSX('./test-point.js') const Test = importJSX('./test.js') class Base extends React.Component { get Summary () { return Summary } get Runs () { return Runs } get Log () { return Log } get Footer () { return Footer } constructor ({ tap }) { super() this.mounted = false this.state = { // the stuff in the static section. most importantly, errors in // real time, but also console output, test summaries, etc. log: [], // all tests, done and queued tests: [], // currently running runs: [], // tap.results at the end results: null, // counts of all relevant test points // debounced on this.assertCounts assertCounts: { total: 0, pass: 0, fail: 0, skip: 0, todo: 0, }, // a count of all the test suites run suiteCounts: { total: 0, pass: 0, fail: 0, skip: 0, todo: 0, }, // total elapsed time time: 0, bailout: null, } this.start = Date.now() this.timer = setInterval(() => this.setState(prevState => ({ ...prevState, time: this.time, assertCounts: this.assertCounts, })), 200) // keep counters on the object itself, to debounce this.assertCounts = { total: 0, pass: 0, fail: 0, skip: 0, todo: 0, }, this.counterBouncer = null tap.on('subtestAdd', t => this.addTest(t)) tap.on('subtestStart', t => this.startTest(t)) tap.on('subtestEnd', t => this.endTest(t)) tap.on('end', () => this.endAll(tap)) tap.once('bailout', message => this.bailout(message)) // Handle data that might come out of the tap object itself. tap.on('extra', this.onRaw(tap, 1)) tap.parser.on('result', res => { if (res.fullname === 'TAP') { this.logRes(tap, res) this.inc(res.todo ? 'todo' : res.skip ? 'skip' : res.ok ? 'pass' : 'fail' ) } }) // consume the text stream, but ignore it. // we get all we need from the child test objects. this.tapResume(tap) } tapResume (tap) { tap.resume() } get time () { return Date.now() - this.start } componentDidMount () { this.mounted = true } componentWillUnmount () { this.mounted = false clearTimeout(this.counterBouncer) clearInterval(this.timer) } bailout (bailout, test = null) { this.bailedOut = bailout return this.setState(prevState => prevState.bailout ? prevState : ({ ...prevState, runs: test ? prevState.runs.filter(t => t.childId !== test.childId) : [], // if we bail out, then we should only show the bailout, // or the counts get confusing, because we never receive a testEnd // for the other ones. tests: prevState.tests.filter(t => test ? t.childId === test.childId : t.results && t.results.bailout === bailout), bailout, assertCounts: this.assertCounts, time: this.time, })) } inc (type) { this.assertCounts.total++ this.assertCounts[type]++ if (this.counterBouncer) return this.counterBouncer = setTimeout(() => { this.counterBouncer = null this.setState(prevState => ({ ...prevState, assertCounts: this.assertCounts })) }, 50) } addTest (test) { this.setState(prevState => ({ ...prevState, tests: prevState.tests.concat(test), suiteCounts: { ...prevState.suiteCounts, total: prevState.suiteCounts.total + 1, }, assertCounts: this.assertCounts, time: this.time, })) test .on('preprocess', options => options.stdio = 'pipe') .on('process', proc => { proc.stderr.setEncoding('utf8') proc.stderr.on('data', this.onRaw(test, 2)) }) .parser .on('extra', this.onRaw(test, 1)) .on('pass', res => this.inc('pass')) .on('todo', res => (this.inc('todo'), this.logRes(test, res))) .on('skip', res => (this.inc('skip'), this.logRes(test, res))) .on('fail', res => (this.inc('fail'), this.logRes(test, res))) } onRaw (test, fd) { const p = ` ${fd}>` return raw => { const pref = chalk.bold.dim(test.name + p + ' ') raw = raw.replace(/\n$/, '').replace(/^/gm, pref) this.setState(prevState => ({ ...prevState, log: prevState.log.concat({raw}), assertCounts: this.assertCounts, })) } } logRes (test, res) { res.testName = test.name this.setState(prevState => ({ ...prevState, log: prevState.log.concat({res}), assertCounts: this.assertCounts, time: this.time, })) } startTest (test) { test.startTime = Date.now() test.once('bailout', message => this.bailout(message, test)) this.setState(prevState => prevState.bailout ? prevState : ({ ...prevState, runs: prevState.runs.concat(test), assertCounts: this.assertCounts, })) } endTest (test) { test.endTime = Date.now() // put it in the appropriate bucket. // live update assertion handed by tap.parser event const ok = test.results && test.results.ok const skip = test.options.skip && ok !== false const todo = test.options.todo && ok !== false const bucket = skip ? 'skip' : todo ? 'todo' : !ok ? 'fail' : 'pass' this.setState(prevState => prevState.bailout ? prevState : ({ ...prevState, log: prevState.log.concat({test}), runs: prevState.runs.filter(t => t.childId !== test.childId), suiteCounts: { ...prevState.suiteCounts, [bucket]: prevState.suiteCounts[bucket] + 1, }, time: this.time, assertCounts: this.assertCounts, })) } endAll (tap) { clearInterval(this.timer) clearInterval(this.counterBouncer) this.setState(prevState => ({ ...prevState, results: tap.results, assertCounts: this.assertCounts, time: tap.time || this.time, })) } render () { const {Log, Runs, Summary, Footer} = this return (<Box flexDirection="column"> <Log log={this.state.log} /> <Runs runs={this.state.runs} />{ this.state.results ? ( <Summary tests={this.state.tests} results={this.state.results} /> ) : <Text></Text> }<Footer suiteCounts={this.state.suiteCounts} assertCounts={this.state.assertCounts} time={this.state.time} /> </Box>) } } Base.AssertCounts = AssertCounts Base.AssertName = AssertName Base.Counts = Counts Base.Footer = Footer Base.Log = Log Base.PassFail = PassFail Base.Result = Result Base.Runs = Runs Base.StatusMark = StatusMark Base.SuiteCounts = SuiteCounts Base.Summary = Summary Base.TestPoint = TestPoint Base.Test = Test module.exports = Base