From 364bb5ef50afa5c3b72b108fa5435c1fd72b5b55 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Thu, 29 Oct 2026 14:45:42 +0800 Subject: [PATCH] fix: resolve frontend and cross-platform CI failures --- crates/ide-core/src/version_managers.rs | 1 + crates/ide-host/tests/remote_env.rs | 8 ++++- src/features/vcs/CommitView.tsx | 31 ++++++++-------- .../vcs/__tests__/multiRootStashes.test.tsx | 35 +++++++++++++++---- src/platform/locales/text/zh.json | 3 -- src/platform/locales/textCatalog.test.ts | 3 ++ 6 files changed, 54 insertions(+), 27 deletions(-) diff --git a/crates/ide-core/src/version_managers.rs b/crates/ide-core/src/version_managers.rs index 18e968b3d..19e7cd4f8 100644 --- a/crates/ide-core/src/version_managers.rs +++ b/crates/ide-core/src/version_managers.rs @@ -158,6 +158,7 @@ pub fn path_with_shims(managers: &[Manager], path: &OsStr) -> OsString { mod tests { use super::*; + #[cfg(unix)] fn mkdir(p: &Path) { std::fs::create_dir_all(p).unwrap(); } diff --git a/crates/ide-host/tests/remote_env.rs b/crates/ide-host/tests/remote_env.rs index 13fdb754e..87e141772 100644 --- a/crates/ide-host/tests/remote_env.rs +++ b/crates/ide-host/tests/remote_env.rs @@ -109,7 +109,13 @@ fn server_environment_from_login_shell_and_devcontainer() { let started = project.join("started.txt"); wait_for("postStartCommand", || std::fs::read_to_string(&started).is_ok_and(|s| s.ends_with('\n'))); assert_eq!(std::fs::read_to_string(&started).unwrap(), "two-demo\n"); - wait_for("the task to end", || events.0.lock().iter().any(|e| matches!(e, IdeEvent::Task { done: true, .. }))); + wait_for("postStartCommand to end", || { + events + .0 + .lock() + .iter() + .any(|e| matches!(e, IdeEvent::Task { title, done: true, .. } if title == "postStartCommand")) + }); assert!(events.0.lock().iter().any( |e| matches!(e, IdeEvent::Task { message: Some(m), title, .. } if m == "post-start-output" && title == "postStartCommand") )); diff --git a/src/features/vcs/CommitView.tsx b/src/features/vcs/CommitView.tsx index da57fc937..ccbe0c788 100644 --- a/src/features/vcs/CommitView.tsx +++ b/src/features/vcs/CommitView.tsx @@ -86,7 +86,6 @@ export function useStashTabCounts(available: boolean) { void Promise.all(roots.map((r) => call(commands.vcsStashes(r)))) .then((lists) => live && setStashCount(lists.reduce((n, list) => n + list.length, 0))) .catch(() => undefined); - else setStashCount(0); return () => { live = false; }; @@ -95,7 +94,7 @@ export function useStashTabCounts(available: boolean) { useEffect(() => { if (available) void loadShelves().catch(() => undefined); }, [available]); - return { stashCount, shelfCount }; + return { stashCount: available ? stashCount : 0, shelfCount }; } /** "Commit" / "Shelf" / "Stash" content tabs; they stand in for the tool window title. */ @@ -842,36 +841,34 @@ export function StashesPanel() { const repo = useVcs((s) => s.repo); const repos = useVcs((s) => s.roots); const version = useVcs((s) => s.version); - const [stashes, setStashes] = useState<(import("@/bindings").Stash & { repo: string | null })[]>([]); - const [loaded, setLoaded] = useState(false); - const [error, setError] = useState(null); const [tick, setTick] = useState(0); const [active, setActive] = useState(0); + const request = useMemo(() => ({ repos, repo, version, tick }), [repos, repo, version, tick]); + const [result, setResult] = useState<{ + request: typeof request; + stashes: (import("@/bindings").Stash & { repo: string | null })[]; + error: string | null; + } | null>(null); + const loaded = result?.request === request; + const stashes = loaded ? result.stashes : []; + const error = loaded ? result.error : null; useEffect(() => { let live = true; - setLoaded(false); void Promise.all( - (repos.length ? repos : [repo]).map(async (root) => + (request.repos.length ? request.repos : [request.repo]).map(async (root) => (await call(commands.vcsStashes(root))).map((s) => ({ ...s, repo: root })), ), ) .then((lists) => { - if (live) { - setStashes(lists.flat()); - setError(null); - setLoaded(true); - } + if (live) setResult({ request, stashes: lists.flat(), error: null }); }) .catch((e) => { - if (live) { - setError(String(e)); - setLoaded(true); - } + if (live) setResult({ request, stashes: [], error: String(e) }); }); return () => { live = false; }; - }, [repos, repo, version, tick]); + }, [request]); const current = stashes[active]; return (
diff --git a/src/features/vcs/__tests__/multiRootStashes.test.tsx b/src/features/vcs/__tests__/multiRootStashes.test.tsx index e7fc5cdfd..eb4458cf9 100644 --- a/src/features/vcs/__tests__/multiRootStashes.test.tsx +++ b/src/features/vcs/__tests__/multiRootStashes.test.tsx @@ -1,17 +1,13 @@ import { beforeEach, expect, it, vi } from "vitest"; import { act, fireEvent, render, screen } from "@testing-library/react"; -const apply = vi.hoisted(() => vi.fn()); +const { apply, list } = vi.hoisted(() => ({ apply: vi.fn(), list: vi.fn() })); vi.mock("@/platform/ipc", async (original) => { const real = await original(); return { ...real, commands: { ...real.commands, - vcsStashes: (repo: string) => - Promise.resolve({ - status: "ok", - data: [{ index: 0, message: `On main: ${repo.endsWith("front") ? "First" : "Second"}`, timeMs: 0 }], - }), + vcsStashes: list, }, }; }); @@ -24,6 +20,12 @@ import { useVcs } from "../store"; beforeEach(() => { apply.mockReset(); + list.mockReset().mockImplementation((repo: string) => + Promise.resolve({ + status: "ok", + data: [{ index: 0, message: `On main: ${repo.endsWith("front") ? "First" : "Second"}`, timeMs: 0 }], + }), + ); useVcs.setState({ root: "/p", roots: ["/p/front", "/p/back"], repo: "/p/front" }); }); @@ -36,3 +38,24 @@ it("lists stashes from every root and applies the selected row in its own reposi fireEvent.click(screen.getByRole("button", { name: "Apply Stash" })); expect(apply).toHaveBeenCalledWith(0, false, "/p/front"); }); + +it("disables stale stash actions while another repository is loading", async () => { + render(); + await screen.findByText("front · First"); + let resolve!: (value: unknown) => void; + list.mockImplementation( + () => + new Promise((done) => { + resolve = done; + }), + ); + act(() => useVcs.setState({ root: "/other", roots: ["/other"], repo: "/other" })); + expect(screen.queryByText("front · First")).not.toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Apply Stash" })).toBeDisabled(); + await act(async () => { + resolve({ status: "ok", data: [{ index: 0, message: "On main: New project", timeMs: 0 }] }); + }); + await screen.findByText("New project"); + fireEvent.click(screen.getByRole("button", { name: "Apply Stash" })); + expect(apply).toHaveBeenCalledWith(0, false, "/other"); +}); diff --git a/src/platform/locales/text/zh.json b/src/platform/locales/text/zh.json index 034465e30..2aae3fffd 100644 --- a/src/platform/locales/text/zh.json +++ b/src/platform/locales/text/zh.json @@ -585,8 +585,6 @@ "Duplicate groups": "重复组", "Duplicates": "重复项", "Duplicate…": "复制…", - "Eclipse": "Eclipse", - "Eclipse (macOS)": "Eclipse (macOS)", "Edit": "编辑", "Edit Cell": "编辑单元格", "Edit Changelist": "编辑更改列表", @@ -2849,7 +2847,6 @@ "Test classpath of the module": "模块的测试类路径", "Run a Java main class": "运行 Java 主类", "Run a Spring Boot application": "运行 Spring Boot 应用程序", - "Spring Boot": "Spring Boot", "Passed to the JVM that spring-boot:run or the tests start, not to Maven": "传给 spring-boot:run 或测试启动的 JVM,而不是 Maven 本身", "Passed to the JVMs the tasks start (bootRun, run, test), not to Gradle": "传给任务启动的 JVM(bootRun、run、test),而不是 Gradle 本身", "Run a package.json script": "运行 package.json 脚本", diff --git a/src/platform/locales/textCatalog.test.ts b/src/platform/locales/textCatalog.test.ts index 17d0d1c24..9daa99dc1 100644 --- a/src/platform/locales/textCatalog.test.ts +++ b/src/platform/locales/textCatalog.test.ts @@ -102,6 +102,8 @@ const UNTRANSLATED = new Set([ "Docker", "Docker Compose", "Dockerfile", + "Eclipse", + "Eclipse (macOS)", "Eclipse Temurin", "EditorConfig", "Git", @@ -123,6 +125,7 @@ const UNTRANSLATED = new Set([ "Node.js", "npm", "OpenIDEA", + "Spring Boot", "PostgreSQL", "Pyright", "Rust",