/** * @file AccountSettings.test.tsx * @description Settings → Account: shows the account's own information, saves * only a changed display name/email, refuses a password change whose two new * passwords differ, sends the current + new password, and asks before signing * the other devices out. * @author Nguyễn Ngọc Trí Vĩ */ import { beforeEach, describe, expect, it, vi } from "vitest"; import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; const me = vi.hoisted(() => ({ account: vi.fn(), updateAccount: vi.fn(), changePassword: vi.fn(), signOutOthers: vi.fn(), })); vi.mock("../../../lib/api", async (importOriginal) => { const actual = await importOriginal>(); return { ...actual, api: { me } }; }); const confirm = vi.hoisted(() => vi.fn()); vi.mock("../../../lib/confirm", () => ({ confirmDialog: confirm })); import { AccountSettings } from "../AccountSettings"; const account = { mode: "server" as const, user: { id: 2, username: "mia", role: "developer" as const, disabled: false, displayName: "Mia Tran", email: null, createdAt: "2026-10-01T00:00:00.000Z", }, workspaces: 3, sessions: 12, logins: 2, lastLoginAt: null, }; beforeEach(() => { for (const fn of Object.values(me)) fn.mockReset(); me.account.mockResolvedValue(account); me.updateAccount.mockResolvedValue(account); me.changePassword.mockResolvedValue({ ok: true }); me.signOutOthers.mockResolvedValue({ ended: 1 }); confirm.mockReset().mockResolvedValue(true); }); describe("AccountSettings", () => { it("shows the account's own information", async () => { render(); const card = await screen.findByTestId("account-card"); expect(card.textContent).toContain("Mia Tran"); expect(card.textContent).toContain("@mia"); expect(card.textContent).toContain("Developer"); expect(card.textContent).toContain("12"); }); it("saves only a changed field", async () => { const ue = userEvent.setup(); render(); await screen.findByTestId("account-card"); await ue.type(screen.getByLabelText("Email"), "mia@x.vn"); await ue.click(screen.getByRole("button", { name: "Save" })); await waitFor(() => expect(me.updateAccount).toHaveBeenCalledWith({ email: "mia@x.vn" })); }); it("refuses two different new passwords", async () => { const ue = userEvent.setup(); render(); await screen.findByTestId("account-card"); await ue.type(screen.getByLabelText("Current password"), "old-pass-1"); await ue.type(screen.getByLabelText("New password"), "new-pass-123"); await ue.type(screen.getByLabelText("Confirm new password"), "new-pass-999"); expect(screen.getByText("The two new passwords differ.")).toBeTruthy(); const button = screen.getByRole("button", { name: "Change password" }); expect(button.hasAttribute("disabled")).toBe(true); expect(me.changePassword).not.toHaveBeenCalled(); }); it("changes the password with the current one", async () => { const ue = userEvent.setup(); render(); await screen.findByTestId("account-card"); await ue.type(screen.getByLabelText("Current password"), "old-pass-1"); await ue.type(screen.getByLabelText("New password"), "new-pass-123"); await ue.type(screen.getByLabelText("Confirm new password"), "new-pass-123"); await ue.click(screen.getByRole("button", { name: "Change password" })); await waitFor(() => expect(me.changePassword).toHaveBeenCalledWith("old-pass-1", "new-pass-123") ); }); it("asks before signing the other devices out", async () => { const ue = userEvent.setup(); render(); await screen.findByTestId("account-card"); await ue.click(screen.getByRole("button", { name: "Sign out other devices" })); expect(confirm).toHaveBeenCalled(); await waitFor(() => expect(me.signOutOthers).toHaveBeenCalled()); }); });