Files
fusion/packages/dashboard/app/components/__tests__/BranchGroupCard.test.tsx
gsxdsm e9de195ef5 FN-5822: wire shared branch-group visibility and merge controls
Add dashboard and API support for viewing shared branch groups and triggering group merge actions.

- add branch-group API routes and register them in integrated routers
- extend dashboard legacy API client with branch-group fetch and merge-control actions
- add BranchGroupCard UI + styles and surface it in task/detail/subtask views
- add dashboard/API test coverage for branch-group routes and UI integration points
- document branch-group behavior and add a changeset for @runfusion/fusion

Files changed:
 .changeset/fn-5822-branch-group-dashboard.md       |   5 +
 docs/dashboard-guide.md                            |  31 ++++++
 packages/dashboard/app/api/legacy.ts               |  54 +++++++++
 .../dashboard/app/components/BranchGroupCard.css   |  83 ++++++++++++++
 .../dashboard/app/components/BranchGroupCard.tsx   | 123 +++++++++++++++++++++
 .../app/components/SubtaskBreakdownModal.tsx       |   3 +
 packages/dashboard/app/components/TaskCard.tsx     |  19 ++++
 .../dashboard/app/components/TaskDetailModal.tsx   |   4 +
 .../components/__tests__/BranchGroupCard.test.tsx  |  96 ++++++++++++++++
 .../__tests__/SubtaskBreakdownModal.test.tsx       |   1 +
 .../app/components/__tests__/TaskCard.test.tsx     |  16 ++++
 .../components/__tests__/TaskDetailModal.test.tsx  |  22 ++++
 .../src/__tests__/routes-branch-groups.test.ts     | 119 ++++++++++++++++++++
 .../src/routes/register-branch-groups-routes.ts    | 118 ++++++++++++++++++++
 .../src/routes/register-integrated-routers.ts      |  13 +++
 packages/dashboard/vitest.config.ts                |   4 +-
 16 files changed, 709 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-5822

Fusion-Task-Lineage: 199c25b8-f6ec-43b4-8fab-509f23fb5ac7
2026-06-01 04:47:19 -07:00

97 lines
3.9 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,
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();
});
});