undo3d-run-test
Version:
A collection of utilities for testing Undo3D, and apps built with Undo3D
174 lines (129 loc) • 5.47 kB
JavaScript
//// LevelSuite
////
//// Test suites are the second level of a test runner. They contain test cases.
import Level from './level.mjs'
import LevelCase from './level-case.mjs'
//// CLASS
export default class LevelSuite extends Level {
constructor (options={}) {
const { PARALLEL, SEQUENTIAL } = options
super(options)
//// Set the `_isParallel` boolean.
if (PARALLEL && SEQUENTIAL)
throw Error('options.PARALLEL and options.SEQUENTIAL are both set')
this._isParallel = !! PARALLEL
//// An array of LevelCases.
this._testCases = []
}
//// PUBLIC PROPERTIES
//// Xx.
get isParallel () { return this._isParallel }
get dash () { return this._isParallel ? '=' : '-' }
//// Together, `first` and `last` make the TAP ‘plan’.
get tapPlan () {
const max = this._testCases.length
return `${Math.min(max,this.last,this.first)}..${Math.min(max,this.last)}`
}
//// The test results, as a TAP string (read-only).
get tap () {
//// A skipped suite does not show its test cases.
if (this.skip) return [
this.tapTestLine + ' {' // eg 'pending 77 - DB is responding {'
, ` ${this.tapPlan} # Skipped: ${this.skip}`
, '}'
]
//// Xx.
return [
this.tapTestLine + ' {' // eg 'pending 77 - DB is responding {'
, ` ${this.tapPlan}` // eg '1..300'
].concat(
this._testCases.map( testCase => ' ' + testCase.tap )
, '}'
)
}
//// PUBLIC METHODS
//// Create one or more new test cases, based on one or more arguments.
add (...args) {
//// Transform the arguments into an array of `options` objects.
const optionses = []
let tally = this._testCases.length
for (let i=0; i<args.length; i++) {
const options = {
hub: this._hub
, index: tally + 1
, suite: this
}
//// Deal with a title/body pair.
if ('string' === typeof args[i] && 'function' === typeof args[i+1]) {
options.title = args[i]
options.body = args[i+1]
i++
//// Deal with just a test body on its own.
} else if ('function' === typeof args[i])
options.body = args[i]
//// Anything else is a mistake.
else throw Error(`Unexpected '${typeof args[i]}' argument #${i}`)
////@TODO display the following skip-message on the TAP line
// console.log(this.first, this.last, tally+1, (this.first > tally+1 || this.last < tally+1) );
if (this.first > tally+1 || this.last < tally+1)
options.skip = `Test case ${tally+1} is outside ${this.tapPlan}`
optionses.push(options)
tally++
}
//// Instantiate and record a test case for each `options` object.
for (const options of optionses)
this._testCases.push( new LevelCase(options) )
}
//// Run all test cases.
async run (parcel) {
const { hub, isParallel, before, after, beforeEach, afterEach } = this
//// Potentially don’t run this suite.
if (this.skip) return //@TODO fire an event?
//// Update the suite status and fire a 'start' event.
this._status = 'running'
hub.fire('suite-start suite-update', this.index)
//// Run `before()`.
before(parcel)
//// Run test cases...
async function runTestCases (testCases) {
//// ...in parallel... @TODO guard against too many threads?
if (isParallel) {
const promises = testCases.map( testCase => {
if ('# skip' === testCase.status) return
beforeEach(parcel)
const promise = testCase.run(parcel)
afterEach(parcel)
//// Update the suite status and fire an 'after' event.
promise.then( testCase => {
if ('ok' !== testCase.status) this._status = 'failing'
hub.fire('suite-update', this.index)
console.log(testCase.status, this._status);
})
return promise
// }).map( promise => {
// if (promise) promise.then( testCase => {
// if ('ok' !== testCase.status) this._status = 'failing'
// })
})
await Promise.all(promises)
//// ...or sequentially.
} else {
for (const testCase of testCases) {
if ('# skip' === testCase.status) continue
beforeEach(parcel)
await testCase.run(parcel)
afterEach(parcel)
if ('ok' !== testCase.status) this._status = 'failing'
}
}
}
await runTestCases.call(this, this._testCases)
//// Run `after()`.
after(parcel)
//// Update the suite status and fire an 'end' event.
this._status = 'failing' === this._status ? 'not ok' : 'ok'
hub.fire('suite-end suite-update', this.index)
//// The returned `this` is used by LevelRunner::run()::runTestSuites().
return this
}
}