feat(i18n): localize recipients UI

This commit is contained in:
2026-06-14 18:33:57 +02:00
parent ea37fc8d70
commit c0ae7a034a
11 changed files with 665 additions and 34 deletions
@@ -0,0 +1,125 @@
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(() => ({
getI18n: vi.fn(),
findById: vi.fn(),
createNewRecipient: vi.fn(),
updateRecipient: vi.fn(),
push: vi.fn(),
toastError: vi.fn(),
toastSuccess: vi.fn(),
}))
vi.mock("@/i18n/server", () => ({
getI18n: mocks.getI18n,
}))
vi.mock("@/services/recipient.service", () => ({
RecipientService: {
findById: mocks.findById,
},
}))
vi.mock("@/actions/recipient.actions", () => ({
createNewRecipient: mocks.createNewRecipient,
updateRecipient: mocks.updateRecipient,
}))
vi.mock("next/navigation", () => ({
useRouter: () => ({
push: mocks.push,
}),
}))
vi.mock("sonner", () => ({
toast: {
error: mocks.toastError,
success: mocks.toastSuccess,
},
}))
describe("recipient form pages localization", () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.getI18n.mockResolvedValue({ dictionary: es, locale: "es" })
})
it("renders the new recipient page with localized form copy and canonical department option values", async () => {
const { default: NewRecipientPage } = await import(
"@/app/(dashboard)/recipients/new/page"
)
const html = renderToStaticMarkup(await NewRecipientPage())
expect(html).toContain("Agregar destinatario")
expect(html).toContain("Usuario")
expect(html).toContain('placeholder="Usuario"')
expect(html).toContain("Nombre")
expect(html).toContain("Apellido")
expect(html).toContain("Selecciona un departamento")
expect(html).toContain('option value="ENGINEERING"')
expect(html).toContain(">Ingeniería</option>")
expect(html).toContain("Correo electrónico")
expect(html).toContain("Teléfono")
expect(html).toContain("Crear destinatario")
})
it("renders the edit recipient page with localized heading and submit text", async () => {
const { default: RecipientEditPage } = await import(
"@/app/(dashboard)/recipients/[recipientId]/edit/page"
)
mocks.findById.mockResolvedValue({
id: "recipient-1",
username: "ada",
firstName: "Ada",
lastName: "Lovelace",
email: "ada@example.test",
phone: "1234",
department: "ENGINEERING",
})
const html = renderToStaticMarkup(
await RecipientEditPage({
params: Promise.resolve({ recipientId: "recipient-1" }),
}),
)
expect(html).toContain("Editar destinatario")
expect(html).toContain("Actualizar destinatario")
expect(html).toContain('placeholder="Correo electrónico"')
expect(html).toContain(">Ingeniería</option>")
})
it("renders a localized edit-page not-found message", async () => {
const { default: RecipientEditPage } = await import(
"@/app/(dashboard)/recipients/[recipientId]/edit/page"
)
mocks.findById.mockResolvedValue(null)
const html = renderToStaticMarkup(
await RecipientEditPage({
params: Promise.resolve({ recipientId: "missing-recipient" }),
}),
)
expect(html).toContain("Destinatario no encontrado")
})
it("wires English recipient form submit copy through the new page", async () => {
const { default: NewRecipientPage } = await import(
"@/app/(dashboard)/recipients/new/page"
)
mocks.getI18n.mockResolvedValueOnce({ dictionary: en, locale: "en" })
const html = renderToStaticMarkup(await NewRecipientPage())
expect(html).toContain("Create Recipient")
})
})
@@ -0,0 +1,165 @@
import { createElement } from "react"
import { renderToStaticMarkup } from "react-dom/server"
import { beforeEach, describe, expect, it, vi } from "vitest"
import { es } from "@/i18n/dictionaries/es"
const mocks = vi.hoisted(() => ({
findAllPaginated: vi.fn(),
findById: vi.fn(),
findAllByRecipient: vi.fn(),
getI18n: vi.fn(),
}))
vi.mock("@/i18n/server", () => ({
getI18n: mocks.getI18n,
}))
vi.mock("@/services/recipient.service", () => ({
RecipientService: {
findAllPaginated: mocks.findAllPaginated,
findById: mocks.findById,
},
}))
vi.mock("@/services/assignment.service", () => ({
AssignmentService: {
findAllByRecipient: mocks.findAllByRecipient,
},
}))
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("recipient pages localization", () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.getI18n.mockResolvedValue({ dictionary: es, locale: "es" })
})
it("renders the recipient list in Spanish while keeping stored department values display-only", async () => {
const { default: RecipientsPage } = await import(
"@/app/(dashboard)/recipients/page"
)
mocks.findAllPaginated.mockResolvedValue({
data: [
{
id: "recipient-1",
username: "ada",
firstName: "Ada",
lastName: "Lovelace",
email: "ada@example.test",
phone: "1234",
department: "ENGINEERING",
},
],
totalPages: 1,
})
const html = renderToStaticMarkup(
await RecipientsPage({ searchParams: Promise.resolve({}) }),
)
expect(html).toContain("Destinatarios")
expect(html).toContain("Agregar destinatario")
expect(html).toContain("Usuario")
expect(html).toContain("Nombre")
expect(html).toContain("Correo electrónico")
expect(html).toContain("Teléfono")
expect(html).toContain("Departamento")
expect(html).toContain("Acciones")
expect(html).toContain("Ada Lovelace")
expect(html).toContain("Ingeniería")
expect(html).toContain('aria-label="Ver destinatario"')
expect(html).toContain('aria-label="Editar destinatario"')
expect(html).not.toContain(">ENGINEERING<")
})
it("renders the localized recipient empty state when no recipients exist", async () => {
const { default: RecipientsPage } = await import(
"@/app/(dashboard)/recipients/page"
)
mocks.findAllPaginated.mockResolvedValue({
data: [],
totalPages: 0,
})
const html = renderToStaticMarkup(
await RecipientsPage({ searchParams: Promise.resolve({}) }),
)
expect(html).toContain("No se encontraron destinatarios.")
})
it("renders localized recipient-owned detail labels and keeps assignments copy unchanged", async () => {
const { default: RecipientInfoPage } = await import(
"@/app/(dashboard)/recipients/[recipientId]/page"
)
mocks.findById.mockResolvedValue({
id: "recipient-1",
username: "ada",
firstName: "Ada",
lastName: "Lovelace",
email: "ada@example.test",
phone: "1234",
department: "DRIVER",
})
mocks.findAllByRecipient.mockResolvedValue([
{
id: "assignment-1",
item: { name: "Laptop" },
asset: { serialNumber: "SN-001" },
quantity: 1,
},
])
const html = renderToStaticMarkup(
await RecipientInfoPage({
params: Promise.resolve({ recipientId: "recipient-1" }),
}),
)
expect(html).toContain("Usuario")
expect(html).toContain("Correo electrónico")
expect(html).toContain("Teléfono")
expect(html).toContain("Departamento")
expect(html).toContain("Chofer")
expect(html).toContain("ada")
expect(html).toContain("ada@example.test")
expect(html).toContain("Assignments")
expect(html).toContain("Laptop")
expect(html).not.toContain(">DRIVER<")
expect(html).not.toContain("Asignaciones")
})
it("renders a localized recipient detail not-found message", async () => {
const { default: RecipientInfoPage } = await import(
"@/app/(dashboard)/recipients/[recipientId]/page"
)
mocks.findById.mockResolvedValue(null)
mocks.findAllByRecipient.mockResolvedValue([])
const html = renderToStaticMarkup(
await RecipientInfoPage({
params: Promise.resolve({ recipientId: "missing-recipient" }),
}),
)
expect(html).toContain("Destinatario no encontrado")
})
})
+132
View File
@@ -559,6 +559,138 @@ describe("i18n dictionaries", () => {
})
})
it("provides localized recipient UI copy for English and Spanish", () => {
expect(getDictionary("en").inventory.recipients).toEqual({
list: {
title: "Recipients",
addLabel: "Add Recipient",
empty: "No recipients found.",
columns: {
username: "Username",
name: "Name",
email: "Email",
phone: "Phone",
department: "Department",
actions: "Actions",
},
actions: {
view: "View recipient",
edit: "Edit recipient",
},
},
detail: {
notFound: "Recipient not found",
labels: {
username: "Username",
email: "Email",
phone: "Phone",
department: "Department",
},
},
new: {
title: "Add Recipient",
},
edit: {
title: "Edit Recipient",
notFound: "Recipient not found",
},
form: {
usernameLabel: "Username",
usernamePlaceholder: "Username",
firstNameLabel: "First Name",
firstNamePlaceholder: "First name",
lastNameLabel: "Last Name",
lastNamePlaceholder: "Last name",
departmentLabel: "Department",
departmentPlaceholder: "Select a department",
emailLabel: "Email",
emailPlaceholder: "Email",
phoneLabel: "Phone",
phonePlaceholder: "Phone",
createSubmit: "Create Recipient",
updateSubmit: "Update Recipient",
},
fallback: {
unknownDepartment: "Unknown department",
},
departments: {
IT: "IT",
ENGINEERING: "Engineering",
LOGISTICS: "Logistics",
TRAFFIC: "Traffic",
DRIVER: "Driver",
ADMINISTRATION: "Administration",
SALES: "Sales",
OTHER: "Other",
},
})
expect(getDictionary("es").inventory.recipients).toEqual({
list: {
title: "Destinatarios",
addLabel: "Agregar destinatario",
empty: "No se encontraron destinatarios.",
columns: {
username: "Usuario",
name: "Nombre",
email: "Correo electrónico",
phone: "Teléfono",
department: "Departamento",
actions: "Acciones",
},
actions: {
view: "Ver destinatario",
edit: "Editar destinatario",
},
},
detail: {
notFound: "Destinatario no encontrado",
labels: {
username: "Usuario",
email: "Correo electrónico",
phone: "Teléfono",
department: "Departamento",
},
},
new: {
title: "Agregar destinatario",
},
edit: {
title: "Editar destinatario",
notFound: "Destinatario no encontrado",
},
form: {
usernameLabel: "Usuario",
usernamePlaceholder: "Usuario",
firstNameLabel: "Nombre",
firstNamePlaceholder: "Nombre",
lastNameLabel: "Apellido",
lastNamePlaceholder: "Apellido",
departmentLabel: "Departamento",
departmentPlaceholder: "Selecciona un departamento",
emailLabel: "Correo electrónico",
emailPlaceholder: "Correo electrónico",
phoneLabel: "Teléfono",
phonePlaceholder: "Teléfono",
createSubmit: "Crear destinatario",
updateSubmit: "Actualizar destinatario",
},
fallback: {
unknownDepartment: "Departamento desconocido",
},
departments: {
IT: "IT",
ENGINEERING: "Ingeniería",
LOGISTICS: "Logística",
TRAFFIC: "Tráfico",
DRIVER: "Chofer",
ADMINISTRATION: "Administración",
SALES: "Ventas",
OTHER: "Otro",
},
})
})
it("provides localized movement UI copy for English and Spanish", () => {
expect(getDictionary("en").inventory.movements).toEqual({
list: {