fix(#101): Remediate code review findings for TaskProgressWidget
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline failed

- Fix CRITICAL: Replace .sort() state mutation with [...tasks].sort()
- Fix CRITICAL: Replace PDA-unfriendly red colors with calm amber tones
- Fix IMPORTANT: Add TaskProgressWidget + ActiveProjectsWidget to WidgetComponentType
- Fix IMPORTANT: Add tests for interval cleanup, HTTP error responses, slice limit
- 3 new tests added (10 total)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Jason Woltje
2026-02-05 13:19:57 -06:00
parent e7f277ff0c
commit 92ae8097df
3 changed files with 71 additions and 9 deletions

View File

@@ -2,8 +2,8 @@
* TaskProgressWidget Component Tests
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, waitFor, cleanup } from "@testing-library/react";
import { TaskProgressWidget } from "../TaskProgressWidget";
const mockFetch = vi.fn();
@@ -14,6 +14,10 @@ describe("TaskProgressWidget", (): void => {
vi.clearAllMocks();
});
afterEach((): void => {
cleanup();
});
it("should render loading state initially", (): void => {
mockFetch.mockImplementation(
() =>
@@ -148,6 +152,62 @@ describe("TaskProgressWidget", (): void => {
});
});
it("should display error for non-ok HTTP responses", async (): Promise<void> => {
mockFetch.mockResolvedValueOnce({
ok: false,
status: 500,
} as unknown as Response);
render(<TaskProgressWidget id="task-progress-1" />);
await waitFor(() => {
expect(screen.getByText(/unable to reach orchestrator/i)).toBeInTheDocument();
});
});
it("should clear interval on unmount", async (): Promise<void> => {
const clearIntervalSpy = vi.spyOn(global, "clearInterval");
mockFetch.mockResolvedValue({
ok: true,
json: () => Promise.resolve([]),
} as unknown as Response);
const { unmount } = render(<TaskProgressWidget id="task-progress-1" />);
await waitFor(() => {
expect(screen.getByText(/no agent tasks in progress/i)).toBeInTheDocument();
});
unmount();
expect(clearIntervalSpy).toHaveBeenCalled();
clearIntervalSpy.mockRestore();
});
it("should limit displayed tasks to 10", async (): Promise<void> => {
const mockTasks = Array.from({ length: 15 }, (_, i) => ({
agentId: `agent-${String(i)}`,
taskId: `SLICE-${String(i).padStart(3, "0")}`,
status: "running",
agentType: "worker",
spawnedAt: new Date().toISOString(),
}));
mockFetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockTasks),
} as unknown as Response);
render(<TaskProgressWidget id="task-progress-1" />);
await waitFor(() => {
// Only 10 task cards should be rendered despite 15 tasks
const workerBadges = screen.getAllByText("Worker");
expect(workerBadges).toHaveLength(10);
});
});
it("should sort active tasks before completed ones", async (): Promise<void> => {
const mockTasks = [
{