|
|
|
@@ -10,9 +10,8 @@ import * as aria from 'vs/base/browser/ui/aria/aria';
|
|
|
|
|
import { Button } from 'vs/base/browser/ui/button/button';
|
|
|
|
|
import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
|
|
|
|
import { DefaultKeyboardNavigationDelegate, 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 { ITreeContextMenuEvent, ITreeEvent, ITreeFilter, ITreeNode, ITreeRenderer, ITreeSorter, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
|
|
|
|
|
import { Action, IAction } from 'vs/base/common/actions';
|
|
|
|
|
import { DeferredPromise, RunOnceScheduler } from 'vs/base/common/async';
|
|
|
|
|
import { Color, RGBA } from 'vs/base/common/color';
|
|
|
|
@@ -22,15 +21,15 @@ import { FuzzyScore } from 'vs/base/common/filters';
|
|
|
|
|
import { splitGlobAware } from 'vs/base/common/glob';
|
|
|
|
|
import { Iterable } from 'vs/base/common/iterator';
|
|
|
|
|
import { KeyCode } from 'vs/base/common/keyCodes';
|
|
|
|
|
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
|
|
|
|
import { Disposable, DisposableStore, dispose, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
|
|
|
|
import { isDefined } from 'vs/base/common/types';
|
|
|
|
|
import { URI } from 'vs/base/common/uri';
|
|
|
|
|
import 'vs/css!./media/testing';
|
|
|
|
|
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
|
|
|
|
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
|
|
|
|
import { localize } from 'vs/nls';
|
|
|
|
|
import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
|
|
|
|
import { MenuItemAction } from 'vs/platform/actions/common/actions';
|
|
|
|
|
import { createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
|
|
|
|
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
|
|
|
|
import { ICommandService } from 'vs/platform/commands/common/commands';
|
|
|
|
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
|
|
|
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
|
|
|
@@ -256,8 +255,8 @@ export class TestingExplorerViewModel extends Disposable {
|
|
|
|
|
listContainer: HTMLElement,
|
|
|
|
|
onDidChangeVisibility: Event<boolean>,
|
|
|
|
|
private listener: TestSubscriptionListener | undefined,
|
|
|
|
|
@ICommandService commandService: ICommandService,
|
|
|
|
|
@IThemeService themeService: IThemeService,
|
|
|
|
|
@IMenuService private readonly menuService: IMenuService,
|
|
|
|
|
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
|
|
|
|
@ITestService private readonly testService: ITestService,
|
|
|
|
|
@ITestExplorerFilterState filterState: TestExplorerFilterState,
|
|
|
|
|
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
|
|
|
@@ -304,6 +303,8 @@ export class TestingExplorerViewModel extends Disposable {
|
|
|
|
|
this.tree.refilter();
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
this._register(this.tree.onContextMenu(e => this.onContextMenu(e)));
|
|
|
|
|
|
|
|
|
|
this._register(editorService.onDidActiveEditorChange(() => {
|
|
|
|
|
if (filterState.currentDocumentOnly.value && editorService.activeEditor?.resource) {
|
|
|
|
|
if (this.projection.hasTestInDocument(editorService.activeEditor.resource)) {
|
|
|
|
@@ -450,6 +451,20 @@ export class TestingExplorerViewModel extends Disposable {
|
|
|
|
|
: false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private onContextMenu(evt: ITreeContextMenuEvent<ITestTreeElement | null>) {
|
|
|
|
|
if (!evt.element) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const actions = getTestItemActions(this.instantiationService, this.contextKeyService, this.menuService, evt.element);
|
|
|
|
|
this.contextMenuService.showContextMenu({
|
|
|
|
|
getAnchor: () => evt.anchor,
|
|
|
|
|
getActions: () => actions.value.secondary,
|
|
|
|
|
getActionsContext: () => evt.element?.test?.item.extId,
|
|
|
|
|
onHide: () => actions.dispose(),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private handleExecuteKeypress(evt: IKeyboardEvent) {
|
|
|
|
|
const focused = this.tree.getFocus();
|
|
|
|
|
const selected = this.tree.getSelection();
|
|
|
|
@@ -747,25 +762,32 @@ interface TestTemplateData {
|
|
|
|
|
label: IResourceLabel;
|
|
|
|
|
icon: HTMLElement;
|
|
|
|
|
actionBar: ActionBar;
|
|
|
|
|
elementDisposable: IDisposable[];
|
|
|
|
|
templateDisposable: IDisposable[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class TestsRenderer implements ITreeRenderer<ITestTreeElement, FuzzyScore, TestTemplateData> {
|
|
|
|
|
class TestsRenderer extends Disposable implements ITreeRenderer<ITestTreeElement, FuzzyScore, TestTemplateData> {
|
|
|
|
|
public static readonly ID = 'testExplorer';
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
private labels: ResourceLabels,
|
|
|
|
|
@IMenuService private readonly menuService: IMenuService,
|
|
|
|
|
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
|
|
|
|
@IInstantiationService private readonly instantiationService: IInstantiationService
|
|
|
|
|
) { }
|
|
|
|
|
|
|
|
|
|
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<ITestTreeElement>, FuzzyScore>, index: number, templateData: TestTemplateData): void {
|
|
|
|
|
const element = node.element.elements[node.element.elements.length - 1];
|
|
|
|
|
this.renderElementDirect(element, templateData);
|
|
|
|
|
) {
|
|
|
|
|
super();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @inheritdoc
|
|
|
|
|
*/
|
|
|
|
|
get templateId(): string {
|
|
|
|
|
return TestsRenderer.ID;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @inheritdoc
|
|
|
|
|
*/
|
|
|
|
|
public renderTemplate(container: HTMLElement): TestTemplateData {
|
|
|
|
|
const wrapper = dom.append(container, dom.$('.test-item'));
|
|
|
|
|
|
|
|
|
@@ -780,14 +802,13 @@ class TestsRenderer implements ITreeRenderer<ITestTreeElement, FuzzyScore, TestT
|
|
|
|
|
: undefined
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return { label, actionBar, icon };
|
|
|
|
|
return { label, actionBar, icon, elementDisposable: [], templateDisposable: [label, actionBar] };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public renderElement(node: ITreeNode<ITestTreeElement, FuzzyScore>, index: number, data: TestTemplateData): void {
|
|
|
|
|
this.renderElementDirect(node.element, data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private renderElementDirect(element: ITestTreeElement, data: TestTemplateData) {
|
|
|
|
|
/**
|
|
|
|
|
* @inheritdoc
|
|
|
|
|
*/
|
|
|
|
|
public renderElement({ element }: ITreeNode<ITestTreeElement, FuzzyScore>, _: number, data: TestTemplateData): void {
|
|
|
|
|
const label: IResourceLabelProps = { name: element.label };
|
|
|
|
|
const options: IResourceLabelOptions = {};
|
|
|
|
|
data.actionBar.clear();
|
|
|
|
@@ -816,30 +837,65 @@ class TestsRenderer implements ITreeRenderer<ITestTreeElement, FuzzyScore, TestT
|
|
|
|
|
options.fileKind = FileKind.ROOT_FOLDER;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const running = element.state === TestRunState.Running;
|
|
|
|
|
if (!Iterable.isEmpty(element.runnable)) {
|
|
|
|
|
data.actionBar.push(
|
|
|
|
|
this.instantiationService.createInstance(RunAction, element.runnable, running),
|
|
|
|
|
{ icon: true, label: false },
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Iterable.isEmpty(element.debuggable)) {
|
|
|
|
|
data.actionBar.push(
|
|
|
|
|
this.instantiationService.createInstance(DebugAction, element.debuggable, running),
|
|
|
|
|
{ icon: true, label: false },
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.fillActionBar(element, data);
|
|
|
|
|
data.label.setResource(label, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @inheritdoc
|
|
|
|
|
*/
|
|
|
|
|
disposeTemplate(templateData: TestTemplateData): void {
|
|
|
|
|
templateData.label.dispose();
|
|
|
|
|
templateData.actionBar.dispose();
|
|
|
|
|
dispose(templateData.templateDisposable);
|
|
|
|
|
templateData.templateDisposable = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @inheritdoc
|
|
|
|
|
*/
|
|
|
|
|
disposeElement(_element: ITreeNode<ITestTreeElement, FuzzyScore>, _: number, templateData: TestTemplateData): void {
|
|
|
|
|
dispose(templateData.elementDisposable);
|
|
|
|
|
templateData.elementDisposable = [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fillActionBar(element: ITestTreeElement, data: TestTemplateData) {
|
|
|
|
|
const actions = getTestItemActions(this.instantiationService, this.contextKeyService, this.menuService, element);
|
|
|
|
|
data.elementDisposable.push(actions);
|
|
|
|
|
data.actionBar.clear();
|
|
|
|
|
data.actionBar.push(actions.value.primary, { icon: true, label: false });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getTestItemActions = (instantionService: IInstantiationService, contextKeyService: IContextKeyService, menuService: IMenuService, element: ITestTreeElement) => {
|
|
|
|
|
const contextOverlay = contextKeyService.createOverlay([
|
|
|
|
|
['view', Testing.ExplorerViewId],
|
|
|
|
|
[TestingContextKeys.testItemExtId.key, element.test?.item.extId]
|
|
|
|
|
]);
|
|
|
|
|
const menu = menuService.createMenu(MenuId.TestItem, contextOverlay);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const primary: IAction[] = [];
|
|
|
|
|
const running = element.state === TestRunState.Running;
|
|
|
|
|
if (!Iterable.isEmpty(element.runnable)) {
|
|
|
|
|
primary.push(instantionService.createInstance(RunAction, element.runnable, running));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Iterable.isEmpty(element.debuggable)) {
|
|
|
|
|
primary.push(instantionService.createInstance(DebugAction, element.debuggable, running));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const secondary: IAction[] = [];
|
|
|
|
|
const result = { primary, secondary };
|
|
|
|
|
const actionsDisposable = createAndFillInActionBarActions(menu, {
|
|
|
|
|
arg: element.test?.item.extId,
|
|
|
|
|
shouldForwardArgs: true,
|
|
|
|
|
}, result, g => /^inline/.test(g));
|
|
|
|
|
|
|
|
|
|
return { value: result, dispose: () => actionsDisposable.dispose };
|
|
|
|
|
} finally {
|
|
|
|
|
menu.dispose();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type CountSummary = ReturnType<typeof collectCounts>;
|
|
|
|
|
|
|
|
|
|
const collectCounts = (count: TestStateCount) => {
|
|
|
|
|