From 73552dbb05bddb203249efee104b8ad60ccf8a35 Mon Sep 17 00:00:00 2001 From: Asis Ferrer Date: Mon, 15 Jun 2026 16:01:19 +0200 Subject: [PATCH] feat(i18n): localize admin users UI surfaces --- .../admin/users/[userId]/edit/page.tsx | 10 +- .../users/_components/edit.user.form.tsx | 41 ++-- .../admin/users/_components/new.user.form.tsx | 59 +++-- .../_components/reset.user.password.form.tsx | 22 +- .../admin/users/_components/user.copy.ts | 19 +- src/app/(dashboard)/admin/users/new/page.tsx | 10 +- src/app/(dashboard)/admin/users/page.tsx | 39 +++- src/i18n/dictionaries/en.ts | 1 + src/i18n/dictionaries/es.ts | 1 + tests/unit/app/users/user-form-pages.test.ts | 221 ++++++++++++++++++ tests/unit/app/users/user-pages.test.ts | 191 +++++++++++++++ tests/unit/app/users/user.copy.test.ts | 35 +++ .../unit/i18n/admin-users-dictionary.test.ts | 2 + 13 files changed, 593 insertions(+), 58 deletions(-) create mode 100644 tests/unit/app/users/user-form-pages.test.ts create mode 100644 tests/unit/app/users/user-pages.test.ts create mode 100644 tests/unit/app/users/user.copy.test.ts diff --git a/src/app/(dashboard)/admin/users/[userId]/edit/page.tsx b/src/app/(dashboard)/admin/users/[userId]/edit/page.tsx index 754f197..a9b0e41 100644 --- a/src/app/(dashboard)/admin/users/[userId]/edit/page.tsx +++ b/src/app/(dashboard)/admin/users/[userId]/edit/page.tsx @@ -14,6 +14,7 @@ export default async function EditUserPage({ const { userId } = await params const user = await getUserProfileById(userId) const { dictionary } = await getI18n() + const copy = dictionary.admin.users if (!user) { notFound() @@ -22,15 +23,20 @@ export default async function EditUserPage({ return (
-

Edit User

+

{copy.edit.title}

-

Reset password

+

{copy.resetPassword.title}

diff --git a/src/app/(dashboard)/admin/users/_components/edit.user.form.tsx b/src/app/(dashboard)/admin/users/_components/edit.user.form.tsx index f8b46df..86a21bf 100644 --- a/src/app/(dashboard)/admin/users/_components/edit.user.form.tsx +++ b/src/app/(dashboard)/admin/users/_components/edit.user.form.tsx @@ -2,6 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useRouter } from "next/navigation" +import { useMemo } from "react" import type { UseFormRegisterReturn } from "react-hook-form" import { useForm } from "react-hook-form" import { toast } from "sonner" @@ -11,26 +12,36 @@ import { type SubmitButtonCopy, } from "@/components/forms/submitButton" import { + buildUpdateUserSchema, type UpdateUserFormType, - updateUserSchema, + type UserSchemaCopy, } from "@/schemas/user.schema" import type { UserWithoutPassword } from "@/services/user.service" +import type { UserFormCopy, UserRoleCopy } from "./user.copy" + export default function EditUserForm({ + formCopy, + schemaCopy, + roleLabels, submitButtonCopy, user, }: { + formCopy: UserFormCopy + schemaCopy: UserSchemaCopy + roleLabels: UserRoleCopy submitButtonCopy: SubmitButtonCopy user: UserWithoutPassword }) { const router = useRouter() + const schema = useMemo(() => buildUpdateUserSchema(schemaCopy), [schemaCopy]) const { register, handleSubmit, setError, formState: { errors, isSubmitting, isSubmitSuccessful }, } = useForm({ - resolver: zodResolver(updateUserSchema), + resolver: zodResolver(schema), defaultValues: { id: user.id, name: user.name, @@ -69,50 +80,50 @@ export default function EditUserForm({
- Update User + {formCopy.updateSubmit} ) diff --git a/src/app/(dashboard)/admin/users/_components/new.user.form.tsx b/src/app/(dashboard)/admin/users/_components/new.user.form.tsx index b33b608..0c1d1b5 100644 --- a/src/app/(dashboard)/admin/users/_components/new.user.form.tsx +++ b/src/app/(dashboard)/admin/users/_components/new.user.form.tsx @@ -2,6 +2,7 @@ import { zodResolver } from "@hookform/resolvers/zod" import { useRouter } from "next/navigation" +import { useMemo } from "react" import type { UseFormRegisterReturn } from "react-hook-form" import { useForm } from "react-hook-form" import { toast } from "sonner" @@ -11,23 +12,33 @@ import { type SubmitButtonCopy, } from "@/components/forms/submitButton" import { + buildCreateUserSchema, type CreateUserFormType, - createUserSchema, + type UserSchemaCopy, } from "@/schemas/user.schema" +import type { UserFormCopy, UserRoleCopy } from "./user.copy" + export default function NewUserForm({ + formCopy, + schemaCopy, + roleLabels, submitButtonCopy, }: { + formCopy: UserFormCopy + schemaCopy: UserSchemaCopy + roleLabels: UserRoleCopy submitButtonCopy: SubmitButtonCopy }) { const router = useRouter() + const schema = useMemo(() => buildCreateUserSchema(schemaCopy), [schemaCopy]) const { register, handleSubmit, setError, formState: { errors, isSubmitting, isSubmitSuccessful }, } = useForm({ - resolver: zodResolver(createUserSchema), + resolver: zodResolver(schema), defaultValues: { role: "STAFF", isActive: true, @@ -61,40 +72,44 @@ export default function NewUserForm({ - + - Create User + {formCopy.createSubmit} ) @@ -132,21 +147,29 @@ function UserTextInput({ ) } -function RoleSelect({ register }: { register: UseFormRegisterReturn }) { +function RoleSelect({ + register, + roleLabel, + roleLabels, +}: { + register: UseFormRegisterReturn + roleLabel: string + roleLabels: UserRoleCopy +}) { return (
) diff --git a/src/app/(dashboard)/admin/users/_components/reset.user.password.form.tsx b/src/app/(dashboard)/admin/users/_components/reset.user.password.form.tsx index 7cd1050..ff90b5a 100644 --- a/src/app/(dashboard)/admin/users/_components/reset.user.password.form.tsx +++ b/src/app/(dashboard)/admin/users/_components/reset.user.password.form.tsx @@ -1,6 +1,7 @@ "use client" import { zodResolver } from "@hookform/resolvers/zod" +import { useMemo } from "react" import { useForm } from "react-hook-form" import { toast } from "sonner" import { resetUserPasswordAction } from "@/actions/user.actions" @@ -9,17 +10,28 @@ import { type SubmitButtonCopy, } from "@/components/forms/submitButton" import { + buildResetUserPasswordSchema, type ResetUserPasswordFormType, - resetUserPasswordSchema, + type UserSchemaCopy, } from "@/schemas/user.schema" +import type { UserResetPasswordCopy } from "./user.copy" + export default function ResetUserPasswordForm({ + formCopy, + schemaCopy, submitButtonCopy, userId, }: { + formCopy: UserResetPasswordCopy + schemaCopy: UserSchemaCopy submitButtonCopy: SubmitButtonCopy userId: string }) { + const schema = useMemo( + () => buildResetUserPasswordSchema(schemaCopy), + [schemaCopy], + ) const { register, handleSubmit, @@ -27,7 +39,7 @@ export default function ResetUserPasswordForm({ setError, formState: { errors, isSubmitting, isSubmitSuccessful }, } = useForm({ - resolver: zodResolver(resetUserPasswordSchema), + resolver: zodResolver(schema), defaultValues: { id: userId, }, @@ -60,12 +72,12 @@ export default function ResetUserPasswordForm({
@@ -78,7 +90,7 @@ export default function ResetUserPasswordForm({ isSubmitting={isSubmitting} isSubmitSuccessful={isSubmitSuccessful} > - Reset Password + {formCopy.submit} ) diff --git a/src/app/(dashboard)/admin/users/_components/user.copy.ts b/src/app/(dashboard)/admin/users/_components/user.copy.ts index 60e3e49..3b6fba5 100644 --- a/src/app/(dashboard)/admin/users/_components/user.copy.ts +++ b/src/app/(dashboard)/admin/users/_components/user.copy.ts @@ -1,11 +1,18 @@ import type { Dictionary } from "@/i18n/dictionaries" -export type UserListCopy = Dictionary["admin"]["users"]["list"] export type UserFormCopy = Dictionary["admin"]["users"]["form"] +export type UserRoleCopy = Dictionary["admin"]["users"]["roles"] +export type UserStatusCopy = Dictionary["admin"]["users"]["status"] +export type UserFallbackCopy = Dictionary["admin"]["users"]["fallback"] export type UserResetPasswordCopy = Dictionary["admin"]["users"]["resetPassword"] -export type UserRolesCopy = Dictionary["admin"]["users"]["roles"] -export type UserStatusCopy = Dictionary["admin"]["users"]["status"] -export type UserActionCopy = Dictionary["admin"]["users"]["actions"] -export type UserSchemaCopy = Dictionary["admin"]["users"]["schema"] -export type UserFallbackCopy = Dictionary["admin"]["users"]["fallback"] + +export function formatUserRole( + role: string, + roleCopy: UserRoleCopy, + fallbackCopy: UserFallbackCopy, +): string { + return role in roleCopy + ? roleCopy[role as keyof UserRoleCopy] + : fallbackCopy.unknownRole +} diff --git a/src/app/(dashboard)/admin/users/new/page.tsx b/src/app/(dashboard)/admin/users/new/page.tsx index d132df1..ea78dbe 100644 --- a/src/app/(dashboard)/admin/users/new/page.tsx +++ b/src/app/(dashboard)/admin/users/new/page.tsx @@ -4,13 +4,19 @@ import NewUserForm from "../_components/new.user.form" export default async function NewUserPage() { const { dictionary } = await getI18n() + const copy = dictionary.admin.users return (
-

New User

+

{copy.new.title}

- +
) } diff --git a/src/app/(dashboard)/admin/users/page.tsx b/src/app/(dashboard)/admin/users/page.tsx index d3b6143..135bce1 100644 --- a/src/app/(dashboard)/admin/users/page.tsx +++ b/src/app/(dashboard)/admin/users/page.tsx @@ -4,8 +4,16 @@ import Link from "next/link" import PageHeader from "@/components/common/pageheader" import PaginationButtons from "@/components/common/pagination" import { Button } from "@/components/ui/button" +import { getI18n } from "@/i18n/server" import { getUsers } from "@/services/user.service" +import { + formatUserRole, + type UserFallbackCopy, + type UserRoleCopy, + type UserStatusCopy, +} from "./_components/user.copy" + export default async function UsersPage(props: { searchParams?: Promise<{ page?: string @@ -20,11 +28,14 @@ export default async function UsersPage(props: { pageSize: 10, search, }) + const { dictionary } = await getI18n() + const copy = dictionary.admin.users return (
- No users found. + {copy.list.empty}
)} @@ -42,22 +53,22 @@ export default async function UsersPage(props: { - Name + {copy.list.columns.name} - Username + {copy.list.columns.username} - Email + {copy.list.columns.email} - Role + {copy.list.columns.role} - Status + {copy.list.columns.status} - Actions + {copy.list.columns.actions} @@ -67,9 +78,17 @@ export default async function UsersPage(props: { {user.name} {user.username} {user.email} - {user.role} - {user.isActive ? "Active" : "Inactive"} + {formatUserRole( + user.role, + copy.roles as UserRoleCopy, + copy.fallback as UserFallbackCopy, + )} + + + {user.isActive + ? (copy.status as UserStatusCopy).active + : (copy.status as UserStatusCopy).inactive} diff --git a/src/i18n/dictionaries/en.ts b/src/i18n/dictionaries/en.ts index 68643df..dcc5046 100644 --- a/src/i18n/dictionaries/en.ts +++ b/src/i18n/dictionaries/en.ts @@ -436,6 +436,7 @@ export const en = { users: { list: { title: "Users", + addLabel: "Add User", empty: "No users found.", columns: { name: "Name", diff --git a/src/i18n/dictionaries/es.ts b/src/i18n/dictionaries/es.ts index 4a3d768..cdfa4b7 100644 --- a/src/i18n/dictionaries/es.ts +++ b/src/i18n/dictionaries/es.ts @@ -441,6 +441,7 @@ export const es = { users: { list: { title: "Usuarios", + addLabel: "Agregar usuario", empty: "No se encontraron usuarios.", columns: { name: "Nombre", diff --git a/tests/unit/app/users/user-form-pages.test.ts b/tests/unit/app/users/user-form-pages.test.ts new file mode 100644 index 0000000..349aa9c --- /dev/null +++ b/tests/unit/app/users/user-form-pages.test.ts @@ -0,0 +1,221 @@ +import { renderToStaticMarkup } from "react-dom/server" +import { beforeEach, describe, expect, it, vi } from "vitest" + +import { en } from "@/i18n/dictionaries/en" +import { es } from "@/i18n/dictionaries/es" + +const mocks = vi.hoisted(() => ({ + createUserAction: vi.fn(), + updateUserAction: vi.fn(), + resetUserPasswordAction: vi.fn(), + getUserProfileById: vi.fn(), + getI18n: vi.fn(), + push: vi.fn(), + toastError: vi.fn(), + toastSuccess: vi.fn(), +})) + +vi.mock("@/i18n/server", () => ({ + getI18n: mocks.getI18n, +})) + +vi.mock("@/actions/user.actions", () => ({ + createUserAction: mocks.createUserAction, + updateUserAction: mocks.updateUserAction, + resetUserPasswordAction: mocks.resetUserPasswordAction, +})) + +vi.mock("@/services/user.service", () => ({ + getUserProfileById: mocks.getUserProfileById, +})) + +vi.mock("next/navigation", () => ({ + useRouter: () => ({ + push: mocks.push, + }), + notFound: () => { + throw new Error("NOT_FOUND") + }, +})) + +vi.mock("sonner", () => ({ + toast: { + error: mocks.toastError, + success: mocks.toastSuccess, + }, +})) + +describe("new user form localization", () => { + beforeEach(() => { + vi.clearAllMocks() + mocks.getI18n.mockResolvedValue({ dictionary: es, locale: "es" }) + }) + + it("passes server-resolved schema and role copy into the new user form boundary", async () => { + const { default: NewUserPage } = await import( + "@/app/(dashboard)/admin/users/new/page" + ) + + renderToStaticMarkup(await NewUserPage()) + + // The page must pass formCopy, schemaCopy, and roleLabels to the form + // We verify this by checking the form renders localized content + }) + + it("renders new user page with localized title and form labels in Spanish", async () => { + const { default: NewUserPage } = await import( + "@/app/(dashboard)/admin/users/new/page" + ) + + const html = renderToStaticMarkup(await NewUserPage()) + + // Title + expect(html).toContain("Nuevo usuario") + + // Form labels from dictionary + expect(html).toContain("Nombre") + expect(html).toContain("Usuario") + expect(html).toContain("Correo electrónico") + expect(html).toContain("Contraseña") + expect(html).toContain("Rol") + + // Placeholders from dictionary + expect(html).toContain("Nombre completo") + expect(html).toContain("Mínimo 8 caracteres") + + // Role labels (display) with canonical values + expect(html).toContain("Administrador") + expect(html).toContain("Gerente") + expect(html).toContain("Personal") + expect(html).toContain("Visor") + + // Submit button text + expect(html).toContain("Crear usuario") + }) + + it("renders new user page with English form labels in English locale", async () => { + mocks.getI18n.mockResolvedValue({ dictionary: en, locale: "en" }) + + const { default: NewUserPage } = await import( + "@/app/(dashboard)/admin/users/new/page" + ) + + const html = renderToStaticMarkup(await NewUserPage()) + + expect(html).toContain("New User") + expect(html).toContain("Full name") + expect(html).toContain("Username") + expect(html).toContain("Password") + expect(html).toContain("Minimum 8 characters") + expect(html).toContain("Create User") + expect(html).toContain("Admin") + expect(html).toContain("Manager") + expect(html).toContain("Staff") + expect(html).toContain("Viewer") + }) + + it("keeps canonical role values in option value attributes, not localized labels", async () => { + const { default: NewUserPage } = await import( + "@/app/(dashboard)/admin/users/new/page" + ) + + const html = renderToStaticMarkup(await NewUserPage()) + + // Canonical values must be in value attributes + expect(html).toContain('value="ADMIN"') + expect(html).toContain('value="MANAGER"') + expect(html).toContain('value="STAFF"') + expect(html).toContain('value="VIEWER"') + }) +}) + +describe("edit user form localization", () => { + beforeEach(() => { + vi.clearAllMocks() + mocks.getI18n.mockResolvedValue({ dictionary: es, locale: "es" }) + mocks.getUserProfileById.mockResolvedValue({ + id: "user-1", + name: "Ada Lovelace", + username: "ada", + email: "ada@example.test", + role: "ADMIN", + isActive: true, + }) + }) + + it("renders edit user page with localized title, form labels, and reset-password section in Spanish", async () => { + const { default: EditUserPage } = await import( + "@/app/(dashboard)/admin/users/[userId]/edit/page" + ) + + const html = renderToStaticMarkup( + await EditUserPage({ + params: Promise.resolve({ userId: "user-1" }), + }), + ) + + // Title + expect(html).toContain("Editar usuario") + + // Form labels + expect(html).toContain("Nombre") + expect(html).toContain("Nueva contraseña") + + // Role labels with canonical values + expect(html).toContain("Administrador") + expect(html).toContain('value="ADMIN"') + + // Active user checkbox label + expect(html).toContain("Usuario activo") + + // Submit button + expect(html).toContain("Actualizar usuario") + + // Reset password section + expect(html).toContain("Restablecer contraseña") + expect(html).toContain("Nueva contraseña") + expect(html).toContain("Mínimo 8 caracteres") + }) + + it("renders edit user page with English labels in English locale", async () => { + mocks.getI18n.mockResolvedValue({ dictionary: en, locale: "en" }) + + const { default: EditUserPage } = await import( + "@/app/(dashboard)/admin/users/[userId]/edit/page" + ) + + const html = renderToStaticMarkup( + await EditUserPage({ + params: Promise.resolve({ userId: "user-1" }), + }), + ) + + expect(html).toContain("Edit User") + expect(html).toContain("Active user") + expect(html).toContain("Update User") + expect(html).toContain("Reset password") + expect(html).toContain("New password") + expect(html).toContain("Reset Password") + }) + + it("renders edit user form with role option values as canonical enums regardless of locale", async () => { + const { default: EditUserPage } = await import( + "@/app/(dashboard)/admin/users/[userId]/edit/page" + ) + + const html = renderToStaticMarkup( + await EditUserPage({ + params: Promise.resolve({ userId: "user-1" }), + }), + ) + + // Canonical role values + expect(html).toContain('value="ADMIN"') + expect(html).toContain('value="MANAGER"') + expect(html).toContain('value="STAFF"') + expect(html).toContain('value="VIEWER"') + + // Spanish labels for roles + expect(html).toContain("Administrador") + }) +}) diff --git a/tests/unit/app/users/user-pages.test.ts b/tests/unit/app/users/user-pages.test.ts new file mode 100644 index 0000000..232e392 --- /dev/null +++ b/tests/unit/app/users/user-pages.test.ts @@ -0,0 +1,191 @@ +import { createElement } from "react" +import { renderToStaticMarkup } from "react-dom/server" +import { beforeEach, describe, expect, it, vi } from "vitest" + +import { en } from "@/i18n/dictionaries/en" +import { es } from "@/i18n/dictionaries/es" +import type { getUsers as _getUsers } from "@/services/user.service" + +type UserData = Awaited>["data"][number] + +const mocks = vi.hoisted(() => ({ + getUsers: vi.fn(), + getI18n: vi.fn(), +})) + +vi.mock("@/i18n/server", () => ({ + getI18n: mocks.getI18n, +})) + +vi.mock("@/services/user.service", () => ({ + getUsers: mocks.getUsers, +})) + +vi.mock("@/components/common/pageheader", () => ({ + default: ({ title, addLabel }: { title?: string; addLabel?: string }) => + createElement( + "header", + null, + [title, addLabel].filter(Boolean).join(" | "), + ), +})) + +vi.mock("@/components/common/pagination", () => ({ + default: ({ totalPages }: { totalPages: number }) => + createElement("nav", { "aria-label": "Pagination" }, totalPages), +})) + +describe("user pages localization", () => { + beforeEach(() => { + vi.clearAllMocks() + mocks.getI18n.mockResolvedValue({ dictionary: es, locale: "es" }) + }) + + it("renders the user list in Spanish with localized headers, status/role labels, and unchanged user data", async () => { + const { default: UsersPage } = await import( + "@/app/(dashboard)/admin/users/page" + ) + + mocks.getUsers.mockResolvedValue({ + data: [ + { + id: "user-1", + name: "Ada Lovelace", + username: "ada", + email: "ada@example.test", + role: "ADMIN", + isActive: true, + }, + { + id: "user-2", + name: "Grace Hopper", + username: "grace", + email: "grace@example.test", + role: "STAFF", + isActive: false, + }, + ], + totalPages: 1, + }) + + const html = renderToStaticMarkup( + await UsersPage({ searchParams: Promise.resolve({}) }), + ) + + // Title and add label + expect(html).toContain("Usuarios") + expect(html).toContain("Agregar usuario") + + // Table headers from dictionary + expect(html).toContain("Nombre") + expect(html).toContain("Usuario") + expect(html).toContain("Correo electrónico") + expect(html).toContain("Rol") + expect(html).toContain("Estado") + expect(html).toContain("Acciones") + + // Status labels from dictionary (display-only, not canonical) + expect(html).toContain("Activo") + expect(html).toContain("Inactivo") + + // Role labels from dictionary (display-only, not canonical) + expect(html).toContain("Administrador") + expect(html).toContain("Personal") + + // User data is never translated + expect(html).toContain("Ada Lovelace") + expect(html).toContain("ada") + expect(html).toContain("ada@example.test") + expect(html).toContain("Grace Hopper") + expect(html).toContain("grace") + expect(html).toContain("grace@example.test") + + // Canonical role values must NOT appear as display text + expect(html).not.toContain(">ADMIN<") + expect(html).not.toContain(">STAFF<") + }) + + it("renders the localized user empty state when no users exist", async () => { + const { default: UsersPage } = await import( + "@/app/(dashboard)/admin/users/page" + ) + + mocks.getUsers.mockResolvedValue({ + data: [], + totalPages: 0, + }) + + const html = renderToStaticMarkup( + await UsersPage({ searchParams: Promise.resolve({}) }), + ) + + expect(html).toContain("No se encontraron usuarios.") + }) + + it("renders the user list in English with English dictionary labels", async () => { + const { default: UsersPage } = await import( + "@/app/(dashboard)/admin/users/page" + ) + + mocks.getI18n.mockResolvedValue({ dictionary: en, locale: "en" }) + mocks.getUsers.mockResolvedValue({ + data: [ + { + id: "user-1", + name: "Ada Lovelace", + username: "ada", + email: "ada@example.test", + role: "MANAGER", + isActive: true, + }, + ], + totalPages: 1, + }) + + const html = renderToStaticMarkup( + await UsersPage({ searchParams: Promise.resolve({}) }), + ) + + // English dictionary labels + expect(html).toContain("Users") + expect(html).toContain("Add User") + expect(html).toContain("Name") + expect(html).toContain("Username") + expect(html).toContain("Email") + expect(html).toContain("Role") + expect(html).toContain("Status") + expect(html).toContain("Actions") + expect(html).toContain("Active") + expect(html).toContain("Manager") + + // Canonical enum value must NOT appear as display text + expect(html).not.toContain(">MANAGER<") + }) + + it("renders unknown role via fallback when role is not in dictionary", async () => { + const { default: UsersPage } = await import( + "@/app/(dashboard)/admin/users/page" + ) + + mocks.getUsers.mockResolvedValue({ + data: [ + { + id: "user-1", + name: "Test User", + username: "testuser", + email: "test@example.test", + role: "UNKNOWN_ROLE", + isActive: true, + } as unknown as UserData, + ], + totalPages: 1, + }) + + const html = renderToStaticMarkup( + await UsersPage({ searchParams: Promise.resolve({}) }), + ) + + // Unknown role should use fallback + expect(html).toContain("Rol desconocido") + }) +}) diff --git a/tests/unit/app/users/user.copy.test.ts b/tests/unit/app/users/user.copy.test.ts new file mode 100644 index 0000000..0fcbdd2 --- /dev/null +++ b/tests/unit/app/users/user.copy.test.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from "vitest" + +import { formatUserRole } from "@/app/(dashboard)/admin/users/_components/user.copy" + +describe("user copy helpers", () => { + const roleCopy = { + ADMIN: "Administrador", + MANAGER: "Gerente", + STAFF: "Personal", + VIEWER: "Visor", + } + + const fallbackCopy = { + unknownRole: "Rol desconocido", + } + + it("formats known role values with localized display labels", () => { + expect(formatUserRole("ADMIN", roleCopy, fallbackCopy)).toBe( + "Administrador", + ) + expect(formatUserRole("STAFF", roleCopy, fallbackCopy)).toBe("Personal") + }) + + it("falls back for unknown role values without exposing the raw enum value", () => { + expect(formatUserRole("UNKNOWN_ROLE", roleCopy, fallbackCopy)).toBe( + "Rol desconocido", + ) + }) + + it("falls back for null role values", () => { + expect( + formatUserRole(null as unknown as string, roleCopy, fallbackCopy), + ).toBe("Rol desconocido") + }) +}) diff --git a/tests/unit/i18n/admin-users-dictionary.test.ts b/tests/unit/i18n/admin-users-dictionary.test.ts index 676ff2d..6403a14 100644 --- a/tests/unit/i18n/admin-users-dictionary.test.ts +++ b/tests/unit/i18n/admin-users-dictionary.test.ts @@ -8,6 +8,7 @@ describe("admin users dictionary", () => { expect(users.list).toEqual({ title: "Users", + addLabel: "Add User", empty: "No users found.", columns: { name: "Name", @@ -95,6 +96,7 @@ describe("admin users dictionary", () => { expect(users.list).toEqual({ title: "Usuarios", + addLabel: "Agregar usuario", empty: "No se encontraron usuarios.", columns: { name: "Nombre",