Improve dashboard mobile log usability and make branch group cards collapsible for cleaner review workflows. - preserve/restore AgentLogViewer scroll position when toggling mobile drawer and lock body scroll while open - add collapse/expand state to BranchGroupCard with a summary header and hidden details when collapsed - add regression tests for AgentLogViewer mobile scroll behavior and BranchGroupCard collapse interactions - add a changeset and dashboard guide note for the new branch group card behavior Files changed: .changeset/fn-5845-branch-group-collapse.md | 5 ++ docs/dashboard-guide.md | 1 + .../dashboard/app/components/AgentLogViewer.tsx | 21 ++++++++ .../dashboard/app/components/BranchGroupCard.css | 23 ++++++++ .../dashboard/app/components/BranchGroupCard.tsx | 63 +++++++++++++++++----- .../components/__tests__/AgentLogViewer.test.tsx | 48 +++++++++++++++++ .../components/__tests__/BranchGroupCard.test.tsx | 18 +++++++ 7 files changed, 166 insertions(+), 13 deletions(-) Fusion-Task-Id: FN-5845 Fusion-Task-Lineage: 6a0f2967-5df8-4761-ae6c-50991f01ab78
115 lines
4.6 KiB
TypeScript
115 lines
4.6 KiB
TypeScript
import React from "react";
|
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
|
import { BranchGroupCard } from "../BranchGroupCard";
|
|
|
|
const apiGetBranchGroup = vi.fn();
|
|
const apiPromoteBranchGroup = vi.fn();
|
|
|
|
vi.mock("../../api", () => ({
|
|
apiGetBranchGroup: (...args: unknown[]) => apiGetBranchGroup(...args),
|
|
apiPromoteBranchGroup: (...args: unknown[]) => apiPromoteBranchGroup(...args),
|
|
}));
|
|
|
|
vi.mock("../../sse-bus", () => ({
|
|
subscribeSse: () => () => {},
|
|
}));
|
|
|
|
vi.mock("lucide-react", () => ({
|
|
CheckCircle2: () => null,
|
|
ChevronDown: () => null,
|
|
ChevronRight: () => null,
|
|
CircleDashed: () => null,
|
|
ExternalLink: () => null,
|
|
GitBranch: () => null,
|
|
GitPullRequest: () => null,
|
|
Loader2: () => null,
|
|
}));
|
|
|
|
function makeGroup(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
id: "BG-1",
|
|
sourceType: "planning",
|
|
sourceId: "PS-1",
|
|
branchName: "feature/shared",
|
|
autoMerge: false,
|
|
prState: "none",
|
|
status: "open",
|
|
createdAt: Date.now(),
|
|
updatedAt: Date.now(),
|
|
members: [
|
|
{ taskId: "FN-1", title: "one", column: "done", landed: true },
|
|
{ taskId: "FN-2", title: "two", column: "in-review", landed: false },
|
|
],
|
|
completion: { landed: 1, total: 2, complete: false },
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe("BranchGroupCard", () => {
|
|
beforeEach(() => {
|
|
apiGetBranchGroup.mockReset();
|
|
apiPromoteBranchGroup.mockReset();
|
|
});
|
|
|
|
it("hides promote control while incomplete", async () => {
|
|
apiGetBranchGroup.mockResolvedValue({ group: makeGroup() });
|
|
render(<BranchGroupCard groupId="BG-1" />);
|
|
await screen.findByText("1 of 2 members finished");
|
|
expect(screen.queryByRole("button", { name: /open pr|merge group into main/i })).toBeNull();
|
|
});
|
|
|
|
it("shows promote and calls API when complete + autoMerge off", async () => {
|
|
apiGetBranchGroup
|
|
.mockResolvedValueOnce({ group: makeGroup({ completion: { landed: 2, total: 2, complete: true }, members: [{ taskId: "FN-1", title: "one", column: "done", landed: true }, { taskId: "FN-2", title: "two", column: "done", landed: true }] }) })
|
|
.mockResolvedValueOnce({ group: makeGroup({ completion: { landed: 2, total: 2, complete: true }, members: [{ taskId: "FN-1", title: "one", column: "done", landed: true }, { taskId: "FN-2", title: "two", column: "done", landed: true }], prState: "open", prNumber: 22, prUrl: "https://example/pr/22" }) });
|
|
apiPromoteBranchGroup.mockResolvedValue({ groupId: "BG-1", prState: "open" });
|
|
|
|
render(<BranchGroupCard groupId="BG-1" />);
|
|
const button = await screen.findByRole("button", { name: /open pr/i });
|
|
fireEvent.click(button);
|
|
|
|
await waitFor(() => {
|
|
expect(apiPromoteBranchGroup).toHaveBeenCalledWith("BG-1", undefined);
|
|
});
|
|
});
|
|
|
|
it("shows auto-merge badge when complete and autoMerge is on", async () => {
|
|
apiGetBranchGroup.mockResolvedValue({
|
|
group: makeGroup({
|
|
autoMerge: true,
|
|
completion: { landed: 2, total: 2, complete: true },
|
|
members: [{ taskId: "FN-1", title: "one", column: "done", landed: true }, { taskId: "FN-2", title: "two", column: "done", landed: true }],
|
|
}),
|
|
});
|
|
|
|
render(<BranchGroupCard groupId="BG-1" />);
|
|
expect(await screen.findByText("Auto-merge enabled")).toBeInTheDocument();
|
|
expect(screen.queryByRole("button", { name: /open pr|merge group into main/i })).toBeNull();
|
|
});
|
|
|
|
it("renders tracked group PR link when present", async () => {
|
|
apiGetBranchGroup.mockResolvedValue({
|
|
group: makeGroup({ completion: { landed: 2, total: 2, complete: true }, members: [{ taskId: "FN-1", title: "one", column: "done", landed: true }, { taskId: "FN-2", title: "two", column: "done", landed: true }], prState: "open", prNumber: 9, prUrl: "https://example/pr/9" }),
|
|
});
|
|
render(<BranchGroupCard groupId="BG-1" />);
|
|
expect(await screen.findByRole("link", { name: /pr #9/i })).toBeInTheDocument();
|
|
});
|
|
|
|
it("shows members by default and collapses via toggle", async () => {
|
|
apiGetBranchGroup.mockResolvedValue({ group: makeGroup() });
|
|
render(<BranchGroupCard groupId="BG-1" />);
|
|
|
|
expect(await screen.findByText("FN-1 · one")).toBeInTheDocument();
|
|
|
|
const toggle = screen.getByRole("button", { name: /collapse branch group/i });
|
|
expect(toggle).toHaveAttribute("aria-expanded", "true");
|
|
|
|
fireEvent.click(toggle);
|
|
|
|
expect(screen.getByRole("button", { name: /expand branch group/i })).toHaveAttribute("aria-expanded", "false");
|
|
expect(screen.queryByText("FN-1 · one")).toBeNull();
|
|
expect(screen.getByText("1 of 2 members finished")).toBeInTheDocument();
|
|
});
|
|
});
|