diff --git a/src/app/(dashboard)/recipients/[recipientId]/edit/page.tsx b/src/app/(dashboard)/recipients/[recipientId]/edit/page.tsx index 81f76dd..d539580 100644 --- a/src/app/(dashboard)/recipients/[recipientId]/edit/page.tsx +++ b/src/app/(dashboard)/recipients/[recipientId]/edit/page.tsx @@ -9,21 +9,25 @@ export default async function RecipientEditPage({ params: Promise<{ recipientId: string }> }) { const { recipientId } = await params - const recipient = await RecipientService.findById(recipientId) const { dictionary } = await getI18n() + const copy = dictionary.inventory.recipients + const recipient = await RecipientService.findById(recipientId) if (!recipient) { - return
| - Username + {copy.list.columns.username} | - Name + {copy.list.columns.name} | - Email + {copy.list.columns.email} | - Phone + {copy.list.columns.phone} | - Department + {copy.list.columns.department} | - Actions + {copy.list.columns.actions} | {recipient.email} | {recipient.phone} | -{recipient.department} | ++ {formatRecipientDepartment( + recipient.department, + copy.departments, + copy.fallback, + )} + | - diff --git a/src/i18n/dictionaries/en.ts b/src/i18n/dictionaries/en.ts index d224073..09bc31b 100644 --- a/src/i18n/dictionaries/en.ts +++ b/src/i18n/dictionaries/en.ts @@ -255,6 +255,70 @@ export const en = { invalidUpdateStatus: "Invalid status", }, }, + recipients: { + 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", + }, + }, movements: { list: { title: "Movements", diff --git a/src/i18n/dictionaries/es.ts b/src/i18n/dictionaries/es.ts index d00eb41..d2fc1c2 100644 --- a/src/i18n/dictionaries/es.ts +++ b/src/i18n/dictionaries/es.ts @@ -259,6 +259,70 @@ export const es = { invalidUpdateStatus: "Estado inválido", }, }, + recipients: { + 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", + }, + }, movements: { list: { title: "Movimientos", diff --git a/tests/unit/app/recipients/recipient-form-pages.test.ts b/tests/unit/app/recipients/recipient-form-pages.test.ts new file mode 100644 index 0000000..f4dab57 --- /dev/null +++ b/tests/unit/app/recipients/recipient-form-pages.test.ts @@ -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") + 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") + }) + + 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") + }) +}) diff --git a/tests/unit/app/recipients/recipient-pages.test.ts b/tests/unit/app/recipients/recipient-pages.test.ts new file mode 100644 index 0000000..a0bf867 --- /dev/null +++ b/tests/unit/app/recipients/recipient-pages.test.ts @@ -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") + }) +}) diff --git a/tests/unit/i18n/dictionaries.test.ts b/tests/unit/i18n/dictionaries.test.ts index 391cd58..0cfe626 100644 --- a/tests/unit/i18n/dictionaries.test.ts +++ b/tests/unit/i18n/dictionaries.test.ts @@ -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: { |
|---|