import * as Path from 'path' ; import { flatten , get , omit , trimStart } from 'lodash' ; import { File } from '@main/file' ; import { Status , parseStatus } from '@lib/frameworks/status' ; import { IFramework } from '@lib/frameworks/framework' ; import { ITest , ITestResult , Test } from '@lib/frameworks/test' ; import { Nugget } from '@lib/frameworks/nugget' ; export interface ISuite extends Nugget { readonly file : string ; getId ( ) : string ; getFile ( ) : string ; getFilePath ( ) : string ; getRelativePath ( ) : string ; getDisplayName ( ) : string ; getStatus ( ) : Status ; getNuggetIds ( selective ) : Array < string > ; getMeta ( ) : any ; resetMeta ( ) : void ; getConsole ( ) : Array < any > | null ; getFramework ( ) : IFramework ; testsLoaded ( ) : boolean ; rebuildTests ( result ) : Promise < void > ; canBeOpened ( ) : boolean ; canToggleTests ( ) : boolean ; toggleSelected ( toggle ? , cascade ? ) : Promise < void > ; toggleExpanded ( toggle ? , cascade ? ) : Promise < void > ; debrief ( result , selective ) : Promise < void > ; render ( status ? ) : ISuiteResult ; persist ( status ? ) : ISuiteResult ; setFresh ( fresh ) : void ; isFresh ( ) : boolean ; countChildren ( ) : number ; hasChildren ( ) : boolean ; contextMenu ( ) : Array < Electron . MenuItemConstructorOptions > ; getRunningOrder ( ) : number | null ; } export interface ISuiteResult { file : string ; status ? : Status ; tests ? : Array < ITestResult > ; meta ? : object | null ; console ? : Array < any > ; testsLoaded ? : boolean ; hasChildren ? : boolean ; selected ? : boolean ; partial ? : boolean ; relative ? : string ; } export class Suite extends Nugget implements ISuite { public file ! : string ; protected result ! : ISuiteResult ; constructor ( framework , result ) { super ( framework ) ; this . build ( result ) ; } param status public render ( status = 'idle' ) { return { file : this . file , meta : this . getMeta ( ) , hasChildren : this . testsLoaded ( ) && this . hasChildren ( ) , selected : this . selected , partial : this . partial , relative : this . getRelativePath ( ) } ; } param status public persist ( status = 'idle' ) { return omit ( { ... this . render ( ) , testsLoaded : this . testsLoaded ( ) , tests : this . bloomed ? this . tests . map ( ( test ) => test . persist ( status ) ) : this . getTestResults ( ) . map ( ( test ) => this . defaults ( test , status ) ) } , [ 'hasChildren' , 'selected' , 'partial' , 'relative' ] ) ; } param result protected build ( result ) { this . file = result . file ; this . result = result ; if ( this . expanded ) { this . bloom ( ) . then ( ( ) => { this . updateStatus ( ) ; } ) ; } else { this . updateStatus ( ) ; } } param result public async rebuildTests ( result ) { this . tests = ( result . tests || [ ] ) . map ( ( result ) => { return this . makeTest ( result , false ) ; } ) ; if ( ! this . expanded ) { await this . wither ( ) ; } this . updateStatus ( ) ; } /**
     * Whether the suite's file can be opened
     */ public canBeOpened ( ) { return File . isSafe ( this . getFilePath ( ) ) && File . exists ( this . getFilePath ( ) ) ; } /**
     * Whether the suite can run tests selectively.
     */ public canToggleTests ( ) { return this . framework . canToggleTests ; } param result protected newTest ( result ) { return new Test ( this . framework , result ) ; } 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 ) ; if ( to === 'empty' && ! this . testsLoaded ( ) ) { to = 'idle' ; } } const from = this . getStatus ( ) ; if ( to !== from ) { this . framework . setNuggetStatus ( this . getId ( ) , to , from , true ) ; } } /**
     * Get this suite's id.
     */ public getId ( ) { return this . file ! ; } /**
     * Get this suite's file.
     */ public getFile ( ) { return this . file ! ; } /**
     * Get this suite's local file path, regardless of running remotely.
     */ public getFilePath ( ) { if ( ! this . framework . runsInRemote ) { return this . file ; } return Path . join ( this . framework . fullPath , Path . relative ( Path . join ( this . framework . getRemotePath ( ) , this . framework . path ) , this . file ) ) ; } /**
     * Get this suite's relative file path.
     */ public getRelativePath ( ) { return this . framework . runsInRemote && ( ! this . framework . getRemotePath ( ) || this . framework . getRemotePath ( ) === '/' ) ? trimStart ( this . file , '/' ) : Path . relative ( this . framework . runsInRemote ? ( Path . join ( this . framework . getRemotePath ( ) , this . framework . path ) ) : this . framework . fullPath , this . file ) ; } /**
     * Get this suite's display name.
     */ public getDisplayName ( ) { return this . getRelativePath ( ) ; } /**
     * Get this suite's status.
     */ public getStatus ( ) { if ( super . getStatus ( ) === 'empty' && ! this . testsLoaded ( ) ) { return 'idle' ; } return super . getStatus ( ) ; } /**
     * Get the ids of this suite and the nuggets it is supposed to run.
     */ public getNuggetIds ( selective ) { return flatten ( [ this . getId ( ) , ... ( selective && this . canToggleTests ( ) ? this . tests . filter ( test => test . selected ) . map ( ( test ) => this . getRecursiveNuggetIds ( test . getResult ( ) ) ) : ( this . bloomed ? this . tests . map ( ( test ) => this . getRecursiveNuggetIds ( test . getResult ( ) ) ) : ( this . result . tests || [ ] ) . map ( ( test ) => { return this . getRecursiveNuggetIds ( test ) ; } ) ) ) ] ) ; } /**
     * Get metadata for this suite.
     */ public getMeta ( key ? , fallback ? ) { if ( ! key ) { return this . result . meta || { } ; } return ! this . result . meta ? fallback : get ( this . result . meta ! , key , fallback ) ; } /**
     * Reset metadata for this suite.
     */ public resetMeta ( ) { if ( this . result . meta ) { this . result . meta = null ; } } /**
     * Get this suite's console output.
     */ public getConsole ( ) { return this . result . console && this . result . console . length ? this . result . console : null ; } /**
     * Get this suites's parent framework.
     */ public getFramework ( ) { return this . framework ; } /**
     * Get this nugget's running order, if any.
     */ public getRunningOrder ( ) { return this . getMeta ( 'n' , null ) ; } /**
     * Whether this suite's tests are loaded.
     */ public testsLoaded ( ) { return this . result . testsLoaded !== false ; } param result param cleanup public async debrief ( result , cleanup ) { const emit = ! this . testsLoaded ( ) ; this . file = result . file ; this . result . meta = result . meta ; this . result . console = result . console ; this . result . testsLoaded = result . testsLoaded ; return new Promise ( ( resolve , reject ) => { this . bloom ( ) . then ( ( ) => { this . debriefTests ( result . tests || [ ] , cleanup ) . then ( ( ) => { if ( emit ) { this . emitToRenderer ( `${ this . getId ( ) }:children` , this . testsLoaded ( ) && this . hasChildren ( ) ) ; } resolve ( ) ; } ) ; } ) ; } ) ; } }