From 3e55989cca8ad3a8bbc4f55660fba0205ed1b0b5 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Thu, 21 Jan 2021 11:16:01 -0800 Subject: [PATCH] testing: move test filter to action bar --- .../contrib/testing/browser/media/testing.css | 27 ++++-- .../testing/browser/testing.contribution.ts | 6 +- .../testing/browser/testingExplorerFilter.ts | 86 +++++++++++++++---- .../testing/browser/testingExplorerView.ts | 40 +++++++-- .../contrib/testing/common/constants.ts | 1 + .../testing/common/testingContextKeys.ts | 2 + 6 files changed, 127 insertions(+), 35 deletions(-) diff --git a/src/vs/workbench/contrib/testing/browser/media/testing.css b/src/vs/workbench/contrib/testing/browser/media/testing.css index af9c76b7b6a..d775c55a26d 100644 --- a/src/vs/workbench/contrib/testing/browser/media/testing.css +++ b/src/vs/workbench/contrib/testing/browser/media/testing.css @@ -9,11 +9,6 @@ flex-direction: column; } -.test-explorer > .monaco-inputbox { - flex-shrink: 0; - margin: 4px 12px; -} - .test-explorer > .test-explorer-tree { flex-grow: 1; height: 0px; @@ -75,3 +70,25 @@ .monaco-editor .zone-widget.test-output-peek .zone-widget-container.peekview-widget .peekview-title .filename { max-height: 29px; } + + +/** -- filter */ +.testing-filter-action-bar { + flex-shrink: 0; + margin: 4px 12px; +} + +.testing-filter-action-item { + display: flex !important; + flex-grow: 1; + max-width: 400px; + align-items: center; +} + +.testing-filter-action-bar .testing-filter-action-item { + max-width: none; +} + +.testing-filter-action-item .testing-filter-wrapper { + flex-grow: 1; +} diff --git a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts index 2678d4260a9..be0927ed41a 100644 --- a/src/vs/workbench/contrib/testing/browser/testing.contribution.ts +++ b/src/vs/workbench/contrib/testing/browser/testing.contribution.ts @@ -20,23 +20,25 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { Extensions as ViewContainerExtensions, IViewContainersRegistry, IViewsRegistry, ViewContainerLocation } from 'vs/workbench/common/views'; import { testingViewIcon } from 'vs/workbench/contrib/testing/browser/icons'; +import { ITestExplorerFilterState, TestExplorerFilterState } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter'; import { TestingExplorerView } from 'vs/workbench/contrib/testing/browser/testingExplorerView'; import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browser/testingOutputPeek'; import { TestingViewPaneContainer } from 'vs/workbench/contrib/testing/browser/testingViewPaneContainer'; import { Testing } from 'vs/workbench/contrib/testing/common/constants'; import { TestIdWithProvider } from 'vs/workbench/contrib/testing/common/testCollection'; -import { IWorkspaceTestCollectionService, WorkspaceTestCollectionService } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService'; import { TestingContentProvider } from 'vs/workbench/contrib/testing/common/testingContentProvider'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; +import { ITestResultService, TestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; import { ITestService } from 'vs/workbench/contrib/testing/common/testService'; import { TestService } from 'vs/workbench/contrib/testing/common/testServiceImpl'; +import { IWorkspaceTestCollectionService, WorkspaceTestCollectionService } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import * as Action from './testExplorerActions'; -import { ITestResultService, TestResultService } from 'vs/workbench/contrib/testing/common/testResultService'; registerSingleton(ITestService, TestService); registerSingleton(ITestResultService, TestResultService); +registerSingleton(ITestExplorerFilterState, TestExplorerFilterState); registerSingleton(IWorkspaceTestCollectionService, WorkspaceTestCollectionService); const viewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({ diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts index e434d327b14..285b1608d92 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerFilter.ts @@ -3,23 +3,40 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addStandardDisposableListener, EventType } from 'vs/base/browser/dom'; +import * as dom from 'vs/base/browser/dom'; +import { BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox'; -import { Widget } from 'vs/base/browser/ui/widget'; +import { IAction } from 'vs/base/common/actions'; import { Delayer } from 'vs/base/common/async'; -import { Emitter } from 'vs/base/common/event'; +import { Event, Emitter } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { localize } from 'vs/nls'; +import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; +import { ContextKeyEqualsExpr, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ViewContainerLocation } from 'vs/workbench/common/views'; +import { Testing } from 'vs/workbench/contrib/testing/common/constants'; import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue'; +import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; + +export interface ITestExplorerFilterState { + _serviceBrand: undefined; + readonly onDidChange: Event; + value: string; +} + +export const ITestExplorerFilterState = createDecorator('testingFilterState'); + +export class TestExplorerFilterState implements ITestExplorerFilterState { + declare _serviceBrand: undefined; -export class TestingFilterState { private readonly changeEmitter = new Emitter(); + private _value = ''; public readonly onDidChange = this.changeEmitter.event; @@ -33,12 +50,10 @@ export class TestingFilterState { this.changeEmitter.fire(v); } } - - constructor(private _value = '') { } } -export class TestingExplorerFilter extends Widget { - private readonly input: HistoryInputBox; +export class TestingExplorerFilter extends BaseActionViewItem { + private input!: HistoryInputBox; private readonly history: StoredValue = this.instantiationService.createInstance(StoredValue, { key: 'testing.filterHistory', scope: StorageScope.WORKSPACE, @@ -46,24 +61,33 @@ export class TestingExplorerFilter extends Widget { }); constructor( - container: HTMLElement, - private readonly state: TestingFilterState, - @IContextViewService contextViewService: IContextViewService, - @IThemeService themeService: IThemeService, + action: IAction, + @ITestExplorerFilterState private readonly state: ITestExplorerFilterState, + @IContextViewService private readonly contextViewService: IContextViewService, + @IThemeService private readonly themeService: IThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService, ) { - super(); + super(null, action); + } + + /** + * @override + */ + public render(container: HTMLElement) { + container.classList.add('testing-filter-action-item'); const updateDelayer = this._register(new Delayer(400)); + const wrapper = dom.$('.testing-filter-wrapper'); + container.appendChild(wrapper); - const input = this.input = this._register(instantiationService.createInstance(ContextScopedHistoryInputBox, container, contextViewService, { + const input = this.input = this._register(this.instantiationService.createInstance(ContextScopedHistoryInputBox, wrapper, this.contextViewService, { placeholder: localize('testExplorerFilter', "Filter (e.g. text, !exclude)"), history: this.history.get([]), })); - input.value = state.value; - this._register(attachInputBoxStyler(input, themeService)); + input.value = this.state.value; + this._register(attachInputBoxStyler(input, this.themeService)); - this._register(state.onDidChange(newValue => { + this._register(this.state.onDidChange(newValue => { input.value = newValue; })); @@ -72,7 +96,7 @@ export class TestingExplorerFilter extends Widget { this.state.value = input.value; }))); - this._register(addStandardDisposableListener(input.inputElement, EventType.KEY_DOWN, e => { + this._register(dom.addStandardDisposableListener(input.inputElement, dom.EventType.KEY_DOWN, e => { if (e.equals(KeyCode.Escape)) { input.value = ''; e.stopPropagation(); @@ -100,4 +124,28 @@ export class TestingExplorerFilter extends Widget { this.history.delete(); } } + + /** + * @override + */ + public dispose() { + this.saveState(); + super.dispose(); + } } + +registerAction2(class extends Action2 { + constructor() { + super({ + id: Testing.FilterActionId, + title: localize('filter', "Filter"), + menu: { + id: MenuId.ViewTitle, + when: ContextKeyExpr.and(ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId), TestingContextKeys.explorerLocation.isEqualTo(ViewContainerLocation.Panel)), + group: 'navigation', + order: 1, + }, + }); + } + async run(): Promise { } +}); diff --git a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts index ccfaba349f0..47d9690bdef 100644 --- a/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts +++ b/src/vs/workbench/contrib/testing/browser/testingExplorerView.ts @@ -10,6 +10,7 @@ import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { ITreeEvent, ITreeFilter, ITreeNode, ITreeRenderer, ITreeSorter, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree'; +import { Action, IAction, IActionViewItem } from 'vs/base/common/actions'; import { DeferredPromise } from 'vs/base/common/async'; import { Color, RGBA } from 'vs/base/common/color'; import { throttle } from 'vs/base/common/decorators'; @@ -41,7 +42,7 @@ import { TestRunState } from 'vs/workbench/api/common/extHostTypes'; import { IResourceLabel, IResourceLabelOptions, IResourceLabelProps, ResourceLabels } from 'vs/workbench/browser/labels'; import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IViewDescriptorService } from 'vs/workbench/common/views'; +import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views'; import { ITestTreeElement, ITestTreeProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections'; import { HierarchicalByLocationProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation'; import { HierarchicalByNameProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName'; @@ -50,7 +51,7 @@ import { StateByLocationProjection } from 'vs/workbench/contrib/testing/browser/ import { StateByNameProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateByName'; import { StateElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateNodes'; import { testingStatesToIcons } from 'vs/workbench/contrib/testing/browser/icons'; -import { TestingExplorerFilter, TestingFilterState } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter'; +import { ITestExplorerFilterState, TestingExplorerFilter, TestExplorerFilterState } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter'; import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browser/testingOutputPeek'; import { TestExplorerViewGrouping, TestExplorerViewMode, Testing } from 'vs/workbench/contrib/testing/common/constants'; import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys'; @@ -64,11 +65,11 @@ import { DebugAction, RunAction } from './testExplorerActions'; export class TestingExplorerView extends ViewPane { public viewModel!: TestingExplorerViewModel; - private readonly filterState = new TestingFilterState(); - private filter!: TestingExplorerFilter; + private filterActionBar = this._register(new MutableDisposable()); private currentSubscription?: TestSubscriptionListener; private container!: HTMLElement; private finishDiscovery?: () => void; + private readonly location = TestingContextKeys.explorerLocation.bindTo(this.contextKeyService);; constructor( options: IViewletViewOptions, @@ -87,6 +88,7 @@ export class TestingExplorerView extends ViewPane { ) { super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService); this._register(testService.onDidChangeProviders(() => this._onDidChangeViewWelcomeState.fire())); + this.location.set(viewDescriptorService.getViewLocationById(Testing.ExplorerViewId) ?? ViewContainerLocation.Sidebar); } /** @@ -103,14 +105,16 @@ export class TestingExplorerView extends ViewPane { super.renderBody(container); this.container = dom.append(container, dom.$('.test-explorer')); - this.filter = this.instantiationService.createInstance(TestingExplorerFilter, this.container, this.filterState); - this._register(this.filter); + + if (this.location.get() === ViewContainerLocation.Sidebar) { + this.filterActionBar.value = this.createFilterActionBar(); + } const messagesContainer = dom.append(this.container, dom.$('.test-explorer-messages')); this._register(this.instantiationService.createInstance(TestRunProgress, messagesContainer, this.getProgressLocation())); const listContainer = dom.append(this.container, dom.$('.test-explorer-tree')); - this.viewModel = this.instantiationService.createInstance(TestingExplorerViewModel, listContainer, this.onDidChangeBodyVisibility, this.currentSubscription, this.filterState); + this.viewModel = this.instantiationService.createInstance(TestingExplorerViewModel, listContainer, this.onDidChangeBodyVisibility, this.currentSubscription); this._register(this.viewModel); this._register(this.onDidChangeBodyVisibility(visible => { @@ -125,12 +129,29 @@ export class TestingExplorerView extends ViewPane { })); } + /** + * @override + */ + public getActionViewItem(action: IAction): IActionViewItem | undefined { + if (action.id === Testing.FilterActionId) { + return this.instantiationService.createInstance(TestingExplorerFilter, action); + } + + return super.getActionViewItem(action); + } + /** * @override */ public saveState() { super.saveState(); - this.filter.saveState(); + } + + private createFilterActionBar() { + const bar = new ActionBar(this.container, { actionViewItemProvider: action => this.getActionViewItem(action) }); + bar.push(new Action(Testing.FilterActionId)); + bar.getContainer().classList.add('testing-filter-action-bar'); + return bar; } private updateDiscoveryProgress(busy: number) { @@ -205,7 +226,7 @@ export class TestingExplorerViewModel extends Disposable { listContainer: HTMLElement, onDidChangeVisibility: Event, private listener: TestSubscriptionListener | undefined, - filterState: TestingFilterState, + @ITestExplorerFilterState filterState: TestExplorerFilterState, @IInstantiationService instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, @ICodeEditorService codeEditorService: ICodeEditorService, @@ -220,6 +241,7 @@ export class TestingExplorerViewModel extends Disposable { const labels = this._register(instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: onDidChangeVisibility })); this.filter = new TestsFilter(filterState.value); + this._register(filterState.onDidChange(text => { this.filter.setFilter(text); this.tree.refilter(); diff --git a/src/vs/workbench/contrib/testing/common/constants.ts b/src/vs/workbench/contrib/testing/common/constants.ts index e1317f87249..1e0eab6ce37 100644 --- a/src/vs/workbench/contrib/testing/common/constants.ts +++ b/src/vs/workbench/contrib/testing/common/constants.ts @@ -11,6 +11,7 @@ export const enum Testing { ViewletId = 'workbench.view.extension.test', ExplorerViewId = 'workbench.view.testing', OutputPeekContributionId = 'editor.contrib.testingOutputPeek', + FilterActionId = 'workbench.actions.treeView.testExplorer.filter', } export const enum TestExplorerViewMode { diff --git a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts index f46626e41a0..99ced60e7b0 100644 --- a/src/vs/workbench/contrib/testing/common/testingContextKeys.ts +++ b/src/vs/workbench/contrib/testing/common/testingContextKeys.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ViewContainerLocation } from 'vs/workbench/common/views'; import { TestExplorerViewMode, TestExplorerViewGrouping } from 'vs/workbench/contrib/testing/common/constants'; export namespace TestingContextKeys { @@ -12,4 +13,5 @@ export namespace TestingContextKeys { export const viewGrouping = new RawContextKey('testExplorerViewGrouping', TestExplorerViewGrouping.ByLocation); export const isRunning = new RawContextKey('testIsrunning', false); export const peekVisible = new RawContextKey('testPeekVisible', false); + export const explorerLocation = new RawContextKey('testExplorerLocation', ViewContainerLocation.Sidebar); } -- GitLab