mirror of
https://github.com/Microsoft/vscode
synced 2024-09-13 13:46:13 +00:00
testing: add test actions to editor context menus (#167091)
* testing: add test actions to editor context menus Fixes #130548 * add an efficient test by uri lookup, and editor context key
This commit is contained in:
parent
afe8a3475b
commit
387bab6993
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -18,6 +18,7 @@
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/node_modules": true,
|
"**/node_modules": true,
|
||||||
"**/bower_components": true,
|
"**/bower_components": true,
|
||||||
|
"cli/target/**": true,
|
||||||
".build/**": true,
|
".build/**": true,
|
||||||
"out/**": true,
|
"out/**": true,
|
||||||
"out-build/**": true,
|
"out-build/**": true,
|
||||||
|
|
|
@ -727,7 +727,7 @@ interface MirroredCollectionTestItem extends IncrementalTestCollectionItem {
|
||||||
depth: number;
|
depth: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollectionTestItem> {
|
class MirroredChangeCollector implements IncrementalChangeCollector<MirroredCollectionTestItem> {
|
||||||
private readonly added = new Set<MirroredCollectionTestItem>();
|
private readonly added = new Set<MirroredCollectionTestItem>();
|
||||||
private readonly updated = new Set<MirroredCollectionTestItem>();
|
private readonly updated = new Set<MirroredCollectionTestItem>();
|
||||||
private readonly removed = new Set<MirroredCollectionTestItem>();
|
private readonly removed = new Set<MirroredCollectionTestItem>();
|
||||||
|
@ -739,20 +739,19 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private readonly emitter: Emitter<vscode.TestsChangeEvent>) {
|
constructor(private readonly emitter: Emitter<vscode.TestsChangeEvent>) {
|
||||||
super();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
public override add(node: MirroredCollectionTestItem): void {
|
public add(node: MirroredCollectionTestItem): void {
|
||||||
this.added.add(node);
|
this.added.add(node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
public override update(node: MirroredCollectionTestItem): void {
|
public update(node: MirroredCollectionTestItem): void {
|
||||||
Object.assign(node.revived, Convert.TestItem.toPlain(node.item));
|
Object.assign(node.revived, Convert.TestItem.toPlain(node.item));
|
||||||
if (!this.added.has(node)) {
|
if (!this.added.has(node)) {
|
||||||
this.updated.add(node);
|
this.updated.add(node);
|
||||||
|
@ -760,9 +759,9 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
public override remove(node: MirroredCollectionTestItem): void {
|
public remove(node: MirroredCollectionTestItem): void {
|
||||||
if (this.added.has(node)) {
|
if (this.added.has(node)) {
|
||||||
this.added.delete(node);
|
this.added.delete(node);
|
||||||
return;
|
return;
|
||||||
|
@ -780,7 +779,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
public getChangeEvent(): vscode.TestsChangeEvent {
|
public getChangeEvent(): vscode.TestsChangeEvent {
|
||||||
const { added, updated, removed } = this;
|
const { added, updated, removed } = this;
|
||||||
|
@ -791,7 +790,7 @@ class MirroredChangeCollector extends IncrementalChangeCollector<MirroredCollect
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override complete() {
|
public complete() {
|
||||||
if (!this.isEmpty) {
|
if (!this.isEmpty) {
|
||||||
this.emitter.fire(this.getChangeEvent());
|
this.emitter.fire(this.getChangeEvent());
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,8 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
|
||||||
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
|
import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite';
|
||||||
import { getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
|
import { getTestingConfiguration, TestingConfigKeys } from 'vs/workbench/contrib/testing/common/configuration';
|
||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { MessageController } from 'vs/editor/contrib/message/browser/messageController';
|
||||||
|
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||||
|
|
||||||
const category = Categories.Test;
|
const category = Categories.Test;
|
||||||
|
|
||||||
|
@ -669,10 +671,15 @@ abstract class ExecuteTestAtCursor extends Action2 {
|
||||||
constructor(options: IAction2Options, protected readonly group: TestRunProfileBitset) {
|
constructor(options: IAction2Options, protected readonly group: TestRunProfileBitset) {
|
||||||
super({
|
super({
|
||||||
...options,
|
...options,
|
||||||
menu: {
|
menu: [{
|
||||||
id: MenuId.CommandPalette,
|
id: MenuId.CommandPalette,
|
||||||
when: hasAnyTestProvider,
|
when: hasAnyTestProvider,
|
||||||
},
|
}, {
|
||||||
|
id: MenuId.EditorContext,
|
||||||
|
group: 'testing',
|
||||||
|
order: group === TestRunProfileBitset.Run ? ActionOrder.Run : ActionOrder.Debug,
|
||||||
|
when: ContextKeyExpr.and(TestingContextKeys.activeEditorHasTests, TestingContextKeys.capabilityToContextKey[group]),
|
||||||
|
}]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -749,6 +756,8 @@ abstract class ExecuteTestAtCursor extends Action2 {
|
||||||
group: this.group,
|
group: this.group,
|
||||||
tests: bestNodes.length ? bestNodes : bestNodesBefore,
|
tests: bestNodes.length ? bestNodes : bestNodesBefore,
|
||||||
});
|
});
|
||||||
|
} else if (isCodeEditor(activeControl)) {
|
||||||
|
MessageController.get(activeControl)?.showMessage(localize('noTestsAtCursor', "No tests found here"), position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -787,10 +796,16 @@ abstract class ExecuteTestsInCurrentFile extends Action2 {
|
||||||
constructor(options: IAction2Options, protected readonly group: TestRunProfileBitset) {
|
constructor(options: IAction2Options, protected readonly group: TestRunProfileBitset) {
|
||||||
super({
|
super({
|
||||||
...options,
|
...options,
|
||||||
menu: {
|
menu: [{
|
||||||
id: MenuId.CommandPalette,
|
id: MenuId.CommandPalette,
|
||||||
when: TestingContextKeys.capabilityToContextKey[group].isEqualTo(true),
|
when: TestingContextKeys.capabilityToContextKey[group].isEqualTo(true),
|
||||||
},
|
}, {
|
||||||
|
id: MenuId.EditorContext,
|
||||||
|
group: 'testing',
|
||||||
|
// add 0.1 to be after the "at cursor" commands
|
||||||
|
order: (group === TestRunProfileBitset.Run ? ActionOrder.Run : ActionOrder.Debug) + 0.1,
|
||||||
|
when: ContextKeyExpr.and(TestingContextKeys.activeEditorHasTests, TestingContextKeys.capabilityToContextKey[group]),
|
||||||
|
}],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -830,6 +845,10 @@ abstract class ExecuteTestsInCurrentFile extends Action2 {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isCodeEditor(control)) {
|
||||||
|
MessageController.get(control)?.showMessage(localize('noTestsInFile', "No tests found in this file"), position);
|
||||||
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -200,8 +200,8 @@ export class TestingDecorationService extends Disposable implements ITestingDeco
|
||||||
const map = model.changeDecorations(accessor => {
|
const map = model.changeDecorations(accessor => {
|
||||||
const newDecorations: ITestDecoration[] = [];
|
const newDecorations: ITestDecoration[] = [];
|
||||||
const runDecorations = new TestDecorations<{ line: number; id: ''; test: IncrementalTestCollectionItem; resultItem: TestResultItem | undefined }>();
|
const runDecorations = new TestDecorations<{ line: number; id: ''; test: IncrementalTestCollectionItem; resultItem: TestResultItem | undefined }>();
|
||||||
for (const test of this.testService.collection.all) {
|
for (const test of this.testService.collection.getNodeByUrl(model.uri)) {
|
||||||
if (!test.item.range || test.item.uri?.toString() !== uriStr) {
|
if (!test.item.range) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,14 @@
|
||||||
|
|
||||||
import { Emitter } from 'vs/base/common/event';
|
import { Emitter } from 'vs/base/common/event';
|
||||||
import { Iterable } from 'vs/base/common/iterator';
|
import { Iterable } from 'vs/base/common/iterator';
|
||||||
import { AbstractIncrementalTestCollection, IncrementalTestCollectionItem, InternalTestItem, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testTypes';
|
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestDiffOpType, TestsDiff } from 'vs/workbench/contrib/testing/common/testTypes';
|
||||||
import { IMainThreadTestCollection } from 'vs/workbench/contrib/testing/common/testService';
|
import { IMainThreadTestCollection } from 'vs/workbench/contrib/testing/common/testService';
|
||||||
|
import { ResourceMap } from 'vs/base/common/map';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
|
||||||
export class MainThreadTestCollection extends AbstractIncrementalTestCollection<IncrementalTestCollectionItem> implements IMainThreadTestCollection {
|
export class MainThreadTestCollection extends AbstractIncrementalTestCollection<IncrementalTestCollectionItem> implements IMainThreadTestCollection {
|
||||||
|
private testsByUrl = new ResourceMap<Set<IncrementalTestCollectionItem>>();
|
||||||
|
|
||||||
private busyProvidersChangeEmitter = new Emitter<number>();
|
private busyProvidersChangeEmitter = new Emitter<number>();
|
||||||
private expandPromises = new WeakMap<IncrementalTestCollectionItem, {
|
private expandPromises = new WeakMap<IncrementalTestCollectionItem, {
|
||||||
pendingLvl: number;
|
pendingLvl: number;
|
||||||
|
@ -78,6 +82,13 @@ export class MainThreadTestCollection extends AbstractIncrementalTestCollection<
|
||||||
return this.items.get(id);
|
return this.items.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
public getNodeByUrl(uri: URI): Iterable<IncrementalTestCollectionItem> {
|
||||||
|
return this.testsByUrl.get(uri) || Iterable.empty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
@ -138,6 +149,40 @@ export class MainThreadTestCollection extends AbstractIncrementalTestCollection<
|
||||||
return { ...internal, children: new Set() };
|
return { ...internal, children: new Set() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly changeCollector: IncrementalChangeCollector<IncrementalTestCollectionItem> = {
|
||||||
|
add: node => {
|
||||||
|
if (!node.item.uri) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const s = this.testsByUrl.get(node.item.uri);
|
||||||
|
if (!s) {
|
||||||
|
this.testsByUrl.set(node.item.uri, new Set([node]));
|
||||||
|
} else {
|
||||||
|
s.add(node);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remove: node => {
|
||||||
|
if (!node.item.uri) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const s = this.testsByUrl.get(node.item.uri);
|
||||||
|
if (!s) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s.delete(node);
|
||||||
|
if (s.size === 0) {
|
||||||
|
this.testsByUrl.delete(node.item.uri);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override createChangeCollector(): IncrementalChangeCollector<IncrementalTestCollectionItem> {
|
||||||
|
return this.changeCollector;
|
||||||
|
}
|
||||||
|
|
||||||
private *getIterator() {
|
private *getIterator() {
|
||||||
const queue = [this.rootIds];
|
const queue = [this.rootIds];
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
|
|
|
@ -61,6 +61,12 @@ export interface IMainThreadTestCollection extends AbstractIncrementalTestCollec
|
||||||
*/
|
*/
|
||||||
getNodeById(id: string): IncrementalTestCollectionItem | undefined;
|
getNodeById(id: string): IncrementalTestCollectionItem | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all tests that have the given URL. Tests returned from this
|
||||||
|
* method are *not* in any particular order.
|
||||||
|
*/
|
||||||
|
getNodeByUrl(uri: URI): Iterable<IncrementalTestCollectionItem>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests that children be revealed for the given test. "Levels" may
|
* Requests that children be revealed for the given test. "Levels" may
|
||||||
* be infinite.
|
* be infinite.
|
||||||
|
|
|
@ -41,6 +41,8 @@ export class TestService extends Disposable implements ITestService {
|
||||||
private readonly providerCount: IContextKey<number>;
|
private readonly providerCount: IContextKey<number>;
|
||||||
private readonly canRefreshTests: IContextKey<boolean>;
|
private readonly canRefreshTests: IContextKey<boolean>;
|
||||||
private readonly isRefreshingTests: IContextKey<boolean>;
|
private readonly isRefreshingTests: IContextKey<boolean>;
|
||||||
|
private readonly activeEditorHasTests: IContextKey<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancellation for runs requested by the user being managed by the UI.
|
* Cancellation for runs requested by the user being managed by the UI.
|
||||||
* Test runs initiated by extensions are not included here.
|
* Test runs initiated by extensions are not included here.
|
||||||
|
@ -97,6 +99,9 @@ export class TestService extends Disposable implements ITestService {
|
||||||
this.providerCount = TestingContextKeys.providerCount.bindTo(contextKeyService);
|
this.providerCount = TestingContextKeys.providerCount.bindTo(contextKeyService);
|
||||||
this.canRefreshTests = TestingContextKeys.canRefreshTests.bindTo(contextKeyService);
|
this.canRefreshTests = TestingContextKeys.canRefreshTests.bindTo(contextKeyService);
|
||||||
this.isRefreshingTests = TestingContextKeys.isRefreshingTests.bindTo(contextKeyService);
|
this.isRefreshingTests = TestingContextKeys.isRefreshingTests.bindTo(contextKeyService);
|
||||||
|
this.activeEditorHasTests = TestingContextKeys.activeEditorHasTests.bindTo(contextKeyService);
|
||||||
|
|
||||||
|
this._register(editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -228,6 +233,7 @@ export class TestService extends Disposable implements ITestService {
|
||||||
public publishDiff(_controllerId: string, diff: TestsDiff) {
|
public publishDiff(_controllerId: string, diff: TestsDiff) {
|
||||||
this.willProcessDiffEmitter.fire(diff);
|
this.willProcessDiffEmitter.fire(diff);
|
||||||
this.collection.apply(diff);
|
this.collection.apply(diff);
|
||||||
|
this.updateEditorContextKeys();
|
||||||
this.didProcessDiffEmitter.fire(diff);
|
this.didProcessDiffEmitter.fire(diff);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,6 +319,15 @@ export class TestService extends Disposable implements ITestService {
|
||||||
return disposable;
|
return disposable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateEditorContextKeys() {
|
||||||
|
const uri = this.editorService.activeEditor?.resource;
|
||||||
|
if (uri) {
|
||||||
|
this.activeEditorHasTests.set(!Iterable.isEmpty(this.collection.getNodeByUrl(uri)));
|
||||||
|
} else {
|
||||||
|
this.activeEditorHasTests.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async saveAllBeforeTest(req: ResolvedTestRunRequest, configurationService: IConfigurationService = this.configurationService, editorService: IEditorService = this.editorService): Promise<void> {
|
private async saveAllBeforeTest(req: ResolvedTestRunRequest, configurationService: IConfigurationService = this.configurationService, editorService: IEditorService = this.editorService): Promise<void> {
|
||||||
if (req.isUiTriggered === false) {
|
if (req.isUiTriggered === false) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -626,26 +626,26 @@ export interface IncrementalTestCollectionItem extends InternalTestItem {
|
||||||
* and called with diff changes as they're applied. This is used in the
|
* and called with diff changes as they're applied. This is used in the
|
||||||
* ext host to create a cohesive change event from a diff.
|
* ext host to create a cohesive change event from a diff.
|
||||||
*/
|
*/
|
||||||
export class IncrementalChangeCollector<T> {
|
export interface IncrementalChangeCollector<T> {
|
||||||
/**
|
/**
|
||||||
* A node was added.
|
* A node was added.
|
||||||
*/
|
*/
|
||||||
public add(node: T): void { }
|
add?(node: T): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A node in the collection was updated.
|
* A node in the collection was updated.
|
||||||
*/
|
*/
|
||||||
public update(node: T): void { }
|
update?(node: T): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A node was removed.
|
* A node was removed.
|
||||||
*/
|
*/
|
||||||
public remove(node: T, isNestedOperation: boolean): void { }
|
remove?(node: T, isNestedOperation: boolean): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the diff has been applied.
|
* Called when the diff has been applied.
|
||||||
*/
|
*/
|
||||||
public complete(): void { }
|
complete?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -687,80 +687,17 @@ export abstract class AbstractIncrementalTestCollection<T extends IncrementalTes
|
||||||
|
|
||||||
for (const op of diff) {
|
for (const op of diff) {
|
||||||
switch (op.op) {
|
switch (op.op) {
|
||||||
case TestDiffOpType.Add: {
|
case TestDiffOpType.Add:
|
||||||
const internalTest = InternalTestItem.deserialize(op.item);
|
this.add(InternalTestItem.deserialize(op.item), changes);
|
||||||
const parentId = TestId.parentId(internalTest.item.extId)?.toString();
|
|
||||||
if (!parentId) {
|
|
||||||
const created = this.createItem(internalTest);
|
|
||||||
this.roots.add(created);
|
|
||||||
this.items.set(internalTest.item.extId, created);
|
|
||||||
changes.add(created);
|
|
||||||
} else if (this.items.has(parentId)) {
|
|
||||||
const parent = this.items.get(parentId)!;
|
|
||||||
parent.children.add(internalTest.item.extId);
|
|
||||||
const created = this.createItem(internalTest, parent);
|
|
||||||
this.items.set(internalTest.item.extId, created);
|
|
||||||
changes.add(created);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (internalTest.expand === TestItemExpandState.BusyExpanding) {
|
|
||||||
this.busyControllerCount++;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case TestDiffOpType.Update: {
|
case TestDiffOpType.Update:
|
||||||
const patch = ITestItemUpdate.deserialize(op.item);
|
this.update(ITestItemUpdate.deserialize(op.item), changes);
|
||||||
const existing = this.items.get(patch.extId);
|
|
||||||
if (!existing) {
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
if (patch.expand !== undefined) {
|
case TestDiffOpType.Remove:
|
||||||
if (existing.expand === TestItemExpandState.BusyExpanding) {
|
this.remove(op.itemId, changes);
|
||||||
this.busyControllerCount--;
|
|
||||||
}
|
|
||||||
if (patch.expand === TestItemExpandState.BusyExpanding) {
|
|
||||||
this.busyControllerCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
applyTestItemUpdate(existing, patch);
|
|
||||||
changes.update(existing);
|
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
case TestDiffOpType.Remove: {
|
|
||||||
const toRemove = this.items.get(op.itemId);
|
|
||||||
if (!toRemove) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parentId = TestId.parentId(toRemove.item.extId)?.toString();
|
|
||||||
if (parentId) {
|
|
||||||
const parent = this.items.get(parentId)!;
|
|
||||||
parent.children.delete(toRemove.item.extId);
|
|
||||||
} else {
|
|
||||||
this.roots.delete(toRemove);
|
|
||||||
}
|
|
||||||
|
|
||||||
const queue: Iterable<string>[] = [[op.itemId]];
|
|
||||||
while (queue.length) {
|
|
||||||
for (const itemId of queue.pop()!) {
|
|
||||||
const existing = this.items.get(itemId);
|
|
||||||
if (existing) {
|
|
||||||
queue.push(existing.children);
|
|
||||||
this.items.delete(itemId);
|
|
||||||
changes.remove(existing, existing !== toRemove);
|
|
||||||
|
|
||||||
if (existing.expand === TestItemExpandState.BusyExpanding) {
|
|
||||||
this.busyControllerCount--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case TestDiffOpType.Retire:
|
case TestDiffOpType.Retire:
|
||||||
this.retireTest(op.itemId);
|
this.retireTest(op.itemId);
|
||||||
|
@ -780,7 +717,85 @@ export abstract class AbstractIncrementalTestCollection<T extends IncrementalTes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changes.complete();
|
changes.complete?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected add(item: InternalTestItem, changes: IncrementalChangeCollector<T>
|
||||||
|
) {
|
||||||
|
const parentId = TestId.parentId(item.item.extId)?.toString();
|
||||||
|
let created: T;
|
||||||
|
if (!parentId) {
|
||||||
|
created = this.createItem(item);
|
||||||
|
this.roots.add(created);
|
||||||
|
this.items.set(item.item.extId, created);
|
||||||
|
} else if (this.items.has(parentId)) {
|
||||||
|
const parent = this.items.get(parentId)!;
|
||||||
|
parent.children.add(item.item.extId);
|
||||||
|
created = this.createItem(item, parent);
|
||||||
|
this.items.set(item.item.extId, created);
|
||||||
|
} else {
|
||||||
|
console.error(`Test with unknown parent ID: ${JSON.stringify(item)}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
changes.add?.(created);
|
||||||
|
if (item.expand === TestItemExpandState.BusyExpanding) {
|
||||||
|
this.busyControllerCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected update(patch: ITestItemUpdate, changes: IncrementalChangeCollector<T>
|
||||||
|
) {
|
||||||
|
const existing = this.items.get(patch.extId);
|
||||||
|
if (!existing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (patch.expand !== undefined) {
|
||||||
|
if (existing.expand === TestItemExpandState.BusyExpanding) {
|
||||||
|
this.busyControllerCount--;
|
||||||
|
}
|
||||||
|
if (patch.expand === TestItemExpandState.BusyExpanding) {
|
||||||
|
this.busyControllerCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
applyTestItemUpdate(existing, patch);
|
||||||
|
changes.update?.(existing);
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected remove(itemId: string, changes: IncrementalChangeCollector<T>) {
|
||||||
|
const toRemove = this.items.get(itemId);
|
||||||
|
if (!toRemove) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parentId = TestId.parentId(toRemove.item.extId)?.toString();
|
||||||
|
if (parentId) {
|
||||||
|
const parent = this.items.get(parentId)!;
|
||||||
|
parent.children.delete(toRemove.item.extId);
|
||||||
|
} else {
|
||||||
|
this.roots.delete(toRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
const queue: Iterable<string>[] = [[itemId]];
|
||||||
|
while (queue.length) {
|
||||||
|
for (const itemId of queue.pop()!) {
|
||||||
|
const existing = this.items.get(itemId);
|
||||||
|
if (existing) {
|
||||||
|
queue.push(existing.children);
|
||||||
|
this.items.delete(itemId);
|
||||||
|
changes.remove?.(existing, existing !== toRemove);
|
||||||
|
|
||||||
|
if (existing.expand === TestItemExpandState.BusyExpanding) {
|
||||||
|
this.busyControllerCount--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -802,8 +817,8 @@ export abstract class AbstractIncrementalTestCollection<T extends IncrementalTes
|
||||||
/**
|
/**
|
||||||
* Called before a diff is applied to create a new change collector.
|
* Called before a diff is applied to create a new change collector.
|
||||||
*/
|
*/
|
||||||
protected createChangeCollector() {
|
protected createChangeCollector(): IncrementalChangeCollector<T> {
|
||||||
return new IncrementalChangeCollector<T>();
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,6 +17,7 @@ export namespace TestingContextKeys {
|
||||||
export const hasCoverableTests = new RawContextKey('testing.hasCoverableTests', false, { type: 'boolean', description: localize('testing.hasCoverableTests', 'Indicates whether any test controller has registered a coverage configuration') });
|
export const hasCoverableTests = new RawContextKey('testing.hasCoverableTests', false, { type: 'boolean', description: localize('testing.hasCoverableTests', 'Indicates whether any test controller has registered a coverage configuration') });
|
||||||
export const hasNonDefaultProfile = new RawContextKey('testing.hasNonDefaultProfile', false, { type: 'boolean', description: localize('testing.hasNonDefaultConfig', 'Indicates whether any test controller has registered a non-default configuration') });
|
export const hasNonDefaultProfile = new RawContextKey('testing.hasNonDefaultProfile', false, { type: 'boolean', description: localize('testing.hasNonDefaultConfig', 'Indicates whether any test controller has registered a non-default configuration') });
|
||||||
export const hasConfigurableProfile = new RawContextKey('testing.hasConfigurableProfile', false, { type: 'boolean', description: localize('testing.hasConfigurableConfig', 'Indicates whether any test configuration can be configured') });
|
export const hasConfigurableProfile = new RawContextKey('testing.hasConfigurableProfile', false, { type: 'boolean', description: localize('testing.hasConfigurableConfig', 'Indicates whether any test configuration can be configured') });
|
||||||
|
export const activeEditorHasTests = new RawContextKey('testing.activeEditorHasTests', false, { type: 'boolean', description: localize('testing.activeEditorHasTests', 'Indicates whether any tests are present in the current editor') });
|
||||||
|
|
||||||
export const capabilityToContextKey: { [K in TestRunProfileBitset]: RawContextKey<boolean> } = {
|
export const capabilityToContextKey: { [K in TestRunProfileBitset]: RawContextKey<boolean> } = {
|
||||||
[TestRunProfileBitset.Run]: hasRunnableTests,
|
[TestRunProfileBitset.Run]: hasRunnableTests,
|
||||||
|
|
Loading…
Reference in a new issue