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
89 changes: 89 additions & 0 deletions apps/web/src/components/app/reviews-sidebar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// @vitest-environment jsdom
import { render, screen } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { describe, expect, it, vi } from "vitest";

import type { ReviewListItem } from "@/hooks/use-agent-reviews";
import { ReviewsSidebarContent } from "./reviews-sidebar";

const reviews: ReviewListItem[] = [
{
id: 1,
agentId: "agent-1",
assignedAgentId: "agent-1",
reviewerType: "human",
reviewerAgentId: null,
summary: null,
status: "open",
baseRef: "main",
createdAt: "2026-07-13T18:00:00.000Z",
updatedAt: "2026-07-13T18:00:00.000Z",
itemCount: 2,
resolvedCount: 0,
},
{
id: 2,
agentId: "agent-1",
assignedAgentId: "agent-1",
reviewerType: "human",
reviewerAgentId: null,
summary: "Resolved review",
status: "resolved",
baseRef: "main",
createdAt: "2026-07-13T17:00:00.000Z",
updatedAt: "2026-07-13T17:00:00.000Z",
itemCount: 1,
resolvedCount: 1,
},
{
id: 3,
agentId: "agent-1",
assignedAgentId: "agent-1",
reviewerType: "human",
reviewerAgentId: null,
summary: "Partially resolved review",
status: "partially_resolved",
baseRef: "main",
createdAt: "2026-07-13T16:00:00.000Z",
updatedAt: "2026-07-13T16:00:00.000Z",
itemCount: 2,
resolvedCount: 1,
},
];

vi.mock("@/hooks/use-agent-reviews", () => ({
useAgentReviews: () => ({ reviews, isLoading: false }),
useAgentReviewDetail: () => ({ review: null, isLoading: false }),
useAddReviewThreadMessage: vi.fn(),
useSetReviewFeedbackResolution: vi.fn(),
}));

vi.mock("@/hooks/use-agent-diff", () => ({
useAgentDiff: () => ({ data: null }),
}));

function reviewRowFor(text: string): HTMLElement {
const row = screen.getByText(text).closest("button")?.parentElement;
if (!row) throw new Error(`Could not find review row for ${text}`);
return row;
}

describe("ReviewsSidebarContent", () => {
it("uses status rails and labels a review without a summary", () => {
render(
<MemoryRouter>
<ReviewsSidebarContent agentId="agent-1" />
</MemoryRouter>
);

expect(reviewRowFor("Review feedback").className).toContain(
"border-l-status-waiting/60"
);
expect(reviewRowFor("Partially resolved review").className).toContain(
"border-l-status-waiting/60"
);
expect(reviewRowFor("Resolved review").className).toContain(
"border-l-status-working/60"
);
});
});
34 changes: 22 additions & 12 deletions apps/web/src/components/app/reviews-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,22 @@ import { Textarea } from "@/components/ui/textarea";
import { Markdown } from "@/components/ui/markdown";
import { ReviewDiffSnapshot } from "@/components/app/review-diff-snapshot";

const REVIEW_STATUS_STYLES: Record<string, { badge: string; label: string }> = {
const REVIEW_STATUS_STYLES: Record<
string,
{ rail: string; badge: string; label: string }
> = {
open: {
rail: "border-l-status-waiting/60",
badge: "bg-status-waiting/15 text-status-waiting",
label: "Open",
},
partially_resolved: {
rail: "border-l-status-waiting/60",
badge: "bg-status-waiting/15 text-status-waiting",
label: "Open",
},
resolved: {
rail: "border-l-status-working/60",
badge: "bg-status-working/15 text-status-working",
label: "Resolved",
},
Expand Down Expand Up @@ -189,7 +195,12 @@ function ReviewRow({
});

return (
<div className="relative mb-3 overflow-clip rounded-md bg-muted/[0.07]">
<div
className={cn(
"relative mb-3 overflow-clip rounded-md border-l-2 bg-muted/[0.07]",
statusStyle.rail
)}
>
<button
type="button"
className="flex w-full items-start gap-2 overflow-hidden rounded-md px-3 py-2.5 text-left transition-colors hover:bg-muted/30"
Expand All @@ -207,16 +218,15 @@ function ReviewRow({
<Bot className="mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" />
)}
<div className="min-w-0 flex-1">
{review.summary && (
<p
className={cn(
"text-xs text-foreground/90",
!expanded && "truncate"
)}
>
{review.summary}
</p>
)}
<p
className={cn(
"text-xs text-foreground/90",
!expanded && "truncate",
!review.summary && "text-muted-foreground"
)}
>
{review.summary || "Review feedback"}
</p>
<div className="mt-1.5 flex items-center gap-2 text-[10px] text-muted-foreground">
<span className="flex items-center gap-1">
<MessageCircle className="h-2.5 w-2.5" />
Expand Down
Loading