Adds void to floating promise in bestiary-cache.ts, extracts shared polyfillDialog() helper to eliminate unbound-method warnings in 3 test files. Adds --deny warnings to oxlint so future warnings fail the build. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
166 lines
5.2 KiB
TypeScript
166 lines
5.2 KiB
TypeScript
// @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<Parameters<typeof CreatePlayerModal>[0]> = {},
|
|
) {
|
|
const defaults = {
|
|
open: true,
|
|
onClose: vi.fn(),
|
|
onSave: vi.fn(),
|
|
};
|
|
const props = { ...defaults, ...overrides };
|
|
return { ...render(<CreatePlayerModal {...props} />), ...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,
|
|
);
|
|
});
|
|
});
|