From d9c47405c015486e0264b0b425f6532d06ee159c Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Wed, 4 Mar 2026 16:23:33 +0800 Subject: [PATCH 1/2] fix: trigger UI except other lsp event --- src/commands/testExplorerCommands.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/commands/testExplorerCommands.ts b/src/commands/testExplorerCommands.ts index 6a7c21f2..b2fd762b 100644 --- a/src/commands/testExplorerCommands.ts +++ b/src/commands/testExplorerCommands.ts @@ -60,15 +60,20 @@ export async function refreshProject(classpathUri: Uri): Promise { sendInfo('', { name: 'refreshProject' }); const uriString: string = classpathUri.toString(); - // Find the project root with the longest matching URI prefix (most specific match) + // Find the project root with the longest matching URI prefix (most specific match), + // or find a project whose URI is a child of the classpath URI (e.g. workspace root changed). let matchedProject: TestItem | undefined; let matchedUriLength: number = 0; + let childProjectMatched: boolean = false; testController?.items.forEach((root: TestItem) => { if (root.uri) { const rootUriString: string = root.uri.toString(); if (uriString.startsWith(rootUriString) && rootUriString.length > matchedUriLength) { matchedProject = root; matchedUriLength = rootUriString.length; + } else if (rootUriString.startsWith(uriString)) { + // The classpath URI is an ancestor of this project (e.g. workspace root) + childProjectMatched = true; } } }); @@ -76,9 +81,18 @@ export async function refreshProject(classpathUri: Uri): Promise { if (matchedProject) { // Re-resolve only the matched project's children await loadChildren(matchedProject); + } else if (childProjectMatched) { + // The classpath URI is an ancestor containing test projects – refresh all children + const loadPromises: Promise[] = []; + testController?.items.forEach((root: TestItem) => { + if (root.uri && root.uri.toString().startsWith(uriString)) { + loadPromises.push(loadChildren(root)); + } + }); + await Promise.all(loadPromises); } else { - // No matching project found – do incremental full refresh - await loadJavaProjects(); + // URI doesn't match any known test project – skip to avoid unnecessary full refresh + return; } await showTestItemsInCurrentFile(); } From e460036e39f254e33dd6ef71848ce0397cb0e0d2 Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Wed, 4 Mar 2026 16:34:57 +0800 Subject: [PATCH 2/2] fix: update --- src/commands/testExplorerCommands.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/commands/testExplorerCommands.ts b/src/commands/testExplorerCommands.ts index b2fd762b..f106d83c 100644 --- a/src/commands/testExplorerCommands.ts +++ b/src/commands/testExplorerCommands.ts @@ -53,21 +53,31 @@ export async function refreshExplorer(): Promise { } /** + * Ensure the URI string ends with '/' so that startsWith comparisons + * are path-segment-aware (e.g. ".../app/" won't prefix-match ".../app2/"). + */ +function ensureTrailingSeparator(uriString: string): string { + return uriString.endsWith('/') ? uriString : uriString + '/'; +}/** * 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. + * If the URI is an ancestor of known test projects, refreshes all matching children. + * If the URI doesn't correspond to any known test project, the refresh is skipped + * to avoid unnecessary full reloads (e.g. for non-test projects like Gradle's buildSrc). */ export async function refreshProject(classpathUri: Uri): Promise { sendInfo('', { name: 'refreshProject' }); - const uriString: string = classpathUri.toString(); + const uriString: string = ensureTrailingSeparator(classpathUri.toString()); // Find the project root with the longest matching URI prefix (most specific match), // or find a project whose URI is a child of the classpath URI (e.g. workspace root changed). + // All comparisons use separator-terminated URIs to avoid false positives + // (e.g. "file:///ws/app" must not match "file:///ws/app2"). let matchedProject: TestItem | undefined; let matchedUriLength: number = 0; let childProjectMatched: boolean = false; testController?.items.forEach((root: TestItem) => { if (root.uri) { - const rootUriString: string = root.uri.toString(); + const rootUriString: string = ensureTrailingSeparator(root.uri.toString()); if (uriString.startsWith(rootUriString) && rootUriString.length > matchedUriLength) { matchedProject = root; matchedUriLength = rootUriString.length; @@ -85,7 +95,7 @@ export async function refreshProject(classpathUri: Uri): Promise { // The classpath URI is an ancestor containing test projects – refresh all children const loadPromises: Promise[] = []; testController?.items.forEach((root: TestItem) => { - if (root.uri && root.uri.toString().startsWith(uriString)) { + if (root.uri && ensureTrailingSeparator(root.uri.toString()).startsWith(uriString)) { loadPromises.push(loadChildren(root)); } });