parent
ceb9bcfb57
commit
7a034baa92
3 changed files with 136 additions and 79 deletions
|
@ -1700,7 +1700,7 @@ export namespace TestItem {
|
|||
id: TestId.fromString(item.extId).localId,
|
||||
label: item.label,
|
||||
uri: URI.revive(item.uri),
|
||||
tags: item.tags.map(t => {
|
||||
tags: (item.tags || []).map(t => {
|
||||
const { tagId } = TestTag.denamespace(t);
|
||||
return new types.TestTag(tagId, tagId);
|
||||
}),
|
||||
|
|
|
@ -18,7 +18,6 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
|
|||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { CATEGORIES } from 'vs/workbench/common/actions';
|
||||
import { FocusedViewContext } from 'vs/workbench/common/views';
|
||||
|
@ -27,7 +26,7 @@ import { REVEAL_IN_EXPLORER_COMMAND_ID } from 'vs/workbench/contrib/files/browse
|
|||
import { IActionableTestTreeElement, TestItemTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/index';
|
||||
import * as icons from 'vs/workbench/contrib/testing/browser/icons';
|
||||
import { ITestExplorerFilterState } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter';
|
||||
import type { TestingExplorerView, TestingExplorerViewModel } from 'vs/workbench/contrib/testing/browser/testingExplorerView';
|
||||
import type { TestingExplorerView } from 'vs/workbench/contrib/testing/browser/testingExplorerView';
|
||||
import { ITestingOutputTerminalService } from 'vs/workbench/contrib/testing/browser/testingOutputTerminalService';
|
||||
import { TestExplorerViewMode, TestExplorerViewSorting, Testing } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { InternalTestItem, ITestItem, ITestRunProfile, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
|
@ -263,15 +262,25 @@ export class ConfigureTestProfilesAction extends Action2 {
|
|||
}
|
||||
|
||||
abstract class ExecuteSelectedAction extends ViewAction<TestingExplorerView> {
|
||||
constructor(id: string, title: string, icon: ThemeIcon, private readonly group: TestRunProfileBitset) {
|
||||
constructor(options: IAction2Options, private readonly group: TestRunProfileBitset) {
|
||||
super({
|
||||
id,
|
||||
title,
|
||||
icon,
|
||||
viewId: Testing.ExplorerViewId,
|
||||
f1: true,
|
||||
...options,
|
||||
menu: [{
|
||||
id: MenuId.ViewTitle,
|
||||
order: group === TestRunProfileBitset.Run
|
||||
? ActionOrder.Run
|
||||
: group === TestRunProfileBitset.Debug
|
||||
? ActionOrder.Debug
|
||||
: ActionOrder.Coverage,
|
||||
group: 'navigation',
|
||||
when: ContextKeyAndExpr.create([
|
||||
ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId),
|
||||
TestingContextKeys.isRunning.isEqualTo(false),
|
||||
TestingContextKeys.capabilityToContextKey[group].isEqualTo(true),
|
||||
])
|
||||
}],
|
||||
category,
|
||||
precondition: FocusedViewContext.isEqualTo(Testing.ExplorerViewId),
|
||||
viewId: Testing.ExplorerViewId,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -279,26 +288,8 @@ abstract class ExecuteSelectedAction extends ViewAction<TestingExplorerView> {
|
|||
* @override
|
||||
*/
|
||||
public runInView(accessor: ServicesAccessor, view: TestingExplorerView): Promise<ITestResult | undefined> {
|
||||
const tests = this.getActionableTests(accessor.get(ITestService), view.viewModel);
|
||||
if (!tests.length) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return accessor.get(ITestService).runTests({ tests, group: this.group });
|
||||
}
|
||||
|
||||
private getActionableTests(testService: ITestService, viewModel: TestingExplorerViewModel) {
|
||||
const selected = viewModel.getSelectedTests();
|
||||
let tests: InternalTestItem[];
|
||||
if (!selected.length) {
|
||||
tests = [...testService.collection.rootItems];
|
||||
} else {
|
||||
tests = selected
|
||||
.map(treeElement => treeElement instanceof TestItemTreeElement ? treeElement.test : undefined)
|
||||
.filter(isDefined);
|
||||
}
|
||||
|
||||
return tests;
|
||||
const { include, exclude } = view.getSelectedOrVisibleItems();
|
||||
return accessor.get(ITestService).runTests({ tests: include, exclude, group: this.group });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -306,24 +297,23 @@ export class RunSelectedAction extends ExecuteSelectedAction {
|
|||
public static readonly ID = 'testing.runSelected';
|
||||
|
||||
constructor() {
|
||||
super(
|
||||
RunSelectedAction.ID,
|
||||
localize('runSelectedTests', 'Run Selected Tests'),
|
||||
icons.testingRunIcon,
|
||||
TestRunProfileBitset.Run,
|
||||
);
|
||||
super({
|
||||
id: RunSelectedAction.ID,
|
||||
title: localize('runSelectedTests', 'Run Tests'),
|
||||
icon: icons.testingRunAllIcon,
|
||||
}, TestRunProfileBitset.Run);
|
||||
}
|
||||
}
|
||||
|
||||
export class DebugSelectedAction extends ExecuteSelectedAction {
|
||||
public static readonly ID = 'testing.debugSelected';
|
||||
constructor() {
|
||||
super(
|
||||
DebugSelectedAction.ID,
|
||||
localize('debugSelectedTests', 'Debug Selected Tests'),
|
||||
icons.testingDebugIcon,
|
||||
TestRunProfileBitset.Debug,
|
||||
);
|
||||
|
||||
super({
|
||||
id: DebugSelectedAction.ID,
|
||||
title: localize('debugSelectedTests', 'Debug Tests'),
|
||||
icon: icons.testingDebugAllIcon,
|
||||
}, TestRunProfileBitset.Debug);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -343,19 +333,6 @@ abstract class RunOrDebugAllTestsAction extends Action2 {
|
|||
...options,
|
||||
category,
|
||||
menu: [{
|
||||
id: MenuId.ViewTitle,
|
||||
order: group === TestRunProfileBitset.Run
|
||||
? ActionOrder.Run
|
||||
: group === TestRunProfileBitset.Debug
|
||||
? ActionOrder.Debug
|
||||
: ActionOrder.Coverage,
|
||||
group: 'navigation',
|
||||
when: ContextKeyAndExpr.create([
|
||||
ContextKeyEqualsExpr.create('view', Testing.ExplorerViewId),
|
||||
TestingContextKeys.isRunning.isEqualTo(false),
|
||||
TestingContextKeys.capabilityToContextKey[group].isEqualTo(true),
|
||||
])
|
||||
}, {
|
||||
id: MenuId.CommandPalette,
|
||||
when: TestingContextKeys.capabilityToContextKey[group].isEqualTo(true),
|
||||
}]
|
||||
|
|
|
@ -48,7 +48,7 @@ import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
|
|||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IViewDescriptorService, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { HierarchicalByLocationProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByLocation';
|
||||
import { HierarchicalByNameProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName';
|
||||
import { ByNameTestItemElement, HierarchicalByNameProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName';
|
||||
import { ITestTreeProjection, TestExplorerTreeElement, TestItemTreeElement, TestTreeErrorMessage } from 'vs/workbench/contrib/testing/browser/explorerProjections/index';
|
||||
import { getTestItemContextOverlay } from 'vs/workbench/contrib/testing/browser/explorerProjections/testItemContextOverlay';
|
||||
import * as icons from 'vs/workbench/contrib/testing/browser/icons';
|
||||
|
@ -56,17 +56,17 @@ import { ITestExplorerFilterState, TestExplorerFilterState, TestingExplorerFilte
|
|||
import { ITestingProgressUiService } from 'vs/workbench/contrib/testing/browser/testingProgressUiService';
|
||||
import { getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
|
||||
import { labelForTestInState, TestExplorerStateFilter, TestExplorerViewMode, TestExplorerViewSorting, Testing, testStateNames } from 'vs/workbench/contrib/testing/common/constants';
|
||||
import { ITestRunProfile, TestItemExpandState, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { InternalTestItem, ITestRunProfile, TestItemExpandState, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
|
||||
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
|
||||
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
|
||||
import { ITestingPeekOpener } from 'vs/workbench/contrib/testing/common/testingPeekOpener';
|
||||
import { cmpPriority, isFailedState, isStateWithResult } from 'vs/workbench/contrib/testing/common/testingStates';
|
||||
import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService';
|
||||
import { canUseProfileWithTest, ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService';
|
||||
import { TestResultItemChangeReason } from 'vs/workbench/contrib/testing/common/testResult';
|
||||
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
|
||||
import { IMainThreadTestCollection, ITestService, testCollectionIsEmpty } from 'vs/workbench/contrib/testing/common/testService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ConfigureTestProfilesAction, DebugAllAction, RunAllAction, SelectDefaultTestProfiles } from './testExplorerActions';
|
||||
import { ConfigureTestProfilesAction, DebugSelectedAction, RunSelectedAction, SelectDefaultTestProfiles } from './testExplorerActions';
|
||||
|
||||
export class TestingExplorerView extends ViewPane {
|
||||
public viewModel!: TestingExplorerViewModel;
|
||||
|
@ -117,6 +117,92 @@ export class TestingExplorerView extends ViewPane {
|
|||
return this.viewModel?.welcomeExperience === WelcomeExperience.ForWorkspace ?? true;
|
||||
}
|
||||
|
||||
public getSelectedOrVisibleItems(profile?: ITestRunProfile) {
|
||||
const projection = this.viewModel.projection.value;
|
||||
if (!projection) {
|
||||
return { include: [], exclude: [] };
|
||||
}
|
||||
|
||||
if (projection instanceof ByNameTestItemElement) {
|
||||
return {
|
||||
include: [...this.testService.collection.rootItems],
|
||||
exclude: [],
|
||||
};
|
||||
}
|
||||
|
||||
// To calculate includes and excludes, we include the first children that
|
||||
// have a majority of their items included too, and then apply exclusions.
|
||||
const include: InternalTestItem[] = [];
|
||||
const exclude: InternalTestItem[] = [];
|
||||
|
||||
const attempt = (element: TestExplorerTreeElement, alreadyIncluded: boolean) => {
|
||||
// sanity check hasElement since updates are debounced and they may exist
|
||||
// but not be rendered yet
|
||||
if (!(element instanceof TestItemTreeElement) || !this.viewModel.tree.hasElement(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the current node is not visible or runnable in the current profile, it's excluded
|
||||
const inTree = this.viewModel.tree.getNode(element);
|
||||
if (!inTree.visible) {
|
||||
if (alreadyIncluded) { exclude.push(element.test); }
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's not already included but most of its children are, then add it
|
||||
// if it can be run under the current profile (when specified)
|
||||
if (
|
||||
// If it's not already included...
|
||||
!alreadyIncluded
|
||||
// And it can be run using the current profile (if any)
|
||||
&& (!profile || canUseProfileWithTest(profile, element.test))
|
||||
// And either it's a leaf node or most children are included, the include it.
|
||||
&& (inTree.children.length === 0 || inTree.visibleChildrenCount * 2 >= inTree.children.length)
|
||||
// And not if we're only showing a single of its children, since it
|
||||
// probably fans out later. (Worse case we'll directly include its single child)
|
||||
&& inTree.visibleChildrenCount !== 1
|
||||
) {
|
||||
include.push(element.test);
|
||||
alreadyIncluded = true;
|
||||
}
|
||||
|
||||
// Recurse ✨
|
||||
for (const child of element.children) {
|
||||
attempt(child, alreadyIncluded);
|
||||
}
|
||||
};
|
||||
|
||||
for (const root of this.testService.collection.rootItems) {
|
||||
const element = projection.getElementByTestId(root.item.extId);
|
||||
if (!element) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (profile && !canUseProfileWithTest(profile, root)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// single controllers won't have visible root ID nodes, handle that case specially
|
||||
if (!this.viewModel.tree.hasElement(element)) {
|
||||
const visibleChildren = [...element.children].reduce((acc, c) =>
|
||||
this.viewModel.tree.hasElement(c) && this.viewModel.tree.getNode(c).visible ? acc + 1 : acc, 0);
|
||||
|
||||
// note we intentionally check children > 0 here, unlike above, since
|
||||
// we don't want to bother dispatching to controllers who have no discovered tests
|
||||
if (element.children.size > 0 && visibleChildren * 2 >= element.children.size) {
|
||||
include.push(element.test);
|
||||
element.children.forEach(c => attempt(c, true));
|
||||
} else {
|
||||
element.children.forEach(c => attempt(c, false));
|
||||
}
|
||||
} else {
|
||||
attempt(element, false);
|
||||
}
|
||||
}
|
||||
|
||||
return { include, exclude };
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
|
@ -168,9 +254,9 @@ export class TestingExplorerView extends ViewPane {
|
|||
switch (action.id) {
|
||||
case Testing.FilterActionId:
|
||||
return this.instantiationService.createInstance(TestingExplorerFilter, action);
|
||||
case RunAllAction.ID:
|
||||
case RunSelectedAction.ID:
|
||||
return this.getRunGroupDropdown(TestRunProfileBitset.Run, action);
|
||||
case DebugAllAction.ID:
|
||||
case DebugSelectedAction.ID:
|
||||
return this.getRunGroupDropdown(TestRunProfileBitset.Debug, action);
|
||||
default:
|
||||
return super.getActionViewItem(action);
|
||||
|
@ -204,16 +290,18 @@ export class TestingExplorerView extends ViewPane {
|
|||
defaults.includes(profile) ? localize('defaultTestProfile', '{0} (Default)', profile.label) : profile.label,
|
||||
undefined,
|
||||
undefined,
|
||||
() => this.testService.runResolvedTests({
|
||||
() => {
|
||||
const { include, exclude } = this.getSelectedOrVisibleItems(profile);
|
||||
this.testService.runResolvedTests({
|
||||
exclude: exclude.map(e => e.item.extId),
|
||||
targets: [{
|
||||
profileGroup: profile.group,
|
||||
profileId: profile.profileId,
|
||||
controllerId: profile.controllerId,
|
||||
testIds: this.getSelectedOrVisibleItems()
|
||||
.filter(i => i.controllerId === profile.controllerId)
|
||||
.map(i => i.item.extId),
|
||||
testIds: include.map(i => i.item.extId),
|
||||
}]
|
||||
}),
|
||||
});
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -254,14 +342,6 @@ export class TestingExplorerView extends ViewPane {
|
|||
super.saveState();
|
||||
}
|
||||
|
||||
/**
|
||||
* If items in the tree are selected, returns them. Otherwise, returns
|
||||
* visible tests.
|
||||
*/
|
||||
private getSelectedOrVisibleItems() {
|
||||
return [...this.testService.collection.rootItems]; // todo
|
||||
}
|
||||
|
||||
private getRunGroupDropdown(group: TestRunProfileBitset, defaultAction: IAction) {
|
||||
const dropdownActions = this.getTestConfigGroupActions(group);
|
||||
if (dropdownActions.length < 2) {
|
||||
|
|
Loading…
Reference in a new issue