import { createElement } from "react" import { renderToStaticMarkup } from "react-dom/server" import { beforeEach, describe, expect, it, vi } from "vitest" import { en } from "@/i18n/dictionaries/en" const mocks = vi.hoisted(() => ({ findAllPaginated: vi.fn(), findById: vi.fn(), findByIdWithUser: vi.fn(), findAllByPerson: vi.fn(), getI18n: vi.fn(), })) vi.mock("@/i18n/server", () => ({ getI18n: mocks.getI18n, })) vi.mock("@/services/person.service", () => ({ PersonService: { findAllPaginated: mocks.findAllPaginated, findById: mocks.findById, findByIdWithUser: mocks.findByIdWithUser, }, })) vi.mock("@/services/assignment.service", () => ({ AssignmentService: { findAllByPerson: mocks.findAllByPerson, }, })) 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("person pages", () => { beforeEach(() => { vi.clearAllMocks() mocks.getI18n.mockResolvedValue({ dictionary: en, locale: "en" }) }) it("renders the person list page with Person data and no username column", async () => { const { default: PeoplePage } = await import( "@/app/(dashboard)/people/page" ) mocks.findAllPaginated.mockResolvedValue({ data: [ { id: "person-1", firstName: "Ada", lastName: "Lovelace", email: "ada@example.test", phone: "1234", department: "ENGINEERING", userId: null, isActive: true, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), user: null, }, ], totalPages: 1, }) const html = renderToStaticMarkup( await PeoplePage({ searchParams: Promise.resolve({}) }), ) // Uses Person copy (inventory.people), not Recipient copy expect(html).toContain("People") expect(html).toContain("Add Person") // No username column — username header must not appear expect(html).not.toContain("Username") // No standalone username cell — only name, email, phone, department columns expect(html).not.toContain(">ada<") // Name and other fields rendered expect(html).toContain("Ada Lovelace") expect(html).toContain("Engineering") // Links point to /people, not /recipients expect(html).toContain("/people/person-1") expect(html).toContain("/people/person-1/edit") }) it("renders role and status columns for people with linked users", async () => { const { default: PeoplePage } = await import( "@/app/(dashboard)/people/page" ) mocks.findAllPaginated.mockResolvedValue({ data: [ { id: "person-1", firstName: "Ada", lastName: "Lovelace", email: "ada@example.test", phone: "1234", department: "ENGINEERING", userId: "user-1", isActive: true, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), user: { id: "user-1", name: "Ada Lovelace", email: "ada@example.test", role: "ADMIN", isActive: true, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), password: "hashed", movements: [], assignments: [], person: null, }, }, { id: "person-2", firstName: "Bob", lastName: "Jones", email: "bob@example.test", phone: null, department: "IT", userId: null, isActive: true, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), user: null, }, ], totalPages: 1, }) const html = renderToStaticMarkup( await PeoplePage({ searchParams: Promise.resolve({}) }), ) // Column headers from inventory.people.list.columns expect(html).toContain("Role") expect(html).toContain("Status") // Person with linked user: role label + active label expect(html).toContain("Admin") expect(html).toContain("Active") // Person without user: no canonical enum leaks, just placeholder expect(html).not.toContain(">STAFF<") expect(html).not.toContain(">ADMIN<") }) it("renders the person list empty state from Person copy", async () => { const { default: PeoplePage } = await import( "@/app/(dashboard)/people/page" ) mocks.findAllPaginated.mockResolvedValue({ data: [], totalPages: 0, }) const html = renderToStaticMarkup( await PeoplePage({ searchParams: Promise.resolve({}) }), ) expect(html).toContain("No people found.") }) it("renders person detail page without username and uses PersonService + AssignmentService.findAllByPerson", async () => { const { default: PersonInfoPage } = await import( "@/app/(dashboard)/people/[personId]/page" ) mocks.findByIdWithUser.mockResolvedValue({ id: "person-1", firstName: "Ada", lastName: "Lovelace", email: "ada@example.test", phone: "1234", department: "DRIVER", userId: null, isActive: true, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), user: null, }) mocks.findAllByPerson.mockResolvedValue([ { id: "assignment-1", item: { name: "Laptop" }, asset: { serialNumber: "SN-001" }, quantity: 1, }, ]) const html = renderToStaticMarkup( await PersonInfoPage({ params: Promise.resolve({ personId: "person-1" }), }), ) // No username label or value expect(html).not.toContain("Username") expect(html).not.toContain(">ada<") // Person detail fields expect(html).toContain("Email") expect(html).toContain("Phone") expect(html).toContain("Department") expect(html).toContain("ada@example.test") expect(html).toContain("Driver") // Embedded assignments expect(html).toContain("Laptop") }) it("renders person detail User role and status when person has linked User", async () => { const { default: PersonInfoPage } = await import( "@/app/(dashboard)/people/[personId]/page" ) mocks.findByIdWithUser.mockResolvedValue({ id: "person-1", firstName: "Ada", lastName: "Lovelace", email: "ada@example.test", phone: "1234", department: "DRIVER", userId: "user-1", isActive: true, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), user: { id: "user-1", name: "Ada Lovelace", email: "ada@example.test", role: "ADMIN", isActive: true, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), password: "hashed", movements: [], assignments: [], person: null, }, }) mocks.findAllByPerson.mockResolvedValue([]) const html = renderToStaticMarkup( await PersonInfoPage({ params: Promise.resolve({ personId: "person-1" }), }), ) expect(html).toContain("Role") expect(html).toContain("Status") expect(html).toContain("Admin") expect(html).toContain("Active") // Canonical enum value must not leak into display expect(html).not.toContain(">ADMIN<") }) it("renders 'No user account' placeholder for person without linked User", async () => { const { default: PersonInfoPage } = await import( "@/app/(dashboard)/people/[personId]/page" ) mocks.findByIdWithUser.mockResolvedValue({ id: "person-1", firstName: "Ada", lastName: "Lovelace", email: "ada@example.test", phone: "1234", department: "DRIVER", userId: null, isActive: true, createdAt: new Date("2024-01-01"), updatedAt: new Date("2024-01-01"), user: null, }) mocks.findAllByPerson.mockResolvedValue([]) const html = renderToStaticMarkup( await PersonInfoPage({ params: Promise.resolve({ personId: "person-1" }), }), ) expect(html).toContain("No user account") }) it("renders person detail not-found from Person copy", async () => { const { default: PersonInfoPage } = await import( "@/app/(dashboard)/people/[personId]/page" ) mocks.findByIdWithUser.mockResolvedValue(null) mocks.findAllByPerson.mockResolvedValue([]) const html = renderToStaticMarkup( await PersonInfoPage({ params: Promise.resolve({ personId: "missing-person" }), }), ) expect(html).toContain("Person not found") }) })