mirror of
https://github.com/formbricks/formbricks.git
synced 2026-04-25 23:18:54 -05:00
feat: adds unit tests in modules/ee/teams (#5620)
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
import { validateInputs } from "@/lib/utils/validate";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { DatabaseError, UnknownError } from "@formbricks/types/errors";
|
||||
import { getProjectPermissionByUserId, getTeamRoleByTeamIdUserId } from "./roles";
|
||||
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
prisma: {
|
||||
projectTeam: { findMany: vi.fn() },
|
||||
teamUser: { findUnique: vi.fn() },
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@formbricks/logger", () => ({ logger: { error: vi.fn() } }));
|
||||
vi.mock("@/lib/utils/validate", () => ({ validateInputs: vi.fn() }));
|
||||
|
||||
const mockUserId = "user-1";
|
||||
const mockProjectId = "project-1";
|
||||
const mockTeamId = "team-1";
|
||||
|
||||
describe("roles lib", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("getProjectPermissionByUserId", () => {
|
||||
test("returns null if no memberships", async () => {
|
||||
vi.mocked(prisma.projectTeam.findMany).mockResolvedValueOnce([]);
|
||||
const result = await getProjectPermissionByUserId(mockUserId, mockProjectId);
|
||||
expect(result).toBeNull();
|
||||
expect(validateInputs).toHaveBeenCalledWith(
|
||||
[mockUserId, expect.anything()],
|
||||
[mockProjectId, expect.anything()]
|
||||
);
|
||||
});
|
||||
|
||||
test("returns 'manage' if any membership has manage", async () => {
|
||||
vi.mocked(prisma.projectTeam.findMany).mockResolvedValueOnce([
|
||||
{ permission: "read" },
|
||||
{ permission: "manage" },
|
||||
{ permission: "readWrite" },
|
||||
] as any);
|
||||
const result = await getProjectPermissionByUserId(mockUserId, mockProjectId);
|
||||
expect(result).toBe("manage");
|
||||
});
|
||||
|
||||
test("returns 'readWrite' if highest is readWrite", async () => {
|
||||
vi.mocked(prisma.projectTeam.findMany).mockResolvedValueOnce([
|
||||
{ permission: "read" },
|
||||
{ permission: "readWrite" },
|
||||
] as any);
|
||||
const result = await getProjectPermissionByUserId(mockUserId, mockProjectId);
|
||||
expect(result).toBe("readWrite");
|
||||
});
|
||||
|
||||
test("returns 'read' if only read", async () => {
|
||||
vi.mocked(prisma.projectTeam.findMany).mockResolvedValueOnce([{ permission: "read" }] as any);
|
||||
const result = await getProjectPermissionByUserId(mockUserId, mockProjectId);
|
||||
expect(result).toBe("read");
|
||||
});
|
||||
|
||||
test("throws DatabaseError on PrismaClientKnownRequestError", async () => {
|
||||
const error = new Prisma.PrismaClientKnownRequestError("fail", {
|
||||
code: "P2002",
|
||||
clientVersion: "1.0.0",
|
||||
});
|
||||
vi.mocked(prisma.projectTeam.findMany).mockRejectedValueOnce(error);
|
||||
await expect(getProjectPermissionByUserId(mockUserId, mockProjectId)).rejects.toThrow(DatabaseError);
|
||||
expect(logger.error).toHaveBeenCalledWith(error, expect.any(String));
|
||||
});
|
||||
|
||||
test("throws UnknownError on generic error", async () => {
|
||||
const error = new Error("fail");
|
||||
vi.mocked(prisma.projectTeam.findMany).mockRejectedValueOnce(error);
|
||||
await expect(getProjectPermissionByUserId(mockUserId, mockProjectId)).rejects.toThrow(UnknownError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getTeamRoleByTeamIdUserId", () => {
|
||||
test("returns null if no teamUser", async () => {
|
||||
vi.mocked(prisma.teamUser.findUnique).mockResolvedValueOnce(null);
|
||||
const result = await getTeamRoleByTeamIdUserId(mockTeamId, mockUserId);
|
||||
expect(result).toBeNull();
|
||||
expect(validateInputs).toHaveBeenCalledWith(
|
||||
[mockTeamId, expect.anything()],
|
||||
[mockUserId, expect.anything()]
|
||||
);
|
||||
});
|
||||
|
||||
test("returns role if teamUser exists", async () => {
|
||||
vi.mocked(prisma.teamUser.findUnique).mockResolvedValueOnce({ role: "member" });
|
||||
const result = await getTeamRoleByTeamIdUserId(mockTeamId, mockUserId);
|
||||
expect(result).toBe("member");
|
||||
});
|
||||
|
||||
test("throws DatabaseError on PrismaClientKnownRequestError", async () => {
|
||||
const error = new Prisma.PrismaClientKnownRequestError("fail", {
|
||||
code: "P2002",
|
||||
clientVersion: "1.0.0",
|
||||
});
|
||||
vi.mocked(prisma.teamUser.findUnique).mockRejectedValueOnce(error);
|
||||
await expect(getTeamRoleByTeamIdUserId(mockTeamId, mockUserId)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
test("throws error on generic error", async () => {
|
||||
const error = new Error("fail");
|
||||
vi.mocked(prisma.teamUser.findUnique).mockRejectedValueOnce(error);
|
||||
await expect(getTeamRoleByTeamIdUserId(mockTeamId, mockUserId)).rejects.toThrow(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { TProjectTeam } from "@/modules/ee/teams/project-teams/types/team";
|
||||
import { TeamPermissionMapping } from "@/modules/ee/teams/utils/teams";
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { AccessTable } from "./access-table";
|
||||
|
||||
vi.mock("@tolgee/react", () => ({
|
||||
useTranslate: () => ({ t: (k: string) => k }),
|
||||
}));
|
||||
|
||||
describe("AccessTable", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("renders no teams found row when teams is empty", () => {
|
||||
render(<AccessTable teams={[]} />);
|
||||
expect(screen.getByText("environments.project.teams.no_teams_found")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders team rows with correct data and permission mapping", () => {
|
||||
const teams: TProjectTeam[] = [
|
||||
{ id: "1", name: "Team A", memberCount: 1, permission: "readWrite" },
|
||||
{ id: "2", name: "Team B", memberCount: 2, permission: "read" },
|
||||
];
|
||||
render(<AccessTable teams={teams} />);
|
||||
expect(screen.getByText("Team A")).toBeInTheDocument();
|
||||
expect(screen.getByText("Team B")).toBeInTheDocument();
|
||||
expect(screen.getByText("1 common.member")).toBeInTheDocument();
|
||||
expect(screen.getByText("2 common.members")).toBeInTheDocument();
|
||||
expect(screen.getByText(TeamPermissionMapping["readWrite"])).toBeInTheDocument();
|
||||
expect(screen.getByText(TeamPermissionMapping["read"])).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders table headers with tolgee keys", () => {
|
||||
render(<AccessTable teams={[]} />);
|
||||
expect(screen.getByText("environments.project.teams.team_name")).toBeInTheDocument();
|
||||
expect(screen.getByText("common.size")).toBeInTheDocument();
|
||||
expect(screen.getByText("environments.project.teams.permission")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,72 @@
|
||||
import { TProjectTeam } from "@/modules/ee/teams/project-teams/types/team";
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { AccessView } from "./access-view";
|
||||
|
||||
vi.mock("@/app/(app)/environments/[environmentId]/settings/components/SettingsCard", () => ({
|
||||
SettingsCard: ({ title, description, children }: any) => (
|
||||
<div data-testid="SettingsCard">
|
||||
<div>{title}</div>
|
||||
<div>{description}</div>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ee/teams/project-teams/components/manage-team", () => ({
|
||||
ManageTeam: ({ environmentId, isOwnerOrManager }: any) => (
|
||||
<button data-testid="ManageTeam">
|
||||
ManageTeam {environmentId} {isOwnerOrManager ? "owner" : "not-owner"}
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ee/teams/project-teams/components/access-table", () => ({
|
||||
AccessTable: ({ teams }: any) => (
|
||||
<div data-testid="AccessTable">
|
||||
{teams.length === 0 ? "No teams" : `Teams: ${teams.map((t: any) => t.name).join(",")}`}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("AccessView", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
const baseProps = {
|
||||
environmentId: "env-1",
|
||||
isOwnerOrManager: true,
|
||||
teams: [
|
||||
{ id: "1", name: "Team A", memberCount: 2, permission: "readWrite" } as TProjectTeam,
|
||||
{ id: "2", name: "Team B", memberCount: 1, permission: "read" } as TProjectTeam,
|
||||
],
|
||||
};
|
||||
|
||||
test("renders SettingsCard with tolgee strings and children", () => {
|
||||
render(<AccessView {...baseProps} />);
|
||||
expect(screen.getByTestId("SettingsCard")).toBeInTheDocument();
|
||||
expect(screen.getByText("common.team_access")).toBeInTheDocument();
|
||||
expect(screen.getByText("environments.project.teams.team_settings_description")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders ManageTeam with correct props", () => {
|
||||
render(<AccessView {...baseProps} />);
|
||||
expect(screen.getByTestId("ManageTeam")).toHaveTextContent("ManageTeam env-1 owner");
|
||||
});
|
||||
|
||||
test("renders AccessTable with teams", () => {
|
||||
render(<AccessView {...baseProps} />);
|
||||
expect(screen.getByTestId("AccessTable")).toHaveTextContent("Teams: Team A,Team B");
|
||||
});
|
||||
|
||||
test("renders AccessTable with no teams", () => {
|
||||
render(<AccessView {...baseProps} teams={[]} />);
|
||||
expect(screen.getByTestId("AccessTable")).toHaveTextContent("No teams");
|
||||
});
|
||||
|
||||
test("renders ManageTeam as not-owner when isOwnerOrManager is false", () => {
|
||||
render(<AccessView {...baseProps} isOwnerOrManager={false} />);
|
||||
expect(screen.getByTestId("ManageTeam")).toHaveTextContent("not-owner");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { ManageTeam } from "./manage-team";
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({ push: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ui/components/button", () => ({
|
||||
Button: ({ children, ...props }: any) => <button {...props}>{children}</button>,
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ui/components/tooltip", () => ({
|
||||
TooltipRenderer: ({ tooltipContent, children }: any) => (
|
||||
<div data-testid="TooltipRenderer">
|
||||
<span>{tooltipContent}</span>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("ManageTeam", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("renders enabled button and navigates when isOwnerOrManager is true", async () => {
|
||||
render(<ManageTeam environmentId="env-123" isOwnerOrManager={true} />);
|
||||
const button = screen.getByRole("button");
|
||||
expect(button).toBeEnabled();
|
||||
expect(screen.getByText("environments.project.teams.manage_teams")).toBeInTheDocument();
|
||||
await userEvent.click(button);
|
||||
});
|
||||
|
||||
test("renders disabled button with tooltip when isOwnerOrManager is false", () => {
|
||||
render(<ManageTeam environmentId="env-123" isOwnerOrManager={false} />);
|
||||
const button = screen.getByRole("button");
|
||||
expect(button).toBeDisabled();
|
||||
expect(screen.getByText("environments.project.teams.manage_teams")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("TooltipRenderer")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("environments.project.teams.only_organization_owners_and_managers_can_manage_teams")
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { getTeamsByProjectId } from "./team";
|
||||
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
prisma: {
|
||||
project: { findUnique: vi.fn() },
|
||||
team: { findMany: vi.fn() },
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/cache/team", () => ({ teamCache: { tag: { byProjectId: vi.fn(), byId: vi.fn() } } }));
|
||||
vi.mock("@/lib/project/cache", () => ({ projectCache: { tag: { byId: vi.fn() } } }));
|
||||
|
||||
const mockProject = { id: "p1" };
|
||||
const mockTeams = [
|
||||
{
|
||||
id: "t1",
|
||||
name: "Team 1",
|
||||
projectTeams: [{ permission: "readWrite" }],
|
||||
_count: { teamUsers: 2 },
|
||||
},
|
||||
{
|
||||
id: "t2",
|
||||
name: "Team 2",
|
||||
projectTeams: [{ permission: "manage" }],
|
||||
_count: { teamUsers: 3 },
|
||||
},
|
||||
];
|
||||
|
||||
describe("getTeamsByProjectId", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("returns mapped teams for valid project", async () => {
|
||||
vi.mocked(prisma.project.findUnique).mockResolvedValueOnce(mockProject);
|
||||
vi.mocked(prisma.team.findMany).mockResolvedValueOnce(mockTeams);
|
||||
const result = await getTeamsByProjectId("p1");
|
||||
expect(result).toEqual([
|
||||
{ id: "t1", name: "Team 1", permission: "readWrite", memberCount: 2 },
|
||||
{ id: "t2", name: "Team 2", permission: "manage", memberCount: 3 },
|
||||
]);
|
||||
expect(prisma.project.findUnique).toHaveBeenCalledWith({ where: { id: "p1" } });
|
||||
expect(prisma.team.findMany).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("throws ResourceNotFoundError if project does not exist", async () => {
|
||||
vi.mocked(prisma.project.findUnique).mockResolvedValueOnce(null);
|
||||
await expect(getTeamsByProjectId("p1")).rejects.toThrow(ResourceNotFoundError);
|
||||
});
|
||||
|
||||
test("throws DatabaseError on Prisma known error", async () => {
|
||||
vi.mocked(prisma.project.findUnique).mockResolvedValueOnce(mockProject);
|
||||
vi.mocked(prisma.team.findMany).mockRejectedValueOnce(
|
||||
new Prisma.PrismaClientKnownRequestError("fail", { code: "P2002", clientVersion: "1.0.0" })
|
||||
);
|
||||
await expect(getTeamsByProjectId("p1")).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
|
||||
test("throws unknown error on unexpected error", async () => {
|
||||
vi.mocked(prisma.project.findUnique).mockResolvedValueOnce(mockProject);
|
||||
vi.mocked(prisma.team.findMany).mockRejectedValueOnce(new Error("unexpected"));
|
||||
await expect(getTeamsByProjectId("p1")).rejects.toThrow("unexpected");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { TeamsLoading } from "./loading";
|
||||
|
||||
vi.mock("@/modules/projects/settings/components/project-config-navigation", () => ({
|
||||
ProjectConfigNavigation: ({ activeId, loading }: any) => (
|
||||
<div data-testid="ProjectConfigNavigation">{`${activeId}-${loading}`}</div>
|
||||
),
|
||||
}));
|
||||
vi.mock("@/modules/ui/components/page-content-wrapper", () => ({
|
||||
PageContentWrapper: ({ children }: any) => <div data-testid="PageContentWrapper">{children}</div>,
|
||||
}));
|
||||
vi.mock("@/modules/ui/components/page-header", () => ({
|
||||
PageHeader: ({ children, pageTitle }: any) => (
|
||||
<div data-testid="PageHeader">
|
||||
<span>{pageTitle}</span>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("TeamsLoading", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("renders loading skeletons and navigation", () => {
|
||||
render(<TeamsLoading />);
|
||||
expect(screen.getByTestId("PageContentWrapper")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("PageHeader")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("ProjectConfigNavigation")).toHaveTextContent("teams-true");
|
||||
|
||||
// Check for the presence of multiple skeleton loaders (at least one)
|
||||
const skeletonLoaders = screen.getAllByRole("generic", { name: "" }); // Assuming skeleton divs don't have specific roles/names
|
||||
// Filter for elements with animate-pulse class
|
||||
const pulseElements = skeletonLoaders.filter((el) => el.classList.contains("animate-pulse"));
|
||||
expect(pulseElements.length).toBeGreaterThan(0);
|
||||
|
||||
expect(screen.getByText("common.project_configuration")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { getEnvironmentAuth } from "@/modules/environments/lib/utils";
|
||||
import { getTranslate } from "@/tolgee/server";
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { getTeamsByProjectId } from "./lib/team";
|
||||
import { ProjectTeams } from "./page";
|
||||
|
||||
vi.mock("@/modules/ee/teams/project-teams/components/access-view", () => ({
|
||||
AccessView: (props: any) => <div data-testid="AccessView">{JSON.stringify(props)}</div>,
|
||||
}));
|
||||
vi.mock("@/modules/environments/lib/utils", () => ({
|
||||
getEnvironmentAuth: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/modules/projects/settings/components/project-config-navigation", () => ({
|
||||
ProjectConfigNavigation: (props: any) => (
|
||||
<div data-testid="ProjectConfigNavigation">{JSON.stringify(props)}</div>
|
||||
),
|
||||
}));
|
||||
vi.mock("@/modules/ui/components/page-content-wrapper", () => ({
|
||||
PageContentWrapper: ({ children }: any) => <div data-testid="PageContentWrapper">{children}</div>,
|
||||
}));
|
||||
vi.mock("@/modules/ui/components/page-header", () => ({
|
||||
PageHeader: ({ children, pageTitle }: any) => (
|
||||
<div data-testid="PageHeader">
|
||||
<span>{pageTitle}</span>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
vi.mock("./lib/team", () => ({
|
||||
getTeamsByProjectId: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/tolgee/server", () => ({
|
||||
getTranslate: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("ProjectTeams", () => {
|
||||
const params = Promise.resolve({ environmentId: "env-1" });
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(getTeamsByProjectId).mockResolvedValue([
|
||||
{ id: "team-1", name: "Team 1", memberCount: 2, permission: "readWrite" },
|
||||
{ id: "team-2", name: "Team 2", memberCount: 1, permission: "read" },
|
||||
]);
|
||||
vi.mocked(getTranslate).mockResolvedValue((key) => key);
|
||||
|
||||
vi.mocked(getEnvironmentAuth).mockResolvedValue({
|
||||
project: { id: "project-1" },
|
||||
isOwner: true,
|
||||
isManager: false,
|
||||
} as any);
|
||||
});
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("renders all main components and passes correct props", async () => {
|
||||
const ui = await ProjectTeams({ params });
|
||||
render(ui);
|
||||
expect(screen.getByTestId("PageContentWrapper")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("PageHeader")).toBeInTheDocument();
|
||||
expect(screen.getByText("common.project_configuration")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("ProjectConfigNavigation")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("AccessView")).toHaveTextContent('"environmentId":"env-1"');
|
||||
expect(screen.getByTestId("AccessView")).toHaveTextContent('"isOwnerOrManager":true');
|
||||
});
|
||||
|
||||
test("throws error if teams is null", async () => {
|
||||
vi.mocked(getTeamsByProjectId).mockResolvedValue(null);
|
||||
await expect(ProjectTeams({ params })).rejects.toThrow("common.teams_not_found");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import { ZTeamSettingsFormSchema } from "@/modules/ee/teams/team-list/types/team";
|
||||
import { cleanup } from "@testing-library/react";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import {
|
||||
createTeamAction,
|
||||
deleteTeamAction,
|
||||
getTeamDetailsAction,
|
||||
getTeamRoleAction,
|
||||
updateTeamDetailsAction,
|
||||
} from "./actions";
|
||||
|
||||
vi.mock("@/lib/utils/action-client", () => ({
|
||||
authenticatedActionClient: {
|
||||
schema: () => ({
|
||||
action: (fn: any) => fn,
|
||||
}),
|
||||
},
|
||||
checkAuthorizationUpdated: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/utils/action-client-middleware", () => ({
|
||||
checkAuthorizationUpdated: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/utils/helper", () => ({
|
||||
getOrganizationIdFromTeamId: vi.fn(async (id: string) => `org-${id}`),
|
||||
}));
|
||||
vi.mock("@/modules/ee/role-management/actions", () => ({
|
||||
checkRoleManagementPermission: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/modules/ee/teams/lib/roles", () => ({
|
||||
getTeamRoleByTeamIdUserId: vi.fn(async () => "admin"),
|
||||
}));
|
||||
vi.mock("@/modules/ee/teams/team-list/lib/team", () => ({
|
||||
createTeam: vi.fn(async () => "team-created"),
|
||||
getTeamDetails: vi.fn(async () => ({ id: "team-1" })),
|
||||
deleteTeam: vi.fn(async () => true),
|
||||
updateTeamDetails: vi.fn(async () => ({ updated: true })),
|
||||
}));
|
||||
|
||||
describe("action.ts", () => {
|
||||
const ctx = {
|
||||
user: { id: "user-1" },
|
||||
} as any;
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("createTeamAction calls dependencies and returns result", async () => {
|
||||
const result = await createTeamAction({
|
||||
ctx,
|
||||
parsedInput: { organizationId: "org-1", name: "Team X" },
|
||||
} as any);
|
||||
expect(result).toBe("team-created");
|
||||
});
|
||||
|
||||
test("getTeamDetailsAction calls dependencies and returns result", async () => {
|
||||
const result = await getTeamDetailsAction({
|
||||
ctx,
|
||||
parsedInput: { teamId: "team-1" },
|
||||
} as any);
|
||||
expect(result).toEqual({ id: "team-1" });
|
||||
});
|
||||
|
||||
test("deleteTeamAction calls dependencies and returns result", async () => {
|
||||
const result = await deleteTeamAction({
|
||||
ctx,
|
||||
parsedInput: { teamId: "team-1" },
|
||||
} as any);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
test("updateTeamDetailsAction calls dependencies and returns result", async () => {
|
||||
const result = await updateTeamDetailsAction({
|
||||
ctx,
|
||||
parsedInput: { teamId: "team-1", data: {} as typeof ZTeamSettingsFormSchema._type },
|
||||
} as any);
|
||||
expect(result).toEqual({ updated: true });
|
||||
});
|
||||
|
||||
test("getTeamRoleAction calls dependencies and returns result", async () => {
|
||||
const result = await getTeamRoleAction({
|
||||
ctx,
|
||||
parsedInput: { teamId: "team-1" },
|
||||
} as any);
|
||||
expect(result).toBe("admin");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { CreateTeamButton } from "./create-team-button";
|
||||
|
||||
vi.mock("@/modules/ee/teams/team-list/components/create-team-modal", () => ({
|
||||
CreateTeamModal: ({ open, setOpen, organizationId }: any) =>
|
||||
open ? <div data-testid="CreateTeamModal">{organizationId}</div> : null,
|
||||
}));
|
||||
|
||||
describe("CreateTeamButton", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("renders button with tolgee string", () => {
|
||||
render(<CreateTeamButton organizationId="org-1" />);
|
||||
expect(screen.getByRole("button")).toBeInTheDocument();
|
||||
expect(screen.getByText("environments.settings.teams.create_new_team")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("opens CreateTeamModal on button click", async () => {
|
||||
render(<CreateTeamButton organizationId="org-2" />);
|
||||
await userEvent.click(screen.getByRole("button"));
|
||||
expect(screen.getByTestId("CreateTeamModal")).toHaveTextContent("org-2");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,77 @@
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { createTeamAction } from "@/modules/ee/teams/team-list/actions";
|
||||
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import toast from "react-hot-toast";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { CreateTeamModal } from "./create-team-modal";
|
||||
|
||||
vi.mock("@/modules/ui/components/modal", () => ({
|
||||
Modal: ({ children }: any) => <div data-testid="Modal">{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ee/teams/team-list/actions", () => ({
|
||||
createTeamAction: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/utils/helper", () => ({
|
||||
getFormattedErrorMessage: vi.fn(() => "error-message"),
|
||||
}));
|
||||
|
||||
describe("CreateTeamModal", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
const setOpen = vi.fn();
|
||||
|
||||
test("renders modal, form, and tolgee strings", () => {
|
||||
render(<CreateTeamModal open={true} setOpen={setOpen} organizationId="org-1" />);
|
||||
expect(screen.getByTestId("Modal")).toBeInTheDocument();
|
||||
expect(screen.getByText("environments.settings.teams.create_new_team")).toBeInTheDocument();
|
||||
expect(screen.getByText("environments.settings.teams.team_name")).toBeInTheDocument();
|
||||
expect(screen.getByText("common.cancel")).toBeInTheDocument();
|
||||
expect(screen.getByText("environments.settings.teams.create")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("calls setOpen(false) and resets teamName on cancel", async () => {
|
||||
render(<CreateTeamModal open={true} setOpen={setOpen} organizationId="org-1" />);
|
||||
const input = screen.getByPlaceholderText("environments.settings.teams.enter_team_name");
|
||||
await userEvent.type(input, "My Team");
|
||||
await userEvent.click(screen.getByText("common.cancel"));
|
||||
expect(setOpen).toHaveBeenCalledWith(false);
|
||||
expect((input as HTMLInputElement).value).toBe("");
|
||||
});
|
||||
|
||||
test("submit button is disabled when input is empty", () => {
|
||||
render(<CreateTeamModal open={true} setOpen={setOpen} organizationId="org-1" />);
|
||||
expect(screen.getByText("environments.settings.teams.create")).toBeDisabled();
|
||||
});
|
||||
|
||||
test("calls createTeamAction, shows success toast, calls onCreate, refreshes and closes modal on success", async () => {
|
||||
vi.mocked(createTeamAction).mockResolvedValue({ data: "team-123" });
|
||||
const onCreate = vi.fn();
|
||||
render(<CreateTeamModal open={true} setOpen={setOpen} organizationId="org-1" onCreate={onCreate} />);
|
||||
const input = screen.getByPlaceholderText("environments.settings.teams.enter_team_name");
|
||||
await userEvent.type(input, "My Team");
|
||||
await userEvent.click(screen.getByText("environments.settings.teams.create"));
|
||||
await waitFor(() => {
|
||||
expect(createTeamAction).toHaveBeenCalledWith({ name: "My Team", organizationId: "org-1" });
|
||||
expect(toast.success).toHaveBeenCalledWith("environments.settings.teams.team_created_successfully");
|
||||
expect(onCreate).toHaveBeenCalledWith("team-123");
|
||||
expect(setOpen).toHaveBeenCalledWith(false);
|
||||
expect((input as HTMLInputElement).value).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
test("shows error toast if createTeamAction fails", async () => {
|
||||
vi.mocked(createTeamAction).mockResolvedValue({});
|
||||
render(<CreateTeamModal open={true} setOpen={setOpen} organizationId="org-1" />);
|
||||
const input = screen.getByPlaceholderText("environments.settings.teams.enter_team_name");
|
||||
await userEvent.type(input, "My Team");
|
||||
await userEvent.click(screen.getByText("environments.settings.teams.create"));
|
||||
await waitFor(() => {
|
||||
expect(getFormattedErrorMessage).toHaveBeenCalled();
|
||||
expect(toast.error).toHaveBeenCalledWith("error-message");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { createTeamAction } from "@/modules/ee/teams/team-list/action";
|
||||
import { createTeamAction } from "@/modules/ee/teams/team-list/actions";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { Input } from "@/modules/ui/components/input";
|
||||
import { Label } from "@/modules/ui/components/label";
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { ManageTeamButton } from "./manage-team-button";
|
||||
|
||||
vi.mock("@/modules/ui/components/tooltip", () => ({
|
||||
TooltipRenderer: ({ shouldRender, tooltipContent, children }: any) =>
|
||||
shouldRender ? (
|
||||
<div data-testid="TooltipRenderer">
|
||||
{tooltipContent}
|
||||
{children}
|
||||
</div>
|
||||
) : (
|
||||
<>{children}</>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("ManageTeamButton", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("renders enabled button and calls onClick", async () => {
|
||||
const onClick = vi.fn();
|
||||
render(<ManageTeamButton onClick={onClick} disabled={false} />);
|
||||
const button = screen.getByRole("button");
|
||||
expect(button).toBeEnabled();
|
||||
expect(screen.getByText("environments.settings.teams.manage_team")).toBeInTheDocument();
|
||||
await userEvent.click(button);
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("renders disabled button with tooltip", () => {
|
||||
const onClick = vi.fn();
|
||||
render(<ManageTeamButton onClick={onClick} disabled={true} />);
|
||||
const button = screen.getByRole("button");
|
||||
expect(button).toBeDisabled();
|
||||
expect(screen.getByText("environments.settings.teams.manage_team")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("TooltipRenderer")).toBeInTheDocument();
|
||||
expect(screen.getByText("environments.settings.teams.manage_team_disabled")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
import { deleteTeamAction } from "@/modules/ee/teams/team-list/actions";
|
||||
import { TTeam } from "@/modules/ee/teams/team-list/types/team";
|
||||
import { cleanup, render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import toast from "react-hot-toast";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { DeleteTeam } from "./delete-team";
|
||||
|
||||
vi.mock("@/modules/ui/components/label", () => ({
|
||||
Label: ({ children }: any) => <label>{children}</label>,
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ui/components/button", () => ({
|
||||
Button: ({ children, ...props }: any) => <button {...props}>{children}</button>,
|
||||
}));
|
||||
vi.mock("@/modules/ui/components/tooltip", () => ({
|
||||
TooltipRenderer: ({ shouldRender, tooltipContent, children }: any) =>
|
||||
shouldRender ? (
|
||||
<div data-testid="TooltipRenderer">
|
||||
{tooltipContent}
|
||||
{children}
|
||||
</div>
|
||||
) : (
|
||||
<>{children}</>
|
||||
),
|
||||
}));
|
||||
vi.mock("@/modules/ui/components/delete-dialog", () => ({
|
||||
DeleteDialog: ({ open, setOpen, deleteWhat, text, onDelete, isDeleting }: any) =>
|
||||
open ? (
|
||||
<div data-testid="DeleteDialog">
|
||||
<span>{deleteWhat}</span>
|
||||
<span>{text}</span>
|
||||
<button onClick={onDelete} disabled={isDeleting}>
|
||||
Confirm
|
||||
</button>
|
||||
</div>
|
||||
) : null,
|
||||
}));
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({ refresh: vi.fn() }),
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ee/teams/team-list/actions", () => ({
|
||||
deleteTeamAction: vi.fn(),
|
||||
}));
|
||||
|
||||
describe("DeleteTeam", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
const baseProps = {
|
||||
teamId: "team-1" as TTeam["id"],
|
||||
onDelete: vi.fn(),
|
||||
isOwnerOrManager: true,
|
||||
};
|
||||
|
||||
test("renders danger zone label and delete button enabled for owner/manager", () => {
|
||||
render(<DeleteTeam {...baseProps} />);
|
||||
expect(screen.getByText("common.danger_zone")).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "environments.settings.teams.delete_team" })).toBeEnabled();
|
||||
});
|
||||
|
||||
test("renders tooltip and disables button if not owner/manager", () => {
|
||||
render(<DeleteTeam {...baseProps} isOwnerOrManager={false} />);
|
||||
expect(screen.getByTestId("TooltipRenderer")).toBeInTheDocument();
|
||||
expect(screen.getByText("environments.settings.teams.team_deletion_not_allowed")).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "environments.settings.teams.delete_team" })).toBeDisabled();
|
||||
});
|
||||
|
||||
test("opens dialog on delete button click", async () => {
|
||||
render(<DeleteTeam {...baseProps} />);
|
||||
await userEvent.click(screen.getByRole("button", { name: "environments.settings.teams.delete_team" }));
|
||||
expect(screen.getByTestId("DeleteDialog")).toBeInTheDocument();
|
||||
expect(screen.getByText("common.team")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText("environments.settings.teams.are_you_sure_you_want_to_delete_this_team")
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("calls deleteTeamAction, shows success toast, calls onDelete, and refreshes on confirm", async () => {
|
||||
vi.mocked(deleteTeamAction).mockResolvedValue({ data: true });
|
||||
const onDelete = vi.fn();
|
||||
render(<DeleteTeam {...baseProps} onDelete={onDelete} />);
|
||||
await userEvent.click(screen.getByRole("button", { name: "environments.settings.teams.delete_team" }));
|
||||
await userEvent.click(screen.getByText("Confirm"));
|
||||
expect(deleteTeamAction).toHaveBeenCalledWith({ teamId: baseProps.teamId });
|
||||
expect(toast.success).toHaveBeenCalledWith("environments.settings.teams.team_deleted_successfully");
|
||||
expect(onDelete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("shows error toast if deleteTeamAction fails", async () => {
|
||||
vi.mocked(deleteTeamAction).mockResolvedValue({ data: false });
|
||||
render(<DeleteTeam {...baseProps} />);
|
||||
await userEvent.click(screen.getByRole("button", { name: "environments.settings.teams.delete_team" }));
|
||||
await userEvent.click(screen.getByText("Confirm"));
|
||||
expect(toast.error).toHaveBeenCalledWith("common.something_went_wrong_please_try_again");
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { deleteTeamAction } from "@/modules/ee/teams/team-list/action";
|
||||
import { deleteTeamAction } from "@/modules/ee/teams/team-list/actions";
|
||||
import { TTeam } from "@/modules/ee/teams/team-list/types/team";
|
||||
import { Button } from "@/modules/ui/components/button";
|
||||
import { DeleteDialog } from "@/modules/ui/components/delete-dialog";
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
import { ZTeamPermission } from "@/modules/ee/teams/project-teams/types/team";
|
||||
import { updateTeamDetailsAction } from "@/modules/ee/teams/team-list/actions";
|
||||
import { TOrganizationMember, TTeamDetails, ZTeamRole } from "@/modules/ee/teams/team-list/types/team";
|
||||
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import toast from "react-hot-toast";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { TeamSettingsModal } from "./team-settings-modal";
|
||||
|
||||
vi.mock("@/modules/ui/components/modal", () => ({
|
||||
Modal: ({ children, ...props }: any) => <div data-testid="Modal">{children}</div>,
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ee/teams/team-list/components/team-settings/delete-team", () => ({
|
||||
DeleteTeam: () => <div data-testid="DeleteTeam" />,
|
||||
}));
|
||||
vi.mock("@/modules/ee/teams/team-list/actions", () => ({
|
||||
updateTeamDetailsAction: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: () => ({ refresh: vi.fn() }),
|
||||
}));
|
||||
|
||||
describe("TeamSettingsModal", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
const orgMembers: TOrganizationMember[] = [
|
||||
{ id: "1", name: "Alice", role: "member" },
|
||||
{ id: "2", name: "Bob", role: "manager" },
|
||||
];
|
||||
const orgProjects = [
|
||||
{ id: "p1", name: "Project 1" },
|
||||
{ id: "p2", name: "Project 2" },
|
||||
];
|
||||
const team: TTeamDetails = {
|
||||
id: "t1",
|
||||
name: "Team 1",
|
||||
members: [{ name: "Alice", userId: "1", role: ZTeamRole.enum.contributor }],
|
||||
projects: [
|
||||
{ projectName: "pro1", projectId: "p1", permission: ZTeamPermission.enum.read },
|
||||
{ projectName: "pro2", projectId: "p2", permission: ZTeamPermission.enum.readWrite },
|
||||
],
|
||||
organizationId: "org1",
|
||||
};
|
||||
const setOpen = vi.fn();
|
||||
|
||||
test("renders modal, form, and tolgee strings", () => {
|
||||
render(
|
||||
<TeamSettingsModal
|
||||
open={true}
|
||||
setOpen={setOpen}
|
||||
team={team}
|
||||
orgMembers={orgMembers}
|
||||
orgProjects={orgProjects}
|
||||
userTeamRole={ZTeamRole.enum.admin}
|
||||
membershipRole={"owner"}
|
||||
currentUserId="1"
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId("Modal")).toBeInTheDocument();
|
||||
expect(screen.getByText("environments.settings.teams.team_name_settings_title")).toBeInTheDocument();
|
||||
expect(screen.getByText("environments.settings.teams.team_settings_description")).toBeInTheDocument();
|
||||
expect(screen.getByText("common.team_name")).toBeInTheDocument();
|
||||
expect(screen.getByText("common.members")).toBeInTheDocument();
|
||||
expect(screen.getByText("environments.settings.teams.add_members_description")).toBeInTheDocument();
|
||||
expect(screen.getByText("Add member")).toBeInTheDocument();
|
||||
expect(screen.getByText("Projects")).toBeInTheDocument();
|
||||
expect(screen.getByText("Add project")).toBeInTheDocument();
|
||||
expect(screen.getByText("environments.settings.teams.add_projects_description")).toBeInTheDocument();
|
||||
expect(screen.getByText("common.cancel")).toBeInTheDocument();
|
||||
expect(screen.getByText("common.save")).toBeInTheDocument();
|
||||
expect(screen.getByTestId("DeleteTeam")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("calls setOpen(false) when cancel button is clicked", async () => {
|
||||
render(
|
||||
<TeamSettingsModal
|
||||
open={true}
|
||||
setOpen={setOpen}
|
||||
team={team}
|
||||
orgMembers={orgMembers}
|
||||
orgProjects={orgProjects}
|
||||
userTeamRole={ZTeamRole.enum.admin}
|
||||
membershipRole={"owner"}
|
||||
currentUserId="1"
|
||||
/>
|
||||
);
|
||||
await userEvent.click(screen.getByText("common.cancel"));
|
||||
expect(setOpen).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
test("calls updateTeamDetailsAction and shows success toast on submit", async () => {
|
||||
vi.mocked(updateTeamDetailsAction).mockResolvedValue({ data: true });
|
||||
render(
|
||||
<TeamSettingsModal
|
||||
open={true}
|
||||
setOpen={setOpen}
|
||||
team={team}
|
||||
orgMembers={orgMembers}
|
||||
orgProjects={orgProjects}
|
||||
userTeamRole={ZTeamRole.enum.admin}
|
||||
membershipRole={"owner"}
|
||||
currentUserId="1"
|
||||
/>
|
||||
);
|
||||
await userEvent.click(screen.getByText("common.save"));
|
||||
await waitFor(() => {
|
||||
expect(updateTeamDetailsAction).toHaveBeenCalled();
|
||||
expect(toast.success).toHaveBeenCalledWith("environments.settings.teams.team_updated_successfully");
|
||||
expect(setOpen).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
|
||||
test("shows error toast if updateTeamDetailsAction fails", async () => {
|
||||
vi.mocked(updateTeamDetailsAction).mockResolvedValue({ data: false });
|
||||
render(
|
||||
<TeamSettingsModal
|
||||
open={true}
|
||||
setOpen={setOpen}
|
||||
team={team}
|
||||
orgMembers={orgMembers}
|
||||
orgProjects={orgProjects}
|
||||
userTeamRole={ZTeamRole.enum.admin}
|
||||
membershipRole={"owner"}
|
||||
currentUserId="1"
|
||||
/>
|
||||
);
|
||||
await userEvent.click(screen.getByText("common.save"));
|
||||
await waitFor(() => {
|
||||
expect(toast.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { cn } from "@/lib/cn";
|
||||
import { getAccessFlags } from "@/lib/membership/utils";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { ZTeamPermission } from "@/modules/ee/teams/project-teams/types/team";
|
||||
import { updateTeamDetailsAction } from "@/modules/ee/teams/team-list/action";
|
||||
import { updateTeamDetailsAction } from "@/modules/ee/teams/team-list/actions";
|
||||
import { DeleteTeam } from "@/modules/ee/teams/team-list/components/team-settings/delete-team";
|
||||
import { TOrganizationProject } from "@/modules/ee/teams/team-list/types/project";
|
||||
import {
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import { getTeamDetailsAction, getTeamRoleAction } from "@/modules/ee/teams/team-list/actions";
|
||||
import { TOrganizationMember, TOtherTeam, TUserTeam } from "@/modules/ee/teams/team-list/types/team";
|
||||
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import toast from "react-hot-toast";
|
||||
import { afterEach, describe, expect, test, vi } from "vitest";
|
||||
import { TeamsTable } from "./teams-table";
|
||||
|
||||
vi.mock("@/modules/ee/teams/team-list/components/create-team-button", () => ({
|
||||
CreateTeamButton: ({ organizationId }: any) => (
|
||||
<button data-testid="CreateTeamButton">{organizationId}</button>
|
||||
),
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ee/teams/team-list/components/manage-team-button", () => ({
|
||||
ManageTeamButton: ({ disabled, onClick }: any) => (
|
||||
<button data-testid="ManageTeamButton" disabled={disabled} onClick={onClick}>
|
||||
environments.settings.teams.manage_team
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
vi.mock("@/modules/ee/teams/team-list/components/team-settings/team-settings-modal", () => ({
|
||||
TeamSettingsModal: (props: any) => <div data-testid="TeamSettingsModal">{props.team?.name}</div>,
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ee/teams/team-list/actions", () => ({
|
||||
getTeamDetailsAction: vi.fn(),
|
||||
getTeamRoleAction: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/modules/ui/components/badge", () => ({
|
||||
Badge: ({ text }: any) => <span data-testid="Badge">{text}</span>,
|
||||
}));
|
||||
|
||||
const userTeams: TUserTeam[] = [
|
||||
{ id: "1", name: "Alpha", memberCount: 2, userRole: "admin" },
|
||||
{ id: "2", name: "Beta", memberCount: 1, userRole: "contributor" },
|
||||
];
|
||||
const otherTeams: TOtherTeam[] = [
|
||||
{ id: "3", name: "Gamma", memberCount: 3 },
|
||||
{ id: "4", name: "Delta", memberCount: 1 },
|
||||
];
|
||||
const orgMembers: TOrganizationMember[] = [{ id: "u1", name: "User 1", role: "manager" }];
|
||||
const orgProjects = [{ id: "p1", name: "Project 1" }];
|
||||
|
||||
describe("TeamsTable", () => {
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
test("renders CreateTeamButton for owner/manager", () => {
|
||||
render(
|
||||
<TeamsTable
|
||||
teams={{ userTeams: [], otherTeams: [] }}
|
||||
organizationId="org-1"
|
||||
orgMembers={orgMembers}
|
||||
orgProjects={orgProjects}
|
||||
membershipRole="owner"
|
||||
currentUserId="u1"
|
||||
/>
|
||||
);
|
||||
expect(screen.getByTestId("CreateTeamButton")).toHaveTextContent("org-1");
|
||||
});
|
||||
|
||||
test("does not render CreateTeamButton for non-owner/manager", () => {
|
||||
render(
|
||||
<TeamsTable
|
||||
teams={{ userTeams: [], otherTeams: [] }}
|
||||
organizationId="org-1"
|
||||
orgMembers={orgMembers}
|
||||
orgProjects={orgProjects}
|
||||
membershipRole={undefined}
|
||||
currentUserId="u1"
|
||||
/>
|
||||
);
|
||||
expect(screen.queryByTestId("CreateTeamButton")).toBeNull();
|
||||
});
|
||||
|
||||
test("renders empty state row if no teams", () => {
|
||||
render(
|
||||
<TeamsTable
|
||||
teams={{ userTeams: [], otherTeams: [] }}
|
||||
organizationId="org-1"
|
||||
orgMembers={orgMembers}
|
||||
orgProjects={orgProjects}
|
||||
membershipRole="owner"
|
||||
currentUserId="u1"
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText("environments.settings.teams.empty_teams_state")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders userTeams and otherTeams rows", () => {
|
||||
render(
|
||||
<TeamsTable
|
||||
teams={{ userTeams, otherTeams }}
|
||||
organizationId="org-1"
|
||||
orgMembers={orgMembers}
|
||||
orgProjects={orgProjects}
|
||||
membershipRole="owner"
|
||||
currentUserId="u1"
|
||||
/>
|
||||
);
|
||||
expect(screen.getByText("Alpha")).toBeInTheDocument();
|
||||
expect(screen.getByText("Beta")).toBeInTheDocument();
|
||||
expect(screen.getByText("Gamma")).toBeInTheDocument();
|
||||
expect(screen.getByText("Delta")).toBeInTheDocument();
|
||||
expect(screen.getAllByTestId("ManageTeamButton").length).toBe(4);
|
||||
expect(screen.getAllByTestId("Badge")[0]).toHaveTextContent(
|
||||
"environments.settings.teams.you_are_a_member"
|
||||
);
|
||||
expect(screen.getByText("2 common.members")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("opens TeamSettingsModal when ManageTeamButton is clicked and team details are returned", async () => {
|
||||
vi.mocked(getTeamDetailsAction).mockResolvedValue({
|
||||
data: { id: "1", name: "Alpha", organizationId: "org-1", members: [], projects: [] },
|
||||
});
|
||||
vi.mocked(getTeamRoleAction).mockResolvedValue({ data: "admin" });
|
||||
render(
|
||||
<TeamsTable
|
||||
teams={{ userTeams, otherTeams }}
|
||||
organizationId="org-1"
|
||||
orgMembers={orgMembers}
|
||||
orgProjects={orgProjects}
|
||||
membershipRole="owner"
|
||||
currentUserId="u1"
|
||||
/>
|
||||
);
|
||||
await userEvent.click(screen.getAllByTestId("ManageTeamButton")[0]);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("TeamSettingsModal")).toHaveTextContent("Alpha");
|
||||
});
|
||||
});
|
||||
|
||||
test("shows error toast if getTeamDetailsAction fails", async () => {
|
||||
vi.mocked(getTeamDetailsAction).mockResolvedValue({ data: undefined });
|
||||
vi.mocked(getTeamRoleAction).mockResolvedValue({ data: undefined });
|
||||
render(
|
||||
<TeamsTable
|
||||
teams={{ userTeams, otherTeams }}
|
||||
organizationId="org-1"
|
||||
orgMembers={orgMembers}
|
||||
orgProjects={orgProjects}
|
||||
membershipRole="owner"
|
||||
currentUserId="u1"
|
||||
/>
|
||||
);
|
||||
await userEvent.click(screen.getAllByTestId("ManageTeamButton")[0]);
|
||||
await waitFor(() => {
|
||||
expect(toast.error).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { getAccessFlags } from "@/lib/membership/utils";
|
||||
import { getFormattedErrorMessage } from "@/lib/utils/helper";
|
||||
import { getTeamDetailsAction, getTeamRoleAction } from "@/modules/ee/teams/team-list/action";
|
||||
import { getTeamDetailsAction, getTeamRoleAction } from "@/modules/ee/teams/team-list/actions";
|
||||
import { CreateTeamButton } from "@/modules/ee/teams/team-list/components/create-team-button";
|
||||
import { ManageTeamButton } from "@/modules/ee/teams/team-list/components/manage-team-button";
|
||||
import { TeamSettingsModal } from "@/modules/ee/teams/team-list/components/team-settings/team-settings-modal";
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { DatabaseError, UnknownError } from "@formbricks/types/errors";
|
||||
import { getProjectsByOrganizationId } from "./project";
|
||||
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
prisma: {
|
||||
project: { findMany: vi.fn() },
|
||||
},
|
||||
}));
|
||||
vi.mock("@formbricks/logger", () => ({ logger: { error: vi.fn() } }));
|
||||
|
||||
const mockProjects = [
|
||||
{ id: "p1", name: "Project 1" },
|
||||
{ id: "p2", name: "Project 2" },
|
||||
];
|
||||
|
||||
describe("getProjectsByOrganizationId", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
test("returns mapped projects for valid organization", async () => {
|
||||
vi.mocked(prisma.project.findMany).mockResolvedValueOnce(mockProjects);
|
||||
const result = await getProjectsByOrganizationId("org1");
|
||||
expect(result).toEqual([
|
||||
{ id: "p1", name: "Project 1" },
|
||||
{ id: "p2", name: "Project 2" },
|
||||
]);
|
||||
expect(prisma.project.findMany).toHaveBeenCalledWith({
|
||||
where: { organizationId: "org1" },
|
||||
select: { id: true, name: true },
|
||||
});
|
||||
});
|
||||
|
||||
test("throws DatabaseError on Prisma known error", async () => {
|
||||
const error = new Prisma.PrismaClientKnownRequestError("fail", { code: "P2002", clientVersion: "1.0.0" });
|
||||
vi.mocked(prisma.project.findMany).mockRejectedValueOnce(error);
|
||||
await expect(getProjectsByOrganizationId("org1")).rejects.toThrow(DatabaseError);
|
||||
expect(logger.error).toHaveBeenCalledWith(error, "Error fetching projects by organization id");
|
||||
});
|
||||
|
||||
test("throws UnknownError on unknown error", async () => {
|
||||
const error = new Error("fail");
|
||||
vi.mocked(prisma.project.findMany).mockRejectedValueOnce(error);
|
||||
await expect(getProjectsByOrganizationId("org1")).rejects.toThrow(UnknownError);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,343 @@
|
||||
import { organizationCache } from "@/lib/cache/organization";
|
||||
import { teamCache } from "@/lib/cache/team";
|
||||
import { projectCache } from "@/lib/project/cache";
|
||||
import { TTeamSettingsFormSchema } from "@/modules/ee/teams/team-list/types/team";
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { DatabaseError, InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import {
|
||||
createTeam,
|
||||
deleteTeam,
|
||||
getOtherTeams,
|
||||
getTeamDetails,
|
||||
getTeams,
|
||||
getTeamsByOrganizationId,
|
||||
getUserTeams,
|
||||
updateTeamDetails,
|
||||
} from "./team";
|
||||
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
prisma: {
|
||||
team: {
|
||||
findMany: vi.fn(),
|
||||
findFirst: vi.fn(),
|
||||
create: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
update: vi.fn(),
|
||||
delete: vi.fn(),
|
||||
},
|
||||
membership: { findUnique: vi.fn(), count: vi.fn() },
|
||||
project: { count: vi.fn() },
|
||||
environment: { findMany: vi.fn() },
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/cache/team", () => ({
|
||||
teamCache: {
|
||||
tag: { byOrganizationId: vi.fn(), byUserId: vi.fn(), byId: vi.fn(), projectId: vi.fn() },
|
||||
revalidate: vi.fn(),
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/project/cache", () => ({
|
||||
projectCache: { tag: { byId: vi.fn(), byOrganizationId: vi.fn() }, revalidate: vi.fn() },
|
||||
}));
|
||||
vi.mock("@/lib/cache/organization", () => ({ organizationCache: { revalidate: vi.fn() } }));
|
||||
|
||||
const mockTeams = [
|
||||
{ id: "t1", name: "Team 1" },
|
||||
{ id: "t2", name: "Team 2" },
|
||||
];
|
||||
const mockUserTeams = [
|
||||
{
|
||||
id: "t1",
|
||||
name: "Team 1",
|
||||
teamUsers: [{ role: "admin" }],
|
||||
_count: { teamUsers: 2 },
|
||||
},
|
||||
];
|
||||
const mockOtherTeams = [
|
||||
{
|
||||
id: "t2",
|
||||
name: "Team 2",
|
||||
_count: { teamUsers: 3 },
|
||||
},
|
||||
];
|
||||
const mockMembership = { role: "admin" };
|
||||
const mockTeamDetails = {
|
||||
id: "t1",
|
||||
name: "Team 1",
|
||||
organizationId: "org1",
|
||||
teamUsers: [
|
||||
{ userId: "u1", role: "admin", user: { name: "User 1" } },
|
||||
{ userId: "u2", role: "member", user: { name: "User 2" } },
|
||||
],
|
||||
projectTeams: [{ projectId: "p1", project: { name: "Project 1" }, permission: "manage" }],
|
||||
};
|
||||
|
||||
describe("getTeamsByOrganizationId", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
test("returns mapped teams", async () => {
|
||||
vi.mocked(prisma.team.findMany).mockResolvedValueOnce(mockTeams);
|
||||
const result = await getTeamsByOrganizationId("org1");
|
||||
expect(result).toEqual([
|
||||
{ id: "t1", name: "Team 1" },
|
||||
{ id: "t2", name: "Team 2" },
|
||||
]);
|
||||
});
|
||||
test("throws DatabaseError on Prisma error", async () => {
|
||||
vi.mocked(prisma.team.findMany).mockRejectedValueOnce(
|
||||
new Prisma.PrismaClientKnownRequestError("fail", { code: "P2002", clientVersion: "1.0.0" })
|
||||
);
|
||||
await expect(getTeamsByOrganizationId("org1")).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUserTeams", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
test("returns mapped user teams", async () => {
|
||||
vi.mocked(prisma.team.findMany).mockResolvedValueOnce(mockUserTeams);
|
||||
|
||||
const result = await getUserTeams("u1", "org1");
|
||||
expect(result).toEqual([{ id: "t1", name: "Team 1", userRole: "admin", memberCount: 2 }]);
|
||||
});
|
||||
test("throws DatabaseError on Prisma error", async () => {
|
||||
vi.mocked(prisma.team.findMany).mockRejectedValueOnce(
|
||||
new Prisma.PrismaClientKnownRequestError("fail", { code: "P2002", clientVersion: "1.0.0" })
|
||||
);
|
||||
await expect(getUserTeams("u1", "org1")).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getOtherTeams", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
test("returns mapped other teams", async () => {
|
||||
vi.mocked(prisma.team.findMany).mockResolvedValueOnce(mockOtherTeams);
|
||||
const result = await getOtherTeams("u1", "org1");
|
||||
expect(result).toEqual([{ id: "t2", name: "Team 2", memberCount: 3 }]);
|
||||
});
|
||||
test("throws DatabaseError on Prisma error", async () => {
|
||||
vi.mocked(prisma.team.findMany).mockRejectedValueOnce(
|
||||
new Prisma.PrismaClientKnownRequestError("fail", { code: "P2002", clientVersion: "1.0.0" })
|
||||
);
|
||||
await expect(getOtherTeams("u1", "org1")).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getTeams", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
test("returns userTeams and otherTeams", async () => {
|
||||
vi.mocked(prisma.membership.findUnique).mockResolvedValueOnce(mockMembership);
|
||||
vi.mocked(prisma.team.findMany).mockResolvedValueOnce(mockUserTeams);
|
||||
vi.mocked(prisma.team.findMany).mockResolvedValueOnce(mockOtherTeams);
|
||||
const result = await getTeams("u1", "org1");
|
||||
expect(result).toEqual({
|
||||
userTeams: [{ id: "t1", name: "Team 1", userRole: "admin", memberCount: 2 }],
|
||||
otherTeams: [{ id: "t2", name: "Team 2", memberCount: 3 }],
|
||||
});
|
||||
});
|
||||
test("throws ResourceNotFoundError if membership not found", async () => {
|
||||
vi.mocked(prisma.membership.findUnique).mockResolvedValueOnce(null);
|
||||
await expect(getTeams("u1", "org1")).rejects.toThrow(ResourceNotFoundError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createTeam", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
test("creates and returns team id", async () => {
|
||||
vi.mocked(prisma.team.findFirst).mockResolvedValueOnce(null);
|
||||
vi.mocked(prisma.team.create).mockResolvedValueOnce({
|
||||
id: "t1",
|
||||
name: "Team 1",
|
||||
organizationId: "org1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
const result = await createTeam("org1", "Team 1");
|
||||
expect(result).toBe("t1");
|
||||
expect(teamCache.revalidate).toHaveBeenCalledWith({ organizationId: "org1" });
|
||||
});
|
||||
test("throws InvalidInputError if team exists", async () => {
|
||||
vi.mocked(prisma.team.findFirst).mockResolvedValueOnce({ id: "t1" });
|
||||
await expect(createTeam("org1", "Team 1")).rejects.toThrow(InvalidInputError);
|
||||
});
|
||||
test("throws InvalidInputError if name too short", async () => {
|
||||
vi.mocked(prisma.team.findFirst).mockResolvedValueOnce(null);
|
||||
await expect(createTeam("org1", "")).rejects.toThrow(InvalidInputError);
|
||||
});
|
||||
test("throws DatabaseError on Prisma error", async () => {
|
||||
vi.mocked(prisma.team.findFirst).mockRejectedValueOnce(
|
||||
new Prisma.PrismaClientKnownRequestError("fail", { code: "P2002", clientVersion: "1.0.0" })
|
||||
);
|
||||
await expect(createTeam("org1", "Team 1")).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getTeamDetails", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
test("returns mapped team details", async () => {
|
||||
vi.mocked(prisma.team.findUnique).mockResolvedValueOnce(mockTeamDetails);
|
||||
const result = await getTeamDetails("t1");
|
||||
expect(result).toEqual({
|
||||
id: "t1",
|
||||
name: "Team 1",
|
||||
organizationId: "org1",
|
||||
members: [
|
||||
{ userId: "u1", name: "User 1", role: "admin" },
|
||||
{ userId: "u2", name: "User 2", role: "member" },
|
||||
],
|
||||
projects: [{ projectId: "p1", projectName: "Project 1", permission: "manage" }],
|
||||
});
|
||||
});
|
||||
test("returns null if team not found", async () => {
|
||||
vi.mocked(prisma.team.findUnique).mockResolvedValueOnce(null);
|
||||
const result = await getTeamDetails("t1");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
test("throws DatabaseError on Prisma error", async () => {
|
||||
vi.mocked(prisma.team.findUnique).mockRejectedValueOnce(
|
||||
new Prisma.PrismaClientKnownRequestError("fail", { code: "P2002", clientVersion: "1.0.0" })
|
||||
);
|
||||
await expect(getTeamDetails("t1")).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteTeam", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
test("deletes team and revalidates caches", async () => {
|
||||
const mockTeam = {
|
||||
id: "t1",
|
||||
organizationId: "org1",
|
||||
name: "Team 1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
projectTeams: [{ projectId: "p1" }],
|
||||
};
|
||||
vi.mocked(prisma.team.delete).mockResolvedValueOnce(mockTeam);
|
||||
const result = await deleteTeam("t1");
|
||||
expect(result).toBe(true);
|
||||
expect(teamCache.revalidate).toHaveBeenCalledWith({ id: "t1", organizationId: "org1" });
|
||||
expect(teamCache.revalidate).toHaveBeenCalledWith({ projectId: "p1" });
|
||||
});
|
||||
test("throws DatabaseError on Prisma error", async () => {
|
||||
vi.mocked(prisma.team.delete).mockRejectedValueOnce(
|
||||
new Prisma.PrismaClientKnownRequestError("fail", { code: "P2002", clientVersion: "1.0.0" })
|
||||
);
|
||||
await expect(deleteTeam("t1")).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateTeamDetails", () => {
|
||||
const data: TTeamSettingsFormSchema = {
|
||||
name: "Team 1 Updated",
|
||||
members: [{ userId: "u1", role: "admin" }],
|
||||
projects: [{ projectId: "p1", permission: "manage" }],
|
||||
};
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
test("updates team details and revalidates caches", async () => {
|
||||
vi.mocked(prisma.team.findUnique).mockResolvedValueOnce({
|
||||
id: "t1",
|
||||
organizationId: "org1",
|
||||
name: "Team 1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
vi.mocked(prisma.team.findUnique).mockResolvedValueOnce(mockTeamDetails);
|
||||
vi.mocked(prisma.team.findMany).mockResolvedValueOnce(mockUserTeams);
|
||||
|
||||
vi.mocked(prisma.membership.count).mockResolvedValueOnce(1);
|
||||
vi.mocked(prisma.project.count).mockResolvedValueOnce(1);
|
||||
vi.mocked(prisma.team.update).mockResolvedValueOnce({
|
||||
id: "t1",
|
||||
name: "Team 1 Updated",
|
||||
organizationId: "org1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
vi.mocked(prisma.environment.findMany).mockResolvedValueOnce([{ id: "env1" }]);
|
||||
const result = await updateTeamDetails("t1", data);
|
||||
expect(result).toBe(true);
|
||||
expect(teamCache.revalidate).toHaveBeenCalled();
|
||||
expect(projectCache.revalidate).toHaveBeenCalled();
|
||||
expect(organizationCache.revalidate).toHaveBeenCalledWith({ environmentId: "env1" });
|
||||
});
|
||||
test("throws ResourceNotFoundError if team not found", async () => {
|
||||
vi.mocked(prisma.team.findUnique).mockResolvedValueOnce(null);
|
||||
await expect(updateTeamDetails("t1", data)).rejects.toThrow(ResourceNotFoundError);
|
||||
});
|
||||
test("throws error if getTeamDetails returns null", async () => {
|
||||
vi.mocked(prisma.team.findUnique).mockResolvedValueOnce({
|
||||
id: "t1",
|
||||
organizationId: "org1",
|
||||
name: "Team 1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
vi.mocked(prisma.team.findUnique).mockResolvedValueOnce(null);
|
||||
await expect(updateTeamDetails("t1", data)).rejects.toThrow("Team not found");
|
||||
});
|
||||
test("throws error if user not in org membership", async () => {
|
||||
vi.mocked(prisma.team.findUnique).mockResolvedValueOnce({
|
||||
id: "t1",
|
||||
organizationId: "org1",
|
||||
name: "Team 1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
vi.mocked(prisma.team.findUnique).mockResolvedValueOnce({
|
||||
id: "t1",
|
||||
name: "Team 1",
|
||||
organizationId: "org1",
|
||||
members: [],
|
||||
projects: [],
|
||||
});
|
||||
vi.mocked(prisma.membership.count).mockResolvedValueOnce(0);
|
||||
await expect(updateTeamDetails("t1", data)).rejects.toThrow();
|
||||
});
|
||||
test("throws error if project not in org", async () => {
|
||||
vi.mocked(prisma.team.findUnique).mockResolvedValueOnce({
|
||||
id: "t1",
|
||||
organizationId: "org1",
|
||||
name: "Team 1",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
vi.mocked(prisma.team.findUnique).mockResolvedValueOnce({
|
||||
id: "t1",
|
||||
name: "Team 1",
|
||||
organizationId: "org1",
|
||||
members: [],
|
||||
projects: [],
|
||||
});
|
||||
vi.mocked(prisma.membership.count).mockResolvedValueOnce(1);
|
||||
vi.mocked(prisma.project.count).mockResolvedValueOnce(0);
|
||||
await expect(
|
||||
updateTeamDetails("t1", {
|
||||
name: "x",
|
||||
members: [],
|
||||
projects: [{ projectId: "p1", permission: "manage" }],
|
||||
})
|
||||
).rejects.toThrow();
|
||||
});
|
||||
test("throws DatabaseError on Prisma error", async () => {
|
||||
vi.mocked(prisma.team.findUnique).mockRejectedValueOnce(
|
||||
new Prisma.PrismaClientKnownRequestError("fail", { code: "P2002", clientVersion: "1.0.0" })
|
||||
);
|
||||
await expect(updateTeamDetails("t1", data)).rejects.toThrow(DatabaseError);
|
||||
});
|
||||
});
|
||||
@@ -57,7 +57,7 @@ export const getTeamsByOrganizationId = reactCache(
|
||||
)()
|
||||
);
|
||||
|
||||
const getUserTeams = reactCache(
|
||||
export const getUserTeams = reactCache(
|
||||
async (userId: string, organizationId: string): Promise<TUserTeam[]> =>
|
||||
cache(
|
||||
async () => {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { ProjectTeamPermission, TeamUserRole } from "@prisma/client";
|
||||
import { describe, expect, test } from "vitest";
|
||||
import { TeamPermissionMapping, TeamRoleMapping, getTeamAccessFlags, getTeamPermissionFlags } from "./teams";
|
||||
|
||||
describe("TeamPermissionMapping", () => {
|
||||
test("maps ProjectTeamPermission to correct labels", () => {
|
||||
expect(TeamPermissionMapping[ProjectTeamPermission.read]).toBe("Read");
|
||||
expect(TeamPermissionMapping[ProjectTeamPermission.readWrite]).toBe("Read & write");
|
||||
expect(TeamPermissionMapping[ProjectTeamPermission.manage]).toBe("Manage");
|
||||
});
|
||||
});
|
||||
|
||||
describe("TeamRoleMapping", () => {
|
||||
test("maps TeamUserRole to correct labels", () => {
|
||||
expect(TeamRoleMapping[TeamUserRole.admin]).toBe("Team Admin");
|
||||
expect(TeamRoleMapping[TeamUserRole.contributor]).toBe("Contributor");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getTeamAccessFlags", () => {
|
||||
test("returns correct flags for admin", () => {
|
||||
expect(getTeamAccessFlags(TeamUserRole.admin)).toEqual({ isAdmin: true, isContributor: false });
|
||||
});
|
||||
test("returns correct flags for contributor", () => {
|
||||
expect(getTeamAccessFlags(TeamUserRole.contributor)).toEqual({ isAdmin: false, isContributor: true });
|
||||
});
|
||||
test("returns false flags for undefined/null", () => {
|
||||
expect(getTeamAccessFlags()).toEqual({ isAdmin: false, isContributor: false });
|
||||
expect(getTeamAccessFlags(null)).toEqual({ isAdmin: false, isContributor: false });
|
||||
});
|
||||
});
|
||||
|
||||
describe("getTeamPermissionFlags", () => {
|
||||
test("returns correct flags for read", () => {
|
||||
expect(getTeamPermissionFlags(ProjectTeamPermission.read)).toEqual({
|
||||
hasReadAccess: true,
|
||||
hasReadWriteAccess: false,
|
||||
hasManageAccess: false,
|
||||
});
|
||||
});
|
||||
test("returns correct flags for readWrite", () => {
|
||||
expect(getTeamPermissionFlags(ProjectTeamPermission.readWrite)).toEqual({
|
||||
hasReadAccess: false,
|
||||
hasReadWriteAccess: true,
|
||||
hasManageAccess: false,
|
||||
});
|
||||
});
|
||||
test("returns correct flags for manage", () => {
|
||||
expect(getTeamPermissionFlags(ProjectTeamPermission.manage)).toEqual({
|
||||
hasReadAccess: false,
|
||||
hasReadWriteAccess: false,
|
||||
hasManageAccess: true,
|
||||
});
|
||||
});
|
||||
test("returns all false for undefined/null", () => {
|
||||
expect(getTeamPermissionFlags()).toEqual({
|
||||
hasReadAccess: false,
|
||||
hasReadWriteAccess: false,
|
||||
hasManageAccess: false,
|
||||
});
|
||||
expect(getTeamPermissionFlags(null)).toEqual({
|
||||
hasReadAccess: false,
|
||||
hasReadWriteAccess: false,
|
||||
hasManageAccess: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -126,6 +126,8 @@ export default defineConfig({
|
||||
"modules/survey/editor/components/file-upload-question-form.tsx",
|
||||
"modules/survey/editor/components/how-to-send-card.tsx",
|
||||
"modules/survey/editor/components/image-survey-bg.tsx",
|
||||
"modules/ee/teams/**/*.ts",
|
||||
"modules/ee/teams/**/*.tsx",
|
||||
"app/(app)/environments/**/*.tsx",
|
||||
"app/(app)/environments/**/*.ts",
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user