UNPKG

undo3d-run-test

Version:

A collection of utilities for testing Undo3D, and apps built with Undo3D

174 lines (129 loc) 5.47 kB
//// 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 } }