Guard issue icons against unknown values (#4206)

This commit is contained in:
Wes
2026-06-17 14:56:10 +08:00
committed by GitHub
parent 6f2e9aa7a8
commit d26cac0008
4 changed files with 50 additions and 7 deletions

View File

@@ -844,6 +844,26 @@ describe("IssueDetail (shared)", () => {
expect(screen.getByText(/changed priority/i)).toBeInTheDocument();
});
it("renders activity rows with unknown status values without crashing", async () => {
mockApiObj.listTimeline.mockResolvedValue([
{
type: "activity",
id: "act-unknown-status",
actor_type: "member",
actor_id: "user-1",
action: "status_changed",
details: { from: "todo", to: "mystery_status" },
created_at: "2026-01-18T00:00:00Z",
},
] as TimelineEntry[]);
renderIssueDetail();
await waitFor(() => {
expect(screen.getByText(/from Todo to mystery_status/i)).toBeInTheDocument();
});
});
it("truncates the trailing activity block to the most recent 8 entries with a show-more toggle", async () => {
// 10 activities, all in the trailing block (no comment after them, so it's
// the trailing block by definition). Alternating action types so the

View File

@@ -6,14 +6,14 @@ export function PriorityIcon({
className = "",
inheritColor = false,
}: {
priority: IssuePriority;
priority: IssuePriority | string;
className?: string;
inheritColor?: boolean;
}) {
const cfg = PRIORITY_CONFIG[priority];
const cfg = priority in PRIORITY_CONFIG ? PRIORITY_CONFIG[priority as IssuePriority] : null;
// "none" — simple horizontal dashes
if (cfg.bars === 0) {
if (!cfg || cfg.bars === 0) {
return (
<svg
viewBox="0 0 16 16"

View File

@@ -0,0 +1,22 @@
// @vitest-environment jsdom
import { render } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { PriorityIcon } from "./priority-icon";
import { StatusIcon } from "./status-icon";
describe("issue icons", () => {
it("renders a muted fallback for unknown status values", () => {
const { container } = render(<StatusIcon status="unexpected_status" />);
const icon = container.querySelector("svg");
expect(icon).toHaveClass("text-muted-foreground");
});
it("renders a muted fallback for unknown priority values", () => {
const { container } = render(<PriorityIcon priority="unexpected_priority" />);
const icon = container.querySelector("svg");
expect(icon).toHaveClass("text-muted-foreground");
});
});

View File

@@ -162,18 +162,19 @@ export function StatusIcon({
className = "h-4 w-4",
inheritColor = false,
}: {
status: IssueStatus;
status: IssueStatus | string;
className?: string;
inheritColor?: boolean;
}) {
const cfg = STATUS_CONFIG[status];
const Renderer = STATUS_RENDERERS[status];
const knownStatus = status in STATUS_RENDERERS ? (status as IssueStatus) : null;
const cfg = knownStatus ? STATUS_CONFIG[knownStatus] : null;
const Renderer = knownStatus ? STATUS_RENDERERS[knownStatus] : TodoIcon;
return (
<svg
viewBox="0 0 14 14"
fill="none"
className={`${className} ${inheritColor ? "" : cfg.iconColor} shrink-0`}
className={`${className} ${inheritColor ? "" : cfg?.iconColor ?? "text-muted-foreground"} shrink-0`}
>
<Renderer />
</svg>