import type { ReactElement, ReactNode } from "react"; import type { TeamRecord } from "@/lib/api/teams"; import { WorkspaceMemberRole } from "@mosaic/shared"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { createTeam, deleteTeam, fetchTeams, updateTeam } from "@/lib/api/teams"; import { fetchUserWorkspaces } from "@/lib/api/workspaces"; import TeamsSettingsPage from "./page"; vi.mock("next/link", () => ({ default: function LinkMock({ children, href, }: { children: ReactNode; href: string; }): ReactElement { return {children}; }, })); vi.mock("@/lib/api/teams", () => ({ fetchTeams: vi.fn(), createTeam: vi.fn(), updateTeam: vi.fn(), deleteTeam: vi.fn(), })); vi.mock("@/lib/api/workspaces", () => ({ fetchUserWorkspaces: vi.fn(), })); const fetchTeamsMock = vi.mocked(fetchTeams); const createTeamMock = vi.mocked(createTeam); const updateTeamMock = vi.mocked(updateTeam); const deleteTeamMock = vi.mocked(deleteTeam); const fetchUserWorkspacesMock = vi.mocked(fetchUserWorkspaces); const baseTeam: TeamRecord = { id: "team-1", workspaceId: "workspace-1", name: "Platform Team", description: "Owns platform services", metadata: {}, createdAt: "2026-02-01T00:00:00.000Z", updatedAt: "2026-02-01T00:00:00.000Z", _count: { members: 3, }, }; describe("TeamsSettingsPage", () => { beforeEach(() => { vi.clearAllMocks(); fetchTeamsMock.mockResolvedValue([]); fetchUserWorkspacesMock.mockResolvedValue([ { id: "workspace-1", name: "Personal Workspace", ownerId: "owner-1", role: WorkspaceMemberRole.OWNER, createdAt: "2026-01-01T00:00:00.000Z", }, ]); }); it("shows access denied to non-admin users", async () => { fetchUserWorkspacesMock.mockResolvedValueOnce([ { id: "workspace-1", name: "Personal Workspace", ownerId: "owner-1", role: WorkspaceMemberRole.MEMBER, createdAt: "2026-01-01T00:00:00.000Z", }, ]); render(); expect(await screen.findByText("Access Denied")).toBeInTheDocument(); expect(fetchTeamsMock).not.toHaveBeenCalled(); }); it("loads and renders teams from the API", async () => { fetchTeamsMock.mockResolvedValue([baseTeam]); render(); expect(await screen.findByText("Team Directory")).toBeInTheDocument(); expect(screen.getByText("Platform Team")).toBeInTheDocument(); expect(fetchTeamsMock).toHaveBeenCalledTimes(1); }); it("shows empty state when the API returns no teams", async () => { fetchTeamsMock.mockResolvedValue([]); render(); expect(await screen.findByText("No Teams Yet")).toBeInTheDocument(); expect(screen.getByText("Create the first team to get started.")).toBeInTheDocument(); }); it("shows error state and does not show empty state", async () => { fetchTeamsMock.mockRejectedValue(new Error("Unable to load teams")); render(); expect(await screen.findByText("Unable to load teams")).toBeInTheDocument(); }); it("creates a team from the create dialog", async () => { const user = userEvent.setup(); fetchTeamsMock.mockResolvedValue([baseTeam]); createTeamMock.mockResolvedValue({ ...baseTeam, id: "team-2", name: "Design Team", description: "Owns design quality", }); render(); expect(await screen.findByText("Platform Team")).toBeInTheDocument(); const triggerButton = screen.getByRole("button", { name: "Create Team" }); await user.click(triggerButton); await user.type(screen.getByLabelText("Name"), "Design Team"); await user.type(screen.getByLabelText("Description"), "Owns design quality"); const submitButton = screen.getAllByRole("button", { name: "Create Team" })[1]; if (!submitButton) { throw new Error("Expected create-team submit button to be rendered"); } await user.click(submitButton); await waitFor(() => { expect(createTeamMock).toHaveBeenCalledWith({ name: "Design Team", description: "Owns design quality", }); }); }); it("opens team details and updates name", async () => { const user = userEvent.setup(); fetchTeamsMock.mockResolvedValue([baseTeam]); updateTeamMock.mockResolvedValue({ ...baseTeam, name: "Platform Engineering", }); render(); expect(await screen.findByText("Platform Team")).toBeInTheDocument(); await user.click(screen.getByText("Platform Team")); const nameInput = await screen.findByLabelText("Name"); await user.clear(nameInput); await user.type(nameInput, "Platform Engineering"); await user.click(screen.getByRole("button", { name: "Save Changes" })); await waitFor(() => { expect(updateTeamMock).toHaveBeenCalledWith("team-1", { name: "Platform Engineering", }); }); }); it("deletes a team from the confirmation dialog", async () => { const user = userEvent.setup(); fetchTeamsMock.mockResolvedValue([baseTeam]); deleteTeamMock.mockResolvedValue(); render(); expect(await screen.findByText("Platform Team")).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Delete" })); await user.click(screen.getByRole("button", { name: "Delete Team" })); await waitFor(() => { expect(deleteTeamMock).toHaveBeenCalledWith("team-1"); }); }); });