From 3c8e7587d874a513cdb8bfd8f63b22ffbe544c10 Mon Sep 17 00:00:00 2001 From: wenytang-ms Date: Tue, 13 Oct 2026 16:00:55 +0800 Subject: [PATCH 1/2] fix: synchronize project JDK runtime selection Refresh runtime choices with project classpath settings and keep unavailable selections explicit. Add focused regressions and extend the existing classpath AutoTest plan for issue #1443. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9bfdb23d-9b06-41ae-8d99-0ab1fb561743 --- .../classpathConfigurationViewSlice.ts | 3 + .../features/components/JdkRuntime.tsx | 32 ++- .../handlers/ClasspathRequestHandler.ts | 55 +++-- test-plans/java-extension-pack.yaml | 49 +++- tests/project-settings.test.js | 231 ++++++++++++++++++ 5 files changed, 338 insertions(+), 32 deletions(-) create mode 100644 tests/project-settings.test.js diff --git a/src/project-settings/assets/classpath/features/classpathConfigurationViewSlice.ts b/src/project-settings/assets/classpath/features/classpathConfigurationViewSlice.ts index fa1a7b13..9c7634f8 100644 --- a/src/project-settings/assets/classpath/features/classpathConfigurationViewSlice.ts +++ b/src/project-settings/assets/classpath/features/classpathConfigurationViewSlice.ts @@ -47,6 +47,9 @@ export const classpathConfigurationViewSlice = createSlice({ }, loadClasspath: (state, action) => { const activeProjectIndex = action.payload.activeProjectIndex; + if (action.payload.vmInstalls !== undefined) { + state.data.vmInstalls = action.payload.vmInstalls; + } state.data.output[activeProjectIndex] = action.payload.output; state.data.effective.output[activeProjectIndex] = action.payload.output; diff --git a/src/project-settings/assets/classpath/features/components/JdkRuntime.tsx b/src/project-settings/assets/classpath/features/components/JdkRuntime.tsx index 216faaa6..3c8810fa 100644 --- a/src/project-settings/assets/classpath/features/components/JdkRuntime.tsx +++ b/src/project-settings/assets/classpath/features/components/JdkRuntime.tsx @@ -5,6 +5,7 @@ import "@vscode-elements/elements/dist/vscode-divider/index.js"; import "@vscode-elements/elements/dist/vscode-single-select/index.js"; import "@vscode-elements/elements/dist/vscode-option/index.js"; import "@vscode-elements/elements/dist/vscode-button/index.js"; +import type { VscodeSingleSelect } from "@vscode-elements/elements/dist/vscode-single-select/index.js"; import { useCallback, useEffect, useRef } from "react"; import { ClasspathRequest, CommonRequest } from "../../../vscode/utils"; @@ -22,10 +23,11 @@ const JdkRuntime = (): JSX.Element => { }, [activeProjectIndex]); const vmInstalls: VmInstall[] = useSelector((state: any) => state.classpathConfig.data.vmInstalls); - const activeVmInstallPath: string = useSelector((state: any) => state.classpathConfig.data.activeVmInstallPath[activeProjectIndex]); + const activeVmInstallPath: string = useSelector((state: any) => state.classpathConfig.data.activeVmInstallPath[activeProjectIndex]) ?? ""; + const hasActiveVmInstall = vmInstalls.some(install => install.path === activeVmInstallPath); const dispatch = useDispatch(); - const selectRef = useRef(null); + const selectRef = useRef(null); const handleSelectJdk = useCallback((path: string) => { if (path === "add-new-jdk") { @@ -44,21 +46,21 @@ const JdkRuntime = (): JSX.Element => { useEffect(() => { const el = selectRef.current; if (!el) return; - const handleChange = (e: Event) => { - const selectedValue = (e.target as any).value; - if (selectedValue) { + const handleChange = () => { + const selectedValue = el.value; + if (vmInstalls.some(install => install.path === selectedValue)) { handleSelectJdk(selectedValue); } }; el.addEventListener("change", handleChange); return () => el.removeEventListener("change", handleChange); - }, [handleSelectJdk]); + }, [handleSelectJdk, vmInstalls]); // Sync the value property on the select element when activeVmInstallPath changes useEffect(() => { const el = selectRef.current; - if (el && activeVmInstallPath) { - (el as any).value = activeVmInstallPath; + if (el) { + el.value = activeVmInstallPath; } }, [activeVmInstallPath, vmInstalls]); @@ -89,6 +91,11 @@ const JdkRuntime = (): JSX.Element => { id="jdk-dropdown" className="setting-section-dropdown" > + {!hasActiveVmInstall && ( + + {activeVmInstallPath ? "Current project JDK (not listed)" : "Project JDK not available"} + + )} {vmInstalls.map((vmInstall) => ( {
{activeVmInstallPath}
+ {!hasActiveVmInstall && activeVmInstallPath && ( +

The current project JDK is not in the available runtime list. Refresh JDKs or locate its installation.

+ )}
+ ClasspathRequest.onWillListVmInstalls()}> + + Refresh JDKs + ClasspathRequest.onWillAddNewJdk()}> Find a local JDK... @@ -121,7 +135,7 @@ const JdkRuntime = (): JSX.Element => { interface OnDidChangeJdkEvent { data: { command: string; - vmInstalls: VmInstall[]; + vmInstalls?: VmInstall[]; activeVmInstallPath: string; }; } diff --git a/src/project-settings/handlers/ClasspathRequestHandler.ts b/src/project-settings/handlers/ClasspathRequestHandler.ts index 9245338e..2201cd3f 100644 --- a/src/project-settings/handlers/ClasspathRequestHandler.ts +++ b/src/project-settings/handlers/ClasspathRequestHandler.ts @@ -14,6 +14,8 @@ export class ClasspathRequestHandler implements vscode.Disposable { private webview: vscode.Webview; private currentProjectRoot: vscode.Uri | undefined; private disposables: vscode.Disposable[] = []; + private classpathRequestId = 0; + private vmInstallsRequestId = 0; constructor(webview: vscode.Webview) { this.webview = webview; @@ -69,17 +71,15 @@ export class ClasspathRequestHandler implements vscode.Disposable { } private listVmInstalls = instrumentOperation("projectSettings.classpath.listVmInstalls", async (operationId: string) => { - let vmInstalls: VmInstall[] = await this.getVmInstallsFromLS(); - vmInstalls = vmInstalls.sort((vmA: VmInstall, vmB: VmInstall) => { - return vmA.name.localeCompare(vmB.name); + const vmInstalls = await this.getVmInstallsFromLS(); + if (vmInstalls === undefined) { + return; + } + this.webview.postMessage({ + command: "classpath.onDidListVmInstalls", + vmInstalls, }); - - if (vmInstalls.length > 0) { - this.webview.postMessage({ - command: "classpath.onDidListVmInstalls", - vmInstalls, - }); - } else { + if (vmInstalls.length === 0) { sendInfo(operationId, { vmNumber: vmInstalls.length, }); @@ -93,7 +93,16 @@ export class ClasspathRequestHandler implements vscode.Disposable { return; } + const requestId = ++this.classpathRequestId; const classpath = await this.getProjectClasspathFromLS(uri); + if (requestId !== this.classpathRequestId || !this.currentProjectRoot || path.relative(uri.fsPath, this.currentProjectRoot.fsPath)) { + return; + } + // Resolving the project's VM can register a JDK after the initial inventory query. + const vmInstalls = await this.getVmInstallsFromLS(); + if (requestId !== this.classpathRequestId || !this.currentProjectRoot || path.relative(uri.fsPath, this.currentProjectRoot.fsPath)) { + return; + } if (classpath) { this.webview.postMessage({ command: "classpath.onDidLoadProjectClasspath", @@ -101,6 +110,7 @@ export class ClasspathRequestHandler implements vscode.Disposable { sources: classpath.sourcePaths, output: classpath.defaultOutputPath, activeVmInstallPath: classpath.jdkPath, + vmInstalls, libraries: classpath.libraries }); } @@ -310,10 +320,7 @@ export class ClasspathRequestHandler implements vscode.Disposable { if (result.success) { const activeVmInstallPath = result.message; - let vmInstalls: VmInstall[] = await this.getVmInstallsFromLS(); - vmInstalls = vmInstalls.sort((vmA: VmInstall, vmB: VmInstall) => { - return vmA.name.localeCompare(vmB.name); - }); + const vmInstalls = await this.getVmInstallsFromLS(); this.webview.postMessage({ command: "classpath.onDidChangeJdk", activeVmInstallPath, @@ -345,10 +352,7 @@ export class ClasspathRequestHandler implements vscode.Disposable { if (result.success) { const activeVmInstallPath = result.message; - let vmInstalls: VmInstall[] = await this.getVmInstallsFromLS(); - vmInstalls = vmInstalls.sort((vmA: VmInstall, vmB: VmInstall) => { - return vmA.name.localeCompare(vmB.name); - }); + const vmInstalls = await this.getVmInstallsFromLS(); this.webview.postMessage({ command: "classpath.onDidChangeJdk", activeVmInstallPath, @@ -436,13 +440,20 @@ export class ClasspathRequestHandler implements vscode.Disposable { }); }); - private async getVmInstallsFromLS(): Promise { - const ret: VmInstall[] = []; + private async getVmInstallsFromLS(): Promise { + const requestId = ++this.vmInstallsRequestId; try { - ret.push(...await vscode.commands.executeCommand("java.execute.workspaceCommand", "java.vm.getAllInstalls") || []); + const installs = await vscode.commands.executeCommand("java.execute.workspaceCommand", "java.vm.getAllInstalls") || []; + if (requestId !== this.vmInstallsRequestId) { + return undefined; + } + return installs.sort((a, b) => a.name.localeCompare(b.name)); } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + sendError(err); + void vscode.window.showErrorMessage(`Unable to load Java runtimes: ${err.message}`); + return undefined; } - return ret; } private async getProjectClasspathFromLS(uri: vscode.Uri): Promise { diff --git a/test-plans/java-extension-pack.yaml b/test-plans/java-extension-pack.yaml index 900b1510..5e61d03f 100644 --- a/test-plans/java-extension-pack.yaml +++ b/test-plans/java-extension-pack.yaml @@ -2,6 +2,7 @@ # # Source: wiki Test-Plan.md "Java Extension Pack" scenario # Verify: Trigger Java: Configure Classpath command → configuration page appears; +# retain the selected project JDK when refreshing the runtime list; # add a project-local JAR to a non-unmanaged project and apply it. # # Prerequisites: @@ -14,7 +15,8 @@ name: "Java Extension Pack — Classpath Configuration" description: | Corresponds to the Java Extension Pack scenario in the wiki Test Plan: Trigger the Java: Configure Classpath command, verify the configuration page - appears, and cover the non-unmanaged-project Add Library flow. + appears, verify the current project JDK remains selected after refreshing + available runtimes, and cover the non-unmanaged-project Add Library flow. setup: extension: "redhat.java" @@ -23,6 +25,9 @@ setup: vscodeVersion: "stable" workspace: "../../vscode-java/test/resources/projects/eclipse/simple-app" timeout: 90 + settings: + java.help.showReleaseNotes: false + java.help.firstView: "none" steps: # ── Wait for LS ready ──────────────────────────────────── @@ -59,6 +64,48 @@ steps: - "Classpath" - "Libraries" + - id: "open-project-jdk-tab" + action: "clickInWebview text=JDK Runtime" + verify: "The project JDK tab shows runtime controls and an available current JDK" + verifyWebview: + contains: + - "Refresh JDKs" + - "Find a local JDK..." + notContains: + - "Current project JDK (not listed)" + - "Project JDK not available" + timeout: 30 + + - id: "open-selected-project-jdk" + action: "clickInWebview #jdk-dropdown:has(vscode-option[selected]:not([disabled]))" + verify: "The dropdown has a real selected runtime option, not an unavailable-current-JDK placeholder" + verifyWebview: + contains: "Refresh JDKs" + notContains: "Current project JDK (not listed)" + + - id: "close-project-jdk-dropdown" + action: "clickInWebview #jdk-dropdown" + + - id: "refresh-project-jdks" + action: "clickInWebview text=Refresh JDKs" + verify: "Refreshing runtimes keeps the current project JDK available without cleaning or restarting" + verifyWebview: + contains: "Refresh JDKs" + notContains: + - "Current project JDK (not listed)" + - "Project JDK not available" + timeout: 30 + + - id: "verify-selected-project-jdk-after-refresh" + action: "clickInWebview #jdk-dropdown:has(vscode-option[selected]:not([disabled]))" + verify: "A real current project JDK is still selected after the inventory refresh" + verifyWebview: + contains: "Refresh JDKs" + notContains: "Current project JDK (not listed)" + + - id: "close-refreshed-project-jdk-dropdown" + action: "clickInWebview #jdk-dropdown" + - id: "open-libraries-tab" action: "clickInWebview text=Libraries" verify: "The Libraries tab is selected and shows library management actions" diff --git a/tests/project-settings.test.js b/tests/project-settings.test.js new file mode 100644 index 00000000..fd759249 --- /dev/null +++ b/tests/project-settings.test.js @@ -0,0 +1,231 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const path = require("node:path"); +const test = require("node:test"); +const vm = require("node:vm"); +const { fileURLToPath, pathToFileURL } = require("node:url"); +const ts = require("typescript"); + +function loadSource(relativePath, mocks = {}) { + const filename = path.join(__dirname, "..", relativePath); + const { outputText } = ts.transpileModule(fs.readFileSync(filename, "utf8"), { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020, esModuleInterop: true, jsx: ts.JsxEmit.ReactJSX }, + fileName: filename, + }); + const module = { exports: {} }; + vm.runInNewContext(outputText, { + module, exports: module.exports, process, console, + require: name => Object.prototype.hasOwnProperty.call(mocks, name) ? mocks[name] : require(name), + }, { filename }); + return module.exports; +} + +const jdk21 = { name: "JDK 21", path: path.join(__dirname, "jdk-21"), version: "21", typeName: "Standard VM" }; +const jdk24 = { name: "JDK 24", path: path.join(__dirname, "jdk-24"), version: "24", typeName: "Standard VM" }; +const projectUri = pathToFileURL(path.join(__dirname, "sample")).toString(); + +function setupHandler() { + const messages = []; + const errors = []; + const notifications = []; + const commands = []; + let receive; + let projectJdk = jdk21.path; + let installs = [jdk21]; + const uri = value => ({ fsPath: fileURLToPath(value), toString: () => value }); + const vscode = { + Uri: { parse: uri }, + workspace: { onDidChangeConfiguration: () => ({ dispose() {} }) }, + window: { showErrorMessage: async message => { notifications.push(message); } }, + commands: { + executeCommand: async (_, command) => { + commands.push(command); + if (command === "java.vm.getAllInstalls") { + return installs; + } + return { + "org.eclipse.jdt.ls.core.natureIds": [], + "org.eclipse.jdt.ls.core.outputPath": path.join(__dirname, "sample", "bin"), + "org.eclipse.jdt.ls.core.vm.location": projectJdk, + "org.eclipse.jdt.ls.core.classpathEntries": [], + }; + }, + }, + }; + const { ClasspathRequestHandler } = loadSource(path.join("src", "project-settings", "handlers", "ClasspathRequestHandler.ts"), { + vscode, + lodash: { ...require("lodash"), debounce: action => action }, + "../../utils/jdt": { getProjectType: () => "Unmanaged folder" }, + "../../utils/webview": { ProjectType: {}, isProjectType: () => true }, + "../types": { ClasspathEntryKind: {} }, + "vscode-extension-telemetry-wrapper": { + instrumentOperation: (name, action) => (...args) => action(name, ...args), + sendInfo() {}, setUserError() {}, sendError: error => errors.push(error), + }, + }); + const handler = new ClasspathRequestHandler({ + onDidReceiveMessage: callback => { receive = callback; return { dispose() {} }; }, + postMessage: message => { messages.push(message); }, + }); + return { + handler, vscode, messages, errors, notifications, commands, + setJdk: jdk => { projectJdk = jdk.path; installs = [jdk21, jdk]; }, + send: (command, data = {}) => receive({ command, ...data }), + refresh: () => handler.debounceLoadProjectClasspath(uri(projectUri)), + }; +} + +test("classpath refresh loads newly registered JDKs with the current project path", async () => { + const s = setupHandler(); + await s.send("classpath.onWillLoadProjectClasspath", { uri: projectUri }); + s.setJdk(jdk24); + s.commands.length = 0; + await s.refresh(); + const snapshot = s.messages.at(-1); + assert.equal(snapshot.activeVmInstallPath, jdk24.path); + assert.ok(snapshot.vmInstalls.some(install => install.path === jdk24.path)); + assert.deepEqual(s.commands, ["java.project.getSettings", "java.vm.getAllInstalls"]); +}); + +test("an older inventory response cannot overwrite a newer classpath snapshot", async () => { + const s = setupHandler(); + const execute = s.vscode.commands.executeCommand; + let resolveOld; + let first = true; + s.vscode.commands.executeCommand = (...args) => { + if (args[1] === "java.vm.getAllInstalls" && first) { + first = false; + return new Promise(resolve => { resolveOld = resolve; }); + } + return execute(...args); + }; + const older = s.send("classpath.onWillListVmInstalls"); + s.setJdk(jdk24); + await s.send("classpath.onWillLoadProjectClasspath", { uri: projectUri }); + resolveOld([jdk21]); + await older; + assert.equal(s.messages.length, 1); + assert.equal(s.messages[0].activeVmInstallPath, jdk24.path); + assert.ok(s.messages[0].vmInstalls.some(install => install.path === jdk24.path)); +}); + +test("an empty inventory refresh clears previously listed JDKs", async () => { + const s = setupHandler(); + s.vscode.commands.executeCommand = async () => []; + await s.send("classpath.onWillListVmInstalls"); + assert.equal(s.messages.length, 1); + assert.equal(s.messages[0].command, "classpath.onDidListVmInstalls"); + assert.equal(s.messages[0].vmInstalls.length, 0); +}); + +test("inventory errors are reported without discarding the actual project JDK", async () => { + const s = setupHandler(); + const execute = s.vscode.commands.executeCommand; + s.vscode.commands.executeCommand = (...args) => { + if (args[1] === "java.vm.getAllInstalls") { + throw new Error("VM inventory unavailable"); + } + return execute(...args); + }; + await s.send("classpath.onWillLoadProjectClasspath", { uri: projectUri }); + assert.equal(s.messages.at(-1).activeVmInstallPath, jdk21.path); + assert.equal(s.messages.at(-1).vmInstalls, undefined); + assert.equal(s.errors.length, 1); + assert.match(s.notifications[0], /VM inventory unavailable/); +}); + +test("a late classpath response is ignored after switching projects", async () => { + const s = setupHandler(); + const execute = s.vscode.commands.executeCommand; + let resolveOld; + let first = true; + s.vscode.commands.executeCommand = (...args) => { + if (args[1] === "java.project.getSettings" && first) { + first = false; + return new Promise(resolve => { resolveOld = () => execute(...args).then(resolve); }); + } + return execute(...args); + }; + const older = s.send("classpath.onWillLoadProjectClasspath", { uri: projectUri }); + const nextUri = pathToFileURL(path.join(__dirname, "other-sample")).toString(); + await s.send("classpath.onWillLoadProjectClasspath", { uri: nextUri }); + await resolveOld(); + await older; + assert.equal(s.messages.length, 1); +}); + +function setupState() { + const slice = loadSource(path.join("src", "project-settings", "assets", "classpath", "features", "classpathConfigurationViewSlice.ts")); + let state = slice.default(undefined, slice.initializeClasspathData({ projectsNum: 1 })); + return { + slice, + get state() { return state; }, + dispatch: action => { state = slice.default(state, action); }, + }; +} + +test("classpath snapshot updates the JDK list and selected path together", () => { + const s = setupState(); + s.dispatch(s.slice.listVmInstalls([jdk21])); + s.dispatch(s.slice.loadClasspath({ + activeProjectIndex: 0, activeVmInstallPath: jdk24.path, vmInstalls: [jdk21, jdk24], + sources: [], output: "", libraries: [], + })); + assert.equal(s.state.data.activeVmInstallPath[0], jdk24.path); + assert.ok(s.state.data.vmInstalls.some(install => install.path === jdk24.path)); +}); + +test("inventory-only refresh preserves an unapplied JDK choice", () => { + const s = setupState(); + s.dispatch(s.slice.loadClasspath({ + activeProjectIndex: 0, activeVmInstallPath: jdk21.path, vmInstalls: [jdk21], + sources: [], output: "", libraries: [], + })); + s.dispatch(s.slice.setJdks({ activeProjectIndex: 0, activeVmInstallPath: jdk24.path })); + s.dispatch(s.slice.listVmInstalls([jdk21, jdk24])); + assert.equal(s.state.data.activeVmInstallPath[0], jdk24.path); + assert.equal(s.state.data.effective.activeVmInstallPath[0], jdk21.path); +}); + +function renderJdk(installs, activePath) { + const { createElement } = require("react"); + const { renderToStaticMarkup } = require("react-dom/server"); + const state = { + commonConfig: { ui: { activeProjectIndex: 0 } }, + classpathConfig: { data: { vmInstalls: installs, activeVmInstallPath: [activePath] } }, + }; + const { default: JdkRuntime } = loadSource(path.join("src", "project-settings", "assets", "classpath", "features", "components", "JdkRuntime.tsx"), { + "@vscode-elements/elements/dist/vscode-divider/index.js": {}, + "@vscode-elements/elements/dist/vscode-single-select/index.js": {}, + "@vscode-elements/elements/dist/vscode-option/index.js": {}, + "@vscode-elements/elements/dist/vscode-button/index.js": {}, + "react-redux": { useSelector: selector => selector(state), useDispatch: () => () => {} }, + "../../../vscode/utils": { ClasspathRequest: {}, CommonRequest: {} }, + "../classpathConfigurationViewSlice": {}, + }); + return renderToStaticMarkup(createElement(JdkRuntime)); +} + +test("an unlisted current JDK is shown explicitly instead of selecting JDK 21", () => { + const html = renderJdk([jdk21], jdk24.path); + assert.match(html, /Current project JDK \(not listed\)/); + assert.match(html, /not in the available runtime list/); + assert.match(html, /Refresh JDKs/); + assert.doesNotMatch(html, /value="[^"]*jdk-21" selected/); + assert.match(html, /value="[^"]*jdk-24" selected/); +}); + +test("missing project JDK metadata does not select the first installed JDK", () => { + const html = renderJdk([jdk21], ""); + assert.match(html, /Project JDK not available/); + assert.doesNotMatch(html, /value="[^"]*jdk-21" selected/); +}); + +test("a matching current JDK uses its real runtime label without an unavailable placeholder", () => { + const html = renderJdk([jdk21, jdk24], jdk24.path); + assert.match(html, /value="[^"]*jdk-24" selected/); + assert.doesNotMatch(html, /Current project JDK \(not listed\)|Project JDK not available/); +}); From 78cd9be9e6b1753c70b28f37ad128dd98bb4a03f Mon Sep 17 00:00:00 2001 From: wenyutang-ms Date: Wed, 14 Oct 2026 10:14:18 +0800 Subject: [PATCH 2/2] fix: ignore superseded runtime inventory errors Check the inventory request ID before reporting errors so a stale failed request cannot emit telemetry or notify users after a newer request succeeds. Add a regression test while preserving error reporting for the current request. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a7196b98-8fa1-4856-a96b-663c8c3cd0c9 --- .../handlers/ClasspathRequestHandler.ts | 3 +++ tests/project-settings.test.js | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/project-settings/handlers/ClasspathRequestHandler.ts b/src/project-settings/handlers/ClasspathRequestHandler.ts index 2201cd3f..40e001b1 100644 --- a/src/project-settings/handlers/ClasspathRequestHandler.ts +++ b/src/project-settings/handlers/ClasspathRequestHandler.ts @@ -449,6 +449,9 @@ export class ClasspathRequestHandler implements vscode.Disposable { } return installs.sort((a, b) => a.name.localeCompare(b.name)); } catch (error) { + if (requestId !== this.vmInstallsRequestId) { + return undefined; + } const err = error instanceof Error ? error : new Error(String(error)); sendError(err); void vscode.window.showErrorMessage(`Unable to load Java runtimes: ${err.message}`); diff --git a/tests/project-settings.test.js b/tests/project-settings.test.js index fd759249..2d1ee2af 100644 --- a/tests/project-settings.test.js +++ b/tests/project-settings.test.js @@ -112,6 +112,30 @@ test("an older inventory response cannot overwrite a newer classpath snapshot", assert.ok(s.messages[0].vmInstalls.some(install => install.path === jdk24.path)); }); +test("an older inventory failure is ignored after a newer classpath snapshot succeeds", async () => { + const s = setupHandler(); + const execute = s.vscode.commands.executeCommand; + let rejectOld; + let first = true; + s.vscode.commands.executeCommand = (...args) => { + if (args[1] === "java.vm.getAllInstalls" && first) { + first = false; + return new Promise((_, reject) => { rejectOld = reject; }); + } + return execute(...args); + }; + const older = s.send("classpath.onWillListVmInstalls"); + s.setJdk(jdk24); + await s.send("classpath.onWillLoadProjectClasspath", { uri: projectUri }); + rejectOld(new Error("Superseded inventory unavailable")); + await older; + assert.equal(s.messages.length, 1); + assert.equal(s.messages[0].activeVmInstallPath, jdk24.path); + assert.ok(s.messages[0].vmInstalls.some(install => install.path === jdk24.path)); + assert.deepEqual(s.errors, []); + assert.deepEqual(s.notifications, []); +}); + test("an empty inventory refresh clears previously listed JDKs", async () => { const s = setupHandler(); s.vscode.commands.executeCommand = async () => [];