From d73167bffbde73d194a021040866a016d3254388 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Mon, 2 Mar 2026 14:23:59 +0800 Subject: [PATCH 1/2] enhancement: refresh test unit treeview for specfic ur; --- src/commands/testExplorerCommands.ts | 31 ++++++++++++++++++++++++---- src/controller/utils.ts | 8 +++++++ src/extension.ts | 6 +++--- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/commands/testExplorerCommands.ts b/src/commands/testExplorerCommands.ts index b35f66f6..95c6ba11 100644 --- a/src/commands/testExplorerCommands.ts +++ b/src/commands/testExplorerCommands.ts @@ -1,9 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. -import { DebugConfiguration, TestItem, TestRunRequest } from 'vscode'; +import { DebugConfiguration, TestItem, TestRunRequest, Uri } from 'vscode'; import { sendInfo } from 'vscode-extension-telemetry-wrapper'; -import { runTests, testController } from '../controller/testController'; +import { loadChildren, runTests, testController } from '../controller/testController'; import { loadJavaProjects } from '../controller/utils'; import { showTestItemsInCurrentFile } from '../extension'; @@ -39,10 +39,33 @@ export async function runTestsFromTestExplorer(testItem: TestItem, launchConfigu export async function refreshExplorer(): Promise { sendInfo('', { name: 'refreshTests' }); + + await loadJavaProjects(); + await showTestItemsInCurrentFile(); +} + +/** + * Refresh only the project subtree that matches the given classpath-change URI. + * Falls back to a full (incremental) refresh if no matching project is found. + */ +export async function refreshProject(classpathUri: Uri): Promise { + sendInfo('', { name: 'refreshProject' }); + const uriString: string = classpathUri.toString(); + + // Find the project root whose URI is a prefix of the classpath URI + let matchedProject: TestItem | undefined; testController?.items.forEach((root: TestItem) => { - testController?.items.delete(root.id); + if (root.uri && uriString.startsWith(root.uri.toString())) { + matchedProject = root; + } }); - await loadJavaProjects(); + if (matchedProject) { + // Re-resolve only the matched project's children + await loadChildren(matchedProject); + } else { + // No matching project found – do incremental full refresh + await loadJavaProjects(); + } await showTestItemsInCurrentFile(); } diff --git a/src/controller/utils.ts b/src/controller/utils.ts index af54caeb..946d4c97 100644 --- a/src/controller/utils.ts +++ b/src/controller/utils.ts @@ -34,6 +34,14 @@ export async function loadJavaProjects(): Promise { return project.testKind !== TestKind.None; }); + // Remove projects that no longer exist + const projectIds: Set = new Set(testProjects.map((p: IJavaTestItem) => p.id)); + testController?.items.forEach((root: TestItem) => { + if (!projectIds.has(root.id)) { + testController?.items.delete(root.id); + } + }); + for (const project of testProjects) { if (testController?.items.get(project.id)) { continue; diff --git a/src/extension.ts b/src/extension.ts index 24051eb2..18a786c7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -7,7 +7,7 @@ import { dispose as disposeTelemetryWrapper, initializeFromJsonFile, instrumentO import { navigateToTestOrTarget } from './commands/navigation/navigationCommands'; import { generateTests } from './commands/generationCommands'; import { runTestsFromJavaProjectExplorer } from './commands/projectExplorerCommands'; -import { refreshExplorer, runTestsFromTestExplorer } from './commands/testExplorerCommands'; +import { refreshExplorer, refreshProject, runTestsFromTestExplorer } from './commands/testExplorerCommands'; import { openStackTrace } from './commands/testReportCommands'; import { Context, ExtensionName, JavaTestRunnerCommands, VSCodeCommands } from './constants'; import { createTestController, testController, watchers } from './controller/testController'; @@ -76,11 +76,11 @@ async function doActivate(_operationId: string, context: ExtensionContext): Prom if (extensionApi.onDidClasspathUpdate) { const onDidClasspathUpdate: Event = extensionApi.onDidClasspathUpdate; - context.subscriptions.push(onDidClasspathUpdate(async () => { + context.subscriptions.push(onDidClasspathUpdate(async (uri: Uri) => { // workaround: wait more time to make sure Language Server has updated all caches setTimeout(() => { testSourceProvider.clear(); - refreshExplorer(); + refreshProject(uri); }, 1000 /* ms */); })); } From 0b563a26540f50e6a142e4900fe96bd18ff8ec24 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Mon, 2 Mar 2026 14:36:04 +0800 Subject: [PATCH 2/2] fix: update --- src/commands/testExplorerCommands.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/commands/testExplorerCommands.ts b/src/commands/testExplorerCommands.ts index 95c6ba11..6a7c21f2 100644 --- a/src/commands/testExplorerCommands.ts +++ b/src/commands/testExplorerCommands.ts @@ -41,6 +41,14 @@ export async function refreshExplorer(): Promise { sendInfo('', { name: 'refreshTests' }); await loadJavaProjects(); + + // Force re-resolution of all existing project roots + const loadPromises: Promise[] = []; + testController?.items.forEach((root: TestItem) => { + loadPromises.push(loadChildren(root)); + }); + await Promise.all(loadPromises); + await showTestItemsInCurrentFile(); } @@ -52,11 +60,16 @@ export async function refreshProject(classpathUri: Uri): Promise { sendInfo('', { name: 'refreshProject' }); const uriString: string = classpathUri.toString(); - // Find the project root whose URI is a prefix of the classpath URI + // Find the project root with the longest matching URI prefix (most specific match) let matchedProject: TestItem | undefined; + let matchedUriLength: number = 0; testController?.items.forEach((root: TestItem) => { - if (root.uri && uriString.startsWith(root.uri.toString())) { - matchedProject = root; + if (root.uri) { + const rootUriString: string = root.uri.toString(); + if (uriString.startsWith(rootUriString) && rootUriString.length > matchedUriLength) { + matchedProject = root; + matchedUriLength = rootUriString.length; + } } });