// @vitest-environment jsdom import type { PlayerCharacter } from "@initiative/domain"; import { 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"; import { CreatePlayerModal } from "../create-player-modal.js"; beforeAll(() => { polyfillDialog(); }); afterEach(cleanup); function renderModal( overrides: Partial[0]> = {}, ) { const defaults = { open: true, onClose: vi.fn(), onSave: vi.fn(), }; const props = { ...defaults, ...overrides }; return { ...render(), ...props }; } describe("CreatePlayerModal", () => { it("renders create form with defaults", () => { renderModal(); expect(screen.getByText("Create Player")).toBeDefined(); expect(screen.getByLabelText("Name")).toBeDefined(); expect(screen.getByLabelText("AC")).toBeDefined(); expect(screen.getByLabelText("Max HP")).toBeDefined(); expect(screen.getByLabelText("Level")).toBeDefined(); expect(screen.getByRole("button", { name: "Create" })).toBeDefined(); }); it("renders edit form when playerCharacter is provided", () => { const pc: PlayerCharacter = { id: playerCharacterId("pc-1"), name: "Gandalf", ac: 15, maxHp: 40, color: "blue", icon: "wand", level: 10, }; renderModal({ playerCharacter: pc }); expect(screen.getByText("Edit Player")).toBeDefined(); expect(screen.getByLabelText("Name")).toHaveProperty("value", "Gandalf"); expect(screen.getByLabelText("AC")).toHaveProperty("value", "15"); expect(screen.getByLabelText("Max HP")).toHaveProperty("value", "40"); expect(screen.getByLabelText("Level")).toHaveProperty("value", "10"); expect(screen.getByRole("button", { name: "Save" })).toBeDefined(); }); it("calls onSave with valid data", async () => { const { onSave, onClose } = renderModal(); const user = userEvent.setup(); await user.type(screen.getByLabelText("Name"), "Aria"); await user.clear(screen.getByLabelText("AC")); await user.type(screen.getByLabelText("AC"), "16"); await user.clear(screen.getByLabelText("Max HP")); await user.type(screen.getByLabelText("Max HP"), "30"); await user.type(screen.getByLabelText("Level"), "5"); await user.click(screen.getByRole("button", { name: "Create" })); expect(onSave).toHaveBeenCalledWith( "Aria", 16, 30, undefined, undefined, 5, ); expect(onClose).toHaveBeenCalled(); }); it("shows error for empty name", async () => { const { onSave } = renderModal(); const user = userEvent.setup(); await user.click(screen.getByRole("button", { name: "Create" })); expect(screen.getByText("Name is required")).toBeDefined(); expect(onSave).not.toHaveBeenCalled(); }); it("shows error for invalid AC", async () => { const { onSave } = renderModal(); const user = userEvent.setup(); await user.type(screen.getByLabelText("Name"), "Test"); await user.clear(screen.getByLabelText("AC")); await user.type(screen.getByLabelText("AC"), "abc"); await user.click(screen.getByRole("button", { name: "Create" })); expect(screen.getByText("AC must be a non-negative number")).toBeDefined(); expect(onSave).not.toHaveBeenCalled(); }); it("shows error for invalid Max HP", async () => { const { onSave } = renderModal(); const user = userEvent.setup(); await user.type(screen.getByLabelText("Name"), "Test"); await user.clear(screen.getByLabelText("Max HP")); await user.type(screen.getByLabelText("Max HP"), "0"); await user.click(screen.getByRole("button", { name: "Create" })); expect(screen.getByText("Max HP must be at least 1")).toBeDefined(); expect(onSave).not.toHaveBeenCalled(); }); it("shows error for invalid level", async () => { const { onSave } = renderModal(); const user = userEvent.setup(); await user.type(screen.getByLabelText("Name"), "Test"); await user.type(screen.getByLabelText("Level"), "25"); await user.click(screen.getByRole("button", { name: "Create" })); expect(screen.getByText("Level must be between 1 and 20")).toBeDefined(); expect(onSave).not.toHaveBeenCalled(); }); it("clears error when name is edited", async () => { renderModal(); const user = userEvent.setup(); await user.click(screen.getByRole("button", { name: "Create" })); expect(screen.getByText("Name is required")).toBeDefined(); await user.type(screen.getByLabelText("Name"), "A"); expect(screen.queryByText("Name is required")).toBeNull(); }); it("calls onClose when cancel is clicked", async () => { const { onClose } = renderModal(); const user = userEvent.setup(); await user.click(screen.getByRole("button", { name: "Cancel" })); expect(onClose).toHaveBeenCalledOnce(); }); it("omits level when field is empty", async () => { const { onSave } = renderModal(); const user = userEvent.setup(); await user.type(screen.getByLabelText("Name"), "Aria"); await user.click(screen.getByRole("button", { name: "Create" })); expect(onSave).toHaveBeenCalledWith( "Aria", 10, 10, undefined, undefined, undefined, ); }); });