diff --git a/canvas/src/components/CreateWorkspaceDialog.tsx b/canvas/src/components/CreateWorkspaceDialog.tsx index 23496405..1e1b35e5 100644 --- a/canvas/src/components/CreateWorkspaceDialog.tsx +++ b/canvas/src/components/CreateWorkspaceDialog.tsx @@ -1,8 +1,14 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { api } from "@/lib/api"; +interface WorkspaceOption { + id: string; + name: string; + tier: number; +} + export function CreateWorkspaceButton() { const [open, setOpen] = useState(false); @@ -31,6 +37,13 @@ function CreateDialog({ onClose }: { onClose: () => void }) { const [parentId, setParentId] = useState(""); const [creating, setCreating] = useState(false); const [error, setError] = useState(null); + const [workspaces, setWorkspaces] = useState([]); + + useEffect(() => { + api.get("/workspaces") + .then((ws) => setWorkspaces(ws)) + .catch(() => {}); + }, []); const handleCreate = async () => { if (!name.trim()) { @@ -47,7 +60,7 @@ function CreateDialog({ onClose }: { onClose: () => void }) { role: role.trim() || undefined, template: template.trim() || undefined, tier, - parent_id: parentId.trim() || undefined, + parent_id: parentId || undefined, canvas: { x: Math.random() * 400 + 100, y: Math.random() * 300 + 100 }, }); onClose(); @@ -87,13 +100,27 @@ function CreateDialog({ onClose }: { onClose: () => void }) { }`} >
{t.label}
-
{t.desc}
+
{t.desc}
))} - +
+ + +
{error && ( diff --git a/canvas/src/components/SidePanel.tsx b/canvas/src/components/SidePanel.tsx index 0a18358b..7ba93f62 100644 --- a/canvas/src/components/SidePanel.tsx +++ b/canvas/src/components/SidePanel.tsx @@ -163,7 +163,7 @@ export function SidePanel() { onClick={() => { useCanvasStore.getState().restartWorkspace(selectedNodeId).catch(() => showToast("Restart failed", "error")); }} - className="text-[9px] px-2 py-1 bg-sky-800/40 hover:bg-sky-700/50 text-sky-200 rounded transition-colors" + className="text-[11px] px-2 py-1 bg-sky-800/40 hover:bg-sky-700/50 text-sky-200 rounded transition-colors" > Restart Now diff --git a/canvas/src/components/__tests__/CreateWorkspaceDialog.test.tsx b/canvas/src/components/__tests__/CreateWorkspaceDialog.test.tsx new file mode 100644 index 00000000..38de7a8c --- /dev/null +++ b/canvas/src/components/__tests__/CreateWorkspaceDialog.test.tsx @@ -0,0 +1,130 @@ +// @vitest-environment jsdom +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { render, screen, fireEvent, waitFor, cleanup } from "@testing-library/react"; +import { CreateWorkspaceButton } from "../CreateWorkspaceDialog"; + +vi.mock("@/lib/api", () => ({ + api: { + get: vi.fn(), + post: vi.fn(), + }, +})); + +import { api } from "@/lib/api"; + +const mockGet = vi.mocked(api.get); +const mockPost = vi.mocked(api.post); + +const SAMPLE_WORKSPACES = [ + { id: "ws-1", name: "Platform Team", tier: 1 }, + { id: "ws-2", name: "Research Agent", tier: 2 }, +]; + +beforeEach(() => { + vi.clearAllMocks(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mockGet.mockResolvedValue(SAMPLE_WORKSPACES as any); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mockPost.mockResolvedValue({} as any); +}); + +afterEach(() => { + cleanup(); +}); + +async function openDialog() { + render(); + const btn = screen.getAllByRole("button").find((b) => b.textContent?.includes("New Workspace")); + expect(btn).toBeTruthy(); + fireEvent.click(btn!); + await waitFor(() => expect(screen.getByText("Create Workspace")).toBeTruthy()); +} + +describe("CreateWorkspaceDialog", () => { + it("opens the dialog when New Workspace button is clicked", async () => { + await openDialog(); + expect(screen.getByText("Create Workspace")).toBeTruthy(); + }); + + it("renders a setFormCron(e.target.value)} className="w-full text-[10px] bg-zinc-800 border border-zinc-700 rounded px-2 py-1 text-zinc-200 font-mono" /> -
+
{cronToHuman(formCron)}
- +