未验证 提交 f91663e8 编写于 作者: C Connor Peet

testing: add explicit 'peek error' action to testitems

Fixes #125240
上级 3b8141a1
......@@ -30,6 +30,7 @@ import { testingConfiguation } from 'vs/workbench/contrib/testing/common/configu
import { Testing } from 'vs/workbench/contrib/testing/common/constants';
import { identifyTest, ITestIdWithSrc, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
import { ITestProfileService, TestProfileService } from 'vs/workbench/contrib/testing/common/testConfigurationService';
import { TestId, TestPosition } from 'vs/workbench/contrib/testing/common/testId';
import { ITestingAutoRun, TestingAutoRun } from 'vs/workbench/contrib/testing/common/testingAutoRun';
import { TestingContentProvider } from 'vs/workbench/contrib/testing/common/testingContentProvider';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
......@@ -142,9 +143,23 @@ CommandsRegistry.registerCommand({
id: 'vscode.peekTestError',
handler: async (accessor: ServicesAccessor, extId: string) => {
const lookup = accessor.get(ITestResultService).getStateById(extId);
if (lookup) {
accessor.get(ITestingPeekOpener).tryPeekFirstError(lookup[0], lookup[1]);
if (!lookup) {
return false;
}
const [result, ownState] = lookup;
const opener = accessor.get(ITestingPeekOpener);
if (opener.tryPeekFirstError(result, ownState)) { // fast path
return true;
}
for (const test of result.tests) {
if (TestId.compare(ownState.item.extId, test.item.extId) === TestPosition.IsChild && opener.tryPeekFirstError(result, test)) {
return true;
}
}
return false;
}
});
......
......@@ -32,10 +32,10 @@ import { DefaultGutterClickAction, getTestingConfiguration, TestingConfigKeys }
import { labelForTestInState } from 'vs/workbench/contrib/testing/common/constants';
import { identifyTest, IncrementalTestCollectionItem, InternalTestItem, IRichLocation, ITestMessage, ITestRunProfile, TestResultItem, TestResultState, TestRunProfileBitset } from 'vs/workbench/contrib/testing/common/testCollection';
import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testConfigurationService';
import { maxPriority } from 'vs/workbench/contrib/testing/common/testingStates';
import { isFailedState, maxPriority } from 'vs/workbench/contrib/testing/common/testingStates';
import { buildTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { IMainThreadTestCollection, ITestService, testsInFile } from 'vs/workbench/contrib/testing/common/testService';
import { ITestService, testsInFile } from 'vs/workbench/contrib/testing/common/testService';
function isOriginalInDiffEditor(codeEditorService: ICodeEditorService, codeEditor: ICodeEditor): boolean {
const diffEditors = codeEditorService.listDiffEditors();
......@@ -402,7 +402,7 @@ abstract class RunTestDecoration extends Disposable {
/**
* Gets context menu actions relevant for a singel test.
*/
protected getTestContextMenuActions(collection: IMainThreadTestCollection, test: InternalTestItem) {
protected getTestContextMenuActions(test: InternalTestItem, resultItem?: TestResultItem) {
const testActions: IAction[] = [];
const capabilities = this.testProfileService.controllerCapabilities(test.controllerId);
if (capabilities & TestRunProfileBitset.Run) {
......@@ -437,6 +437,11 @@ abstract class RunTestDecoration extends Disposable {
}));
}
if (resultItem && isFailedState(resultItem.computedState)) {
testActions.push(new Action('testing.gutter.peekFailure', localize('peek failure', 'Peek Error'), undefined, undefined,
() => this.commandService.executeCommand('vscode.peekTestError', test.item.extId)));
}
testActions.push(new Action('testing.gutter.reveal', localize('reveal test', 'Reveal in Test Explorer'), undefined, undefined,
() => this.commandService.executeCommand('vscode.revealTestInExplorer', test.item.extId)));
......@@ -476,8 +481,8 @@ class MultiRunTestDecoration extends RunTestDecoration implements ITestDecoratio
allActions.push(new Action('testing.gutter.debugAll', localize('debug all test', 'Debug All Tests'), undefined, undefined, () => this.defaultDebug()));
}
const testSubmenus = this.tests.map(({ test }) =>
new SubmenuAction(test.item.extId, test.item.label, this.getTestContextMenuActions(this.testService.collection, test)));
const testSubmenus = this.tests.map(({ test, resultItem }) =>
new SubmenuAction(test.item.extId, test.item.label, this.getTestContextMenuActions(test, resultItem)));
return Separator.join(allActions, testSubmenus);
}
......@@ -519,7 +524,7 @@ class RunSingleTestDecoration extends RunTestDecoration implements ITestDecorati
}
protected override getContextMenuActions(e: IEditorMouseEvent) {
return this.getTestContextMenuActions(this.testService.collection, this.test);
return this.getTestContextMenuActions(this.test, this.resultItem);
}
protected override defaultRun() {
......
......@@ -470,7 +470,7 @@ export class TestingExplorerViewModel extends Disposable {
}
}));
this._register(this.tree.onDidChangeSelection(async evt => {
this._register(this.tree.onDidChangeSelection(evt => {
if (evt.browserEvent instanceof MouseEvent && (evt.browserEvent.altKey || evt.browserEvent.shiftKey)) {
return; // don't focus when alt-clicking to multi select
}
......@@ -478,7 +478,7 @@ export class TestingExplorerViewModel extends Disposable {
const selected = evt.elements[0];
if (selected && evt.browserEvent && selected instanceof TestItemTreeElement
&& selected.children.size === 0 && selected.test.expand === TestItemExpandState.NotExpandable) {
await this.tryPeekError(selected);
this.tryPeekError(selected);
}
}));
......@@ -629,7 +629,7 @@ export class TestingExplorerViewModel extends Disposable {
/**
* Tries to peek the first test error, if the item is in a failed state.
*/
private async tryPeekError(item: TestItemTreeElement) {
private tryPeekError(item: TestItemTreeElement) {
const lookup = item.test && this.testResults.getStateById(item.test.item.extId);
return lookup && lookup[1].tasks.some(s => isFailedState(s.state))
? this.peekOpener.tryPeekFirstError(lookup[0], lookup[1], { preserveFocus: true })
......
......@@ -154,14 +154,14 @@ export class TestingPeekOpener extends Disposable implements ITestingPeekOpener
}
/** @inheritdoc */
public async tryPeekFirstError(result: ITestResult, test: TestResultItem, options?: Partial<ITextEditorOptions>) {
public tryPeekFirstError(result: ITestResult, test: TestResultItem, options?: Partial<ITextEditorOptions>) {
const candidate = this.getFailedCandidateMessage(test);
if (!candidate) {
return false;
}
const message = candidate.message;
return this.showPeekFromUri({
this.showPeekFromUri({
type: TestUriType.ResultMessage,
documentUri: message.location!.uri,
taskIndex: candidate.taskId,
......@@ -169,6 +169,7 @@ export class TestingPeekOpener extends Disposable implements ITestingPeekOpener
resultId: result.id,
testExtId: test.item.extId,
}, { selection: message.location!.range, ...options });
return true;
}
/** @inheritdoc */
......
......@@ -69,6 +69,25 @@ export class TestId {
return base.toString() + TestIdPathParts.Delimiter + b;
}
/**
* Compares the position of the two ID strings.
*/
public static compare(a: string, b: string) {
if (a === b) {
return TestPosition.IsSame;
}
if (b.startsWith(a + TestIdPathParts.Delimiter)) {
return TestPosition.IsChild;
}
if (a.startsWith(b + TestIdPathParts.Delimiter)) {
return TestPosition.IsParent;
}
return TestPosition.Disconnected;
}
constructor(
public readonly path: readonly string[],
private readonly viewEnd = path.length,
......@@ -124,7 +143,11 @@ export class TestId {
/**
* Compares the other test ID with this one.
*/
public compare(other: TestId) {
public compare(other: TestId | string) {
if (typeof other === 'string') {
return TestId.compare(this.toString(), other);
}
for (let i = 0; i < other.viewEnd && i < this.viewEnd; i++) {
if (other.path[i] !== this.path[i]) {
return TestPosition.Disconnected;
......
......@@ -15,7 +15,7 @@ export interface ITestingPeekOpener {
* Tries to peek the first test error, if the item is in a failed state.
* @returns a boolean indicating whether a peek was opened
*/
tryPeekFirstError(result: ITestResult, test: TestResultItem, options?: Partial<ITextEditorOptions>): Promise<boolean>;
tryPeekFirstError(result: ITestResult, test: TestResultItem, options?: Partial<ITextEditorOptions>): boolean;
/**
* Opens the peek. Shows any available message.
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册