diff --git a/src/features/workspace/components/__tests__/archive-workspace.test.tsx b/src/features/workspace/components/__tests__/archive-workspace.test.tsx index a66c1417..f27d6183 100644 --- a/src/features/workspace/components/__tests__/archive-workspace.test.tsx +++ b/src/features/workspace/components/__tests__/archive-workspace.test.tsx @@ -2,13 +2,16 @@ import { render } from "@/lib/test-utils"; import { ArchiveWorkspace } from "../archive-workspace"; import userEvent from "@testing-library/user-event"; import { waitFor } from "@testing-library/react"; +import { server } from "@/mocks/msw/node"; +import { http, HttpResponse } from "msw"; test("has correct buttons when not archived", async () => { - const { getByRole } = render( + const { getByRole, queryByRole } = render( , ); expect(getByRole("button", { name: /archive/i })).toBeVisible(); + expect(queryByRole("button", { name: /contextual help/i })).toBe(null); }); test("has correct buttons when archived", async () => { @@ -60,3 +63,38 @@ test("can permanently delete archived workspace", async () => { expect(getByText(/permanently deleted "foo-bar" workspace/i)).toBeVisible(); }); }); + +test("can't archive active workspace", async () => { + server.use( + http.get("*/api/v1/workspaces/active", () => + HttpResponse.json({ + workspaces: [ + { + name: "foo", + is_active: true, + last_updated: new Date(Date.now()).toISOString(), + }, + ], + }), + ), + ); + const { getByRole } = render( + , + ); + + await waitFor(() => { + expect(getByRole("button", { name: /archive/i })).toBeDisabled(); + expect(getByRole("button", { name: /contextual help/i })).toBeVisible(); + }); +}); + +test("can't archive default workspace", async () => { + const { getByRole } = render( + , + ); + + await waitFor(() => { + expect(getByRole("button", { name: /archive/i })).toBeDisabled(); + expect(getByRole("button", { name: /contextual help/i })).toBeVisible(); + }); +}); diff --git a/src/features/workspace/components/__tests__/workspace-name.test.tsx b/src/features/workspace/components/__tests__/workspace-name.test.tsx index 2d1ba447..d92b25b4 100644 --- a/src/features/workspace/components/__tests__/workspace-name.test.tsx +++ b/src/features/workspace/components/__tests__/workspace-name.test.tsx @@ -2,6 +2,8 @@ import { test, expect } from "vitest"; import { WorkspaceName } from "../workspace-name"; import { render, waitFor } from "@/lib/test-utils"; import userEvent from "@testing-library/user-event"; +import { server } from "@/mocks/msw/node"; +import { http, HttpResponse } from "msw"; test("can rename workspace", async () => { const { getByRole, getByText } = render( @@ -29,3 +31,34 @@ test("can't rename archived workspace", async () => { expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled(); expect(getByRole("button", { name: /save/i })).toBeDisabled(); }); + +test("can't rename active workspace", async () => { + server.use( + http.get("*/api/v1/workspaces/active", () => + HttpResponse.json({ + workspaces: [ + { + name: "foo", + is_active: true, + last_updated: new Date(Date.now()).toISOString(), + }, + ], + }), + ), + ); + const { getByRole } = render( + , + ); + + expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled(); + expect(getByRole("button", { name: /save/i })).toBeDisabled(); +}); + +test("can't rename default workspace", async () => { + const { getByRole } = render( + , + ); + + expect(getByRole("textbox", { name: /workspace name/i })).toBeDisabled(); + expect(getByRole("button", { name: /save/i })).toBeDisabled(); +}); diff --git a/src/features/workspace/components/archive-workspace.tsx b/src/features/workspace/components/archive-workspace.tsx index a599f847..aedb2fbf 100644 --- a/src/features/workspace/components/archive-workspace.tsx +++ b/src/features/workspace/components/archive-workspace.tsx @@ -1,15 +1,62 @@ -import { Card, CardBody, Button, Text } from "@stacklok/ui-kit"; +import { + Card, + CardBody, + Button, + Text, + TooltipTrigger, + Tooltip, + TooltipInfoButton, +} from "@stacklok/ui-kit"; import { twMerge } from "tailwind-merge"; import { useRestoreWorkspaceButton } from "../hooks/use-restore-workspace-button"; import { useArchiveWorkspaceButton } from "../hooks/use-archive-workspace-button"; import { useConfirmHardDeleteWorkspace } from "../hooks/use-confirm-hard-delete-workspace"; import { useNavigate } from "react-router-dom"; import { hrefs } from "@/lib/hrefs"; +import { useActiveWorkspaceName } from "../hooks/use-active-workspace-name"; + +function getContextualText({ + activeWorkspaceName, + workspaceName, +}: { + workspaceName: string; + activeWorkspaceName: string; +}) { + if (workspaceName === activeWorkspaceName) { + return "Cannot archive the active workspace"; + } + if (workspaceName === "default") { + return "Cannot archive the default workspace"; + } + return null; +} + +// NOTE: You can't show a tooltip on a disabled button +// React Aria's recommended approach is https://spectrum.adobe.com/page/contextual-help/ +function ContextualHelp({ workspaceName }: { workspaceName: string }) { + const { data: activeWorkspaceName } = useActiveWorkspaceName(); + if (!activeWorkspaceName) return null; + + const text = getContextualText({ activeWorkspaceName, workspaceName }); + if (!text) return null; + + return ( + + + {text} + + ); +} const ButtonsUnarchived = ({ workspaceName }: { workspaceName: string }) => { const archiveButtonProps = useArchiveWorkspaceButton({ workspaceName }); - return