feat(KB-017): add size badge to TaskCard component
- Add SizeBadge sub-component to display task size (XS/S/M/L/XL) in TaskCard - Add CSS styling with color-coded badges for each size level - Add comprehensive tests for size badge rendering across all size variants
This commit is contained in:
@@ -167,6 +167,12 @@ export function TaskCard({ task, queued, onOpenDetail, addToast, globalPaused, t
|
||||
#{task.prInfo.number}
|
||||
</span>
|
||||
)}
|
||||
{/* Size Indicator */}
|
||||
{task.size && (
|
||||
<span className={`card-size-badge size-${task.size.toLowerCase()}`}>
|
||||
{task.size}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="card-title">
|
||||
{task.title || (task.description ? task.description.slice(0, 60) + (task.description.length > 60 ? "…" : "") : task.id)}
|
||||
|
||||
@@ -439,3 +439,99 @@ describe("TaskCard clickable dependencies", () => {
|
||||
expect(depBadge.classList.contains("clickable")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Component tests for size badge rendering in TaskCard.
|
||||
*/
|
||||
describe("TaskCard size badge", () => {
|
||||
const noopToast = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders size badge when task.size is 'S'", () => {
|
||||
const task = makeTask({ size: "S" });
|
||||
|
||||
render(
|
||||
<TaskCard
|
||||
task={task}
|
||||
onOpenDetail={vi.fn()}
|
||||
addToast={noopToast}
|
||||
/>
|
||||
);
|
||||
|
||||
const sizeBadge = screen.getByText("S");
|
||||
expect(sizeBadge).toBeDefined();
|
||||
expect(sizeBadge.classList.contains("card-size-badge")).toBe(true);
|
||||
expect(sizeBadge.classList.contains("size-s")).toBe(true);
|
||||
});
|
||||
|
||||
it("renders size badge when task.size is 'M'", () => {
|
||||
const task = makeTask({ size: "M" });
|
||||
|
||||
render(
|
||||
<TaskCard
|
||||
task={task}
|
||||
onOpenDetail={vi.fn()}
|
||||
addToast={noopToast}
|
||||
/>
|
||||
);
|
||||
|
||||
const sizeBadge = screen.getByText("M");
|
||||
expect(sizeBadge).toBeDefined();
|
||||
expect(sizeBadge.classList.contains("card-size-badge")).toBe(true);
|
||||
expect(sizeBadge.classList.contains("size-m")).toBe(true);
|
||||
});
|
||||
|
||||
it("renders size badge when task.size is 'L'", () => {
|
||||
const task = makeTask({ size: "L" });
|
||||
|
||||
render(
|
||||
<TaskCard
|
||||
task={task}
|
||||
onOpenDetail={vi.fn()}
|
||||
addToast={noopToast}
|
||||
/>
|
||||
);
|
||||
|
||||
const sizeBadge = screen.getByText("L");
|
||||
expect(sizeBadge).toBeDefined();
|
||||
expect(sizeBadge.classList.contains("card-size-badge")).toBe(true);
|
||||
expect(sizeBadge.classList.contains("size-l")).toBe(true);
|
||||
});
|
||||
|
||||
it("does NOT render size badge when task.size is undefined", () => {
|
||||
const task = makeTask({ size: undefined });
|
||||
|
||||
render(
|
||||
<TaskCard
|
||||
task={task}
|
||||
onOpenDetail={vi.fn()}
|
||||
addToast={noopToast}
|
||||
/>
|
||||
);
|
||||
|
||||
const sizeBadge = screen.queryByText(/^[SML]$/);
|
||||
expect(sizeBadge).toBeNull();
|
||||
});
|
||||
|
||||
it("positioned in card-header with other badges", () => {
|
||||
const task = makeTask({ size: "M", status: "executing" });
|
||||
|
||||
const { container } = render(
|
||||
<TaskCard
|
||||
task={task}
|
||||
onOpenDetail={vi.fn()}
|
||||
addToast={noopToast}
|
||||
/>
|
||||
);
|
||||
|
||||
const cardHeader = container.querySelector(".card-header");
|
||||
expect(cardHeader).toBeDefined();
|
||||
|
||||
const sizeBadge = cardHeader?.querySelector(".card-size-badge");
|
||||
expect(sizeBadge).toBeDefined();
|
||||
expect(sizeBadge?.textContent).toBe("M");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -443,6 +443,37 @@ body {
|
||||
color: #da3633;
|
||||
}
|
||||
|
||||
/* Size badge: positioned in card header, subtle color coding for effort estimation */
|
||||
.card-size-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* Size S: subtle green tint (low effort) */
|
||||
.card-size-badge.size-s {
|
||||
background: rgba(63, 185, 80, 0.15);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
/* Size M: neutral tint (medium effort) */
|
||||
.card-size-badge.size-m {
|
||||
background: rgba(139, 148, 158, 0.15);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Size L: subtle amber tint (higher effort) */
|
||||
.card-size-badge.size-l {
|
||||
background: rgba(210, 153, 34, 0.15);
|
||||
color: var(--triage);
|
||||
}
|
||||
|
||||
.card.failed {
|
||||
border-left: 3px solid #da3633;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user