diff --git a/src/client/extension.ts b/src/client/extension.ts index cfafac2957e7..5e59846aaa58 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -93,7 +93,6 @@ import { ReplProvider } from './providers/replProvider'; import { registerTypes as providersRegisterTypes } from './providers/serviceRegistry'; import { activateSimplePythonRefactorProvider } from './providers/simpleRefactorProvider'; import { TerminalProvider } from './providers/terminalProvider'; -import { PythonTestTreeViewProvider } from './providers/testTreeViewProvider'; import { ISortImportsEditingProvider } from './providers/types'; import { activateUpdateSparkLibraryProvider } from './providers/updateSparkLibraryProvider'; import { sendTelemetryEvent } from './telemetry'; @@ -208,8 +207,6 @@ async function activateUnsafe(context: ExtensionContext): Promise context.subscriptions.push(languages.registerCodeActionsProvider(PYTHON, new PythonCodeActionProvider(), { providedCodeActionKinds: [CodeActionKind.SourceOrganizeImports] })); - context.subscriptions.push(window.registerTreeDataProvider('python_tests', new PythonTestTreeViewProvider())); - serviceContainer.getAll(IDebugConfigurationService).forEach(debugConfigProvider => { context.subscriptions.push(debug.registerDebugConfigurationProvider(DebuggerTypeName, debugConfigProvider)); }); diff --git a/src/client/providers/testTreeViewItem.ts b/src/client/providers/testTreeViewItem.ts deleted file mode 100644 index 0c95c8ea52a9..000000000000 --- a/src/client/providers/testTreeViewItem.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { - TreeItem, TreeItemCollapsibleState -} from 'vscode'; -import { TestStatus } from '../unittests/common/types'; - -export enum PythonTestTreeItemType { - Root = 'Root', - Package = 'Package', - File = 'File', - Suite = 'Suite', - Function = 'Function' -} - -export class PythonTestTreeItem extends TreeItem { - - constructor( - kind: PythonTestTreeItemType, - private myParent: PythonTestTreeItem, - private myChildren: PythonTestTreeItem[], - runId: string, - name: string, - testStatus: TestStatus = TestStatus.Unknown) { - - super( - `[${kind}] ${name}`, - kind === PythonTestTreeItemType.Function ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed - ); - - this.contextValue = kind; - this.id = runId; - this.tooltip = `Status: ${testStatus}`; - } - - public get children(): PythonTestTreeItem[] { - return this.myChildren; - } - - public get parent(): PythonTestTreeItem { - return this.myParent; - } -} diff --git a/src/client/providers/testTreeViewProvider.ts b/src/client/providers/testTreeViewProvider.ts deleted file mode 100644 index 1dda3d5733aa..000000000000 --- a/src/client/providers/testTreeViewProvider.ts +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; - -import { - Event, EventEmitter, - ProviderResult, TreeDataProvider -} from 'vscode'; -import { - PythonTestTreeItem, - PythonTestTreeItemType -} from './testTreeViewItem'; - -export class PythonTestTreeViewProvider implements TreeDataProvider { - /** - * This will trigger the view to update the changed element/root and its children recursively (if shown). - * To signal that root has changed, do not pass any argument or pass `undefined` or `null`. - */ - public readonly onDidChangeTreeData: Event; - - private _onDidChangeTreeData: EventEmitter = new EventEmitter(); - private root: PythonTestTreeItem[]; - - constructor() { - this.onDidChangeTreeData = this._onDidChangeTreeData.event; - // set up some dummy data to just show that the test explorer loads. - this.root = this.getTestTree(); - } - - /** - * Get [TreeItem](#TreeItem) representation of the `element` - * - * @param element The element for which [TreeItem](#TreeItem) representation is asked for. - * @return [TreeItem](#TreeItem) representation of the element - */ - public async getTreeItem(element: PythonTestTreeItem): Promise { - return element; - } - - /** - * Get the children of `element` or root if no element is passed. - * - * @param element The element from which the provider gets children. Can be `undefined`. - * @return Children of `element` or root if no element is passed. - */ - public getChildren(element?: PythonTestTreeItem): ProviderResult { - if (element === undefined) { - return this.root; - } - return element.children; - } - - /** - * Optional method to return the parent of `element`. - * Return `null` or `undefined` if `element` is a child of root. - * - * **NOTE:** This method should be implemented in order to access [reveal](#TreeView.reveal) API. - * - * @param element The element for which the parent has to be returned. - * @return Parent of `element`. - */ - public getParent?(element: PythonTestTreeItem): ProviderResult { - return element.parent; - } - - private getTestTree(): PythonTestTreeItem[] { - // create a sample tree just to get the feature up and running - const roots: PythonTestTreeItem[] = []; - const root1: PythonTestTreeItem = new PythonTestTreeItem(PythonTestTreeItemType.Root, undefined, [], '/test', '/test'); - roots.push(root1); - - const root1_pkg1: PythonTestTreeItem = new PythonTestTreeItem(PythonTestTreeItemType.Package, root1, [], '/test/module1', 'module1'); - root1.children.push(root1_pkg1); - - const root1_pkg1_file1: PythonTestTreeItem = new PythonTestTreeItem(PythonTestTreeItemType.File, root1_pkg1, [], '/test/module1/test_file1.py', 'test_file1.py'); - root1_pkg1.children.push(root1_pkg1_file1); - - const root1_pkg1_file1_fn1: PythonTestTreeItem = new PythonTestTreeItem(PythonTestTreeItemType.Function, root1_pkg1_file1, undefined, '/test/module1/test_file1.py::test_function_1', 'test_function_1'); - root1_pkg1_file1.children.push(root1_pkg1_file1_fn1); - - const root1_pkg1_file1_fn2: PythonTestTreeItem = new PythonTestTreeItem(PythonTestTreeItemType.Function, root1_pkg1_file1, undefined, '/test/module1/test_file1.py::test_function_2', 'test_function_2'); - root1_pkg1_file1.children.push(root1_pkg1_file1_fn2); - - const root1_pkg1_file1_suite1: PythonTestTreeItem = new PythonTestTreeItem(PythonTestTreeItemType.Suite, root1_pkg1_file1, [], '/test/module1/test_file1.py::TestSuite1', 'TestSuite1'); - root1_pkg1_file1.children.push(root1_pkg1_file1_suite1); - - const root1_pkg1_file1_suite1_fn1: PythonTestTreeItem = new PythonTestTreeItem(PythonTestTreeItemType.Function, root1_pkg1_file1_suite1, undefined, '/test/module1/test_file1.py::TestSuite1::test_suite1_fn1', 'test_suite1_fn1'); - root1_pkg1_file1_suite1.children.push(root1_pkg1_file1_suite1_fn1); - - const root1_pkg1_file1_suite1_fn2: PythonTestTreeItem = new PythonTestTreeItem(PythonTestTreeItemType.Function, root1_pkg1_file1_suite1, undefined, '/test/module1/test_file1.py::TestSuite1::test_suite1_fn2', 'test_suite1_fn2'); - root1_pkg1_file1_suite1.children.push(root1_pkg1_file1_suite1_fn2); - - const root1_pkg1_file1_suite2: PythonTestTreeItem = new PythonTestTreeItem(PythonTestTreeItemType.Suite, root1_pkg1_file1, [], '/test/module1/test_file1.py::TestSuite2', 'TestSuite2'); - root1_pkg1_file1.children.push(root1_pkg1_file1_suite2); - - const root1_pkg1_file1_suite2_fn1: PythonTestTreeItem = new PythonTestTreeItem(PythonTestTreeItemType.Function, root1_pkg1_file1_suite2, undefined, '/test/module1/test_file1.py::TestSuite2::test_suite2_fn1', 'test_suite2_fn1'); - root1_pkg1_file1_suite2.children.push(root1_pkg1_file1_suite2_fn1); - - const root1_pkg1_file1_suite2_fn2: PythonTestTreeItem = new PythonTestTreeItem(PythonTestTreeItemType.Function, root1_pkg1_file1_suite2, undefined, '/test/module1/test_file1.py::TestSuite2::test_suite2_fn2', 'test_suite2_fn2'); - root1_pkg1_file1_suite2.children.push(root1_pkg1_file1_suite2_fn2); - - return roots; - } -} diff --git a/src/client/providers/types.ts b/src/client/providers/types.ts index f2d1bc6eea3a..8784e4fdf18d 100644 --- a/src/client/providers/types.ts +++ b/src/client/providers/types.ts @@ -3,7 +3,11 @@ 'use strict'; -import { CancellationToken, Uri, WorkspaceEdit } from 'vscode'; +import { + CancellationToken, Event, ProviderResult, + TreeDataProvider, Uri, WorkspaceEdit +} from 'vscode'; +import { TestTreeItem } from '../unittests/providers/testTreeViewItem'; export const ISortImportsEditingProvider = Symbol('ISortImportsEditingProvider'); export interface ISortImportsEditingProvider { @@ -11,3 +15,10 @@ export interface ISortImportsEditingProvider { sortImports(uri?: Uri): Promise; registerCommands(): void; } + +export const ITestTreeViewProvider = Symbol('ITestTreeViewProvider'); +export interface ITestTreeViewProvider extends TreeDataProvider { + onDidChangeTreeData: Event; + getTreeItem(element: TestTreeItem): Promise; + getChildren(element?: TestTreeItem): ProviderResult; +} diff --git a/src/client/unittests/common/services/storageService.ts b/src/client/unittests/common/services/storageService.ts index 5f4843ebf931..a9602a4908a4 100644 --- a/src/client/unittests/common/services/storageService.ts +++ b/src/client/unittests/common/services/storageService.ts @@ -1,13 +1,21 @@ import { inject, injectable } from 'inversify'; -import { Disposable, Uri, workspace } from 'vscode'; +import { + Disposable, Event, EventEmitter, + Uri, workspace +} from 'vscode'; import { IDisposableRegistry } from '../../../common/types'; import { FlattenedTestFunction, FlattenedTestSuite, ITestCollectionStorageService, TestFunction, Tests, TestSuite } from './../types'; @injectable() export class TestCollectionStorageService implements ITestCollectionStorageService { + public readonly onUpdated: Event; + private testsIndexedByWorkspaceUri = new Map(); + private _onTestStoreUpdated: EventEmitter = new EventEmitter(); + constructor(@inject(IDisposableRegistry) disposables: Disposable[]) { disposables.push(this); + this.onUpdated = this._onTestStoreUpdated.event; } public getTests(wkspace: Uri): Tests | undefined { const workspaceFolder = this.getWorkspaceFolderPath(wkspace) || ''; @@ -16,6 +24,7 @@ export class TestCollectionStorageService implements ITestCollectionStorageServi public storeTests(wkspace: Uri, tests: Tests | undefined): void { const workspaceFolder = this.getWorkspaceFolderPath(wkspace) || ''; this.testsIndexedByWorkspaceUri.set(workspaceFolder, tests); + this._onTestStoreUpdated.fire(wkspace); } public findFlattendTestFunction(resource: Uri, func: TestFunction): FlattenedTestFunction | undefined { const tests = this.getTests(resource); @@ -33,6 +42,7 @@ export class TestCollectionStorageService implements ITestCollectionStorageServi } public dispose() { this.testsIndexedByWorkspaceUri.clear(); + this._onTestStoreUpdated.dispose(); } private getWorkspaceFolderPath(resource: Uri): string | undefined { const folder = workspace.getWorkspaceFolder(resource); diff --git a/src/client/unittests/common/types.ts b/src/client/unittests/common/types.ts index c7c758b20623..dab75d0f160c 100644 --- a/src/client/unittests/common/types.ts +++ b/src/client/unittests/common/types.ts @@ -1,4 +1,7 @@ -import { CancellationToken, DiagnosticCollection, Disposable, OutputChannel, Uri } from 'vscode'; +import { + CancellationToken, DiagnosticCollection, Disposable, + Event, OutputChannel, Uri +} from 'vscode'; import { IUnitTestSettings, Product } from '../../common/types'; import { IPythonUnitTestMessage } from '../types'; import { CommandSource } from './constants'; @@ -181,6 +184,7 @@ export interface ITestVisitor { export const ITestCollectionStorageService = Symbol('ITestCollectionStorageService'); export interface ITestCollectionStorageService extends Disposable { + onUpdated: Event; getTests(wkspace: Uri): Tests | undefined; storeTests(wkspace: Uri, tests: Tests | null | undefined): void; findFlattendTestFunction(resource: Uri, func: TestFunction): FlattenedTestFunction | undefined; diff --git a/src/client/unittests/main.ts b/src/client/unittests/main.ts index 9f0ca5d71ec8..367256ca0f76 100644 --- a/src/client/unittests/main.ts +++ b/src/client/unittests/main.ts @@ -3,24 +3,42 @@ // tslint:disable:no-duplicate-imports no-unnecessary-callback-wrapper import { inject, injectable } from 'inversify'; -import { ConfigurationChangeEvent, Disposable, OutputChannel, TextDocument, Uri } from 'vscode'; -import * as vscode from 'vscode'; -import { ICommandManager, IDocumentManager, IWorkspaceService } from '../common/application/types'; +import { + ConfigurationChangeEvent, Disposable, + DocumentSymbolProvider, EventEmitter, + OutputChannel, TextDocument, Uri, window +} from 'vscode'; +import { + ICommandManager, IDocumentManager, IWorkspaceService +} from '../common/application/types'; import * as constants from '../common/constants'; import '../common/extensions'; -import { IConfigurationService, IDisposableRegistry, ILogger, IOutputChannel } from '../common/types'; +import { + IConfigurationService, IDisposableRegistry, + ILogger, IOutputChannel +} from '../common/types'; import { IServiceContainer } from '../ioc/types'; +import { ITestTreeViewProvider } from '../providers/types'; import { EventName } from '../telemetry/constants'; import { sendTelemetryEvent } from '../telemetry/index'; import { activateCodeLenses } from './codeLenses/main'; -import { CANCELLATION_REASON, CommandSource, TEST_OUTPUT_CHANNEL } from './common/constants'; +import { + CANCELLATION_REASON, CommandSource, TEST_OUTPUT_CHANNEL +} from './common/constants'; import { selectTestWorkspace } from './common/testUtils'; -import { ITestCollectionStorageService, ITestManager, IWorkspaceTestManagerService, TestFile, TestFunction, TestStatus, TestsToRun } from './common/types'; -import { ITestDisplay, ITestResultDisplay, IUnitTestConfigurationService, IUnitTestManagementService } from './types'; +import { + ITestCollectionStorageService, ITestManager, + IWorkspaceTestManagerService, TestFile, + TestFunction, TestStatus, TestsToRun +} from './common/types'; +import { + ITestDisplay, ITestResultDisplay, + IUnitTestConfigurationService, IUnitTestManagementService +} from './types'; @injectable() export class UnitTestManagementService implements IUnitTestManagementService, Disposable { - private readonly outputChannel: vscode.OutputChannel; + private readonly outputChannel: OutputChannel; private readonly disposableRegistry: Disposable[]; private workspaceTestManagerService?: IWorkspaceTestManagerService; private documentManager: IDocumentManager; @@ -28,7 +46,7 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di private testResultDisplay?: ITestResultDisplay; private autoDiscoverTimer?: NodeJS.Timer; private configChangedTimer?: NodeJS.Timer; - private readonly onDidChange: vscode.EventEmitter = new vscode.EventEmitter(); + private readonly onDidChange: EventEmitter = new EventEmitter(); constructor(@inject(IServiceContainer) private serviceContainer: IServiceContainer) { this.disposableRegistry = serviceContainer.get(IDisposableRegistry); @@ -43,15 +61,28 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di this.workspaceTestManagerService.dispose(); } } - public async activate(symboldProvider: vscode.DocumentSymbolProvider): Promise { + public async activate(symbolProvider: DocumentSymbolProvider): Promise { this.workspaceTestManagerService = this.serviceContainer.get(IWorkspaceTestManagerService); + const disposablesRegistry = this.serviceContainer.get(IDisposableRegistry); this.registerHandlers(); this.registerCommands(); + + // register provider... + const testViewProvider = this.serviceContainer.get(ITestTreeViewProvider); + const disposable = window.registerTreeDataProvider('python_tests', testViewProvider); + disposablesRegistry.push(disposable); + this.autoDiscoverTests() .catch(ex => this.serviceContainer.get(ILogger).logError('Failed to auto discover tests upon activation', ex)); - await this.registerSymbolProvider(symboldProvider); + await this.registerSymbolProvider(symbolProvider); } + + public async activateCodeLenses(symbolProvider: DocumentSymbolProvider): Promise { + const testCollectionStorage = this.serviceContainer.get(ITestCollectionStorageService); + this.disposableRegistry.push(activateCodeLenses(this.onDidChange, symbolProvider, testCollectionStorage)); + } + public async getTestManager(displayTestNotConfiguredMessage: boolean, resource?: Uri): Promise { let wkspace: Uri | undefined; if (resource) { @@ -278,9 +309,9 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di this.testResultDisplay.displayProgressStatus(promise, debug); await promise; } - private async registerSymbolProvider(symboldProvider: vscode.DocumentSymbolProvider): Promise { + private async registerSymbolProvider(symbolProvider: DocumentSymbolProvider): Promise { const testCollectionStorage = this.serviceContainer.get(ITestCollectionStorageService); - this.disposableRegistry.push(activateCodeLenses(this.onDidChange, symboldProvider, testCollectionStorage)); + this.disposableRegistry.push(activateCodeLenses(this.onDidChange, symbolProvider, testCollectionStorage)); } private registerCommands(): void { const disposablesRegistry = this.serviceContainer.get(IDisposableRegistry); diff --git a/src/client/unittests/providers/testTreeViewItem.ts b/src/client/unittests/providers/testTreeViewItem.ts new file mode 100644 index 000000000000..9e2ce267ec9c --- /dev/null +++ b/src/client/unittests/providers/testTreeViewItem.ts @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { + TreeItem, TreeItemCollapsibleState +} from 'vscode'; +import { + TestFile, TestFolder, TestFunction, + TestStatus, TestSuite +} from '../common/types'; + +export enum TestTreeItemType { + Root = 'Root', + Package = 'Package', + File = 'File', + Suite = 'Suite', + Function = 'Function' +} + +export class TestTreeItem extends TreeItem { + + constructor( + kind: TestTreeItemType, + private readonly myParent: TestTreeItem, + private readonly myChildren: TestTreeItem[], + runId: string, + name: string, + testStatus: TestStatus = TestStatus.Unknown, + // tslint:disable-next-line:no-unused-variable + private readonly data: TestFolder | TestFile | TestSuite | TestFunction + ) { + + super( + `[${kind}] ${name}`, + kind === TestTreeItemType.Function ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed + ); + + this.contextValue = kind; + this.id = runId; + this.tooltip = `Status: ${testStatus}`; + } + + public static createFromFolder( + folder: TestFolder, + parent?: TestTreeItem + ): TestTreeItem { + + const folderItem = new TestTreeItem( + TestTreeItemType.Package, + parent, + [], + folder.nameToRun, + folder.name, + folder.status, + folder + ); + + folder.testFiles.forEach((testFile: TestFile) => { + folderItem.children.push(TestTreeItem.createFromFile(testFile, folderItem)); + }); + + return folderItem; + } + + public static createFromFile( + testFile: TestFile, + parent?: TestTreeItem + ): TestTreeItem { + + const fileItem = new TestTreeItem( + TestTreeItemType.File, + parent, + [], + testFile.nameToRun, + testFile.name, + testFile.status, + testFile + ); + + testFile.functions.forEach((fn: TestFunction) => { + fileItem.children.push(TestTreeItem.createFromFunction(fn, fileItem)); + }); + testFile.suites.forEach((suite: TestSuite) => { + fileItem.children.push(TestTreeItem.createFromSuite(suite, fileItem)); + }); + + return fileItem; + } + + public static createFromSuite( + suite: TestSuite, + parent: TestTreeItem + ): TestTreeItem { + + const suiteItem = new TestTreeItem( + TestTreeItemType.Suite, + parent, + [], + suite.nameToRun, + suite.name, + suite.status, + suite + ); + + suite.suites.forEach((subSuite: TestSuite) => { + suiteItem.children.push(TestTreeItem.createFromSuite(subSuite, suiteItem)); + }); + suite.functions.forEach((fn: TestFunction) => { + suiteItem.children.push(TestTreeItem.createFromFunction(fn, suiteItem)); + }); + + return suiteItem; + } + + public static createFromFunction( + fn: TestFunction, + parent: TestTreeItem + ): TestTreeItem { + + // tslint:disable-next-line:no-unnecessary-local-variable + const funcItem = new TestTreeItem( + TestTreeItemType.Function, + parent, + undefined, + fn.nameToRun, + fn.name, + fn.status, + fn + ); + + return funcItem; + } + + public get children(): TestTreeItem[] { + return this.myChildren; + } + + public get parent(): TestTreeItem { + return this.myParent; + } +} diff --git a/src/client/unittests/providers/testTreeViewProvider.ts b/src/client/unittests/providers/testTreeViewProvider.ts new file mode 100644 index 000000000000..4013d4bd7b34 --- /dev/null +++ b/src/client/unittests/providers/testTreeViewProvider.ts @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { inject, injectable } from 'inversify'; +import { + Event, EventEmitter, ProviderResult, Uri +} from 'vscode'; +import { IWorkspaceService } from '../../common/application/types'; +import { traceDecorators } from '../../common/logger'; +import { + IDisposable, IDisposableRegistry, Resource +} from '../../common/types'; +import { ITestTreeViewProvider } from '../../providers/types'; +import { + ITestCollectionStorageService, TestFolder, Tests, TestStatus +} from '../common/types'; +import { + TestTreeItem, TestTreeItemType +} from './testTreeViewItem'; + +@injectable() +export class TestTreeViewProvider implements ITestTreeViewProvider, IDisposable { + /** + * This will trigger the view to update the changed element/root and its children recursively (if shown). + * To signal that root has changed, do not pass any argument or pass `undefined` or `null`. + */ + public readonly onDidChangeTreeData: Event; + + private _onDidChangeTreeData: EventEmitter = new EventEmitter(); + private root: TestTreeItem[]; + private disposables: IDisposable[] = []; + + constructor( + @inject(ITestCollectionStorageService) private testStore: ITestCollectionStorageService, + @inject(IWorkspaceService) private workspace: IWorkspaceService, + @inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry + ) { + this.onDidChangeTreeData = this._onDidChangeTreeData.event; + this.root = [new TestTreeItem(TestTreeItemType.Root, undefined, undefined, '*', 'no tests discovered yet', TestStatus.Unknown, undefined)]; + this.refresh(this.workspace.workspaceFolders[0].uri); + disposableRegistry.push(this); + this.disposables.push(this.testStore.onUpdated(this.onTestStoreUpdated, this)); + } + + // tslint:disable-next-line:no-empty + public dispose() { + this.disposables.forEach(d => d.dispose()); + this._onDidChangeTreeData.dispose(); + } + + /** + * Get [TreeItem](#TreeItem) representation of the `element` + * + * @param element The element for which [TreeItem](#TreeItem) representation is asked for. + * @return [TreeItem](#TreeItem) representation of the element + */ + public async getTreeItem(element: TestTreeItem): Promise { + return element; + } + + /** + * Get the children of `element` or root if no element is passed. + * + * @param element The element from which the provider gets children. Can be `undefined`. + * @return Children of `element` or root if no element is passed. + */ + public getChildren(element?: TestTreeItem): ProviderResult { + if (element === undefined) { + return this.root; + } + return element.children; + } + + /** + * Optional method to return the parent of `element`. + * Return `null` or `undefined` if `element` is a child of root. + * + * **NOTE:** This method should be implemented in order to access [reveal](#TreeView.reveal) API. + * + * @param element The element for which the parent has to be returned. + * @return Parent of `element`. + */ + public getParent?(element: TestTreeItem): ProviderResult { + return element.parent; + } + + /** + * Refresh the view by rebuilding the model and signalling the tree view to update itself. + * + */ + public refresh(resource: Resource, tests?: Tests): void { + + if (tests === undefined) { + tests = this.testStore.getTests(resource); + } + if (tests && tests.testFolders) { + const newRoot: TestTreeItem[] = []; + tests.testFolders.forEach((tf: TestFolder) => { + newRoot.push(TestTreeItem.createFromFolder(tf)); + }); + this.root = newRoot; + this._onDidChangeTreeData.fire(); + } + } + + @traceDecorators.verbose('>>> DEREK >>> Test store is being updated...') + private onTestStoreUpdated(workspace: Uri): void { + this.refresh(workspace); + } +} diff --git a/src/client/unittests/serviceRegistry.ts b/src/client/unittests/serviceRegistry.ts index d5007cfed20a..07179353295d 100644 --- a/src/client/unittests/serviceRegistry.ts +++ b/src/client/unittests/serviceRegistry.ts @@ -3,6 +3,7 @@ import { Uri } from 'vscode'; import { IServiceContainer, IServiceManager } from '../ioc/types'; +import { ITestTreeViewProvider } from '../providers/types'; import { ArgumentsHelper } from './common/argumentsHelper'; import { NOSETEST_PROVIDER, PYTEST_PROVIDER, UNITTEST_PROVIDER } from './common/constants'; import { DebugLauncher } from './common/debugLauncher'; @@ -18,8 +19,11 @@ import { TestFlatteningVisitor } from './common/testVisitors/flatteningVisitor'; import { TestFolderGenerationVisitor } from './common/testVisitors/folderGenerationVisitor'; import { TestResultResetVisitor } from './common/testVisitors/resultResetVisitor'; import { - ITestCollectionStorageService, ITestConfigSettingsService, ITestDebugLauncher, ITestDiscoveryService, ITestManager, ITestManagerFactory, ITestManagerService, ITestManagerServiceFactory, - ITestMessageService, ITestResultsService, ITestRunner, ITestsHelper, ITestsParser, ITestVisitor, IUnitTestSocketServer, IWorkspaceTestManagerService, IXUnitParser, TestProvider + ITestCollectionStorageService, ITestConfigSettingsService, ITestDebugLauncher, + ITestDiscoveryService, ITestManager, ITestManagerFactory, ITestManagerService, + ITestManagerServiceFactory, ITestMessageService, ITestResultsService, ITestRunner, + ITestsHelper, ITestsParser, ITestVisitor, IUnitTestSocketServer, IWorkspaceTestManagerService, + IXUnitParser, TestProvider } from './common/types'; import { XUnitParser } from './common/xUnitParser'; import { UnitTestConfigurationService } from './configuration'; @@ -33,13 +37,19 @@ import { TestManagerRunner as NoseTestManagerRunner } from './nosetest/runner'; import { ArgumentsService as NoseTestArgumentsService } from './nosetest/services/argsService'; import { TestDiscoveryService as NoseTestDiscoveryService } from './nosetest/services/discoveryService'; import { TestsParser as NoseTestTestsParser } from './nosetest/services/parserService'; +import { TestTreeViewProvider } from './providers/testTreeViewProvider'; import { TestManager as PyTestTestManager } from './pytest/main'; import { TestManagerRunner as PytestManagerRunner } from './pytest/runner'; import { ArgumentsService as PyTestArgumentsService } from './pytest/services/argsService'; import { TestDiscoveryService as PytestTestDiscoveryService } from './pytest/services/discoveryService'; import { TestsParser as PytestTestsParser } from './pytest/services/parserService'; import { TestMessageService } from './pytest/services/testMessageService'; -import { IArgumentsHelper, IArgumentsService, ITestConfigurationManagerFactory, ITestDisplay, ITestManagerRunner, ITestResultDisplay, IUnitTestConfigurationService, IUnitTestDiagnosticService, IUnitTestHelper, IUnitTestManagementService } from './types'; +import { + IArgumentsHelper, IArgumentsService, ITestConfigurationManagerFactory, + ITestDisplay, ITestManagerRunner, ITestResultDisplay, + IUnitTestConfigurationService, IUnitTestDiagnosticService, + IUnitTestHelper, IUnitTestManagementService +} from './types'; import { UnitTestHelper } from './unittest/helper'; import { TestManager as UnitTestTestManager } from './unittest/main'; import { TestManagerRunner as UnitTestTestManagerRunner } from './unittest/runner'; @@ -92,6 +102,7 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IUnitTestDiagnosticService, UnitTestDiagnosticService); serviceManager.addSingleton(ITestMessageService, TestMessageService, PYTEST_PROVIDER); + serviceManager.addSingleton(ITestTreeViewProvider, TestTreeViewProvider); serviceManager.addFactory(ITestManagerFactory, (context) => { return (testProvider: TestProvider, workspaceFolder: Uri, rootDirectory: string) => { diff --git a/src/test/vscode-mock.ts b/src/test/vscode-mock.ts index 33032c9ab3bd..77d1015fe97c 100644 --- a/src/test/vscode-mock.ts +++ b/src/test/vscode-mock.ts @@ -33,7 +33,7 @@ export function initialize() { generateMock('scm'); // When upgrading to npm 9-10, this might have to change, as we could have explicit imports (named imports). - Module._load = function(request: any, _parent: any) { + Module._load = function (request: any, _parent: any) { if (request === 'vscode') { return mockedVSCode; } @@ -71,6 +71,7 @@ mockedVSCode.RelativePattern = vscodeMocks.vscMockExtHostedTypes.RelativePattern mockedVSCode.ProgressLocation = vscodeMocks.vscMockExtHostedTypes.ProgressLocation; mockedVSCode.ViewColumn = vscodeMocks.vscMockExtHostedTypes.ViewColumn; mockedVSCode.TextEditorRevealType = vscodeMocks.vscMockExtHostedTypes.TextEditorRevealType; +mockedVSCode.TreeItem = vscodeMocks.vscMockExtHostedTypes.TreeItem; // This API is used in src/client/telemetry/telemetry.ts const extensions = TypeMoq.Mock.ofType();