fix: resolve frontend and cross-platform CI failures

Цей коміт міститься в:
l0ng-ai committed 2026-10-29 14:45:42 +08:00
1 джерело 4d503ef976
коміт 364bb5ef50
6 files changed
+54 -27

No files matched your search

+14 -17
Переглянути файл
@@ -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<string | null>(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 (
<div className={styles.changes}>
+29 -6
Переглянути файл
@@ -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<typeof import("@/platform/ipc")>();
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(<StashesPanel />);
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");
});