// @vitest-environment jsdom import "@testing-library/jest-dom/vitest"; import { type PlayerCharacter, playerCharacterId } from "@initiative/domain"; import { cleanup, render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { polyfillDialog } from "../../__tests__/polyfill-dialog.js"; afterEach(cleanup); const CREATE_FIRST_PC_REGEX = /create your first player character/i; const LEVEL_REGEX = /^Lv /; import { PlayerManagement } from "../player-management.js"; beforeAll(() => { polyfillDialog(); }); const PC_WARRIOR: PlayerCharacter = { id: playerCharacterId("pc-1"), name: "Thorin", ac: 18, maxHp: 45, color: "red", icon: "sword", }; const PC_WIZARD: PlayerCharacter = { id: playerCharacterId("pc-2"), name: "Gandalf", ac: 12, maxHp: 30, color: "blue", icon: "wand", level: 10, }; function renderManagement( overrides: Partial[0]> = {}, ) { const props = { open: true, onClose: vi.fn(), characters: [] as readonly PlayerCharacter[], onEdit: vi.fn(), onDelete: vi.fn(), onCreate: vi.fn(), ...overrides, }; return { ...render(), props }; } describe("PlayerManagement", () => { it("shows empty state when no characters", () => { renderManagement(); expect(screen.getByText("No player characters yet")).toBeInTheDocument(); }); it("shows create button in empty state that calls onCreate", async () => { const user = userEvent.setup(); const { props } = renderManagement(); await user.click( screen.getByRole("button", { name: CREATE_FIRST_PC_REGEX, }), ); expect(props.onCreate).toHaveBeenCalled(); }); it("renders each character with name, AC, HP", () => { renderManagement({ characters: [PC_WARRIOR, PC_WIZARD] }); expect(screen.getByText("Thorin")).toBeInTheDocument(); expect(screen.getByText("Gandalf")).toBeInTheDocument(); expect(screen.getByText("AC 18")).toBeInTheDocument(); expect(screen.getByText("HP 45")).toBeInTheDocument(); expect(screen.getByText("AC 12")).toBeInTheDocument(); expect(screen.getByText("HP 30")).toBeInTheDocument(); }); it("shows level when present, omits when undefined", () => { renderManagement({ characters: [PC_WARRIOR, PC_WIZARD] }); expect(screen.getByText("Lv 10")).toBeInTheDocument(); // Thorin has no level — there should be only one "Lv" text expect(screen.queryAllByText(LEVEL_REGEX)).toHaveLength(1); }); it("edit button calls onEdit with the character", async () => { const user = userEvent.setup(); const { props } = renderManagement({ characters: [PC_WARRIOR] }); await user.click(screen.getByRole("button", { name: "Edit" })); expect(props.onEdit).toHaveBeenCalledWith(PC_WARRIOR); }); it("delete button calls onDelete after confirmation", async () => { const user = userEvent.setup(); const { props } = renderManagement({ characters: [PC_WARRIOR] }); const deleteBtn = screen.getByRole("button", { name: "Delete player character", }); await user.click(deleteBtn); const confirmBtn = screen.getByRole("button", { name: "Confirm delete player character", }); await user.click(confirmBtn); expect(props.onDelete).toHaveBeenCalledWith(PC_WARRIOR.id); }); it("add button calls onCreate", async () => { const user = userEvent.setup(); const { props } = renderManagement({ characters: [PC_WARRIOR] }); await user.click(screen.getByRole("button", { name: "Add" })); expect(props.onCreate).toHaveBeenCalled(); }); });