import type { ReactElement, ReactNode } from "react"; 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 { deactivateUser, fetchAdminUsers, inviteUser, updateUser, type AdminUsersResponse, } from "@/lib/api/admin"; import { fetchUserWorkspaces, updateWorkspaceMemberRole } from "@/lib/api/workspaces"; import UsersSettingsPage from "./page"; vi.mock("next/link", () => ({ default: function LinkMock({ children, href, }: { children: ReactNode; href: string; }): ReactElement { return {children}; }, })); vi.mock("@/lib/api/admin", () => ({ fetchAdminUsers: vi.fn(), inviteUser: vi.fn(), updateUser: vi.fn(), deactivateUser: vi.fn(), })); vi.mock("@/lib/api/workspaces", () => ({ fetchUserWorkspaces: vi.fn(), updateWorkspaceMemberRole: vi.fn(), })); const fetchAdminUsersMock = vi.mocked(fetchAdminUsers); const inviteUserMock = vi.mocked(inviteUser); const updateUserMock = vi.mocked(updateUser); const deactivateUserMock = vi.mocked(deactivateUser); const fetchUserWorkspacesMock = vi.mocked(fetchUserWorkspaces); const updateWorkspaceMemberRoleMock = vi.mocked(updateWorkspaceMemberRole); const adminUsersResponse: AdminUsersResponse = { data: [ { id: "user-1", name: "Alice", email: "alice@example.com", emailVerified: true, image: null, createdAt: "2026-01-01T00:00:00.000Z", deactivatedAt: null, isLocalAuth: false, invitedAt: null, invitedBy: null, workspaceMemberships: [ { workspaceId: "workspace-1", workspaceName: "Personal Workspace", role: WorkspaceMemberRole.ADMIN, joinedAt: "2026-01-01T00:00:00.000Z", }, ], }, ], meta: { total: 1, page: 1, limit: 50, totalPages: 1, }, }; describe("UsersSettingsPage", () => { beforeEach(() => { vi.clearAllMocks(); fetchAdminUsersMock.mockResolvedValue(adminUsersResponse); fetchUserWorkspacesMock.mockResolvedValue([ { id: "workspace-1", name: "Personal Workspace", ownerId: "owner-1", role: WorkspaceMemberRole.OWNER, createdAt: "2026-01-01T00:00:00.000Z", }, ]); inviteUserMock.mockResolvedValue({ userId: "user-2", invitationToken: "token-1", email: "new@example.com", invitedAt: "2026-01-02T00:00:00.000Z", }); const firstUser = adminUsersResponse.data[0]; if (!firstUser) { throw new Error("Expected at least one admin user in test fixtures"); } updateUserMock.mockResolvedValue(firstUser); deactivateUserMock.mockResolvedValue(firstUser); updateWorkspaceMemberRoleMock.mockResolvedValue({ workspaceId: "workspace-1", userId: "user-1", role: WorkspaceMemberRole.ADMIN, joinedAt: "2026-01-01T00:00:00.000Z", user: { id: "user-1", email: "alice@example.com", name: "Alice", image: null, }, }); }); 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(fetchAdminUsersMock).not.toHaveBeenCalled(); }); it("invites a user with email and role from the dialog", async () => { const user = userEvent.setup(); render(); expect(await screen.findByText("User Directory")).toBeInTheDocument(); await user.click(screen.getByRole("button", { name: "Invite User" })); await user.type(screen.getByLabelText("Email"), "new@example.com"); await user.click(screen.getByRole("button", { name: "Send Invite" })); await waitFor(() => { expect(inviteUserMock).toHaveBeenCalledWith({ email: "new@example.com", role: WorkspaceMemberRole.MEMBER, workspaceId: "workspace-1", }); }); }); it("opens user detail dialog from row click and saves edited profile fields", async () => { const user = userEvent.setup(); render(); expect(await screen.findByText("alice@example.com")).toBeInTheDocument(); await user.click(screen.getByText("Alice")); const nameInput = await screen.findByLabelText("Name"); await user.clear(nameInput); await user.type(nameInput, "Alice Updated"); await user.click(screen.getByRole("button", { name: "Save Changes" })); await waitFor(() => { expect(updateUserMock).toHaveBeenCalledWith("user-1", { name: "Alice Updated" }); }); expect(updateWorkspaceMemberRoleMock).not.toHaveBeenCalled(); }); });