mirror of
https://github.com/mosh-hamedani/helpdesk.git
synced 2026-05-21 11:58:19 +02:00
Write unit tests for adding new users
This commit is contained in:
parent
f8ff41ac01
commit
0a5adb26b2
174
client/src/pages/CreateUserForm.test.tsx
Normal file
174
client/src/pages/CreateUserForm.test.tsx
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
import { screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import axios from "axios";
|
||||
import { renderWithQuery } from "@/test/render";
|
||||
import CreateUserForm from "./CreateUserForm";
|
||||
|
||||
vi.mock("axios");
|
||||
const mockedAxios = vi.mocked(axios, { deep: true });
|
||||
|
||||
const onSuccess = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
function renderForm() {
|
||||
const user = userEvent.setup();
|
||||
renderWithQuery(<CreateUserForm onSuccess={onSuccess} />);
|
||||
return { user };
|
||||
}
|
||||
|
||||
async function fillForm(
|
||||
user: ReturnType<typeof userEvent.setup>,
|
||||
{ name = "John Doe", email = "john@example.com", password = "password123" } = {}
|
||||
) {
|
||||
await user.type(screen.getByLabelText("Name"), name);
|
||||
await user.type(screen.getByLabelText("Email"), email);
|
||||
await user.type(screen.getByLabelText("Password"), password);
|
||||
}
|
||||
|
||||
describe("CreateUserForm", () => {
|
||||
it("should render all form fields and submit button", () => {
|
||||
renderForm();
|
||||
|
||||
expect(screen.getByLabelText("Name")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("Email")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("Password")).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Create User" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should show validation error for short name", async () => {
|
||||
const { user } = renderForm();
|
||||
|
||||
await fillForm(user, { name: "Ab" });
|
||||
await user.click(screen.getByRole("button", { name: "Create User" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Name must be at least 3 characters")).toBeInTheDocument();
|
||||
});
|
||||
expect(mockedAxios.post).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should show validation error for short password", async () => {
|
||||
const { user } = renderForm();
|
||||
|
||||
await fillForm(user, { password: "short" });
|
||||
await user.click(screen.getByRole("button", { name: "Create User" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Password must be at least 8 characters")).toBeInTheDocument();
|
||||
});
|
||||
expect(mockedAxios.post).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should show validation error for missing email", async () => {
|
||||
const { user } = renderForm();
|
||||
|
||||
await user.type(screen.getByLabelText("Name"), "John Doe");
|
||||
await user.type(screen.getByLabelText("Password"), "password123");
|
||||
await user.click(screen.getByRole("button", { name: "Create User" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Invalid email address")).toBeInTheDocument();
|
||||
});
|
||||
expect(mockedAxios.post).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("should set aria-invalid on fields with errors", async () => {
|
||||
const { user } = renderForm();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: "Create User" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText("Name")).toHaveAttribute("aria-invalid", "true");
|
||||
expect(screen.getByLabelText("Password")).toHaveAttribute("aria-invalid", "true");
|
||||
});
|
||||
});
|
||||
|
||||
it("should call POST /api/users with form data on valid submit", async () => {
|
||||
mockedAxios.post.mockResolvedValue({ data: { user: { id: "1" } } });
|
||||
const { user } = renderForm();
|
||||
|
||||
await fillForm(user);
|
||||
await user.click(screen.getByRole("button", { name: "Create User" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockedAxios.post).toHaveBeenCalledWith("/api/users", {
|
||||
name: "John Doe",
|
||||
email: "john@example.com",
|
||||
password: "password123",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("should call onSuccess after successful creation", async () => {
|
||||
mockedAxios.post.mockResolvedValue({ data: { user: { id: "1" } } });
|
||||
const { user } = renderForm();
|
||||
|
||||
await fillForm(user);
|
||||
await user.click(screen.getByRole("button", { name: "Create User" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSuccess).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
it("should reset the form after successful creation", async () => {
|
||||
mockedAxios.post.mockResolvedValue({ data: { user: { id: "1" } } });
|
||||
const { user } = renderForm();
|
||||
|
||||
await fillForm(user);
|
||||
await user.click(screen.getByRole("button", { name: "Create User" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText("Name")).toHaveValue("");
|
||||
expect(screen.getByLabelText("Email")).toHaveValue("");
|
||||
expect(screen.getByLabelText("Password")).toHaveValue("");
|
||||
});
|
||||
});
|
||||
|
||||
it("should display server error message on 409 conflict", async () => {
|
||||
const error = new Error("Conflict");
|
||||
Object.assign(error, {
|
||||
response: { status: 409, data: { error: "Email already exists" } },
|
||||
});
|
||||
mockedAxios.post.mockRejectedValue(error);
|
||||
mockedAxios.isAxiosError.mockReturnValue(true);
|
||||
const { user } = renderForm();
|
||||
|
||||
await fillForm(user);
|
||||
await user.click(screen.getByRole("button", { name: "Create User" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Email already exists")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show generic error for non-Axios errors", async () => {
|
||||
mockedAxios.post.mockRejectedValue(new Error("Network failure"));
|
||||
mockedAxios.isAxiosError.mockReturnValue(false);
|
||||
const { user } = renderForm();
|
||||
|
||||
await fillForm(user);
|
||||
await user.click(screen.getByRole("button", { name: "Create User" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Failed to create user")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should show 'Creating...' and disable button while submitting", async () => {
|
||||
mockedAxios.post.mockReturnValue(new Promise(() => {}));
|
||||
const { user } = renderForm();
|
||||
|
||||
await fillForm(user);
|
||||
await user.click(screen.getByRole("button", { name: "Create User" }));
|
||||
|
||||
await waitFor(() => {
|
||||
const button = screen.getByRole("button", { name: "Creating..." });
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -52,6 +52,7 @@ export default function CreateUserForm({ onSuccess }: CreateUserFormProps) {
|
|||
<Input
|
||||
id="name"
|
||||
placeholder="Full name"
|
||||
aria-invalid={!!form.formState.errors.name}
|
||||
{...form.register("name")}
|
||||
/>
|
||||
{form.formState.errors.name && (
|
||||
|
|
@ -67,6 +68,7 @@ export default function CreateUserForm({ onSuccess }: CreateUserFormProps) {
|
|||
type="email"
|
||||
placeholder="user@example.com"
|
||||
autoComplete="off"
|
||||
aria-invalid={!!form.formState.errors.email}
|
||||
{...form.register("email")}
|
||||
/>
|
||||
{form.formState.errors.email && (
|
||||
|
|
@ -82,6 +84,7 @@ export default function CreateUserForm({ onSuccess }: CreateUserFormProps) {
|
|||
type="password"
|
||||
placeholder="Minimum 8 characters"
|
||||
autoComplete="new-password"
|
||||
aria-invalid={!!form.formState.errors.password}
|
||||
{...form.register("password")}
|
||||
/>
|
||||
{form.formState.errors.password && (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import axios from "axios";
|
||||
import { renderWithQuery } from "@/test/render";
|
||||
|
|
@ -96,4 +97,48 @@ describe("UsersPage", () => {
|
|||
expect(mockedAxios.get).toHaveBeenCalledWith("/api/users");
|
||||
});
|
||||
});
|
||||
|
||||
it("should open the create user dialog when clicking New User", async () => {
|
||||
mockedAxios.get.mockResolvedValue({ data: { users: [] } });
|
||||
const user = userEvent.setup();
|
||||
renderWithQuery(<UsersPage />);
|
||||
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /new user/i }));
|
||||
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
expect(screen.getByRole("heading", { name: "Create User" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should close the dialog when pressing Escape", async () => {
|
||||
mockedAxios.get.mockResolvedValue({ data: { users: [] } });
|
||||
const user = userEvent.setup();
|
||||
renderWithQuery(<UsersPage />);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /new user/i }));
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
|
||||
await user.keyboard("{Escape}");
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("should close the dialog when clicking the overlay", async () => {
|
||||
mockedAxios.get.mockResolvedValue({ data: { users: [] } });
|
||||
const user = userEvent.setup();
|
||||
renderWithQuery(<UsersPage />);
|
||||
|
||||
await user.click(screen.getByRole("button", { name: /new user/i }));
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
|
||||
const overlay = screen.getByRole("dialog").parentElement!.querySelector("[data-slot='dialog-overlay']");
|
||||
await user.click(overlay!);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue