Files
fusion/packages/dashboard/app/components/__tests__/GitManagerModal.test.tsx
gsxdsm a53e4c1420 feat(KB-048): add collapsible list sections to dashboard
- Add section expansion state management with localStorage persistence
- Update section headers with chevron toggle controls
- Implement conditional task row rendering based on section state
- Add Expand All / Collapse All toolbar controls
- Add CSS styles for chevron rotation animation and section headers
- Add comprehensive tests for collapsible section behavior
2026-03-29 19:51:41 -07:00

365 lines
9.2 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { GitManagerModal } from "../GitManagerModal";
import type { Task } from "@kb/core";
// Mock the API module
vi.mock("../../api", async () => {
return {
fetchGitStatus: vi.fn(),
fetchGitCommits: vi.fn(),
fetchCommitDiff: vi.fn(),
fetchGitBranches: vi.fn(),
fetchGitWorktrees: vi.fn(),
createBranch: vi.fn(),
checkoutBranch: vi.fn(),
deleteBranch: vi.fn(),
fetchRemote: vi.fn(),
pullBranch: vi.fn(),
pushBranch: vi.fn(),
};
});
import {
fetchGitStatus,
fetchGitCommits,
fetchGitBranches,
fetchGitWorktrees,
createBranch,
checkoutBranch,
deleteBranch,
fetchRemote,
pullBranch,
pushBranch,
} from "../../api";
const mockAddToast = vi.fn();
const mockTasks: Task[] = [
{ id: "KB-001", description: "Test task 1", column: "in-progress", dependencies: [], worktree: "/worktrees/kb-001", steps: [], currentStep: 0, log: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() },
{ id: "KB-002", description: "Test task 2", column: "todo", dependencies: [], steps: [], currentStep: 0, log: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() },
];
describe("GitManagerModal", () => {
beforeEach(() => {
vi.clearAllMocks();
// Default mock implementations
(fetchGitStatus as any).mockResolvedValue({
branch: "main",
commit: "abc1234",
isDirty: false,
ahead: 0,
behind: 0,
});
(fetchGitCommits as any).mockResolvedValue([
{ hash: "abc1234", shortHash: "abc1", message: "Test commit", author: "User", date: "2026-01-01", parents: [] },
]);
(fetchGitBranches as any).mockResolvedValue([
{ name: "main", isCurrent: true, remote: "origin/main" },
{ name: "feature", isCurrent: false },
]);
(fetchGitWorktrees as any).mockResolvedValue([
{ path: "/worktrees/kb-001", branch: "kb/kb-001", isMain: false, isBare: false, taskId: "KB-001" },
{ path: "/repo", branch: "main", isMain: true, isBare: false },
]);
});
it("renders nothing when not open", () => {
const { container } = render(
<GitManagerModal
isOpen={false}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
expect(container.firstChild).toBeNull();
});
it("renders modal when open", async () => {
render(
<GitManagerModal
isOpen={true}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
await waitFor(() => {
expect(screen.getByText("Git Manager")).toBeInTheDocument();
});
});
it("fetches status on mount", async () => {
render(
<GitManagerModal
isOpen={true}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
await waitFor(() => {
expect(fetchGitStatus).toHaveBeenCalled();
});
});
it("switches tabs when clicking navigation", async () => {
render(
<GitManagerModal
isOpen={true}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
// Wait for initial load
await waitFor(() => {
expect(screen.getByText("Repository Status")).toBeInTheDocument();
});
// Click Commits tab
fireEvent.click(screen.getByText("Commits"));
await waitFor(() => {
expect(fetchGitCommits).toHaveBeenCalled();
});
// Click Branches tab
fireEvent.click(screen.getByText("Branches"));
await waitFor(() => {
expect(fetchGitBranches).toHaveBeenCalled();
});
});
it("closes on Escape key", async () => {
const onClose = vi.fn();
render(
<GitManagerModal
isOpen={true}
onClose={onClose}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
await waitFor(() => {
expect(screen.getByText("Git Manager")).toBeInTheDocument();
});
fireEvent.keyDown(document, { key: "Escape" });
expect(onClose).toHaveBeenCalled();
});
it("shows status information", async () => {
render(
<GitManagerModal
isOpen={true}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
await waitFor(() => {
expect(screen.getByText("main")).toBeInTheDocument();
expect(screen.getByText("Clean")).toBeInTheDocument();
});
});
it("loads commits and shows them", async () => {
render(
<GitManagerModal
isOpen={true}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
fireEvent.click(screen.getByText("Commits"));
await waitFor(() => {
expect(screen.getByText("Test commit")).toBeInTheDocument();
});
});
it("loads branches and shows current branch", async () => {
render(
<GitManagerModal
isOpen={true}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
fireEvent.click(screen.getByText("Branches"));
await waitFor(() => {
expect(screen.getByText("main")).toBeInTheDocument();
expect(screen.getByText("feature")).toBeInTheDocument();
});
});
it("loads worktrees and shows task associations", async () => {
render(
<GitManagerModal
isOpen={true}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
fireEvent.click(screen.getByText("Worktrees"));
await waitFor(() => {
expect(screen.getByText("KB-001")).toBeInTheDocument();
expect(screen.getByText("2 total")).toBeInTheDocument();
});
});
it("calls createBranch when form is submitted", async () => {
const user = userEvent.setup();
(createBranch as any).mockResolvedValue(undefined);
render(
<GitManagerModal
isOpen={true}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
fireEvent.click(screen.getByText("Branches"));
await waitFor(() => {
expect(screen.getByPlaceholderText("New branch name")).toBeInTheDocument();
});
const nameInput = screen.getByPlaceholderText("New branch name");
await user.type(nameInput, "new-feature");
const createButton = screen.getByRole("button", { name: /create/i });
await user.click(createButton);
await waitFor(() => {
expect(createBranch).toHaveBeenCalledWith("new-feature", undefined);
});
});
it("calls checkoutBranch when checkout button clicked", async () => {
const user = userEvent.setup();
(checkoutBranch as any).mockResolvedValue(undefined);
render(
<GitManagerModal
isOpen={true}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
fireEvent.click(screen.getByText("Branches"));
await waitFor(() => {
expect(screen.getByText("feature")).toBeInTheDocument();
});
});
it("shows remote operations buttons", async () => {
render(
<GitManagerModal
isOpen={true}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
fireEvent.click(screen.getByText("Remotes"));
await waitFor(() => {
expect(screen.getByRole("button", { name: /fetch/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /pull/i })).toBeInTheDocument();
expect(screen.getByRole("button", { name: /push/i })).toBeInTheDocument();
});
});
it("calls fetchRemote when Fetch button clicked", async () => {
const user = userEvent.setup();
(fetchRemote as any).mockResolvedValue({ fetched: true, message: "Fetched" });
render(
<GitManagerModal
isOpen={true}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
fireEvent.click(screen.getByText("Remotes"));
const fetchButton = await screen.findByRole("button", { name: /fetch/i });
await user.click(fetchButton);
await waitFor(() => {
expect(fetchRemote).toHaveBeenCalled();
});
});
it("shows error toast when fetch fails", async () => {
const user = userEvent.setup();
(fetchRemote as any).mockRejectedValue(new Error("Network error"));
render(
<GitManagerModal
isOpen={true}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
fireEvent.click(screen.getByText("Remotes"));
const fetchButton = await screen.findByRole("button", { name: /fetch/i });
await user.click(fetchButton);
await waitFor(() => {
expect(mockAddToast).toHaveBeenCalledWith("Network error", "error");
});
});
it("shows ahead/behind indicators in status", async () => {
(fetchGitStatus as any).mockResolvedValue({
branch: "main",
commit: "abc1234",
isDirty: false,
ahead: 2,
behind: 3,
});
render(
<GitManagerModal
isOpen={true}
onClose={vi.fn()}
tasks={mockTasks}
addToast={mockAddToast}
/>
);
await waitFor(() => {
expect(screen.getByText("2")).toBeInTheDocument();
expect(screen.getByText("3")).toBeInTheDocument();
});
});
});