Skip to content

Commit

Permalink
Really early/basic prototype of a spec-like thing
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacs committed Apr 1, 2019
1 parent ac244d0 commit 18d8f34
Show file tree
Hide file tree
Showing 14 changed files with 1,487 additions and 0 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ A lot like Base, but says a lot less. No timer, no list of tests concurrently
running, nothing printed on test passing. Just the failures and the terse
summary.

### Specy

A `spec` style reporter with the current running jobs and Terse summary and
footer.

## Extending

You can extend this by creating a module whose main `module.exports` is a
Expand Down
12 changes: 12 additions & 0 deletions lib/reports/specy/footer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const React = require('react')
const {Box, Color} = require('ink')
const importJSX = require('import-jsx')
const AssertCounts = importJSX('../base/assert-counts.js')
const SuiteCounts = importJSX('../base/suite-counts.js')

module.exports = ({suiteCounts, assertCounts, time}) => (
<Box flexDirection="column">
<SuiteCounts {...suiteCounts} />
<AssertCounts {...assertCounts} />
</Box>
)
13 changes: 13 additions & 0 deletions lib/reports/specy/footer.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const React = require('react')
const importJSX = require('import-jsx')
const t = require('tap')
const {render} = require('ink-testing-library')
const Footer = importJSX('./footer.js')

// not too terribly much to do here!
const [pass, fail, skip, todo, total] = [1,1,1,1,4]
const r = render(<Footer
suiteCounts={{pass,fail,skip,todo,total}}
assertCounts={{pass,fail,skip,todo,total}}
time={123} />)
t.matchSnapshot(r.lastFrame())
77 changes: 77 additions & 0 deletions lib/reports/specy/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const React = require('react')
const importJSX = require('import-jsx')
const Base = importJSX('../base')
const Footer = importJSX('./footer.js')
const Log = importJSX('./log.js')
const Summary = importJSX('./summary.js')
const chalk = require('chalk')

const symbols = {
ok: '✓',
err: '✖',
}

const yaml = require('tap-yaml')

const getSymbol = r =>
r.ok ? chalk.green(symbols.ok)
: chalk.red(symbols.err)

class Specy extends Base {
tapResume () {}

constructor ({tap}) {
super({tap})
this.lastThing = null
this.tap = tap
}

componentWillMount () {
const tap = this.tap
const onAssert = p => r => {
const c = p.lastChild
p.lastChild = null
if (c && r.name === c.name)
return

const ind = new Array(p.level + 2).join(' ')
const raw = ind + getSymbol(r) +
' ' +
r.name + (
r.diag ? '\n' + yaml.stringify(r.diag).replace(/^/gm, ind) : '')

this.lastThing = 'assert'
this.setState(prevState => ({
...prevState,
log: prevState.log.concat({raw}),
}))
}
const onParser = p => {
if (p.parent)
p.parent.lastChild = p
p.on('child', onParser)
p.on('assert', onAssert(p))
const raw = (this.lastThing === 'assert' ? '\n' : '')
+ new Array(p.level + 1).join(' ') + p.name
this.lastThing = 'suite'
this.setState(prevState => ({
...prevState,
log: prevState.log.concat({raw}),
}))
}
onParser(tap.parser)
tap.resume()
}

get Log () {
return Log
}
get Footer () {
return Footer
}
get Summary () {
return Summary
}
}

module.exports = Specy
245 changes: 245 additions & 0 deletions lib/reports/specy/index.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
const importJSX = require('import-jsx')
const React = require('react')
const Report = importJSX('./index.js')
const t = require('tap')
const {Test} = t
const {render, cleanup} = require('ink-testing-library')

let tickCount = 1
Date.now = () => 1000 * tickCount
const tick = () => tickCount += 1

t.test('mostly good test run', async t => {
const tap = new Test({ name: 'TAP', jobs: 4, bail: false })
const r = render(<Report tap={tap} />)
t.matchSnapshot(r.lastFrame())

const tests = []
tap.test('zro', t => tests.push(t))
t.matchSnapshot(r.lastFrame())
tap.test('one', t => tests.push(t))
t.matchSnapshot(r.lastFrame())
tap.test('two', t => tests.push(t))
t.matchSnapshot(r.lastFrame())
tap.test('tre', t => tests.push(t))
t.matchSnapshot(r.lastFrame())
tap.test('for', t => tests.push(t))
t.matchSnapshot(r.lastFrame())
tap.test('fiv', t => tests.push(t))
t.matchSnapshot(r.lastFrame())
tap.test('six', t => tests.push(t))
t.matchSnapshot(r.lastFrame())
tap.test('svn', t => tests.push(t))
t.matchSnapshot(r.lastFrame())
tap.test('eit', t => tests.push(t))
t.matchSnapshot(r.lastFrame())
tap.test('nin', t => tests.push(t))
t.matchSnapshot(r.lastFrame())
tap.test('ten', t => tests.push(t))
t.matchSnapshot(r.lastFrame())

t.matchSnapshot(tests.map(t => t.name), 'current tests')

tests[0].fail('this is a failure')
tests[1].pass('give this one a pass')
tests[2].pass('give this two a pass')
tests[3].pass('give this tre a pass')
t.matchSnapshot(r.lastFrame())

// bump the time by a second
tick()

tests[0].end()
t.matchSnapshot(r.lastFrame())

t.matchSnapshot(tests.map(t => t.name), 'current tests')

tests[1].pass('give this one a pass')
tests[2].pass('give this two a pass')
tests[3].pass('give this tre a pass')

tests[1].pass('give this one a pass')
tests[2].pass('give this two a pass')
tests[3].pass('give this tre a pass')

// bump the time by a second
tick()
t.matchSnapshot(r.lastFrame())

tests.forEach(t => t.results || t.pass('this is fine'))
tests[2].end()
t.matchSnapshot(r.lastFrame())
t.matchSnapshot(tests.map(t => t.name), 'current tests')
tests[1].end()
t.matchSnapshot(r.lastFrame())
t.matchSnapshot(tests.map(t => t.name), 'current tests')

const opt = {}
tests[4].emit('preprocess', opt)
t.match(opt, { stdio: 'pipe' })

const Minipass = require('minipass')
const stderr = new Minipass()
tests[4].emit('process', {stderr})
stderr.write('this is some raw 2> stuff')
t.matchSnapshot(r.lastFrame())

tests[3].fail('do this later', { todo: 'at another time' })
tests[4].options.skip = 'skip this whole thing for now'
tests[4].end()
t.matchSnapshot(r.lastFrame())
t.matchSnapshot(tests.map(t => t.name), 'current tests')

tests[5].parser.write('this is definitely not tap\n')
tests[5].fail('hop over it', { skip: true })
tests[5].test('no function, just a todo')
t.matchSnapshot(r.lastFrame())

tests.forEach(t => t.results || t.pass('this is fine'))
tests.forEach(t => t.results || t.pass('still ok'))
tests.forEach(t => t.results || t.end())

tests[8].fail('fail but will be todo somehow')
tests[8].options.todo = true
tests[8].end()

tests[9].pass('this is fine')
tests[9].options.todo = true

t.matchSnapshot(r.lastFrame())
t.matchSnapshot(tests.map(t => t.name), 'current tests')

tap.end()

tests.forEach(t => t.results || t.pass('this is fine'))
tests.forEach(t => t.results || t.end())

t.matchSnapshot(r.lastFrame())

t.end()
cleanup()
})

t.test('bailout run', async t => {
const tap = new Test({ jobs: 4, name: 'TAP bailer', bail: true })

const r = render(<Report tap={tap} />)
t.matchSnapshot(r.lastFrame())

const tests = []
tap.test('zro', { bail: true }, t => tests.push(t))
tap.test('one', { bail: true }, t => tests.push(t))
tap.test('two', { bail: true }, t => tests.push(t))
tap.test('tre', { bail: true }, t => tests.push(t))
tap.test('for', { bail: true }, t => tests.push(t))
tap.test('fiv', { bail: true }, t => tests.push(t))

tests.forEach(t => t.results || t.pass('this is fine'))
// let the counter bouncer bounce
await new Promise(r => setTimeout(r, 200))

// bump the time by a second
tick()
t.matchSnapshot(r.lastFrame())
tests.forEach(t => t.results || t.pass('this is fine'))
await new Promise(r => setTimeout(r, 200))
tests[2].end()
tests[3].end()
t.matchSnapshot(r.lastFrame())

tests[0].end()
tests.forEach(t => t.results || t.pass('this is fine'))
await new Promise(r => setTimeout(r, 200))

tests[1].fail('not fine')
tests[4].fail('ton enif')
tests[4].end()

// subsequent bailout does nothing
tap.emit('bailout', 'this should not be shown')

t.matchSnapshot(r.lastFrame())

t.end()
cleanup()
})

t.test('weird root bailout', async t => {
const tap = new Test({ jobs: 4, name: 'TAP bailer' })

const r = render(<Report tap={tap} />)
t.matchSnapshot(r.lastFrame())

const tests = []
tap.test('zro', { bail: false }, t => tests.push(t))
tap.test('one', { bail: false }, t => tests.push(t))
tap.test('two', { bail: false }, t => tests.push(t))
tap.test('tre', { bail: false }, t => tests.push(t))
tap.test('for', { bail: false }, t => tests.push(t))
tap.test('fiv', { bail: false }, t => tests.push(t))
tap.test('six', { bail: false }, t => tests.push(t))
tap.test('svn', { bail: false }, t => tests.push(t))
tap.test('eit', { bail: false }, t => tests.push(t))
tap.test('nin', { bail: false }, t => tests.push(t))

tests.forEach(t => t.results || t.pass('this is fine'))
// let the counter bouncer bounce
await new Promise(r => setTimeout(r, 200))

// bump the time by a second
tick()
t.matchSnapshot(r.lastFrame())
tests.forEach(t => t.results || t.pass('this is fine'))
await new Promise(r => setTimeout(r, 200))
tests[2].end()
tests[3].end()
t.matchSnapshot(r.lastFrame())

tests[0].end()
tests.forEach(t => t.results || t.pass('this is fine'))
await new Promise(r => setTimeout(r, 200))

tests[1].fail('not fine')
tap.bailout('not fine')
tests[4].fail('ton enif')
tests[4].end()

// subsequent bailout does nothing
tap.emit('bailout', 'this should not be shown')

t.matchSnapshot(r.lastFrame())

t.end()
cleanup()
})

t.test('one at a time', async t => {
const tap = new Test({ jobs: 1 })

const r = render(<Report tap={tap} />)
t.matchSnapshot(r.lastFrame())

const tests = []
tap.test('zro', { buffered: false }, t => tests.push(t))
tap.test('one', { buffered: false }, t => tests.push(t))
tap.test('two', { buffered: false }, t => tests.push(t))
tap.test('tre', { buffered: false }, t => tests.push(t))
tap.test('for', { buffered: false }, t => tests.push(t))
tap.test('fiv', { buffered: false }, t => tests.push(t))
tap.end()

for (let i = 1; !tap.results; i++) {
// bump the time by a second
tick()
tests.forEach(t => t.results || t.pass('pass '+i))
await new Promise(r => setTimeout(r, 200))
t.matchSnapshot(r.lastFrame())
// bump the time by a second
tick()
tests.forEach(t => t.results || t.end())
t.matchSnapshot(r.lastFrame())
}

t.end()
cleanup()
})
11 changes: 11 additions & 0 deletions lib/reports/specy/log.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const React = require('react')
const importJSX = require('import-jsx')
const {Static} = require('ink')
const {Result} = importJSX('../base')

module.exports = ({log}) => (
<Static>{
log.filter(result => result.hasOwnProperty('raw'))
.map((result, i) => (<Result {...result} key={`${i}`} /> ))
}</Static>
)
13 changes: 13 additions & 0 deletions lib/reports/specy/log.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const importJSX = require('import-jsx')
const React = require('react')
const Log = importJSX('./log.js')
const t = require('tap')
const {render} = require('ink-testing-library')

const r = render(<Log log={[
{raw: 'hello'},
{result: {ok: true, name: 'this is fine', testName: 'foo'}},
{result: {ok: false, name: 'not fine', testName: 'bar'}},
]} />)

t.matchSnapshot(r.lastFrame())
Loading

0 comments on commit 18d8f34

Please sign in to comment.