diff --git a/packages/web/src/__tests__/components.test.tsx b/packages/web/src/__tests__/components.test.tsx index 982d467d4..706d73ac4 100644 --- a/packages/web/src/__tests__/components.test.tsx +++ b/packages/web/src/__tests__/components.test.tsx @@ -208,7 +208,7 @@ describe("SessionCard", () => { const session = makeSession({ id: "backend-5" }); render(); const link = screen.getByText("terminal"); - expect(link).toHaveAttribute("href", "/sessions/backend-5"); + expect(link).toHaveAttribute("href", "/sessions/backend-5#session-terminal-section"); }); it("shows restore button when agent has exited", () => { @@ -269,6 +269,68 @@ describe("SessionCard", () => { expect(onMerge).toHaveBeenCalledWith(42); }); + it("renders passing CI check chips as hyperlinks when url is present", () => { + const pr = makePR({ + state: "open", + ciStatus: "passing", + ciChecks: [ + { name: "lint-and-type-checks", status: "passed", url: "https://github.com/owner/repo/runs/111" }, + { name: "tests", status: "passed", url: "https://github.com/owner/repo/runs/222" }, + { name: "no-url-check", status: "passed" }, + ], + reviewDecision: "approved", + mergeability: { + mergeable: true, + ciPassing: true, + approved: true, + noConflicts: true, + blockers: [], + }, + }); + const session = makeSession({ status: "mergeable", activity: "idle", pr }); + render(); + + const lintLink = screen.getByRole("link", { name: /lint-and-type-checks/ }); + expect(lintLink).toHaveAttribute("href", "https://github.com/owner/repo/runs/111"); + expect(lintLink).toHaveAttribute("target", "_blank"); + expect(lintLink).toHaveAttribute("rel", "noopener noreferrer"); + + const testsLink = screen.getByRole("link", { name: /^tests$/ }); + expect(testsLink).toHaveAttribute("href", "https://github.com/owner/repo/runs/222"); + + // Check without url should still render as plain text, not a link + expect(screen.queryByRole("link", { name: /no-url-check/ })).not.toBeInTheDocument(); + expect(screen.getByText("no-url-check")).toBeInTheDocument(); + }); + + it("stops propagation when clicking a passing CI chip link", () => { + const onClick = vi.fn(); + const pr = makePR({ + state: "open", + ciStatus: "passing", + ciChecks: [ + { name: "build", status: "passed", url: "https://github.com/owner/repo/runs/333" }, + ], + reviewDecision: "approved", + mergeability: { + mergeable: true, + ciPassing: true, + approved: true, + noConflicts: true, + blockers: [], + }, + }); + const session = makeSession({ status: "mergeable", activity: "idle", pr }); + render( +
+ +
, + ); + const link = screen.getByRole("link", { name: /build/ }); + fireEvent.click(link); + expect(onClick).not.toHaveBeenCalled(); + }); + it("shows CI failing alert", () => { const pr = makePR({ state: "open", diff --git a/packages/web/src/components/SessionCard.tsx b/packages/web/src/components/SessionCard.tsx index dface1c17..04181e43b 100644 --- a/packages/web/src/components/SessionCard.tsx +++ b/packages/web/src/components/SessionCard.tsx @@ -571,14 +571,32 @@ function SessionCardView({ session, onSend, onKill, onMerge, onRestore }: Sessio {visiblePassingChecks.length > 0 && (
- {visiblePassingChecks.map((check) => ( - - - {check.name} - - ))} + {visiblePassingChecks.map((check) => { + const chipContent = ( + <> + + {check.name} + + ); + return check.url ? ( + e.stopPropagation()} + > + {chipContent} + + ) : ( + + {chipContent} + + ); + })}
)}