author Santo Pfingsten
 *  see https import { assert } from "chai" ; import { Suite } from "mocha" ; export interface SpyData { ( ... args ) : any ; callCount : number ; thisValues : any [ ] ; args : any [ ] [ ] ; assertCalls : ( args : any [ ] , thisValues ? : any [ ] ) => void ; assertNoCall : ( ) => void ; reset : ( ) => void ; } export function createSpy ( wrappedFunction ? ) { const spyData = function ( ... args ) { spyData . callCount ++ ; spyData . thisValues . push ( this ) ; spyData . args . push ( Array . from ( args ) ) ; if ( wrappedFunction ) { return wrappedFunction . apply ( this , args ) ; } } as SpyData ; spyData . callCount = 0 ; spyData . thisValues = [ ] ; spyData . args = [ ] ; spyData . assertCalls = ( args , thisValues ? ) => { assert . deepEqual ( spyData . args , args ) ; if ( thisValues ) assert . deepEqual ( spyData . thisValues , thisValues ) ; spyData . reset ( ) ; } ; spyData . assertNoCall = ( ) => { assert . strictEqual ( spyData . callCount , 0 ) ; } ; spyData . reset = ( ) => { spyData . callCount = 0 ; spyData . thisValues . length = 0 ; spyData . args . length = 0 ; } ; return spyData ; } export function spyOn < T > ( instance , method ) { const original = instance [ method ] ; assert . isFunction ( original ) ; const spy = createSpy ( original as any ) ; instance [ method ] = spy as any ; return spy ; } export const clone = ( value ) => JSON . parse ( JSON . stringify ( value ) ) ; export function ensureNotNull < T > ( value ) { assert . isNotNull ( value ) ; return value ; } export function doneHandler < T extends Function > ( handler , done , doneCondition ? ) { return ( ... args ) => { try { handler . apply ( null , args ) ; if ( ! doneCondition || doneCondition ( ) ) done ( ) ; } catch ( e ) { done ( e ) ; } } ; } export function sleep ( ms ) { return new Promise ( ( resolve ) => setTimeout ( resolve , ms ) ) ; } function booleanVariations ( count ) { const result = [ ] ; const size = Math . pow ( 2 , count ) ; for ( let i = 0 ; i < size ; i ++ ) { const entry = i . toString ( 2 ) . split ( "" ) . map ( ( b ) => b === "0" ? false : true ) ; while ( entry . length !== count ) entry . unshift ( false ) ; result . push ( entry ) ; } return result ; } function getArgs ( func ) { const match = func . toString ( ) . match ( /.*\(([^)]*)\)/ ) ; if ( ! match ) throw new Error ( "Can't detect argument names for function" ) ; return match [ 1 ] . split ( "," ) . map ( ( arg ) => arg . replace ( /\/\*.*\*\// , "" ) . trim ( ) ) . filter ( ( arg ) => arg ) ; } export interface SimpleSuiteFunction < T > { ( callback ) : void ; only : ( callback : T ) => void ; } function createSimpleSuiteFunction < T > ( wrapper ) { const result = ( callback ) => wrapper ( context , callback ) ; result . only = ( callback ) => wrapper ( context . only , callback ) ; return result ; } export const booleanContext = createSimpleSuiteFunction < ( ... value : boolean [ ] ) => void > ( ( context , callback ) => { const names = getArgs ( callback ) ; booleanVariations ( names . length ) . forEach ( ( booleans ) => { const label = "with " + booleans . map ( ( value , index ) => `${ names [ index ] } = ${ value }` ) . join ( ", " ) ; context ( label , ( ) => callback . apply ( null , booleans ) ) ; } ) ; } ) ; type ContextWithResultRow < CT , RT > = { context : CT ; result : RT ; } ; export function contextWithResult < CT , RT > ( name , rows , callback ) { rows . forEach ( ( row ) => context ( `with ${ name } = ${ row . context }` , ( ) => callback ( row . context , row . result ) ) ) ; } export namespace contextWithResult { export function only < CT , RT > ( name , rows , callback ) { rows . forEach ( ( row ) => context . only ( `with ${ name } = ${ row . context }` , ( ) => callback ( row . context , row . result ) ) ) ; } }