Файли
openidea/src/features/vcs/__tests__/commitMode.test.ts
T
l0ng-ai eb67595e72 feat(vcs): Add a modal commit mode and fill in Git log, branch popup and settings (#144)
- Settings › Commit › "Use non-modal commit interface": when off, Local
  Changes, Shelf and Stash become tabs of the Git tool window and ⌘K opens
  a Commit dialog; switches live.
- Git tool window: Worktrees tab; Show History and Show Git Log for a
  branch open their own closable tabs instead of changing the Log filters.
- Log: toolbar and context menu for a commit's changed files, vertical
  toolbar, Tags node and hiding for the branches pane, Collapse / Expand
  Linear Branches as separate actions, and a Version Control › Log page.
- Branch popup: collapsible groups, branches grouped by prefix, an options
  menu behind the gear, incoming / outgoing icons and "Pull into … Using
  Rebase / Merge"; New Branch on ⌥⌘N outside the editor.
- Git main menu regrouped with Uncommitted Changes and Current File
  submenus; rebase / merge controls enabled only while one is in progress.
- Settings: Version Control lists its pages; new options for clearing the
  initial commit message, showing Update options, confirming dropped
  commits, revealing created patches, large-file and file-name warnings,
  the Push dialog after Commit and Push, combined stashes and shelves, and
  removing applied files from the shelf.
2026-10-28 21:49:43 +08:00

130 рядки
5.6 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const vcsLog = vi.fn();
const fsReadText = vi.fn();
vi.mock("@/platform/ipc", async (importOriginal) => {
const real = await importOriginal<typeof import("@/platform/ipc")>();
return {
...real,
commands: {
...real.commands,
vcsLog: (repo: unknown, query: unknown) => vcsLog(repo, query),
vcsConfigGet: async () => ({ status: "ok", data: null }),
fsReadText: (path: string) => fsReadText(path),
},
};
});
import { gitTabs, shownTab, stashTabOf, stashTabs } from "../gitTabs";
import { pushDialogNeeded } from "../commitUi";
import { installPreparedMessage, keepMessageAfterCommit, messageAfterCommit, useCommit } from "../commitStore";
import { closeLogTab, emptyFilters, openLogTab, selectGitTab, setLogFilters, useGitLog } from "../logStore";
import { useVcs } from "../store";
const ids = (tabs: { id: string }[]) => tabs.map((t) => t.id);
describe("Git and Commit tool window tabs", () => {
const base = { combine: false, shelfCount: 0, stashCount: 0, tab: "log", logTabs: [] };
it("puts Local Changes, Shelf and Stash into the Git window only with the modal commit interface", () => {
expect(ids(gitTabs({ ...base, modal: false }))).toEqual(["log", "console", "worktrees"]);
expect(ids(gitTabs({ ...base, modal: true }))).toEqual(["changes", "stashes", "log", "console", "worktrees"]);
expect(ids(gitTabs({ ...base, modal: true, shelfCount: 2 }))).toEqual([
"changes",
"shelf",
"stashes",
"log",
"console",
"worktrees",
]);
});
it("combines stashes and shelves into one tab", () => {
expect(stashTabs({ ...base, combine: true, shelfCount: 2, stashCount: 1 })).toEqual([
{ id: "stashes", label: "Stashes and Shelves", count: 3 },
]);
expect(stashTabOf("shelf", true)).toBe("stashes");
expect(stashTabOf("shelf", false)).toBe("shelf");
});
it("lists log tabs as closable after the main Log, and falls back to the Log for a missing tab", () => {
const logTabs = [{ id: "history:1", kind: "history" as const, target: "/p/src/a.ts" }];
const tabs = gitTabs({ ...base, modal: false, logTabs });
expect(tabs.find((t) => t.id === "history:1")).toEqual({ id: "history:1", label: "History: a.ts", closable: true });
expect(shownTab("changes", tabs, false)).toBe("log");
expect(shownTab("history:1", tabs, false)).toBe("history:1");
});
});
describe("Commit and Push… push dialog", () => {
const git = { showPushDialog: true, pushDialogOnlyProtected: false, protectedBranches: "master;main" };
it("shows the dialog unless turned off, or only for protected branches", () => {
expect(pushDialogNeeded(git, "feature")).toBe(true);
expect(pushDialogNeeded({ ...git, showPushDialog: false }, "main")).toBe(false);
expect(pushDialogNeeded({ ...git, pushDialogOnlyProtected: true }, "feature")).toBe(false);
expect(pushDialogNeeded({ ...git, pushDialogOnlyProtected: true }, "main")).toBe(true);
// Nothing to tell the target from (detached HEAD): always the dialog.
expect(pushDialogNeeded({ ...git, showPushDialog: false }, null)).toBe(true);
});
});
describe("commit message after a commit", () => {
beforeEach(() => fsReadText.mockReset());
it("keeps the committed message unless Clear initial commit message is on", () => {
expect(messageAfterCommit(" fix: thing \n", false)).toBe("fix: thing");
expect(messageAfterCommit("fix: thing", true)).toBe("");
});
it("lets a prepared merge message replace a kept one, but not a typed one", async () => {
useVcs.setState({ root: "/p", branch: null });
const dispose = installPreparedMessage();
keepMessageAfterCommit("fix: thing", false);
fsReadText.mockResolvedValue({ status: "ok", data: "Merge branch 'x'\n# Conflicts:\n" });
useVcs.setState({ branch: { operation: "merge" } as never });
await vi.waitFor(() => expect(useCommit.getState().message).toBe("Merge branch 'x'"));
useVcs.setState({ branch: null });
useCommit.setState({ message: "typed" });
useVcs.setState({ branch: { operation: "merge" } as never });
await Promise.resolve();
expect(useCommit.getState().message).toBe("typed");
dispose();
});
});
describe("log tabs of their own", () => {
beforeEach(() => {
vcsLog.mockReset().mockResolvedValue({ status: "ok", data: [] });
useVcs.setState({ root: "/p", repo: "/p", roots: ["/p"] });
useGitLog.setState({ tab: "log", logTabs: [], shownLog: "log", filters: emptyFilters() });
});
it("opens Show History in a closable tab and leaves the main Log's filters alone", () => {
setLogFilters({ authors: ["bob"] });
const id = openLogTab("history", "/p/src/a.ts");
expect(useGitLog.getState()).toMatchObject({ tab: id, shownLog: id });
expect(useGitLog.getState().filters).toMatchObject({ path: "/p/src/a.ts", authors: [] });
// The same file again: the same tab.
selectGitTab("log");
expect(useGitLog.getState().filters.authors).toEqual(["bob"]);
expect(openLogTab("history", "/p/src/a.ts")).toBe(id);
expect(useGitLog.getState().logTabs).toHaveLength(1);
closeLogTab(id);
expect(useGitLog.getState()).toMatchObject({ tab: "log", shownLog: "log", logTabs: [] });
expect(useGitLog.getState().filters).toMatchObject({ authors: ["bob"], path: null });
});
it("keeps a log tab's view while another content tab is shown", () => {
const id = openLogTab("branch", "feature");
selectGitTab("console");
expect(useGitLog.getState()).toMatchObject({ tab: "console", shownLog: id });
selectGitTab(id);
expect(useGitLog.getState().filters.branches).toEqual(["feature"]);
closeLogTab(id);
});
});