import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, waitFor, within } from "@testing-library/react"; import { PetProfileCard } from "../components/PetProfileCard.js"; const FULL_SUMMARY = { id: "pet-1", name: "Buddy", breed: "Golden Retriever", dateOfBirth: "2022-03-15", weightKg: 30.5, coatType: "double", temperamentScore: 4, temperamentFlags: ["anxious", "friendly"], medicalAlerts: [ { id: "a1", type: "allergy", description: "Chicken allergy", severity: "high" }, { id: "a2", type: "condition", description: "Hip dysplasia", severity: "medium" }, ], preferredCuts: ["teddy bear", "puppy cut"], recentVisits: [ { id: "v1", petId: "pet-1", appointmentId: "appt-1", staffId: "staff-1", cutStyle: "teddy bear", productsUsed: "oat shampoo", notes: "Good boy", groomedAt: "2025-05-01T10:00:00Z", createdAt: "2025-05-01T10:00:00Z" }, { id: "v2", petId: "pet-1", appointmentId: "appt-2", staffId: "staff-2", cutStyle: "puppy cut", productsUsed: null, notes: null, groomedAt: "2025-04-01T10:00:00Z", createdAt: "2025-04-01T10:00:00Z" }, ], nextAppointment: { id: "appt-3", startTime: "2025-06-01T09:00:00Z", serviceName: "Full groom" }, }; const EMPTY_SUMMARY = { id: "pet-2", name: "Whiskers", breed: null, dateOfBirth: null, weightKg: null, coatType: null, temperamentScore: null, temperamentFlags: [], medicalAlerts: [], preferredCuts: [], recentVisits: [], nextAppointment: null, }; beforeEach(() => { vi.restoreAllMocks(); }); describe("PetProfileCard", () => { it("shows loading skeleton while fetching", () => { global.fetch = vi.fn(() => new Promise(() => {})) as unknown as typeof fetch; render(); expect(screen.getByText(/loading/i)).toBeInTheDocument(); }); it("renders full profile data correctly", async () => { global.fetch = vi.fn(() => Promise.resolve({ ok: true, status: 200, json: async () => FULL_SUMMARY, } as Response) ) as unknown as typeof fetch; render(); await waitFor(() => { expect(screen.getByText("Buddy")).toBeInTheDocument(); }); expect(screen.getByText("Golden Retriever")).toBeInTheDocument(); // age computed from DOB expect(screen.getByText(/yr/)).toBeInTheDocument(); // weight expect(screen.getByText(/30.5 kg/)).toBeInTheDocument(); // coat type badge expect(screen.getByText("double")).toBeInTheDocument(); // medical alerts expect(screen.getByText("Chicken allergy")).toBeInTheDocument(); expect(screen.getByText("Hip dysplasia")).toBeInTheDocument(); // preferred cuts expect(screen.getByText("teddy bear")).toBeInTheDocument(); }); it("displays severity-colored badges for medical alerts", async () => { global.fetch = vi.fn(() => Promise.resolve({ ok: true, status: 200, json: async () => FULL_SUMMARY, } as Response) ) as unknown as typeof fetch; render(); await waitFor(() => { expect(screen.getByText("Chicken allergy")).toBeInTheDocument(); }); const highAlert = screen.getByText("Chicken allergy").closest("span"); expect(highAlert).toHaveStyle({ color: "#dc2626" }); // high = red const mediumAlert = screen.getByText("Hip dysplasia").closest("span"); expect(mediumAlert).toHaveStyle({ color: "#d97706" }); // medium = amber }); it("handles empty pet data gracefully", async () => { global.fetch = vi.fn(() => Promise.resolve({ ok: true, status: 200, json: async () => EMPTY_SUMMARY, } as Response) ) as unknown as typeof fetch; render(); await waitFor(() => { expect(screen.getByText("Whiskers")).toBeInTheDocument(); }); // no section labels with no data expect(screen.queryByText(/medical alerts/i)).not.toBeInTheDocument(); expect(screen.queryByText(/preferred cuts/i)).not.toBeInTheDocument(); expect(screen.queryByText(/recent visits/i)).not.toBeInTheDocument(); }); it("shows error message on fetch failure", async () => { global.fetch = vi.fn(() => Promise.reject(new Error("network error"))) as unknown as typeof fetch; render(); await waitFor(() => { expect(screen.getByText(/failed to load/i)).toBeInTheDocument(); }); }); it("refetches when petId changes", async () => { const fetchMock = vi.fn((url: string) => { if ((url as string).includes("pet-1")) { return Promise.resolve({ ok: true, status: 200, json: async () => ({ ...FULL_SUMMARY, name: "Buddy" }), } as Response); } return Promise.resolve({ ok: true, status: 200, json: async () => ({ ...EMPTY_SUMMARY, name: "Whiskers" }), } as Response); }) as unknown as typeof fetch; global.fetch = fetchMock; const { rerender } = render(); await waitFor(() => expect(screen.getByText("Buddy")).toBeInTheDocument()); expect(fetchMock).toHaveBeenCalledWith("/api/pets/pet-1/profile-summary"); rerender(); await waitFor(() => expect(screen.getByText("Whiskers")).toBeInTheDocument()); expect(fetchMock).toHaveBeenCalledWith("/api/pets/pet-2/profile-summary"); }); });