mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 05:43:12 +00:00
144 lines
5.3 KiB
TypeScript
144 lines
5.3 KiB
TypeScript
import { screen, waitFor, within } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { toast } from "react-toastify";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import MyAccountPage from "@/app/(protected)/account";
|
|
import * as authApi from "@/domain/auth/api/auth-api";
|
|
import type { AuthUser } from "@/domain/auth/types/auth-user";
|
|
import { createAppQueryClient } from "@/lib/query/query-client";
|
|
import { renderWithProviders } from "@/test/test-utils";
|
|
|
|
const { toastSuccessMock } = vi.hoisted(() => ({ toastSuccessMock: vi.fn() }));
|
|
|
|
const SIGNED_IN_USER: AuthUser = {
|
|
token: "test-jwt-token",
|
|
expiration: new Date(Date.now() + 60 * 60 * 1000).toISOString(),
|
|
email: "dana@seahavenind.com",
|
|
userRoles: "Dispatcher",
|
|
phoneNumber: "5551234567",
|
|
fullname: "Dana Reyes",
|
|
id: "user-1",
|
|
};
|
|
|
|
vi.mock("@/domain/auth/api/auth-api", () => ({
|
|
getSession: vi.fn(),
|
|
login: vi.fn(),
|
|
logout: vi.fn(),
|
|
changePassword: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("react-toastify", () => ({
|
|
toast: { success: toastSuccessMock, error: vi.fn(), info: vi.fn() },
|
|
}));
|
|
|
|
/** Uses the app's query client, so the global mutation error toast is live. */
|
|
function renderPage() {
|
|
return renderWithProviders(<MyAccountPage />, {
|
|
route: "/account",
|
|
queryClient: createAppQueryClient(),
|
|
});
|
|
}
|
|
|
|
function field(label: RegExp) {
|
|
return screen.getByLabelText(label, { selector: "input" });
|
|
}
|
|
|
|
async function fillPasswords(
|
|
user: ReturnType<typeof userEvent.setup>,
|
|
{ current, next, confirm }: { current: string; next: string; confirm: string },
|
|
) {
|
|
await user.type(field(/^current password/i), current);
|
|
await user.type(field(/^new password/i), next);
|
|
await user.type(field(/^confirm new password/i), confirm);
|
|
}
|
|
|
|
describe("My Account page", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(authApi.getSession).mockResolvedValue(SIGNED_IN_USER);
|
|
});
|
|
|
|
it("shows the signed-in user's name, role and email as read-only details", async () => {
|
|
renderPage();
|
|
|
|
const details = await screen.findByRole("region", { name: "Account details" });
|
|
expect(within(details).getByText("Dana Reyes")).toBeInTheDocument();
|
|
expect(within(details).getByText("Dispatcher")).toBeInTheDocument();
|
|
expect(within(details).getByText("dana@seahavenind.com")).toBeInTheDocument();
|
|
expect(within(details).queryByRole("textbox")).not.toBeInTheDocument();
|
|
});
|
|
|
|
it("shows the live checklist for the new password", async () => {
|
|
const user = userEvent.setup();
|
|
renderPage();
|
|
|
|
const checklist = screen.getByRole("list", { name: "Password requirements" });
|
|
expect(within(checklist).getAllByText("requirement not met")).toHaveLength(4);
|
|
|
|
await user.type(field(/^new password/i), "Abc1!x");
|
|
|
|
expect(within(checklist).getAllByText("requirement met")).toHaveLength(4);
|
|
});
|
|
|
|
it("keeps Update password disabled until every rule is met and the confirmation matches", async () => {
|
|
const user = userEvent.setup();
|
|
renderPage();
|
|
const submit = screen.getByRole("button", { name: "Update password" });
|
|
|
|
await fillPasswords(user, { current: "Current1!", next: "abcdef", confirm: "abcdef" });
|
|
expect(submit).toBeDisabled();
|
|
|
|
await user.clear(field(/^new password/i));
|
|
await user.type(field(/^new password/i), "Abc1!x");
|
|
expect(submit).toBeDisabled();
|
|
|
|
await user.clear(field(/^confirm new password/i));
|
|
await user.type(field(/^confirm new password/i), "Abc1!x");
|
|
expect(submit).toBeEnabled();
|
|
expect(authApi.changePassword).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("tells the user when the confirmation does not match", async () => {
|
|
const user = userEvent.setup();
|
|
renderPage();
|
|
|
|
await fillPasswords(user, { current: "Current1!", next: "Abc1!x", confirm: "Abc1!y" });
|
|
await user.tab();
|
|
|
|
expect(await screen.findByText("Passwords don't match")).toBeInTheDocument();
|
|
});
|
|
|
|
it("sends the current and new password and clears the form after a successful change", async () => {
|
|
vi.mocked(authApi.changePassword).mockResolvedValue({ status: "Success " });
|
|
const user = userEvent.setup();
|
|
renderPage();
|
|
|
|
await fillPasswords(user, { current: "Current1!", next: "Abc1!x", confirm: "Abc1!x" });
|
|
await user.click(screen.getByRole("button", { name: "Update password" }));
|
|
|
|
await waitFor(() =>
|
|
expect(authApi.changePassword).toHaveBeenCalledWith({
|
|
Currentpassword: "Current1!",
|
|
Newpassword: "Abc1!x",
|
|
Confirmpassword: "Abc1!x",
|
|
}),
|
|
);
|
|
await waitFor(() => expect(field(/^current password/i)).toHaveValue(""));
|
|
expect(toastSuccessMock).toHaveBeenCalledWith("Password updated");
|
|
});
|
|
|
|
it("shows the server's current-password rejection on the current password field", async () => {
|
|
vi.mocked(authApi.changePassword).mockRejectedValue(new Error("Current password is incorrect"));
|
|
const user = userEvent.setup();
|
|
renderPage();
|
|
|
|
await fillPasswords(user, { current: "Wrong1!", next: "Abc1!x", confirm: "Abc1!x" });
|
|
await user.click(screen.getByRole("button", { name: "Update password" }));
|
|
|
|
expect(await screen.findByText("Current password is incorrect")).toBeInTheDocument();
|
|
expect(field(/^current password/i)).toHaveAttribute("aria-invalid", "true");
|
|
expect(toastSuccessMock).not.toHaveBeenCalled();
|
|
expect(vi.mocked(toast.error)).not.toHaveBeenCalled();
|
|
});
|
|
});
|