testing: lazily expand root level items, too

This commit is contained in:
Connor Peet 2021-07-16 14:46:04 -07:00
parent fc528bc5c1
commit cf77875d5c
No known key found for this signature in database
GPG key ID: CF8FD2EA0DBC61BD
7 changed files with 47 additions and 46 deletions

View file

@ -2018,21 +2018,20 @@ declare module 'vscode' {
/**
* A function provided by the extension that the editor may call to request
* children of a test item, if the {@link TestItem.canExpand} is `true`.
* When called, the item should discover children and call
* children of a test item, if the {@link TestItem.canResolveChildren} is
* `true`. When called, the item should discover children and call
* {@link vscode.test.createTestItem} as children are discovered.
*
* The item in the explorer will automatically be marked as "busy" until
* the function returns or the returned thenable resolves.
*
* The controller may wish to set up listeners or watchers to update the
* children as files and documents change.
* The handler will be called `undefined` to resolve the controller's
* initial children.
*
* @param item An unresolved test item for which
* children are being requested
*/
// todo@API maybe just `resolveHandler` so that we could extends its usage in the future?
resolveChildrenHandler?: (item: TestItem) => Thenable<void> | void;
resolveChildrenHandler?: (item: TestItem | undefined) => Thenable<void> | void;
/**
* Creates a {@link TestRun<T>}. This should be called by the

View file

@ -137,6 +137,6 @@ export class HierarchicalByNameProjection extends HierarchicalByLocationProjecti
* @override
*/
protected override getRevealDepth(element: ByLocationTestItemElement) {
return element.depth === 1 ? Infinity : undefined;
return element.depth === 0 ? Infinity : undefined;
}
}

View file

@ -428,6 +428,12 @@ export class TestingExplorerViewModel extends Disposable {
}
}));
this._register(onDidChangeVisibility(visible => {
if (visible) {
this.ensureProjection();
}
}));
this._register(this.tree.onContextMenu(e => this.onContextMenu(e)));
this._register(Event.any(
@ -532,16 +538,14 @@ export class TestingExplorerViewModel extends Disposable {
return;
}
if (!this.projection.value) {
return;
}
const projection = this.ensureProjection();
// If the item itself is visible in the tree, show it. Otherwise, expand
// its closest parent.
let expandToLevel = 0;
const idPath = [...TestId.fromString(id).idsFromRoot()];
for (let i = idPath.length - 1; i >= expandToLevel; i--) {
const element = this.projection.value.getElementByTestId(idPath[i]);
const element = projection.getElementByTestId(idPath[i]);
// Skip all elements that aren't in the tree.
if (!element || !this.tree.hasElement(element)) {
continue;
@ -665,6 +669,10 @@ export class TestingExplorerViewModel extends Disposable {
}
}
private ensureProjection() {
return this.projection.value ?? this.updatePreferredProjection();
}
private updatePreferredProjection() {
this.projection.clear();
@ -682,6 +690,7 @@ export class TestingExplorerViewModel extends Disposable {
});
this.applyProjectionChanges();
return this.projection.value;
}
private applyProjectionChanges() {

View file

@ -42,7 +42,7 @@ export interface OwnedCollectionTestItem {
export class SingleUseTestCollection extends Disposable {
private readonly debounceSendDiff = this._register(new RunOnceScheduler(() => this.flushDiff(), 200));
private readonly diffOpEmitter = this._register(new Emitter<TestsDiff>());
private _resolveHandler?: (item: TestItemRaw) => Promise<void> | void;
private _resolveHandler?: (item: TestItemRaw | undefined) => Promise<void> | void;
public readonly root = new TestItemRootImpl(this.controllerId, this.controllerId);
public readonly tree = new Map</* full test id */string, OwnedCollectionTestItem>();
@ -50,13 +50,14 @@ export class SingleUseTestCollection extends Disposable {
constructor(private readonly controllerId: string) {
super();
this.root.canResolveChildren = true;
this.upsertItem(this.root, undefined);
}
/**
* Handler used for expanding test items.
*/
public set resolveHandler(handler: undefined | ((item: TestItemRaw) => void)) {
public set resolveHandler(handler: undefined | ((item: TestItemRaw | undefined) => void)) {
this._resolveHandler = handler;
for (const test of this.tree.values()) {
this.updateExpandability(test);
@ -201,13 +202,11 @@ export class SingleUseTestCollection extends Disposable {
let internal = this.tree.get(fullId.toString());
// Case 1: a brand new item
if (!internal) {
// always expand root node to know if there are tests (and whether to show the welcome view)
const pExpandLvls = parent ? parent.expandLevels : 1;
internal = {
fullId,
actual,
parent: parent ? fullId.parentId : null,
expandLevels: pExpandLvls /* intentionally undefined or 0 */ ? pExpandLvls - 1 : undefined,
expandLevels: parent?.expandLevels /* intentionally undefined or 0 */ ? parent.expandLevels - 1 : undefined,
expand: TestItemExpandState.NotExpandable, // updated by `connectItemAndChildren`
};
@ -351,7 +350,7 @@ export class SingleUseTestCollection extends Disposable {
let r: Thenable<void> | void;
try {
r = this._resolveHandler(internal.actual);
r = this._resolveHandler(internal.actual === this.root ? undefined : internal.actual);
} catch (err) {
internal.actual.error = err.stack || err.message;
}

View file

@ -76,9 +76,6 @@ export const getCollectionItemParents = function* (collection: IMainThreadTestCo
}
};
const expandFirstLevel = (collection: IMainThreadTestCollection) =>
Promise.all([...collection.rootItems].map(r => collection.expand(r.item.extId, 0)));
export const testCollectionIsEmpty = (collection: IMainThreadTestCollection) =>
!Iterable.some(collection.rootItems, r => r.children.size > 0);
@ -103,7 +100,11 @@ export const expandAndGetTestById = async (collection: IMainThreadTestCollection
return existing;
}
await collection.expand(id, 0);
// expand children only if it looks like it's necessary
if (!existing.children.has(idPath[i + 1])) {
await collection.expand(id, 0);
}
expandToLevel = i + 1; // avoid an infinite loop if the test does not exist
i = idPath.length - 1;
}
@ -131,10 +132,7 @@ export const getAllTestsInHierarchy = async (collection: IMainThreadTestCollecti
* Iterator that expands to and iterates through tests in the file. Iterates
* in strictly descending order.
*/
export const testsInFile = async function* (collection: IMainThreadTestCollection, uri: URI) {
// Expand all direct children since roots will not have URIs, but children should.
await expandFirstLevel(collection);
export const testsInFile = async function* (collection: IMainThreadTestCollection, uri: URI): AsyncIterable<IncrementalTestCollectionItem> {
const demandUriStr = uri.toString();
for (const test of collection.all) {
if (!test.item.uri) {

View file

@ -26,13 +26,12 @@ export const testStubs = {
nested: (idPrefix = 'id-') => {
const collection = new TestSingleUseCollection('ctrlId');
collection.root.label = 'root';
collection.root.canResolveChildren = true;
collection.resolveHandler = item => {
if (item === collection.root) {
if (item === undefined) {
const a = new TestItemImpl(idPrefix + 'a', 'a', URI.file('/'));
a.canResolveChildren = true;
const b = new TestItemImpl(idPrefix + 'b', 'b', URI.file('/'));
item.children.set([a, b]);
collection.root.children.set([a, b]);
} else if (item.id === idPrefix + 'a') {
item.children.set([
new TestItemImpl(idPrefix + 'aa', 'aa', URI.file('/')),

View file

@ -83,19 +83,7 @@ suite('ExtHost Testing', () => {
],
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: single.root.id, expand: TestItemExpandState.Expandable, item: { ...convert.TestItem.from(a, 'ctrlId') } }
],
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: single.root.id, expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(b, 'ctrlId') }
],
[
TestDiffOpType.Update,
{ extId: single.root.id, expand: TestItemExpandState.Expanded }
],
[
TestDiffOpType.Update,
{ extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.BusyExpanding }
{ controllerId: 'ctrlId', parent: single.root.id, expand: TestItemExpandState.BusyExpanding, item: { ...convert.TestItem.from(a, 'ctrlId') } }
],
[
TestDiffOpType.Add,
@ -109,6 +97,14 @@ suite('ExtHost Testing', () => {
TestDiffOpType.Update,
{ extId: new TestId(['ctrlId', 'id-a']).toString(), expand: TestItemExpandState.Expanded }
],
[
TestDiffOpType.Add,
{ controllerId: 'ctrlId', parent: single.root.id, expand: TestItemExpandState.NotExpandable, item: convert.TestItem.from(b, 'ctrlId') }
],
[
TestDiffOpType.Update,
{ extId: single.root.id, expand: TestItemExpandState.Expanded }
],
]);
});
@ -248,7 +244,8 @@ suite('ExtHost Testing', () => {
assert.deepStrictEqual(newA.parent, undefined);
});
test('moves an item to be a new child', () => {
test('moves an item to be a new child', async () => {
await single.expand(single.root.id, 0);
single.collectDiff();
const b = single.root.children.get('id-b')!;
const a = single.root.children.get('id-a')!;
@ -424,13 +421,16 @@ suite('ExtHost Testing', () => {
let dto: TestRunDto;
setup(() => {
setup(async () => {
proxy = mockObject();
cts = new CancellationTokenSource();
c = new TestRunCoordinator(proxy);
configuration = new TestRunProfileImpl(mockObject<MainThreadTestingShape, {}>(), 'ctrlId', 42, 'Do Run', TestRunProfileGroup.Run, () => { }, false);
await single.expand(single.root.id, Infinity);
single.collectDiff();
req = {
include: undefined,
exclude: [single.root.children.get('id-b')!],
@ -503,7 +503,6 @@ suite('ExtHost Testing', () => {
const tracker = Iterable.first(c.trackers)!;
const expectedArgs: unknown[][] = [];
assert.deepStrictEqual(proxy.$addTestsToRun.args, expectedArgs);
single.expand(single.root.id, Infinity);
task1.setState(single.root.children.get('id-a')!.children.get('id-aa')!, TestResultState.Passed);
expectedArgs.push([
@ -547,8 +546,6 @@ suite('ExtHost Testing', () => {
});
test('excludes tests outside tree or explicitly excluded', () => {
single.expand(single.root.id, Infinity);
const task = c.createTestRun('ctrlId', single, {
profile: configuration,
include: [single.root.children.get('id-a')!],