import { debounce , find , flatten , isArray , pickBy } from 'lodash' ; import { ProjectEventEmitter } from '@lib/frameworks/emitter' ; import { IFramework } from '@lib/frameworks/framework' ; import { ISuiteResult } from '@lib/frameworks/suite' ; import { ITest , ITestResult } from '@lib/frameworks/test' ; import { Status , parseStatus } from '@lib/frameworks/status' ; /**
 * Nuggets are the testable elements inside a repository
 * (i.e. either a suite or a test).
 */ export abstract class Nugget extends ProjectEventEmitter { public tests : Array < ITest > = [ ] ; public selected = false ; public expanded = false ; public partial = false ; protected framework : IFramework ; protected result ? : any ; protected fresh = false ; protected bloomed = false ; protected updateCountsListener : _ . DebouncedFunc < ( nugget : Nugget , toggle : boolean ) => Promise < void > > ; constructor ( framework ) { super ( framework . getApplicationWindow ( ) ) ; this . framework = framework ; this . updateCountsListener = debounce ( this . updateSelectedCounts . bind ( this ) , 50 ) ; } /**
     * Get the nugget's id.
     */ public abstract getId ( ) ; param status public abstract render ( status ? ) ; param result protected abstract newTest ( result ) ; /**
     * Get this nugget's tests' results, if any.
     */ public getTestResults ( ) { return this . result . tests || [ ] ; } /**
     * Count this nugget's children.
     */ public countChildren ( ) { return this . bloomed ? this . tests . length : this . getTestResults ( ) . length ; } /**
     * Whether this nugget has children.
     */ public hasChildren ( ) { return this . countChildren ( ) > 0 ; } param result param status protected defaults ( result , status = 'idle' ) { return ( pickBy ( { id : result . id , name : result . name , displayName : result . displayName !== result . name ? result . displayName : null , status : status || result . status , feedback : result . feedback , console : result . console , params : result . params , stats : result . stats , tests : ( result . tests || [ ] ) . map ( ( test ) => this . defaults ( test , status ) ) } , property => { return isArray ( property ) ? property . length : ! ! property ; } ) as any ) ; } param id public findTest ( id ) { return find ( this . tests , test => test . getId ( ) === id ) ; } param result param force protected makeTest ( result , force = false ) { let test = force ? false : this . findTest ( result . id ) ; if ( ! test ) { test = this . newTest ( result ) ; test . on ( 'selected' , this . updateCountsListener ) ; test . selected = this . selected ; this . tests . push ( test ) ; } return test ; } /**
     * Trigger an update of this nugget's selected count.
     */ protected async updateSelectedCounts ( ) { const total = this . tests . length ; const selectedChildren = this . tests . filter ( test => test . selected ) . length ; const partial = selectedChildren > 0 && total > 0 && total > selectedChildren ; if ( this . partial !== partial ) { this . partial = partial ; this . emit ( 'selective' , this ) ; this . emitToRenderer ( `${ this . getId ( ) }:selective` , this . render ( ) ) ; } if ( selectedChildren && ! this . selected ) { this . toggleSelected ( true , false ) ; } else if ( ! selectedChildren && this . selected ) { this . toggleSelected ( false , false ) ; } } param tests param cleanup protected async debriefTests ( tests , cleanup ) { return new Promise ( ( resolve , reject ) => { const isLast = tests . map ( test => typeof test . isLast === 'undefined' ? - 1 : Number ( test . isLast ) ) . reduce ( ( accumulator , value ) => accumulator + value , 0 ) ; if ( isLast >= 1 ) { cleanup = true ; } if ( isLast === 0 ) { cleanup = false ; } Promise . all ( tests . map ( ( result ) => { return this . makeTest ( result ) . debrief ( result , cleanup ) ; } ) ) . then ( ( ) => { this . afterDebrief ( cleanup ) . then ( ( ) => { resolve ( ) ; } ) ; } ) ; } ) ; } param cleanup protected async afterDebrief ( cleanup ) { if ( cleanup ) { this . cleanTestsByStatus ( 'queued' ) ; if ( ! this . expanded ) { await this . wither ( ) ; } } this . updateStatus ( ) ; if ( this . expanded ) { this . emitTestsToRenderer ( ) ; } } param status protected cleanTestsByStatus ( status ) { this . tests = this . tests . filter ( test => { return test . getStatus ( ) !== status ; } ) ; } param result protected getRecursiveStatus ( result ) { if ( ! result . status ) { result . status = parseStatus ( ( result . tests || [ ] ) . map ( ( test ) => this . getRecursiveStatus ( test ) ) ) ; } return result . status ; } param to protected updateStatus ( to ? ) { if ( typeof to === 'undefined' ) { const statuses = this . bloomed ? this . tests . map ( ( test ) => test . getStatus ( ) ) : this . getTestResults ( ) . map ( ( test ) => this . framework . getNuggetStatus ( test . id ) ) ; to = parseStatus ( statuses ) ; } const from = this . getStatus ( ) ; if ( to !== from ) { this . framework . setNuggetStatus ( this . getId ( ) , to , from , false ) ; } } /**
     * Get this nugget's status.
     */ public getStatus ( ) { return this . framework . getNuggetStatus ( this . getId ( ) ) ; } /**
     * Whether the nugget can run tests selectively.
     */ public canToggleTests ( ) { return false ; } param toggle param cascade public async toggleSelected ( toggle ? , cascade ? ) { this . selected = typeof toggle === 'undefined' ? ! this . selected : toggle ; if ( this . selected ) { await this . bloom ( ) ; } else if ( ! this . expanded ) { await this . wither ( ) ; } if ( this . canToggleTests ( ) && cascade !== false ) { this . tests . forEach ( test => { test . toggleSelected ( this . selected , true ) ; } ) ; } this . emit ( 'selected' , this , toggle ) ; this . emitToRenderer ( `${ this . getId ( ) }:selected` , this . render ( ) , toggle ) ; } param toggle param cascade public async toggleExpanded ( toggle ? , cascade ? ) { this . expanded = typeof toggle === 'undefined' ? ! this . expanded : toggle ; if ( this . expanded ) { await this . bloom ( ) ; } else { await this . wither ( ) ; } if ( cascade !== false ) { this . tests . forEach ( test => { test . toggleExpanded ( this . expanded ) ; } ) ; } } /**
     * Make the test objects nested to this nugget.
     */ protected async bloom ( ) { if ( this . bloomed ) { return ; } return new Promise ( ( resolve , reject ) => { this . tests = this . getTestResults ( ) . map ( ( result ) => { return this . makeTest ( result , true ) ; } ) ; this . bloomed = true ; resolve ( ) ; } ) ; } /**
     * Destroy the test objects nested to this nugget, leaving only the static
     * JSON structure with which to build them again.
     */ protected async wither ( ) { if ( ! this . bloomed ) { return ; } if ( this . selected ) { return ; } this . result . tests = this . tests . map ( ( test ) => test . persist ( false ) ) ; this . tests = [ ] ; this . bloomed = false ; } param fresh public setFresh ( fresh ) { this . fresh = fresh ; } /**
     * Get the freshness state of a nugget.
     */ public isFresh ( ) { return this . fresh ; } /**
     * Send all tests to renderer process.
     */ public async emitTestsToRenderer ( ) { if ( ! this . bloomed ) { await this . bloom ( ) ; } this . emitToRenderer ( `${ this . getId ( ) }:framework-tests` , this . tests . map ( ( test ) => test . render ( false ) ) ) ; } /**
     * Get the ids of this nuggets and its children.
     */ protected getRecursiveNuggetIds ( result ) { return flatten ( [ result . id , ... ( result . tests || [ ] ) . map ( ( test ) => { return this . getRecursiveNuggetIds ( test ) ; } ) ] ) ; } /**
     * A chance for nuggets to append items to the context menus of
     * their representation in the renderer.
     */ public contextMenu ( ) { return [ ] ; } }