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>
178 lines
4.9 KiB
TypeScript
178 lines
4.9 KiB
TypeScript
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 <a href={href}>{children}</a>;
|
|
},
|
|
}));
|
|
|
|
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(<UsersSettingsPage />);
|
|
|
|
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(<UsersSettingsPage />);
|
|
|
|
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(<UsersSettingsPage />);
|
|
|
|
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();
|
|
});
|
|
});
|