All checks were successful
ci/woodpecker/push/ci Pipeline was successful
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
191 lines
5.6 KiB
TypeScript
191 lines
5.6 KiB
TypeScript
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 <a href={href}>{children}</a>;
|
|
},
|
|
}));
|
|
|
|
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(<TeamsSettingsPage />);
|
|
|
|
expect(await screen.findByText("Access Denied")).toBeInTheDocument();
|
|
expect(fetchTeamsMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("loads and renders teams from the API", async () => {
|
|
fetchTeamsMock.mockResolvedValue([baseTeam]);
|
|
|
|
render(<TeamsSettingsPage />);
|
|
|
|
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(<TeamsSettingsPage />);
|
|
|
|
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(<TeamsSettingsPage />);
|
|
|
|
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(<TeamsSettingsPage />);
|
|
|
|
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(<TeamsSettingsPage />);
|
|
|
|
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(<TeamsSettingsPage />);
|
|
|
|
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");
|
|
});
|
|
});
|
|
});
|