diff --git a/frontend/src/components/GatewayPanel.test.tsx b/frontend/src/components/GatewayPanel.test.tsx new file mode 100644 index 00000000..1b0dd2b7 --- /dev/null +++ b/frontend/src/components/GatewayPanel.test.tsx @@ -0,0 +1,37 @@ +/** Tests for GatewayPanel — verifies story id and name rendering in the gateway aggregate view. */ +import { render } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import type { PipelineItem } from "../api/gateway"; +import { StoryRow } from "./GatewayPanel"; + +describe("StoryRow", () => { + it("renders #id prefix before the story name", () => { + const item: PipelineItem = { + story_id: "42_story_add_feature", + name: "Add Feature", + stage: "current", + }; + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders #id prefix for a backlogged story", () => { + const item: PipelineItem = { + story_id: "7_bug_fix_crash", + name: "Fix crash on startup", + stage: "qa", + }; + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it("renders name without id prefix when story_id has no leading number", () => { + const item: PipelineItem = { + story_id: "no-number-id", + name: "Mystery Story", + stage: "merge", + }; + const { container } = render(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/frontend/src/components/GatewayPanel.tsx b/frontend/src/components/GatewayPanel.tsx index ae92cc35..9e34664b 100644 --- a/frontend/src/components/GatewayPanel.tsx +++ b/frontend/src/components/GatewayPanel.tsx @@ -63,9 +63,11 @@ const STAGE_LABELS: Record = { }; /// A single story row inside a project pipeline card. -function StoryRow({ item }: { item: PipelineItem }) { +/** Render one story row in a gateway-aggregate panel: `# ` with stage badge. */ +export function StoryRow({ item }: { item: PipelineItem }) { const color = STAGE_COLORS[item.stage] ?? "#8b949e"; const label = STAGE_LABELS[item.stage] ?? item.stage; + const idNum = item.story_id.match(/^(\d+)/)?.[1]; return (
+ {idNum && #{idNum}{" "}} {item.name}
@@ -110,7 +113,9 @@ function ProjectPipelineCard({ onSwitch: (name: string) => void; }) { const activeItems = pipeline.active ?? []; + const backlogItems = pipeline.backlog ?? []; const backlogCount = pipeline.backlog_count ?? 0; + const remainingBacklog = backlogCount - backlogItems.length; const hasError = Boolean(pipeline.error); return ( @@ -131,7 +136,7 @@ function ProjectPipelineCard({ display: "flex", alignItems: "center", gap: "8px", - marginBottom: activeItems.length > 0 ? "8px" : 0, + marginBottom: activeItems.length > 0 || backlogItems.length > 0 ? "8px" : 0, }} > {name} @@ -149,20 +154,47 @@ function ProjectPipelineCard({ active )} - - {backlogCount > 0 ? `${backlogCount} in backlog` : ""} - {hasError ? (
{pipeline.error}
- ) : activeItems.length === 0 ? ( -
No active stories
+ ) : activeItems.length === 0 && backlogItems.length === 0 ? ( +
+ {backlogCount > 0 ? `${backlogCount} in backlog` : "No active stories"} +
) : (
{activeItems.map((item) => ( ))} + {backlogItems.length > 0 && ( +
0 ? "6px" : 0, borderTop: activeItems.length > 0 ? "1px solid #21262d" : "none", paddingTop: activeItems.length > 0 ? "6px" : 0 }}> + {backlogItems.map((item) => { + const idNum = item.story_id.match(/^(\d+)/)?.[1]; + return ( +
+ {idNum && #{idNum}} + {item.name} +
+ ); + })} + {remainingBacklog > 0 && ( +
+ …and {remainingBacklog} more +
+ )} +
+ )}
)} diff --git a/frontend/src/components/__snapshots__/GatewayPanel.test.tsx.snap b/frontend/src/components/__snapshots__/GatewayPanel.test.tsx.snap new file mode 100644 index 00000000..66361478 --- /dev/null +++ b/frontend/src/components/__snapshots__/GatewayPanel.test.tsx.snap @@ -0,0 +1,72 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`StoryRow > renders #id prefix before the story name 1`] = ` +
+
+ + In Progress + + + + # + 42 + + + Add Feature + +
+
+`; + +exports[`StoryRow > renders #id prefix for a backlogged story 1`] = ` +
+
+ + QA + + + + # + 7 + + + Fix crash on startup + +
+
+`; + +exports[`StoryRow > renders name without id prefix when story_id has no leading number 1`] = ` +
+
+ + Merging + + + Mystery Story + +
+
+`;