fix(web): constrain ci checks popover height (#1441)

This commit is contained in:
Carlos Florêncio
2026-06-18 22:28:56 +01:00
committed by GitHub
parent f100fc97a5
commit ffda4638e9
7 changed files with 201 additions and 6 deletions
@@ -36,6 +36,9 @@ type CountsView = {
const CHECK_GROUP_ORDER: CheckBucket[] = ["passed", "in_progress", "failed"];
export const PR_CI_DESKTOP_POPOVER_SCROLL_CLASS =
"max-h-[min(28rem,calc(100vh-8rem))] overflow-y-auto overscroll-contain";
function countForBucket(counts: CountsView, kind: CheckBucket): number {
if (kind === "passed") return counts.passed;
if (kind === "in_progress") return counts.inProgress;
@@ -5,6 +5,7 @@ import { TooltipProvider } from "@kandev/ui/tooltip";
import { StateProvider } from "@/components/state-provider";
import { ToastProvider } from "@/components/toast-provider";
import { PRStatusChip, aggregateChipStatus } from "./pr-status-chip";
import { PR_CI_DESKTOP_POPOVER_SCROLL_CLASS } from "./pr-ci-popover";
import type { AppState } from "@/lib/state/store";
import type { TaskPR } from "@/lib/types/github";
@@ -90,6 +91,17 @@ function multiState(prs: TaskPR[]): Partial<AppState> {
return { taskPRs: { byTaskId: { "task-1": prs } } };
}
async function expectDesktopHoverPopoverConstrained() {
fireEvent.pointerEnter(screen.getByTestId(CHIP_TESTID));
const inner = await screen.findByTestId("pr-topbar-popover-inner");
const content = inner.closest<HTMLElement>(".overflow-y-auto");
expect(content).not.toBeNull();
const classNames = content!.className.split(/\s+/);
expect(classNames).toEqual(
expect.arrayContaining(PR_CI_DESKTOP_POPOVER_SCROLL_CLASS.split(/\s+/)),
);
}
describe("PRStatusChip", () => {
it("returns null when the task has no PR", () => {
renderWithStore(undefined, <PRStatusChip taskId="missing" />);
@@ -127,6 +139,11 @@ describe("PRStatusChip", () => {
expect(document.querySelector(DRAWER_SELECTOR)).toBeNull();
});
it("constrains the hover popover to the available viewport height", async () => {
renderWithStore(seededState, <PRStatusChip taskId="task-1" />);
await expectDesktopHoverPopoverConstrained();
});
it("exposes the canonical data attributes that desktop tests rely on", () => {
renderWithStore(seededState, <PRStatusChip taskId="task-1" />);
const chip = screen.getByTestId(CHIP_TESTID);
@@ -239,6 +256,11 @@ describe("PRStatusChip — multiple PRs", () => {
expect(chip.getAttribute(ATTR_STATUS)).toBe("failed");
});
it("constrains the aggregate hover popover to the available viewport height", async () => {
renderWithStore(multiState(TWO_OPEN), <PRStatusChip taskId="task-1" />);
await expectDesktopHoverPopoverConstrained();
});
it("stays visible while at least one PR is still open", () => {
renderWithStore(
multiState([makePR({ id: "a", state: "merged" }), makePR({ id: "b", pr_number: 2 })]),
@@ -21,7 +21,7 @@ import {
import { Button } from "@kandev/ui/button";
import { useTaskPR } from "@/hooks/domains/github/use-task-pr";
import { usePRFeedbackBackgroundSync } from "@/hooks/domains/github/use-pr-ci-popover";
import { PRCIPopover } from "@/components/github/pr-ci-popover";
import { PR_CI_DESKTOP_POPOVER_SCROLL_CLASS, PRCIPopover } from "@/components/github/pr-ci-popover";
import { MultiPRCIPopover } from "@/components/github/multi-pr-ci-popover";
import {
isPRAwaitingReview,
@@ -176,7 +176,7 @@ function PRStatusChipHoverCard({ pr }: { pr: TaskPR }) {
side="top"
align="start"
sideOffset={8}
className="w-80 p-2.5"
className={`w-80 p-2.5 ${PR_CI_DESKTOP_POPOVER_SCROLL_CLASS}`}
onPointerDownOutside={onPointerDownOutside}
>
<PRCIPopover pr={pr} enabled={true} />
@@ -233,7 +233,7 @@ function PRStatusChipMultiHoverCard({ prs }: { prs: TaskPR[] }) {
side="top"
align="start"
sideOffset={8}
className="w-96 p-2.5"
className={`w-96 p-2.5 ${PR_CI_DESKTOP_POPOVER_SCROLL_CLASS}`}
onPointerDownOutside={onPointerDownOutside}
>
<MultiPRCIPopover prs={prs} enabled={true} />
@@ -29,7 +29,7 @@ import {
isPRReadyToMerge,
} from "@/components/github/pr-task-icon";
import { prTaskKey } from "@/components/github/pr-detail-panel";
import { PRCIPopover } from "@/components/github/pr-ci-popover";
import { PR_CI_DESKTOP_POPOVER_SCROLL_CLASS, PRCIPopover } from "@/components/github/pr-ci-popover";
import { MultiPRCIPopover } from "@/components/github/multi-pr-ci-popover";
import { useAppStore } from "@/components/state-provider";
import type { TaskPR } from "@/lib/types/github";
@@ -186,7 +186,7 @@ function PRSingleButton({ pr }: { pr: TaskPR }) {
data-testid="pr-topbar-popover"
align="end"
sideOffset={4}
className="w-80"
className={`w-80 ${PR_CI_DESKTOP_POPOVER_SCROLL_CLASS}`}
onMouseEnter={handleEnter}
onMouseLeave={handleLeave}
onOpenAutoFocus={(e) => e.preventDefault()}
@@ -258,7 +258,7 @@ function PRMultiButton({ prs }: { prs: TaskPR[] }) {
data-testid="pr-topbar-popover"
align="end"
sideOffset={4}
className="w-96"
className={`w-96 ${PR_CI_DESKTOP_POPOVER_SCROLL_CLASS}`}
onMouseEnter={handleEnter}
onMouseLeave={handleLeave}
onOpenAutoFocus={(e) => e.preventDefault()}