testing: allow toggling test output visibility

Also, just show outputs for the last test run.
This commit is contained in:
Connor Peet 2021-08-11 14:48:30 -07:00
parent 76cbce4663
commit 4b80b4cd36
No known key found for this signature in database
GPG key ID: CF8FD2EA0DBC61BD
5 changed files with 100 additions and 44 deletions

View file

@ -9,6 +9,7 @@ suite('Arrays', () => {
test('findFirst', () => {
const array = [1, 4, 5, 7, 55, 59, 60, 61, 64, 69];
console.log('hello');
let idx = arrays.findFirstInSorted(array, e => e >= 0);
assert.strictEqual(array[idx], 1);
@ -20,6 +21,7 @@ suite('Arrays', () => {
idx = arrays.findFirstInSorted(array, e => e >= 61);
assert.strictEqual(array[idx], 61);
console.log('world');
idx = arrays.findFirstInSorted(array, e => e >= 69);
assert.strictEqual(array[idx], 69);
@ -28,7 +30,7 @@ suite('Arrays', () => {
assert.strictEqual(idx, array.length);
idx = arrays.findFirstInSorted([], e => e >= 0);
assert.strictEqual(array[idx], 1);
assert.strictEqual(array[idx], 3);
});
test('quickSelect', () => {

View file

@ -1103,6 +1103,30 @@ export class OpenOutputPeek extends Action2 {
}
}
export class ToggleInlineTestOutput extends Action2 {
public static readonly ID = 'testing.toggleInlineTestOutput';
constructor() {
super({
id: ToggleInlineTestOutput.ID,
title: localize('testing.toggleInlineTestOutput', "Toggle Inline Test Output"),
category,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.US_SEMICOLON, KeyMod.CtrlCmd | KeyCode.KEY_I),
},
menu: {
id: MenuId.CommandPalette,
when: TestingContextKeys.hasAnyResults.isEqualTo(true),
},
});
}
public async run(accessor: ServicesAccessor) {
const testService = accessor.get(ITestService);
testService.showInlineOutput.value = !testService.showInlineOutput.value;
}
}
export const allTestActions = [
// todo: these are disabled until we figure out how we want autorun to work
// AutoRunOffAction,
@ -1136,5 +1160,6 @@ export const allTestActions = [
TestingSortByStatusAction,
TestingViewAsListAction,
TestingViewAsTreeAction,
ToggleInlineTestOutput,
UnhideTestAction,
];

View file

@ -38,6 +38,7 @@ import { IncrementalTestCollectionItem, InternalTestItem, IRichLocation, ITestMe
import { isFailedState, maxPriority } from 'vs/workbench/contrib/testing/common/testingStates';
import { buildTestUri, TestUriType } from 'vs/workbench/contrib/testing/common/testingUri';
import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService';
import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult';
import { ITestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
import { getContextForTestItem, ITestService, testsInFile } from 'vs/workbench/contrib/testing/common/testService';
@ -126,17 +127,12 @@ export class TestingDecorations extends Disposable implements IEditorContributio
}
}));
this._register(Event.any(this.results.onResultsChanged, this.testService.excluded.onTestExclusionsChanged)(() => {
if (this.currentUri) {
this.setDecorations(this.currentUri);
}
}));
this._register(this.testService.onDidProcessDiff(() => {
if (this.currentUri) {
this.setDecorations(this.currentUri);
}
}));
this._register(Event.any(
this.results.onResultsChanged,
this.testService.excluded.onTestExclusionsChanged,
this.testService.showInlineOutput.onDidChange,
this.testService.onDidProcessDiff,
)(() => this.setDecorations(this.currentUri)));
}
private attachModel(uri?: URI) {
@ -165,45 +161,56 @@ export class TestingDecorations extends Disposable implements IEditorContributio
this.setDecorations(uri);
}
private setDecorations(uri: URI): void {
private setDecorations(uri: URI | undefined): void {
if (!uri) {
this.clearDecorations();
return;
}
this.editor.changeDecorations(accessor => {
const newDecorations: ITestDecoration[] = [];
for (const test of this.testService.collection.all) {
const stateLookup = this.results.getStateById(test.item.extId);
if (test.item.range && test.item.uri?.toString() === uri.toString()) {
const line = test.item.range.startLineNumber;
const resultItem = stateLookup?.[1];
const existing = newDecorations.findIndex(d => d instanceof RunTestDecoration && d.line === line);
if (existing !== -1) {
newDecorations[existing] = (newDecorations[existing] as RunTestDecoration).merge(test, resultItem);
} else {
newDecorations.push(this.instantiationService.createInstance(RunSingleTestDecoration, test, this.editor, stateLookup?.[1]));
}
}
if (!stateLookup) {
if (!test.item.range || test.item.uri?.toString() !== uri.toString()) {
continue;
}
const [result, stateItem] = stateLookup;
if (stateItem.retired) {
continue; // do not show decorations for outdated tests
const stateLookup = this.results.getStateById(test.item.extId);
const line = test.item.range.startLineNumber;
const resultItem = stateLookup?.[1];
const existing = newDecorations.findIndex(d => d instanceof RunTestDecoration && d.line === line);
if (existing !== -1) {
newDecorations[existing] = (newDecorations[existing] as RunTestDecoration).merge(test, resultItem);
} else {
newDecorations.push(this.instantiationService.createInstance(RunSingleTestDecoration, test, this.editor, stateLookup?.[1]));
}
}
const lastResult = this.results.results[0];
if (this.testService.showInlineOutput.value && lastResult instanceof LiveTestResult) {
for (const task of lastResult.tasks) {
for (const m of task.otherMessages) {
if (!this.invalidatedMessages.has(m) && hasValidLocation(uri, m)) {
newDecorations.push(this.instantiationService.createInstance(TestMessageDecoration, m, uri, m.location, this.editor));
}
}
}
for (let taskId = 0; taskId < stateItem.tasks.length; taskId++) {
const state = stateItem.tasks[taskId];
for (let i = 0; i < state.messages.length; i++) {
const m = state.messages[i];
if (!this.invalidatedMessages.has(m) && hasValidLocation(uri, m)) {
const uri = m.type === TestMessageType.Info ? undefined : buildTestUri({
type: TestUriType.ResultActualOutput,
messageIndex: i,
taskIndex: taskId,
resultId: result.id,
testExtId: stateItem.item.extId,
});
for (const test of lastResult.tests) {
for (let taskId = 0; taskId < test.tasks.length; taskId++) {
const state = test.tasks[taskId];
for (let i = 0; i < state.messages.length; i++) {
const m = state.messages[i];
if (!this.invalidatedMessages.has(m) && hasValidLocation(uri, m)) {
const uri = m.type === TestMessageType.Info ? undefined : buildTestUri({
type: TestUriType.ResultActualOutput,
messageIndex: i,
taskIndex: taskId,
resultId: lastResult.id,
testExtId: test.item.extId,
});
newDecorations.push(this.instantiationService.createInstance(TestMessageDecoration, m, uri, m.location, this.editor));
newDecorations.push(this.instantiationService.createInstance(TestMessageDecoration, m, uri, m.location, this.editor));
}
}
}
}
@ -218,6 +225,10 @@ export class TestingDecorations extends Disposable implements IEditorContributio
}
private clearDecorations(): void {
if (!this.lastDecorations.length) {
return;
}
this.editor.changeDecorations(accessor => {
for (const decoration of this.lastDecorations) {
accessor.removeDecoration(decoration.id);

View file

@ -7,11 +7,11 @@ import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import * as extpath from 'vs/base/common/extpath';
import { Iterable } from 'vs/base/common/iterator';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { MarshalledId } from 'vs/base/common/marshalling';
import { URI } from 'vs/base/common/uri';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
import { IObservableValue, MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
import { AbstractIncrementalTestCollection, IncrementalTestCollectionItem, InternalTestItem, ITestItemContext, ITestTagDisplayInfo, ResolvedTestRunRequest, RunTestForControllerRequest, TestItemExpandState, TestRunProfileBitset, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestExclusions } from 'vs/workbench/contrib/testing/common/testExclusions';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
@ -225,6 +225,11 @@ export interface ITestService {
*/
readonly onDidProcessDiff: Event<TestsDiff>;
/**
* Whether inline editor decorations should be visible.
*/
readonly showInlineOutput: MutableObservableValue<boolean>;
/**
* Registers an interface that runs tests for the given provider ID.
*/

View file

@ -11,8 +11,11 @@ import { localize } from 'vs/nls';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust';
import { MainThreadTestCollection } from 'vs/workbench/contrib/testing/common/mainThreadTestCollection';
import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue';
import { StoredValue } from 'vs/workbench/contrib/testing/common/storedValue';
import { ResolvedTestRunRequest, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestExclusions } from 'vs/workbench/contrib/testing/common/testExclusions';
import { TestId } from 'vs/workbench/contrib/testing/common/testId';
@ -55,9 +58,19 @@ export class TestService extends Disposable implements ITestService {
*/
public readonly excluded: TestExclusions;
/**
* @inheritdoc
*/
public readonly showInlineOutput = MutableObservableValue.stored(new StoredValue<boolean>({
key: 'inlineTestOutputVisible',
scope: StorageScope.WORKSPACE,
target: StorageTarget.USER
}, this.storage), true);
constructor(
@IContextKeyService contextKeyService: IContextKeyService,
@IInstantiationService instantiationService: IInstantiationService,
@IStorageService private readonly storage: IStorageService,
@ITestProfileService private readonly testProfiles: ITestProfileService,
@INotificationService private readonly notificationService: INotificationService,
@ITestResultService private readonly testResults: ITestResultService,