import * as vscode from 'vscode' ; import { TestController , TestAdapter } from 'vscode-test-adapter-api' ; import { TestCollection } from './tree/testCollection' ; import { TreeNode } from './tree/treeNode' ; import { ErrorNode } from './tree/errorNode' ; import { IconPaths } from './iconPaths' ; import { TreeEventDebouncer } from './treeEventDebouncer' ; import { Decorator } from './decorator' ; import { pickNodes , findLineContaining , getAdapterIds } from './util' ; import { SortSetting } from './tree/sort' ; import { TestNode } from './tree/testNode' ; import { TestSuiteNode } from './tree/testSuiteNode' ; export type HideWhenSetting = 'never' | 'noAdapters' | 'noTests' ; export class TestExplorer implements TestController , vscode . TreeDataProvider < TreeNode | ErrorNode > , vscode . CodeLensProvider , vscode . HoverProvider { public disabled = false ; public hideWhen : HideWhenSetting ; public readonly iconPaths : IconPaths ; public readonly decorator : Decorator ; public readonly treeEvents : TreeEventDebouncer ; private readonly outputChannel : vscode . OutputChannel ; private nodesShownInOutputChannel : undefined | { collection : TestCollection ; ids : string [ ] ; } ; private readonly treeDataChanged = new vscode . EventEmitter < TreeNode > ( ) ; public readonly onDidChangeTreeData : vscode . Event < TreeNode > ; public readonly codeLensesChanged = new vscode . EventEmitter < void > ( ) ; public readonly onDidChangeCodeLenses : vscode . Event < void > ; public readonly collections = new Map < TestAdapter , TestCollection > ( ) ; private readonly loadingCollections = new Set < TestCollection > ( ) ; private readonly runningCollections = new Set < TestCollection > ( ) ; private lastTestRun ? : [ TestCollection , string [ ] ] ; constructor ( public readonly context ) { this . iconPaths = new IconPaths ( context ) ; this . decorator = new Decorator ( context , this ) ; this . treeEvents = new TreeEventDebouncer ( this . collections , this . treeDataChanged ) ; this . outputChannel = vscode . window . createOutputChannel ( "Test Explorer" ) ; context . subscriptions . push ( this . outputChannel ) ; this . onDidChangeTreeData = this . treeDataChanged . event ; this . onDidChangeCodeLenses = this . codeLensesChanged . event ; } registerTestAdapter ( adapter ) { this . collections . set ( adapter , new TestCollection ( adapter , this ) ) ; this . updateVisibility ( ) ; } unregisterTestAdapter ( adapter ) { const collection = this . collections . get ( adapter ) ; if ( collection ) { collection . dispose ( ) ; this . collections . delete ( adapter ) ; this . decorator . updateAllDecorations ( ) ; this . treeEvents . sendTreeChangedEvent ( ) ; this . codeLensesChanged . fire ( ) ; this . updateVisibility ( ) ; } } getTreeItem ( node ) { return node . getTreeItem ( ) ; } getChildren ( node ? ) { if ( node ) { if ( ( node instanceof TestSuiteNode ) && node . isMergedNode ) { return ( [ ] as TreeNode [ ] ) . concat ( ... node . children . map ( child => child . children ) ) ; } return node . children ; } else { const nonEmptyCollections = [ ... this . collections . values ( ) ] . filter ( ( collection ) => ( ( collection . suite !== undefined ) || ( collection . error !== undefined ) ) ) ; if ( nonEmptyCollections . length === 0 ) { return [ ] ; } else if ( nonEmptyCollections . length === 1 ) { const collection = nonEmptyCollections [ 0 ] ; if ( collection . suite ) { return collection . suite . children ; } else { return [ collection . error ! ] ; } } else { return nonEmptyCollections . map ( collection => ( collection . suite || collection . error ) ! ) ; } } } getParent ( node ) { return ( < any > node ) . parent ; } reload ( node ? ) { if ( node ) { node . collection . adapter . load ( ) ; } else { for ( const adapter of this . collections . keys ( ) ) { try { adapter . load ( ) ; } catch ( err ) { } } } } async run ( nodes ? , pick = true ) { this . lastTestRun = undefined ; if ( nodes ) { const nodesToRun = pick ? await pickNodes ( nodes ) : nodes ; if ( nodesToRun . length > 0 ) { this . lastTestRun = [ nodesToRun [ 0 ] . collection , getAdapterIds ( nodesToRun ) ] ; nodesToRun [ 0 ] . collection . adapter . run ( getAdapterIds ( nodesToRun ) ) ; } } else { for ( const collection of this . collections . values ( ) ) { if ( collection . suite ) { try { collection . adapter . run ( collection . suite . adapterIds ) ; } catch ( err ) { } } } } } rerun ( ) { if ( this . lastTestRun ) { const collection = this . lastTestRun [ 0 ] ; const testIds = this . lastTestRun [ 1 ] ; return collection . adapter . run ( testIds ) ; } return Promise . resolve ( ) ; } async debug ( nodes ? , pick = true ) { this . lastTestRun = undefined ; if ( nodes ) { const nodesToRun = pick ? await pickNodes ( nodes ) : nodes ; if ( ( nodesToRun . length > 0 ) && nodesToRun [ 0 ] . collection . adapter . debug ) { try { this . lastTestRun = [ nodesToRun [ 0 ] . collection , getAdapterIds ( nodesToRun ) ] ; await nodesToRun [ 0 ] . collection . adapter . debug ( getAdapterIds ( nodesToRun ) ) ; } catch ( e ) { vscode . window . showErrorMessage ( `Error while debugging test: ${ e }` ) ; return ; } } } else { for ( const collection of this . collections . values ( ) ) { if ( collection . suite && collection . adapter . debug ) { try { await collection . adapter . debug ( collection . suite . adapterIds ) ; } catch ( e ) { vscode . window . showErrorMessage ( `Error while debugging test: ${ e }` ) ; return ; } } } } } redebug ( ) { if ( this . lastTestRun ) { const collection = this . lastTestRun [ 0 ] ; const testIds = this . lastTestRun [ 1 ] ; return collection . adapter . debug ! ( testIds ) ; } return Promise . resolve ( ) ; } cancel ( ) { for ( const adapter of this . collections . keys ( ) ) { try { adapter . cancel ( ) ; } catch ( err ) { } } ; } showLog = ( function ( ) { let lastCalled = new Date ( ) ; function showLog ( this , nodes ) { const dateDiff = < any > new Date ( ) - < any > lastCalled ; lastCalled = new Date ( ) ; if ( dateDiff < 250 ) { this . showSource ( nodes [ 0 ] ) ; return ; } if ( nodes . length > 0 ) { this . nodesShownInOutputChannel = { collection : nodes [ 0 ] . collection , ids : nodes . map ( node => node . info . id ) } ; } else { this . nodesShownInOutputChannel = undefined ; } this . updateLog ( ) ; } return showLog ; } ) ( ) ; showError ( message ) { this . outputChannel . clear ( ) ; this . nodesShownInOutputChannel = undefined ; if ( message ) { this . outputChannel . append ( message ) ; this . outputChannel . show ( true ) ; } else { this . outputChannel . hide ( ) ; } } async showSource ( node ) { const fileUri = node . fileUri ; if ( fileUri ) { const document = await vscode . workspace . openTextDocument ( vscode . Uri . parse ( fileUri ) ) ; let line = node . line ; if ( line === undefined ) { line = findLineContaining ( node . info . label , document . getText ( ) ) ; } const range = ( line !== undefined ) ? new vscode . Range ( line , 0 , line , 0 ) : undefined ; const editor = await vscode . window . showTextDocument ( document , { selection : range } ) ; if ( range !== undefined ) { editor . revealRange ( range . with ( new vscode . Position ( Math . max ( line ! - 1 , 0 ) , 0 ) ) , vscode . TextEditorRevealType . AtTop ) ; } } } setAutorun ( node ? ) { if ( ( node instanceof TestNode ) || ( node instanceof TestSuiteNode ) ) { node . collection . setAutorun ( node ) ; } else { for ( const collection of this . collections . values ( ) ) { collection . setAutorun ( collection . suite ) ; } } } clearAutorun ( node ? ) { if ( ( node instanceof TestNode ) || ( node instanceof TestSuiteNode ) ) { node . collection . setAutorun ( undefined ) ; } else { for ( const collection of this . collections . values ( ) ) { collection . setAutorun ( undefined ) ; } } } retireState ( node ? ) { if ( ( node instanceof TestNode ) || ( node instanceof TestSuiteNode ) ) { node . collection . retireState ( node ) ; } else { for ( const collection of this . collections . values ( ) ) { collection . retireState ( ) ; } } } resetState ( node ? ) { if ( ( node instanceof TestNode ) || ( node instanceof TestSuiteNode ) ) { node . collection . resetState ( node ) ; } else { for ( const collection of this . collections . values ( ) ) { collection . resetState ( ) ; } } } provideCodeLenses ( document , token ) { const fileUri = document . uri . toString ( ) ; let codeLenses = [ ] ; for ( const collection of this . collections . values ( ) ) { codeLenses = codeLenses . concat ( collection . getCodeLenses ( fileUri ) ) ; } return codeLenses ; } provideHover ( document , position ) { for ( const collection of this . collections . values ( ) ) { var hover = collection . getHover ( document , position ) ; if ( hover ) { return hover ; } } return undefined ; } async setSortBy ( sortBy ) { for ( const collection of this . collections . values ( ) ) { await collection . setSortBy ( sortBy , true ) ; } } reveal ( node , treeView ) { if ( typeof node === 'string' ) { for ( const collection of this . collections . values ( ) ) { const nodes = collection . findNodesById ( [ node ] ) ; if ( nodes . length > 0 ) { treeView . reveal ( nodes [ 0 ] ) ; return ; } } } else { treeView . reveal ( node ) ; } } testLoadStarted ( collection ) { this . loadingCollections . add ( collection ) ; vscode . commands . executeCommand ( 'setContext' , 'testsLoading' , true ) ; } testLoadFinished ( collection ) { this . loadingCollections . delete ( collection ) ; if ( this . loadingCollections . size === 0 ) { vscode . commands . executeCommand ( 'setContext' , 'testsLoading' , false ) ; } this . updateVisibility ( ) ; } testRunStarted ( collection ) { this . runningCollections . add ( collection ) ; vscode . commands . executeCommand ( 'setContext' , 'testsRunning' , true ) ; } testRunFinished ( collection ) { this . runningCollections . delete ( collection ) ; if ( this . runningCollections . size === 0 ) { vscode . commands . executeCommand ( 'setContext' , 'testsRunning' , false ) ; } } logChanged ( node ) { if ( this . nodesShownInOutputChannel && ( this . nodesShownInOutputChannel . collection === node . collection ) && this . nodesShownInOutputChannel . ids . includes ( node . info . id ) ) { this . updateLog ( ) ; } } updateVisibility ( ) { let visible = ! this . disabled ; if ( ! this . disabled ) { if ( this . hideWhen === 'noAdapters' ) { visible = ( this . collections . size > 0 ) ; } else if ( this . hideWhen === 'noTests' ) { visible = [ ... this . collections . values ( ) ] . some ( collection => ( ( collection . suite !== undefined ) || ( collection . error !== undefined ) ) ) ; } } vscode . commands . executeCommand ( 'setContext' , 'testExplorerVisible' , visible ) ; } private updateLog ( ) { this . outputChannel . clear ( ) ; let logIsEmpty = true ; if ( this . nodesShownInOutputChannel ) { const nodes = this . nodesShownInOutputChannel . collection . findNodesById ( this . nodesShownInOutputChannel . ids ) ; for ( const node of nodes ) { if ( node . log ) { this . outputChannel . append ( node . log ) ; logIsEmpty = false ; } } } if ( logIsEmpty ) { if ( this . nodesShownInOutputChannel && this . nodesShownInOutputChannel . collection . shouldHideEmptyLog ( ) ) { this . outputChannel . hide ( ) ; } } else { this . outputChannel . show ( true ) ; } } }