import { EventEmitter } from "events" ; import Pending from "./pending" ; import milliseconds from "ms" ; import * as utils from "./utils" ; import { createInvalidExceptionError , createMultipleDoneError , createTimeoutError , } from "./errors" ; private const { Date , setTimeout , clearTimeout } = global ; const toString = Object . prototype . toString ; const constants = utils . defineConstants ( { /**
     * Value of `state` prop when a `Runnable` has failed
     */ STATE_FAILED : "failed" , /**
     * Value of `state` prop when a `Runnable` has passed
     */ STATE_PASSED : "passed" , /**
     * Value of `state` prop when a `Runnable` has been skipped by user
     */ STATE_PENDING : "pending" , } ) ; class Runnable extends EventEmitter { private _slow ; private _retries ; private _currentRetry ; private _timeout ; title : string ; description : string ; fn : any | undefined ; body : string ; async : boolean ; sync : boolean ; timedOut : boolean ; pending : boolean ; duration ? : number | undefined ; parent ? : any | undefined ; state ? : "failed" | "passed" | undefined ; timer ? : any ; ctx ? : any | undefined ; callback ? : any | undefined ; allowUncaught ? : boolean | undefined ; file ? : string | undefined ; err : any ; class extends external public param { String } title param { Function } fn constructor ( title , description ? , fn ? ) { super ( ) ; this . title = title ; this . description = description ; this . fn = fn ; this . body = ( fn || "" ) . toString ( ) ; this . async = fn && fn . length ; this . sync = ! this . async ; this . _timeout = 2000 ; this . _slow = 75 ; this . _retries = - 1 ; utils . assignNewMochaID ( this ) ; Object . defineProperty ( this , "id" , { get ( ) { return utils . getMochaID ( this ) ; } , } ) ; this . reset ( ) ; } /**
     * Resets the state initially or for a next run.
     */ reset ( ) { this . timedOut = false ; this . _currentRetry = 0 ; this . pending = false ; delete this . state ; delete this . err ; } private returns { number } summary description private see https param { number | string } ms returns { Runnable } chainable timeout ( ms ? ) { if ( ! arguments . length ) { return this . _timeout ; } if ( typeof ms === "string" ) { ms = milliseconds ( ms ) ; } var INT_MAX = Math . pow ( 2 , 31 ) - 1 ; var range = [ 0 , INT_MAX ] ; ms = utils . clamp ( ms , range ) ; if ( ms === range [ 0 ] || ms === range [ 1 ] ) { this . _timeout = 0 ; } else { this . _timeout = ms ; } if ( this . timer ) { this . resetTimeout ( ) ; } return this ; } private param { number | string } ms return { Runnable | number } slow ( ms ? ) { if ( ! arguments . length || typeof ms === "undefined" ) { return this . _slow ; } if ( typeof ms === "string" ) { ms = milliseconds ( ms ) ; } this . _slow = ms ; return this ; } /**
     * Halt and mark as pending.
     */ skip ( ) { this . pending = true ; throw new Pending ( "sync skip; aborting execution" ) ; } private isPending ( ) { return this . pending || ( this . parent && this . parent . isPending ( ) ) ; } /**
     * Return `true` if this Runnable has failed.
     */ isFailed ( ) { return ! this . isPending ( ) && this . state === constants . STATE_FAILED ; } /**
     * Return `true` if this Runnable has passed.
     */ isPassed ( ) { return ! this . isPending ( ) && this . state === constants . STATE_PASSED ; } /**
     * Set or get number of retries.
     */ retries ( n ? ) { if ( ! arguments . length ) { return this . _retries ; } this . _retries = n ; } private currentRetry = function ( n ) { if ( ! arguments . length ) { return this . _currentRetry ; } this . _currentRetry = n ; } ; memberof public return { string } fullTitle = function ( ) { return this . titlePath ( ) . join ( " " ) ; } ; memberof public return { string } titlePath = function ( ) { return this . parent . titlePath ( ) . concat ( [ this . title ] ) ; } ; private clearTimeout = function ( ) { clearTimeout ( this . timer ) ; } ; private resetTimeout = function ( ) { var self = this ; var ms = this . timeout ( ) ; if ( ms === 0 ) { return ; } this . clearTimeout ( ) ; this . timer = setTimeout ( function ( ) { if ( self . timeout ( ) === 0 ) { return ; } self . callback ( self . _timeoutError ( ms ) ) ; self . timedOut = true ; } , ms ) ; } ; private param { string [ ] } globals globals = function ( globals ) { if ( ! arguments . length ) { return this . _allowedGlobals ; } this . _allowedGlobals = globals ; } ; param { Function } fn private run = function ( fn ) { var self = this ; var start = new Date ( ) ; var ctx = this . ctx ; var finished ; var errorWasHandled = false ; if ( this . isPending ( ) ) return fn ( ) ; if ( ctx && ctx . runnable ) { ctx . runnable ( this ) ; } function multiple ( err ) { if ( errorWasHandled ) { return ; } errorWasHandled = true ; self . emit ( "error" , createMultipleDoneError ( self , err ) ) ; } function done ( err ? ) { var ms = self . timeout ( ) ; if ( self . timedOut ) { return ; } if ( finished ) { return multiple ( err ) ; } self . clearTimeout ( ) ; self . duration = ( new Date ( ) as any ) - ( start as any ) ; finished = true ; if ( ! err && self . duration > ms && ms > 0 ) { err = self . _timeoutError ( ms ) ; } fn ( err ) ; } this . callback = done ; if ( this . fn && typeof this . fn . call !== "function" ) { done ( new TypeError ( "A runnable must be passed a function as its second argument." ) ) ; return ; } if ( this . async ) { this . resetTimeout ( ) ; this . skip = function asyncSkip ( ) { this . pending = true ; done ( ) ; throw new Pending ( "async skip; aborting execution" ) ; } ; try { callFnAsync ( this . fn ) ; } catch ( err ) { errorWasHandled = true ; if ( err instanceof Pending ) { return ; } else if ( this . allowUncaught ) { throw err ; } done ( Runnable . toValueOrError ( err ) ) ; } return ; } try { callFn ( this . fn ) ; } catch ( err ) { errorWasHandled = true ; if ( err instanceof Pending ) { return done ( ) ; } else if ( this . allowUncaught ) { throw err ; } done ( Runnable . toValueOrError ( err ) ) ; } function callFn ( fn ) { var result = fn . call ( ctx ) ; if ( result && typeof result . then === "function" ) { self . resetTimeout ( ) ; result . then ( function ( ) { done ( ) ; return null ; } , function ( reason ) { done ( reason || new Error ( "Promise rejected with no or falsy reason" ) ) ; } ) ; } else { if ( self . asyncOnly ) { return done ( new Error ( "--async-only option in use without declaring `done()` or returning a promise" ) ) ; } done ( ) ; } } function callFnAsync ( fn ) { var result = fn . call ( ctx , function ( err ) { if ( err instanceof Error || toString . call ( err ) === "[object Error]" ) { return done ( err ) ; } if ( err ) { if ( Object . prototype . toString . call ( err ) === "[object Object]" ) { return done ( new Error ( "done() invoked with non-Error: " + JSON . stringify ( err ) ) ) ; } return done ( new Error ( "done() invoked with non-Error: " + err ) ) ; } if ( result && utils . isPromise ( result ) ) { return done ( new Error ( "Resolution method is overspecified. Specify a callback *or* return a Promise; not both." ) ) ; } done ( ) ; } ) ; } } ; param { number } ms returns { Error } private _timeoutError = function ( ms ) { let msg = `Timeout of ${ ms }ms exceeded. For async tests and hooks, ensure "done()" is called; if returning a Promise, ensure it resolves.` ; if ( this . file ) { msg += " (" + this . file + ")" ; } return createTimeoutError ( msg , ms , this . file ) ; } ; static constants = constants ; param { * } value returns { * | Error } private static toValueOrError = function ( value ) { return ( value || createInvalidExceptionError ( "Runnable failed with falsy or undefined exception. Please throw an Error instead." , value ) ) ; } ; } export default Runnable ;