| import React from "react"; |
| import { render, screen } from "@testing-library/react"; |
| import userEvent from "@testing-library/user-event"; |
| import { describe, it, expect, vi } from "vitest"; |
| import { CommitList } from "#/components/features/diff-viewer/commit-list"; |
| import type { GitCommit } from "#/api/open-hands.types"; |
|
|
| vi.mock("react-i18next", () => ({ |
| useTranslation: () => ({ |
| t: (key: string, options?: { count?: number }) => { |
| if ( |
| key === "DIFF_VIEWER$UNCOMMITTED_FILE_COUNT" && |
| typeof options?.count === "number" |
| ) { |
| return options.count === 1 |
| ? `${options.count} file` |
| : `${options.count} files`; |
| } |
| return key; |
| }, |
| }), |
| })); |
|
|
| vi.mock("#/hooks/query/use-commit-changes", () => ({ |
| useCommitChanges: () => ({ |
| data: undefined, |
| isLoading: false, |
| isSuccess: false, |
| }), |
| })); |
|
|
| vi.mock("#/components/features/diff-viewer/diff-change-list", () => ({ |
| DiffChangeList: ({ |
| changes, |
| }: { |
| changes: Array<{ path: string; status: string }>; |
| }) => ( |
| <div data-testid="diff-change-list"> |
| {changes.map((change) => ( |
| <div key={change.path}>{change.path}</div> |
| ))} |
| </div> |
| ), |
| })); |
|
|
| const makeCommit = (overrides: Partial<GitCommit> = {}): GitCommit => ({ |
| sha: "a".repeat(40), |
| shortSha: "aaaaaaa", |
| subject: "add logging", |
| author: "Agent", |
| timestamp: "2026-07-10T12:00:00+07:00", |
| ...overrides, |
| }); |
|
|
| describe("CommitList", () => { |
| it("renders an Uncommitted accordion row above the commit rows", () => { |
| |
| render( |
| <CommitList |
| commits={[makeCommit()]} |
| hasMore={false} |
| uncommittedChanges={[{ path: "src/a.ts", status: "M" }]} |
| />, |
| ); |
|
|
| |
| expect(screen.getByTestId("uncommitted-changes-row")).toBeInTheDocument(); |
| expect(screen.getByText("DIFF_VIEWER$UNCOMMITTED")).toBeInTheDocument(); |
| expect(screen.getByTestId("uncommitted-changes-count")).toHaveTextContent( |
| "1 file", |
| ); |
| const rows = screen.getAllByTestId(/^(uncommitted-changes-row|commit-row)$/); |
| expect(rows[0]).toHaveAttribute("data-testid", "uncommitted-changes-row"); |
| }); |
|
|
| it("pluralizes the Uncommitted file count", () => { |
| |
| render( |
| <CommitList |
| commits={[makeCommit()]} |
| hasMore={false} |
| uncommittedChanges={[ |
| { path: "src/a.ts", status: "M" }, |
| { path: "src/b.ts", status: "A" }, |
| ]} |
| />, |
| ); |
|
|
| |
| expect(screen.getByTestId("uncommitted-changes-count")).toHaveTextContent( |
| "2 files", |
| ); |
| }); |
|
|
| it("expands Uncommitted into the working-tree file list", async () => { |
| |
| const user = userEvent.setup(); |
| render( |
| <CommitList |
| commits={[makeCommit()]} |
| hasMore={false} |
| uncommittedChanges={[{ path: "src/a.ts", status: "M" }]} |
| />, |
| ); |
|
|
| |
| await user.click(screen.getByTestId("uncommitted-changes-row-toggle")); |
|
|
| |
| expect(await screen.findByText("src/a.ts")).toBeInTheDocument(); |
| }); |
|
|
| it("collapses Uncommitted when a commit row is expanded", async () => { |
| |
| const user = userEvent.setup(); |
| render( |
| <CommitList |
| commits={[makeCommit()]} |
| hasMore={false} |
| uncommittedChanges={[{ path: "src/a.ts", status: "M" }]} |
| />, |
| ); |
| const uncommittedToggle = screen.getByTestId( |
| "uncommitted-changes-row-toggle", |
| ); |
| await user.click(uncommittedToggle); |
| expect(uncommittedToggle).toHaveAttribute("aria-expanded", "true"); |
| expect(await screen.findByText("src/a.ts")).toBeInTheDocument(); |
|
|
| |
| await user.click(screen.getByTestId("commit-row-toggle")); |
|
|
| |
| expect(uncommittedToggle).toHaveAttribute("aria-expanded", "false"); |
| expect(screen.getByTestId("commit-row-toggle")).toHaveAttribute( |
| "aria-expanded", |
| "true", |
| ); |
| }); |
|
|
| it("expands Uncommitted on request and clears the request", () => { |
| |
| const onAutoExpandHandled = vi.fn(); |
|
|
| |
| render( |
| <CommitList |
| commits={[makeCommit()]} |
| hasMore={false} |
| uncommittedChanges={[{ path: "src/a.ts", status: "M" }]} |
| autoExpandUncommitted |
| onAutoExpandHandled={onAutoExpandHandled} |
| />, |
| ); |
|
|
| |
| expect( |
| screen.getByTestId("uncommitted-changes-row-toggle"), |
| ).toHaveAttribute("aria-expanded", "true"); |
| expect(screen.getByText("src/a.ts")).toBeInTheDocument(); |
| expect(onAutoExpandHandled).toHaveBeenCalled(); |
| }); |
|
|
| it("still renders Uncommitted when there are no working-tree changes", () => { |
| |
| render( |
| <CommitList |
| commits={[makeCommit()]} |
| hasMore={false} |
| uncommittedChanges={[]} |
| />, |
| ); |
|
|
| |
| expect(screen.getByTestId("uncommitted-changes-row")).toBeInTheDocument(); |
| }); |
| }); |
|
|