Skip to content

Commit 5bc23d5

Browse files
authored
Auto expand failed tests in test explorer (microsoft#5052)
For microsoft#4386
1 parent 6abee0f commit 5bc23d5

File tree

12 files changed

+261
-11
lines changed

12 files changed

+261
-11
lines changed

gulpfile.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,6 @@ function getFilesToProcess(fileList) {
811811
* @param {hygieneOptions} options
812812
*/
813813
function getFileListToProcess(options) {
814-
return [];
815814
const mode = options ? options.mode : 'all';
816815
const gulpSrcOptions = { base: '.' };
817816

news/1 Enhancements/4386.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Auto expand tree view in `Test Explorer` to display failed tests.

src/client/common/application/applicationShell.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// tslint:disable:no-var-requires no-any unified-signatures
66

77
import { injectable } from 'inversify';
8-
import { CancellationToken, Disposable, env, InputBox, InputBoxOptions, MessageItem, MessageOptions, OpenDialogOptions, Progress, ProgressOptions, QuickPick, QuickPickItem, QuickPickOptions, SaveDialogOptions, StatusBarAlignment, StatusBarItem, Uri, window, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode';
8+
import { CancellationToken, Disposable, env, InputBox, InputBoxOptions, MessageItem, MessageOptions, OpenDialogOptions, Progress, ProgressOptions, QuickPick, QuickPickItem, QuickPickOptions, SaveDialogOptions, StatusBarAlignment, StatusBarItem, TreeView, TreeViewOptions, Uri, window, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode';
99
import { IApplicationShell } from './types';
1010

1111
@injectable()
@@ -75,4 +75,8 @@ export class ApplicationShell implements IApplicationShell {
7575
public createInputBox(): InputBox {
7676
return window.createInputBox();
7777
}
78+
public createTreeView<T>(viewId: string, options: TreeViewOptions<T>): TreeView<T> {
79+
return window.createTreeView<T>(viewId, options);
80+
}
81+
7882
}

src/client/common/application/commands.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export interface ICommandNameArgumentTypeMapping extends ICommandNameWithoutArgu
7070
[Commands.Tests_Select_And_Run_File]: [undefined, CommandSource];
7171
[Commands.Tests_Run_Current_File]: [undefined, CommandSource];
7272
[Commands.Tests_Stop]: [undefined, Uri];
73+
[Commands.Test_Reveal_Test_Item]: [TestDataItem];
7374
// When command is invoked from a tree node, first argument is the node data.
7475
[Commands.Tests_Run]: [undefined | TestWorkspaceFolder, undefined | CommandSource, undefined | Uri, undefined | TestsToRun];
7576
// When command is invoked from a tree node, first argument is the node data.

src/client/common/application/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import {
3737
TextEditorOptionsChangeEvent,
3838
TextEditorSelectionChangeEvent,
3939
TextEditorViewColumnChangeEvent,
40+
TreeView,
41+
TreeViewOptions,
4042
Uri,
4143
ViewColumn,
4244
WorkspaceConfiguration,
@@ -331,6 +333,14 @@ export interface IApplicationShell {
331333
* @return The thenable the task-callback returned.
332334
*/
333335
withProgress<R>(options: ProgressOptions, task: (progress: Progress<{ message?: string; increment?: number }>, token: CancellationToken) => Thenable<R>): Thenable<R>;
336+
337+
/**
338+
* Create a [TreeView](#TreeView) for the view contributed using the extension point `views`.
339+
* @param viewId Id of the view contributed using the extension point `views`.
340+
* @param options Options for creating the [TreeView](#TreeView)
341+
* @returns a [TreeView](#TreeView).
342+
*/
343+
createTreeView<T>(viewId: string, options: TreeViewOptions<T>): TreeView<T>;
334344
}
335345

336346
export const ICommandManager = Symbol('ICommandManager');

src/client/common/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export namespace Commands {
3030
export const Tests_Ask_To_Stop_Test = 'python.askToStopUnitTests';
3131
export const Tests_Ask_To_Stop_Discovery = 'python.askToStopUnitTestDiscovery';
3232
export const Tests_Stop = 'python.stopUnitTests';
33+
export const Test_Reveal_Test_Item = 'python.revealTestItem';
3334
export const ViewOutput = 'python.viewOutput';
3435
export const Tests_ViewOutput = 'python.viewTestOutput';
3536
export const Tests_Select_And_Run_Method = 'python.selectAndRunTestMethod';
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { inject, injectable } from 'inversify';
7+
import { Uri } from 'vscode';
8+
import { IExtensionActivationService } from '../../activation/types';
9+
import { ICommandManager } from '../../common/application/types';
10+
import { Commands } from '../../common/constants';
11+
import '../../common/extensions';
12+
import { IDisposable, IDisposableRegistry, Resource } from '../../common/types';
13+
import { debounceAsync } from '../../common/utils/decorators';
14+
import { getTestType } from '../common/testUtils';
15+
import { ITestCollectionStorageService, TestStatus, TestType } from '../common/types';
16+
import { TestDataItem } from '../types';
17+
18+
@injectable()
19+
export class FailedTestHandler implements IExtensionActivationService, IDisposable {
20+
private readonly disposables: IDisposable[] = [];
21+
private readonly failedItems: TestDataItem[] = [];
22+
constructor(@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry,
23+
@inject(ICommandManager) private readonly commandManager: ICommandManager,
24+
@inject(ITestCollectionStorageService) private readonly storage: ITestCollectionStorageService) {
25+
disposableRegistry.push(this);
26+
}
27+
public dispose() {
28+
this.disposables.forEach(d => d.dispose());
29+
}
30+
public async activate(_resource: Resource): Promise<void> {
31+
this.storage.onDidChange(this.onDidChangeTestData, this, this.disposables);
32+
}
33+
public onDidChangeTestData(args: { uri: Uri; data?: TestDataItem }): void {
34+
if (args.data && (args.data.status === TestStatus.Error || args.data.status === TestStatus.Fail) &&
35+
getTestType(args.data) === TestType.testFunction) {
36+
this.failedItems.push(args.data);
37+
this.revealFailedNodes().ignoreErrors();
38+
}
39+
}
40+
41+
@debounceAsync(500)
42+
private async revealFailedNodes(): Promise<void> {
43+
while (this.failedItems.length > 0) {
44+
const item = this.failedItems.pop()!;
45+
await this.commandManager.executeCommand(Commands.Test_Reveal_Test_Item, item);
46+
}
47+
}
48+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { inject, injectable } from 'inversify';
7+
import { TreeView } from 'vscode';
8+
import { IExtensionActivationService } from '../../activation/types';
9+
import { IApplicationShell, ICommandManager } from '../../common/application/types';
10+
import { Commands } from '../../common/constants';
11+
import { IDisposable, IDisposableRegistry, Resource } from '../../common/types';
12+
import { ITestTreeViewProvider, TestDataItem } from '../types';
13+
14+
@injectable()
15+
export class TreeViewService implements IExtensionActivationService, IDisposable {
16+
private _treeView!: TreeView<TestDataItem>;
17+
private readonly disposables: IDisposable[] = [];
18+
public get treeView(): TreeView<TestDataItem> {
19+
return this._treeView;
20+
}
21+
constructor(@inject(ITestTreeViewProvider) private readonly treeViewProvider: ITestTreeViewProvider,
22+
@inject(IDisposableRegistry) disposableRegistry: IDisposableRegistry,
23+
@inject(IApplicationShell) private readonly appShell: IApplicationShell,
24+
@inject(ICommandManager) private readonly commandManager: ICommandManager) {
25+
disposableRegistry.push(this);
26+
}
27+
public dispose() {
28+
this.disposables.forEach(d => d.dispose());
29+
}
30+
public async activate(_resource: Resource): Promise<void> {
31+
this._treeView = this.appShell.createTreeView('python_tests', { showCollapseAll: true, treeDataProvider: this.treeViewProvider });
32+
this.disposables.push(this._treeView);
33+
this.disposables.push(this.commandManager.registerCommand(Commands.Test_Reveal_Test_Item, this.onRevealTestItem, this));
34+
}
35+
public async onRevealTestItem(testItem: TestDataItem): Promise<void> {
36+
await this.treeView.reveal(testItem);
37+
}
38+
}

src/client/unittests/main.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { inject, injectable } from 'inversify';
66
import {
77
ConfigurationChangeEvent, Disposable,
88
DocumentSymbolProvider, Event,
9-
EventEmitter, OutputChannel, TextDocument, Uri, window
9+
EventEmitter, OutputChannel, TextDocument, Uri
1010
} from 'vscode';
1111
import {
1212
IApplicationShell, ICommandManager, IDocumentManager, IWorkspaceService
@@ -32,7 +32,7 @@ import {
3232
TestFunction, TestStatus, TestsToRun
3333
} from './common/types';
3434
import {
35-
ITestDisplay, ITestResultDisplay, ITestTreeViewProvider,
35+
ITestDisplay, ITestResultDisplay,
3636
IUnitTestConfigurationService, IUnitTestManagementService,
3737
TestWorkspaceFolder,
3838
WorkspaceTestStatus
@@ -84,15 +84,10 @@ export class UnitTestManagementService implements IUnitTestManagementService, Di
8484
}
8585
this.activatedOnce = true;
8686
this.workspaceTestManagerService = this.serviceContainer.get<IWorkspaceTestManagerService>(IWorkspaceTestManagerService);
87-
const disposablesRegistry = this.serviceContainer.get<Disposable[]>(IDisposableRegistry);
8887

8988
this.registerHandlers();
9089
this.registerCommands();
9190

92-
const testViewProvider = this.serviceContainer.get<ITestTreeViewProvider>(ITestTreeViewProvider);
93-
const disposable = window.registerTreeDataProvider('python_tests', testViewProvider);
94-
disposablesRegistry.push(disposable);
95-
9691
this.autoDiscoverTests(undefined)
9792
.catch(ex => this.serviceContainer.get<ILogger>(ILogger).logError('Failed to auto discover tests upon activation', ex));
9893
await this.registerSymbolProvider(symbolProvider);

src/client/unittests/serviceRegistry.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
import { Uri } from 'vscode';
5+
import { IExtensionActivationService } from '../activation/types';
56
import { IServiceContainer, IServiceManager } from '../ioc/types';
67
import { ArgumentsHelper } from './common/argumentsHelper';
78
import { NOSETEST_PROVIDER, PYTEST_PROVIDER, UNITTEST_PROVIDER } from './common/constants';
@@ -31,7 +32,9 @@ import { TestConfigurationManagerFactory } from './configurationFactory';
3132
import { TestResultDisplay } from './display/main';
3233
import { TestDisplay } from './display/picker';
3334
import { TestExplorerCommandHandler } from './explorer/commandHandlers';
35+
import { FailedTestHandler } from './explorer/failedTestHandler';
3436
import { TestTreeViewProvider } from './explorer/testTreeViewProvider';
37+
import { TreeViewService } from './explorer/treeView';
3538
import { UnitTestManagementService } from './main';
3639
import { registerTypes as registerNavigationTypes } from './navigation/serviceRegistry';
3740
import { ITestExplorerCommandHandler } from './navigation/types';
@@ -50,8 +53,8 @@ import {
5053
IArgumentsHelper, IArgumentsService, ITestConfigSettingsService,
5154
ITestConfigurationManagerFactory, ITestDataItemResource, ITestDisplay,
5255
ITestManagerRunner, ITestResultDisplay, ITestTreeViewProvider,
53-
IUnitTestConfigurationService, IUnitTestDiagnosticService,
54-
IUnitTestHelper, IUnitTestManagementService
56+
IUnitTestConfigurationService,
57+
IUnitTestDiagnosticService, IUnitTestHelper, IUnitTestManagementService
5558
} from './types';
5659
import { UnitTestHelper } from './unittest/helper';
5760
import { TestManager as UnitTestTestManager } from './unittest/main';
@@ -109,6 +112,8 @@ export function registerTypes(serviceManager: IServiceManager) {
109112
serviceManager.addSingleton<ITestTreeViewProvider>(ITestTreeViewProvider, TestTreeViewProvider);
110113
serviceManager.addSingleton<ITestDataItemResource>(ITestDataItemResource, TestTreeViewProvider);
111114
serviceManager.addSingleton<ITestExplorerCommandHandler>(ITestExplorerCommandHandler, TestExplorerCommandHandler);
115+
serviceManager.addSingleton<IExtensionActivationService>(IExtensionActivationService, TreeViewService);
116+
serviceManager.addSingleton<IExtensionActivationService>(IExtensionActivationService, FailedTestHandler);
112117

113118
serviceManager.addFactory<ITestManager>(ITestManagerFactory, (context) => {
114119
return (testProvider: TestProvider, workspaceFolder: Uri, rootDirectory: string) => {

0 commit comments

Comments
 (0)