diff --git a/src/commands/testExplorerCommands.ts b/src/commands/testExplorerCommands.ts index 6a7c21f2..f106d83c 100644 --- a/src/commands/testExplorerCommands.ts +++ b/src/commands/testExplorerCommands.ts @@ -53,22 +53,37 @@ 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) + // 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; + } else if (rootUriString.startsWith(uriString)) { + // The classpath URI is an ancestor of this project (e.g. workspace root) + childProjectMatched = true; } } }); @@ -76,9 +91,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 && ensureTrailingSeparator(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(); }