huskies: merge 975
This commit is contained in:
@@ -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(<StoryRow item={item} />);
|
||||
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(<StoryRow item={item} />);
|
||||
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(<StoryRow item={item} />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -63,9 +63,11 @@ const STAGE_LABELS: Record<string, string> = {
|
||||
};
|
||||
|
||||
/// A single story row inside a project pipeline card.
|
||||
function StoryRow({ item }: { item: PipelineItem }) {
|
||||
/** Render one story row in a gateway-aggregate panel: `#<id> <name>` 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 (
|
||||
<div
|
||||
@@ -91,6 +93,7 @@ function StoryRow({ item }: { item: PipelineItem }) {
|
||||
{label}
|
||||
</span>
|
||||
<span style={{ color: "#e6edf3", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
|
||||
{idNum && <span style={{ color: "#8b949e", fontFamily: "monospace" }}>#{idNum}{" "}</span>}
|
||||
{item.name}
|
||||
</span>
|
||||
</div>
|
||||
@@ -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,
|
||||
}}
|
||||
>
|
||||
<span style={{ fontWeight: 600, color: "#e6edf3" }}>{name}</span>
|
||||
@@ -149,20 +154,47 @@ function ProjectPipelineCard({
|
||||
active
|
||||
</span>
|
||||
)}
|
||||
<span style={{ marginLeft: "auto", fontSize: "0.75em", color: "#6e7681" }}>
|
||||
{backlogCount > 0 ? `${backlogCount} in backlog` : ""}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{hasError ? (
|
||||
<div style={{ fontSize: "0.8em", color: "#f85149" }}>{pipeline.error}</div>
|
||||
) : activeItems.length === 0 ? (
|
||||
<div style={{ fontSize: "0.8em", color: "#6e7681" }}>No active stories</div>
|
||||
) : activeItems.length === 0 && backlogItems.length === 0 ? (
|
||||
<div style={{ fontSize: "0.8em", color: "#6e7681" }}>
|
||||
{backlogCount > 0 ? `${backlogCount} in backlog` : "No active stories"}
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
{activeItems.map((item) => (
|
||||
<StoryRow key={item.story_id} item={item} />
|
||||
))}
|
||||
{backlogItems.length > 0 && (
|
||||
<div style={{ marginTop: activeItems.length > 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 (
|
||||
<div
|
||||
key={item.story_id}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "6px",
|
||||
padding: "2px 0",
|
||||
fontSize: "0.82em",
|
||||
color: "#6e7681",
|
||||
}}
|
||||
>
|
||||
{idNum && <span style={{ fontFamily: "monospace", flexShrink: 0 }}>#{idNum}</span>}
|
||||
<span style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{item.name}</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{remainingBacklog > 0 && (
|
||||
<div style={{ fontSize: "0.75em", color: "#6e7681", paddingTop: "2px" }}>
|
||||
…and {remainingBacklog} more
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`StoryRow > renders #id prefix before the story name 1`] = `
|
||||
<div>
|
||||
<div
|
||||
style="display: flex; align-items: center; gap: 8px; padding: 4px 0px; font-size: 0.82em;"
|
||||
>
|
||||
<span
|
||||
style="padding: 1px 6px; border-radius: 10px; background: rgba(63, 185, 80, 0.133); color: rgb(63, 185, 80); border: 1px solid rgba(63, 185, 80, 0.267); white-space: nowrap; flex-shrink: 0;"
|
||||
>
|
||||
In Progress
|
||||
</span>
|
||||
<span
|
||||
style="color: rgb(230, 237, 243); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
|
||||
>
|
||||
<span
|
||||
style="color: rgb(139, 148, 158); font-family: monospace;"
|
||||
>
|
||||
#
|
||||
42
|
||||
|
||||
</span>
|
||||
Add Feature
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`StoryRow > renders #id prefix for a backlogged story 1`] = `
|
||||
<div>
|
||||
<div
|
||||
style="display: flex; align-items: center; gap: 8px; padding: 4px 0px; font-size: 0.82em;"
|
||||
>
|
||||
<span
|
||||
style="padding: 1px 6px; border-radius: 10px; background: rgba(210, 166, 121, 0.133); color: rgb(210, 166, 121); border: 1px solid rgba(210, 166, 121, 0.267); white-space: nowrap; flex-shrink: 0;"
|
||||
>
|
||||
QA
|
||||
</span>
|
||||
<span
|
||||
style="color: rgb(230, 237, 243); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
|
||||
>
|
||||
<span
|
||||
style="color: rgb(139, 148, 158); font-family: monospace;"
|
||||
>
|
||||
#
|
||||
7
|
||||
|
||||
</span>
|
||||
Fix crash on startup
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`StoryRow > renders name without id prefix when story_id has no leading number 1`] = `
|
||||
<div>
|
||||
<div
|
||||
style="display: flex; align-items: center; gap: 8px; padding: 4px 0px; font-size: 0.82em;"
|
||||
>
|
||||
<span
|
||||
style="padding: 1px 6px; border-radius: 10px; background: rgba(121, 192, 255, 0.133); color: rgb(121, 192, 255); border: 1px solid rgba(121, 192, 255, 0.267); white-space: nowrap; flex-shrink: 0;"
|
||||
>
|
||||
Merging
|
||||
</span>
|
||||
<span
|
||||
style="color: rgb(230, 237, 243); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
|
||||
>
|
||||
Mystery Story
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
Reference in New Issue
Block a user