huskies: merge 1056
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { PipelineStageItem } from "../api/client";
|
||||
import { StagePanel } from "./StagePanel";
|
||||
@@ -489,6 +490,50 @@ describe("StagePanel", () => {
|
||||
expect(icon).toBeInTheDocument();
|
||||
expect(icon).toHaveTextContent("⏳");
|
||||
});
|
||||
|
||||
it("renders gate output in a bounded box with expand and copy controls", () => {
|
||||
const items: PipelineStageItem[] = [
|
||||
{
|
||||
story_id: "60_story_gate_output",
|
||||
name: "Gate Output Story",
|
||||
error: null,
|
||||
merge_failure: "Quality gates failed: cargo test output here",
|
||||
agent: null,
|
||||
review_hold: null,
|
||||
qa: null,
|
||||
depends_on: null,
|
||||
},
|
||||
];
|
||||
render(<StagePanel title="Merge" items={items} />);
|
||||
expect(screen.getByTestId("gate-output-text")).toHaveTextContent(
|
||||
"Quality gates failed: cargo test output here",
|
||||
);
|
||||
expect(screen.getByTestId("gate-output-toggle")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("gate-output-copy")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("expand toggle changes label from Expand to Collapse", async () => {
|
||||
const user = userEvent.setup();
|
||||
const items: PipelineStageItem[] = [
|
||||
{
|
||||
story_id: "61_story_expand",
|
||||
name: "Expand Story",
|
||||
error: null,
|
||||
merge_failure: "A".repeat(1000),
|
||||
agent: null,
|
||||
review_hold: null,
|
||||
qa: null,
|
||||
depends_on: null,
|
||||
},
|
||||
];
|
||||
render(<StagePanel title="Merge" items={items} />);
|
||||
const toggle = screen.getByTestId("gate-output-toggle");
|
||||
expect(toggle).toHaveTextContent("Expand");
|
||||
await user.click(toggle);
|
||||
expect(toggle).toHaveTextContent("Collapse");
|
||||
await user.click(toggle);
|
||||
expect(toggle).toHaveTextContent("Expand");
|
||||
});
|
||||
});
|
||||
|
||||
describe("StagePanel - defensive rendering", () => {
|
||||
|
||||
@@ -5,6 +5,82 @@ import { useLozengeFly } from "./LozengeFlyContext";
|
||||
|
||||
const { useLayoutEffect, useRef, useState } = React;
|
||||
|
||||
/** Renders merge-failure gate output in a bounded scroll region with expand and copy controls. */
|
||||
function GateOutputBox({ text }: { text: string }) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleToggle = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
setExpanded((prev) => !prev);
|
||||
};
|
||||
|
||||
const handleCopy = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1500);
|
||||
});
|
||||
};
|
||||
|
||||
const btnStyle: React.CSSProperties = {
|
||||
background: "transparent",
|
||||
border: "1px solid #444",
|
||||
borderRadius: "4px",
|
||||
color: "#aaa",
|
||||
cursor: "pointer",
|
||||
fontSize: "0.75em",
|
||||
padding: "1px 6px",
|
||||
lineHeight: 1.4,
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ marginTop: "4px" }}>
|
||||
<div
|
||||
data-testid="gate-output-text"
|
||||
style={{
|
||||
fontSize: "0.8em",
|
||||
color: "#f85149",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
fontFamily: "monospace",
|
||||
background: "#1a0808",
|
||||
borderRadius: "4px",
|
||||
padding: "6px 8px",
|
||||
maxHeight: expanded ? "none" : "10rem",
|
||||
overflowY: expanded ? "visible" : "auto",
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "6px",
|
||||
marginTop: "4px",
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="gate-output-toggle"
|
||||
onClick={handleToggle}
|
||||
style={btnStyle}
|
||||
>
|
||||
{expanded ? "▲ Collapse" : "▼ Expand"}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="gate-output-copy"
|
||||
onClick={handleCopy}
|
||||
style={btnStyle}
|
||||
>
|
||||
{copied ? "✓ Copied" : "⎘ Copy"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
type WorkItemType = "story" | "bug" | "spike" | "refactor" | "unknown";
|
||||
|
||||
const TYPE_COLORS: Record<WorkItemType, string> = {
|
||||
@@ -564,15 +640,8 @@ export function StagePanel({
|
||||
{item.merge_failure && (
|
||||
<div
|
||||
data-testid={`merge-failure-reason-${item.story_id}`}
|
||||
style={{
|
||||
fontSize: "0.8em",
|
||||
color: "#f85149",
|
||||
marginTop: "4px",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
{item.merge_failure}
|
||||
<GateOutputBox text={item.merge_failure} />
|
||||
</div>
|
||||
)}
|
||||
{item.depends_on && item.depends_on.length > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user