mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
Merge pull request #13674 from esengine/fix/flaky-frontend-tests
fix(desktop): the branch menu survives the chip settling, and its list is re-read when the root moves
This commit is contained in:
6 files changed
+63
-4
No files matched your search
@@ -0,0 +1,33 @@
|
||||
// @vitest-environment jsdom
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { afterEach, expect, it, vi } from "vitest";
|
||||
import { MockPort } from "../port/mock";
|
||||
import { WorktreeChip } from "./WorktreeChip";
|
||||
import type { AgentPort } from "../port/port";
|
||||
|
||||
const root = document.createElement("div");
|
||||
let mounted: ReturnType<typeof createRoot> | null = null;
|
||||
afterEach(() => { mounted?.unmount(); mounted = null; root.remove(); delete (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT; });
|
||||
|
||||
it("keeps open a menu opened between the chip's commit and its mount effects", async () => {
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = false;
|
||||
document.body.append(root);
|
||||
const port = new MockPort() as AgentPort;
|
||||
const sources = { available: false, sourceRoot: "/w", sources: [] };
|
||||
mounted = createRoot(root);
|
||||
const clicked = new Promise<void>((resolve) => {
|
||||
const watch = new MutationObserver(() => {
|
||||
const button = root.querySelector<HTMLButtonElement>("button.worktree-trigger");
|
||||
if (!button) return;
|
||||
watch.disconnect();
|
||||
button.click();
|
||||
resolve();
|
||||
});
|
||||
watch.observe(root, { childList: true, subtree: true });
|
||||
});
|
||||
mounted.render(<WorktreeChip port={port} sources={sources} canCreate={false} selection={null} branch="main" busy={false}
|
||||
changeCount={0} onSelect={vi.fn()} refresh={async () => sources} onChanged={vi.fn()} onError={vi.fn()} />);
|
||||
await clicked;
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
expect(root.querySelector("button.worktree-trigger")?.getAttribute("aria-expanded")).toBe("true");
|
||||
});
|
||||
@@ -43,9 +43,14 @@ export function WorktreeChip({ sources, canCreate, selection, branch, busy, chan
|
||||
const isolated = workspace?.current === sources.sourceRoot && !!workspace.isolated;
|
||||
const chosen = sources.sources.find((source) => source.ref === selection?.ref);
|
||||
const label = chosen?.name ?? selection?.ref ?? branch;
|
||||
const scope = useRef({ port, root: sources.sourceRoot });
|
||||
useEffect(() => {
|
||||
let live = true;
|
||||
returnFocus.current = false; setWorkspace(null); setOpen(false); setQuery("");
|
||||
if (scope.current.port !== port || scope.current.root !== sources.sourceRoot) {
|
||||
scope.current = { port, root: sources.sourceRoot };
|
||||
returnFocus.current = false; setOpen(false); setQuery("");
|
||||
}
|
||||
setWorkspace(null);
|
||||
void port.workspaces().then((value) => { if (live) setWorkspace(value); }).catch(() => {});
|
||||
return () => { live = false; };
|
||||
}, [port, sources.sourceRoot]);
|
||||
|
||||
@@ -134,7 +134,9 @@ it("keeps undo after unrelated rerender, supports a refused undo retry and repea
|
||||
it("allows explicit dismissal without invoking undo", async () => {
|
||||
const h = await open(); await restore();
|
||||
await screen.findByRole("button", { name: "撤销这次回退" });
|
||||
fireEvent.click(screen.getByRole("button", { name: "关闭回退提示" }));
|
||||
const close = screen.getByRole<HTMLButtonElement>("button", { name: "关闭回退提示" });
|
||||
await waitFor(() => expect(close.disabled).toBe(false));
|
||||
fireEvent.click(close);
|
||||
expect(screen.queryByText("已还原 1 个文件")).toBeNull(); expect(h.undo).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -22,3 +22,16 @@ it("releases the switch state on port change and ignores the old completion", as
|
||||
expect(switched).not.toHaveBeenCalled();
|
||||
expect(error).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("keeps the list a reader asked for when the root settles while it is being read", async () => {
|
||||
const port = new MockPort();
|
||||
let land!: () => void;
|
||||
const branches = port.branches.bind(port);
|
||||
vi.spyOn(port, "branches").mockImplementation(() => new Promise((done) => { land = () => done(branches()); }) as ReturnType<typeof branches>);
|
||||
const { result, rerender } = renderHook(({ root }) => useWorkspaceBranches(port, vi.fn(), undefined, vi.fn(), root), { initialProps: { root: "/a" } });
|
||||
act(() => result.current.loadBranches());
|
||||
rerender({ root: "/b" });
|
||||
await act(async () => { land(); await Promise.resolve(); });
|
||||
expect(result.current.reading).toBe("ready");
|
||||
expect(result.current.locals.map((b) => b.name)).toContain("studio");
|
||||
});
|
||||
@@ -6,12 +6,16 @@ export function useWorkspaceBranches(port: AgentPort, onChanged: () => void, onS
|
||||
const [moving, setMoving] = useState<string | null>(null);
|
||||
const [reading, setReading] = useState<"idle" | "loading" | "ready" | "failed">("idle");
|
||||
const generation = useRef(0);
|
||||
useEffect(() => { generation.current++; setLocals([]); setMoving(null); setReading("idle"); return () => { generation.current++; }; }, [port, root]);
|
||||
const asked = useRef(false);
|
||||
const load = useRef<() => void>(() => {});
|
||||
useEffect(() => { generation.current++; setLocals([]); setMoving(null); setReading("idle"); if (asked.current) load.current(); return () => { generation.current++; }; }, [port, root]);
|
||||
const loadBranches = useCallback(() => {
|
||||
const current = generation.current;
|
||||
asked.current = true;
|
||||
setReading("loading");
|
||||
void port.branches().then((result) => { if (current === generation.current) { setLocals(result.branches); setReading("ready"); } }).catch((error) => { if (current === generation.current) { setReading("failed"); onError(error); } });
|
||||
void port.branches().then((result) => { if (current === generation.current) { asked.current = false; setLocals(result.branches); setReading("ready"); } }).catch((error) => { if (current === generation.current) { asked.current = false; setReading("failed"); onError(error); } });
|
||||
}, [port, onError, root]);
|
||||
load.current = loadBranches;
|
||||
const switchTo = (name: string) => {
|
||||
if (moving !== null) return;
|
||||
const current = generation.current;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
- 修复:刚进入窗格就打开分支菜单时,菜单会被自己的挂载收尾关掉,或一直停在加载中。关联 #13674。
|
||||
- Fixed: Opening the branch menu just as a pane settles no longer shuts it or leaves it loading forever. Refs #13674.
|
||||
Reference in new issue
Block a user