Файли
openidea/src/features/vcs/__tests__/localChanges.test.tsx
T
arookieofcтаa-rookie-of-C-language 00e717d8fd feat(vcs): diff preview in the Git window's Local Changes tab (#714)
The Local Changes tab of the Git tool window showed only the changes tree; a file's diff was visible only after opening a diff tab.

It now previews the selected file's working-tree diff beside the tree when the window is docked at the bottom and below it when docked on a side. The preview follows the selection (arrow keys included), shows a placeholder for no or multiple selection, and can be turned off with View Options › Show Diff Preview (persisted; the selection is kept when toggling).

The preview reuses the diff editor, whose toolbar did not fit a side-docked window: the editor is now a size container, the whitespace and highlighting pickers move to the settings menu below 640px and the separators go below 380px, buttons no longer shrink and the differences counter truncates.

Fixes #710.

Co-authored-by: a-rookie-of-C-language <[email protected]>
2026-11-03 10:18:39 +08:00

131 рядки
5.6 KiB
TypeScript

import { act, fireEvent, render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("@/platform/ipc", async (importOriginal) => {
const real = await importOriginal<typeof import("@/platform/ipc")>();
return { ...real, call: vi.fn() };
});
vi.mock("../DiffEditor", () => ({
DiffEditor: ({ input }: { input: { data: { path: string } } }) => (
<div data-testid="diff-preview" data-path={input.data.path} />
),
}));
const openDiff = vi.hoisted(() => vi.fn());
vi.mock("../diffInput", async (original) => ({
...(await original<typeof import("../diffInput")>()),
openDiff,
}));
import { LocalChangesView } from "../CommitView";
import { setDiffPreview, useCommit } from "../commitStore";
import { useVcs } from "../store";
import { useToolWindowLayout } from "@/workbench/toolwindows/store";
describe("Local Changes diff preview (#710)", () => {
beforeEach(() => {
localStorage.clear();
openDiff.mockClear();
useCommit.setState({ diffPreview: true, selection: [] });
useVcs.setState({ root: "/p", repo: "/p", roots: ["/p"], status: [] });
useToolWindowLayout.setState({ placements: {} });
});
it("places the preview beside the tree for default bottom docking", () => {
render(<LocalChangesView />);
expect(screen.getByRole("separator")).toHaveAttribute("aria-orientation", "vertical");
});
it.each(["left", "right"] as const)("stacks the preview for %s docking and updates when moved", (anchor) => {
render(<LocalChangesView />);
act(() => useToolWindowLayout.setState({ placements: { git: { anchor } } }));
expect(screen.getByRole("separator")).toHaveAttribute("aria-orientation", "horizontal");
act(() => useToolWindowLayout.setState({ placements: { git: { anchor: "bottom" } } }));
expect(screen.getByRole("separator")).toHaveAttribute("aria-orientation", "vertical");
});
it("previews the single selected file's working-tree diff", () => {
useCommit.setState({ selection: ["/p/src/a.ts"] });
render(<LocalChangesView />);
expect(screen.getByTestId("diff-preview").getAttribute("data-path")).toBe("/p/src/a.ts");
});
it("shows a placeholder until exactly one file is selected", () => {
render(<LocalChangesView />);
expect(screen.getByText("Select a file to preview its changes")).toBeTruthy();
useCommit.setState({ selection: ["/p/a.ts", "/p/b.ts"] });
expect(screen.getByText("Select a file to preview its changes")).toBeTruthy();
});
it("follows the selection", () => {
useCommit.setState({ selection: ["/p/a.ts"] });
render(<LocalChangesView />);
expect(screen.getByTestId("diff-preview").getAttribute("data-path")).toBe("/p/a.ts");
act(() => useCommit.setState({ selection: ["/p/b.ts"] }));
expect(screen.getByTestId("diff-preview").getAttribute("data-path")).toBe("/p/b.ts");
act(() => useCommit.setState({ selection: [] }));
expect(screen.queryByTestId("diff-preview")).toBeNull();
});
it("previews keyboard selection and opens the full diff on Enter and double click", () => {
useVcs.setState({
status: ["a.ts", "b.ts"].map((name) => ({
path: `/p/${name}`,
relPath: name,
staged: null,
unstaged: "modified" as const,
renamedFrom: null,
})),
});
render(<LocalChangesView />);
const tree = screen.getByRole("tree", { name: "Changes" });
tree.focus();
fireEvent.keyDown(tree, { key: "Home" });
fireEvent.keyDown(tree, { key: "ArrowDown" });
expect(screen.getByTestId("diff-preview")).toHaveAttribute("data-path", "/p/a.ts");
fireEvent.keyDown(tree, { key: "ArrowDown" });
expect(screen.getByTestId("diff-preview")).toHaveAttribute("data-path", "/p/b.ts");
expect(tree).toHaveFocus();
expect(openDiff).not.toHaveBeenCalled();
fireEvent.keyDown(tree, { key: "Enter" });
expect(openDiff).toHaveBeenLastCalledWith(expect.objectContaining({ path: "/p/b.ts" }), { preview: true });
fireEvent.doubleClick(screen.getByText("a.ts"));
expect(openDiff).toHaveBeenLastCalledWith(expect.objectContaining({ path: "/p/a.ts" }), { preview: true });
});
it("turns off via the View Options toggle, persists, and keeps the tree alone", () => {
useCommit.setState({ selection: ["/p/a.ts"] });
render(<LocalChangesView />);
act(() => setDiffPreview(false));
expect(useCommit.getState().diffPreview).toBe(false);
expect(screen.queryByTestId("diff-preview")).toBeNull();
expect(screen.getByText("No local changes")).toBeTruthy();
expect(localStorage.getItem("openidea.vcs.diffPreview")).toBe("false");
});
it("keeps the tree selection when the preview is toggled off and on", () => {
useVcs.setState({
status: ["a.ts", "b.ts"].map((name) => ({
path: `/p/${name}`,
relPath: name,
staged: null,
unstaged: "modified" as const,
renamedFrom: null,
})),
});
render(<LocalChangesView />);
const tree = screen.getByRole("tree", { name: "Changes" });
tree.focus();
fireEvent.keyDown(tree, { key: "Home" });
fireEvent.keyDown(tree, { key: "ArrowDown" });
fireEvent.keyDown(tree, { key: "ArrowDown" });
expect(screen.getByText("b.ts").closest('[role="treeitem"]')).toHaveAttribute("aria-selected", "true");
act(() => setDiffPreview(false));
expect(screen.getByText("b.ts").closest('[role="treeitem"]')).toHaveAttribute("aria-selected", "true");
act(() => setDiffPreview(true));
expect(screen.getByText("b.ts").closest('[role="treeitem"]')).toHaveAttribute("aria-selected", "true");
expect(screen.getByTestId("diff-preview")).toHaveAttribute("data-path", "/p/b.ts");
});
});