Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions web-ui/__tests__/components/review/PRStatusPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ const failingCIChecks = [
{ name: 'tests', status: 'completed', conclusion: 'failure' },
];

const pendingCIChecks = [
{ name: 'tests', status: 'in_progress', conclusion: null },
];

const basePRStatus = {
ci_checks: successfulCIChecks,
review_status: 'approved',
Expand Down Expand Up @@ -79,11 +83,20 @@ const proofStatusWithOpenReqs = {

// ── Helpers ───────────────────────────────────────────────────────────────────

const setupSWRMock = (prStatus: object, proofStatus: object) => {
const setupSWRMock = (
prStatus: object | undefined,
proofStatus: object | undefined,
options?: { proofLoading?: boolean; proofError?: unknown }
) => {
mockUseSWR.mockImplementation((key: unknown) => {
const keyStr = typeof key === 'string' ? key : '';
if (keyStr.includes('/api/v2/proof/status')) {
return { data: proofStatus, error: undefined, isLoading: false, mutate: jest.fn() } as any;
return {
data: proofStatus,
error: options?.proofError,
isLoading: options?.proofLoading ?? false,
mutate: jest.fn(),
} as any;
}
return { data: prStatus, error: undefined, isLoading: false, mutate: jest.fn() } as any;
});
Expand Down Expand Up @@ -185,4 +198,32 @@ describe('PRStatusPanel — PROOF9-gated merge button', () => {
expect(screen.getByText(/ci checks failing/i)).toBeInTheDocument();
expect(screen.getByText('Fix critical bug')).toBeInTheDocument();
});

it('shows "Waiting for CI checks" when CI checks are pending', () => {
setupSWRMock({ ...basePRStatus, ci_checks: pendingCIChecks }, cleanProofStatus);
render(<PRStatusPanel {...defaultProps} />);
expect(screen.getByText(/waiting for ci checks/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /^merge$/i })).toBeDisabled();
});

it('shows PROOF9 loading skeleton instead of "All clear" while proof data loads', () => {
setupSWRMock(basePRStatus, undefined, { proofLoading: true });
render(<PRStatusPanel {...defaultProps} />);
expect(screen.queryByText(/all clear/i)).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: /^merge$/i })).toBeDisabled();
});

it('shows PROOF9 error message when proof API fails', () => {
setupSWRMock(basePRStatus, undefined, { proofError: new Error('Network error') });
render(<PRStatusPanel {...defaultProps} />);
expect(screen.getByText(/unable to load proof9 status/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /^merge$/i })).toBeDisabled();
});

it('shows success banner when PR was merged externally', () => {
setupSWRMock({ ...basePRStatus, merge_state: 'merged' }, cleanProofStatus);
render(<PRStatusPanel {...defaultProps} />);
expect(screen.getByText(/merged successfully/i)).toBeInTheDocument();
expect(screen.queryByRole('button', { name: /merge/i })).not.toBeInTheDocument();
});
});
15 changes: 11 additions & 4 deletions web-ui/src/components/review/PRStatusPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export function PRStatusPanel({ prNumber, workspacePath }: PRStatusPanelProps) {
}
);

const { data: proofData } = useSWR<ProofStatusResponse>(
const { data: proofData, error: proofError, isLoading: proofLoading } = useSWR<ProofStatusResponse>(
proofKey,
() => proofApi.getStatus(workspacePath),
{ refreshInterval: merged ? 0 : 15_000 }
Expand All @@ -121,6 +121,7 @@ export function PRStatusPanel({ prNumber, workspacePath }: PRStatusPanelProps) {
);

const ciPassing = !ciFailing && !ciPending;
const alreadyMerged = merged || data?.merge_state === 'merged';
const canMerge = !!data && !!proofData && openRequirements.length === 0 && ciPassing;

// ── Merge handler ─────────────────────────────────────────────────────────
Expand Down Expand Up @@ -204,7 +205,13 @@ export function PRStatusPanel({ prNumber, workspacePath }: PRStatusPanelProps) {
{/* PROOF9 gate section */}
<div className="flex flex-col gap-1.5">
<span className="text-sm font-medium">PROOF9</span>
{openRequirements.length === 0 ? (
{proofLoading ? (
<div className="h-4 animate-pulse rounded bg-muted" />
) : proofError && !proofData ? (
<p className="text-xs text-muted-foreground">
Unable to load PROOF9 status — merge blocked until resolved.
</p>
) : openRequirements.length === 0 ? (
<p className="flex items-center gap-1 text-xs text-muted-foreground">
<CheckmarkCircle01Icon className="h-3 w-3 text-green-600" />
All clear
Expand Down Expand Up @@ -233,7 +240,7 @@ export function PRStatusPanel({ prNumber, workspacePath }: PRStatusPanelProps) {
)}

{/* Blocking messages */}
{data && (ciFailing || ciPending) && !merged && (
{data && (ciFailing || ciPending) && !alreadyMerged && (
<p className="text-xs text-amber-600">
{ciFailing ? 'CI checks failing' : 'Waiting for CI checks'}
</p>
Expand All @@ -247,7 +254,7 @@ export function PRStatusPanel({ prNumber, workspacePath }: PRStatusPanelProps) {
)}

{/* Success banner or Merge button */}
{merged ? (
{alreadyMerged ? (
<div className="flex items-center gap-1 rounded bg-green-50 px-3 py-2 text-xs text-green-700">
<CheckmarkCircle01Icon className="h-3 w-3" />
PR #{prNumber} merged successfully
Expand Down
Loading