+
{task.error}
)}
diff --git a/apps/web/src/components/widgets/__tests__/TaskProgressWidget.test.tsx b/apps/web/src/components/widgets/__tests__/TaskProgressWidget.test.tsx
index f220dc0..47fd72d 100644
--- a/apps/web/src/components/widgets/__tests__/TaskProgressWidget.test.tsx
+++ b/apps/web/src/components/widgets/__tests__/TaskProgressWidget.test.tsx
@@ -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
=> {
+ mockFetch.mockResolvedValueOnce({
+ ok: false,
+ status: 500,
+ } as unknown as Response);
+
+ render();
+
+ await waitFor(() => {
+ expect(screen.getByText(/unable to reach orchestrator/i)).toBeInTheDocument();
+ });
+ });
+
+ it("should clear interval on unmount", async (): Promise => {
+ const clearIntervalSpy = vi.spyOn(global, "clearInterval");
+
+ mockFetch.mockResolvedValue({
+ ok: true,
+ json: () => Promise.resolve([]),
+ } as unknown as Response);
+
+ const { unmount } = render();
+
+ 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 => {
+ 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();
+
+ 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 => {
const mockTasks = [
{
diff --git a/packages/shared/src/types/widget.types.ts b/packages/shared/src/types/widget.types.ts
index 20e6743..6128e50 100644
--- a/packages/shared/src/types/widget.types.ts
+++ b/packages/shared/src/types/widget.types.ts
@@ -69,6 +69,8 @@ export type WidgetComponentType =
| "CalendarWidget"
| "QuickCaptureWidget"
| "AgentStatusWidget"
+ | "ActiveProjectsWidget"
+ | "TaskProgressWidget"
| "StatCardWidget"
| "ChartWidget"
| "ListWidget"