Skip to content

Commit 141c2ff

Browse files
committed
Allow tests to be discovered by expanded suites in the tree without opening editors
Fixes #3591.
1 parent cd7a343 commit 141c2ff

4 files changed

Lines changed: 45 additions & 10 deletions

File tree

‎src/extension/analysis/file_tracker_lsp.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export class LspFileTracker implements IAmDisposable {
4343
return this.outlines[fsPath(file)];
4444
}
4545

46+
public async waitForOutline(document: TextDocument, token?: CancellationToken): Promise<Outline | undefined> {
47+
return waitFor(() => this.outlines[fsPath(document.uri)], 50, 5000, token);
48+
}
49+
4650
// TODO: Change this to withVersion when server sends versions.
4751
public async waitForOutlineWithLength(document: TextDocument, length: number, token: CancellationToken): Promise<Outline | undefined> {
4852
return waitFor(() => {

‎src/extension/extension.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -491,8 +491,11 @@ export async function activate(context: vs.ExtensionContext, isRestart: boolean
491491
vs.debug.onDidTerminateDebugSession((session) => testCoordinator.handleDebugSessionEnd(session.id, session.configuration.dartCodeDebugSessionID)),
492492
vs.workspace.onDidChangeConfiguration((e) => testModel.handleConfigChange()),
493493
);
494+
const testDiscoverer = lspAnalyzer ? new TestDiscoverer(logger, lspAnalyzer.fileTracker, testModel) : undefined;
495+
if (testDiscoverer)
496+
context.subscriptions.push(testDiscoverer);
494497
const vsCodeTestController = config.useVsCodeTestRunner && vs.tests?.createTestController // Feature-detect for Theia
495-
? new VsCodeTestController(logger, testModel)
498+
? new VsCodeTestController(logger, testModel, testDiscoverer)
496499
: undefined;
497500
if (vsCodeTestController)
498501
context.subscriptions.push(vsCodeTestController);
@@ -611,9 +614,6 @@ export async function activate(context: vs.ExtensionContext, isRestart: boolean
611614
context.subscriptions.push(
612615
packagesTreeView,
613616
);
614-
const testDiscoverer = lspAnalyzer ? new TestDiscoverer(logger, lspAnalyzer.fileTracker, testModel) : undefined;
615-
if (testDiscoverer)
616-
context.subscriptions.push(testDiscoverer);
617617
let testTreeProvider: TestResultsProvider | undefined;
618618
if (!vsCodeTestController) {
619619
testTreeProvider = new TestResultsProvider(testModel, flutterCapabilities);

‎src/extension/lsp/test_discoverer.ts‎

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as path from "path";
44
import * as vs from "vscode";
55
import { Outline, OutlineParams } from "../../shared/analysis/lsp/custom_protocol";
66
import { IAmDisposable, Logger } from "../../shared/interfaces";
7-
import { TestModel, TestSource } from "../../shared/test/test_model";
7+
import { SuiteNode, TestModel, TestSource } from "../../shared/test/test_model";
88
import { disposeAll, uriToFilePath } from "../../shared/utils";
99
import { forceWindowsDriveLetterToUppercase, fsPath, getRandomInt } from "../../shared/utils/fs";
1010
import { LspOutlineVisitor } from "../../shared/utils/outline_lsp";
@@ -21,10 +21,9 @@ export class TestDiscoverer implements IAmDisposable {
2121

2222
constructor(private readonly logger: Logger, private readonly fileTracker: LspFileTracker, private readonly model: TestModel) {
2323
this.disposables.push(fileTracker.onOutline.listen((o) => this.handleOutline(o)));
24-
this.startTestDiscovery();
2524
}
2625

27-
private startTestDiscovery() {
26+
public beginTestDiscovery() {
2827
// Set up events for create/rename/delete.
2928
this.disposables.push(
3029
vs.workspace.onDidCreateFiles((e) => {
@@ -77,6 +76,11 @@ export class TestDiscoverer implements IAmDisposable {
7776
}
7877
}
7978

79+
public async discoverTestsForSuite(node: SuiteNode): Promise<void> {
80+
const doc = await vs.workspace.openTextDocument(node.suiteData.path);
81+
await this.fileTracker.waitForOutline(doc, undefined);
82+
}
83+
8084
/// Forces an update for a file based on the last Outline data (if any).
8185
///
8286
/// Used by tests to ensure discovery results are available if the test tree state has
@@ -93,7 +97,12 @@ export class TestDiscoverer implements IAmDisposable {
9397
const existingTimeout = this.debounceTimers[suitePath];
9498
if (existingTimeout)
9599
clearTimeout(existingTimeout);
96-
this.debounceTimers[suitePath] = setTimeout(() => this.rebuildFromOutline(suitePath, outline.outline), this.debounceDuration);
100+
101+
// If this is the first outline for a file (eg. we've never had a timeout)
102+
// we should skip the debounce so things are initially more responsive.
103+
const debounceDuration = existingTimeout ? this.debounceDuration : 0;
104+
105+
this.debounceTimers[suitePath] = setTimeout(() => this.rebuildFromOutline(suitePath, outline.outline), debounceDuration);
97106
}
98107

99108
private rebuildFromOutline(suitePath: string, outline: Outline) {

‎src/extension/test/vs_test_controller.ts‎

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ErrorNotification, PrintNotification } from "../../shared/test_protocol
77
import { disposeAll, notUndefined } from "../../shared/utils";
88
import { fsPath } from "../../shared/utils/fs";
99
import { config } from "../config";
10+
import { TestDiscoverer } from "../lsp/test_discoverer";
1011
import { formatForTerminal } from "../utils/vscode/terminals";
1112

1213
export class VsCodeTestController implements TestEventListener, IAmDisposable {
@@ -16,15 +17,17 @@ export class VsCodeTestController implements TestEventListener, IAmDisposable {
1617
private nodeForItem = new WeakMap<vs.TestItem, TreeNode>();
1718
private testRuns: { [key: string]: vs.TestRun | undefined } = {};
1819

19-
20-
constructor(private readonly logger: Logger, private readonly model: TestModel) {
20+
constructor(private readonly logger: Logger, private readonly model: TestModel, private readonly discoverer: TestDiscoverer | undefined) {
2121
const controller = vs.tests.createTestController("dart", "Dart & Flutter");
2222
this.controller = controller;
2323
this.disposables.push(controller);
2424
this.disposables.push(model.onDidChangeTreeData.listen((node) => this.onDidChangeTreeData(node)));
2525
this.disposables.push(vs.debug.onDidTerminateDebugSession((e) => this.handleDebugSessionEnd(e)));
2626
model.addTestEventListener(this);
2727

28+
if (discoverer)
29+
controller.resolveHandler = (item: vs.TestItem | undefined) => this.resolveTestItem(item);
30+
2831
controller.createRunProfile("Run", vs.TestRunProfileKind.Run, (request, token) => {
2932
this.runTests(false, request, token);
3033
});
@@ -34,6 +37,20 @@ export class VsCodeTestController implements TestEventListener, IAmDisposable {
3437
});
3538
}
3639

40+
private async resolveTestItem(item: vs.TestItem | undefined): Promise<void> {
41+
if (!this.discoverer)
42+
return;
43+
44+
if (!item) {
45+
this.discoverer.beginTestDiscovery();
46+
return;
47+
}
48+
49+
const node = this.nodeForItem.get(item);
50+
if (node instanceof SuiteNode)
51+
await this.discoverer.discoverTestsForSuite(node);
52+
}
53+
3754
public registerTestRun(dartCodeDebugSessionID: string, run: vs.TestRun): void {
3855
this.testRuns[dartCodeDebugSessionID] = run;
3956
}
@@ -140,6 +157,11 @@ export class VsCodeTestController implements TestEventListener, IAmDisposable {
140157
this.updateFields(existingItem, node);
141158
}
142159

160+
// For new suites without chilren, set canResolveChildren because we can
161+
// open the file and discover tests from the Outline if the user expands them.
162+
if (node instanceof SuiteNode && node.children.length === 0)
163+
existingItem.canResolveChildren = true;
164+
143165
existingItem.children.replace(
144166
node.children.map((c) => this.createOrUpdateNode(c)).filter(notUndefined),
145167
);

0 commit comments

Comments
 (0)