Add missing component and hook tests, raise coverage thresholds

13 new test files for untested components (color-palette, player-management,
stat-block, settings-modal, export/import dialogs, bulk-import-prompt,
source-fetch-prompt, player-character-section) and hooks (use-long-press,
use-swipe-to-dismiss, use-bulk-import, use-initiative-rolls). Expand
combatant-row tests with inline editing, HP popover, and condition picker.

Component coverage: 59% → 80% lines, 55% → 71% branches
Hook coverage: 72% → 83% lines, 55% → 66% branches

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-03-29 23:55:21 +02:00
parent 08b5db81ad
commit b229a0dac7
15 changed files with 1802 additions and 4 deletions

View File

@@ -10,6 +10,8 @@ import { CombatantRow } from "../combatant-row.js";
import { PLAYER_COLOR_HEX } from "../player-icon-map.js";
const TEMP_HP_REGEX = /^\+\d/;
const CURRENT_HP_7_REGEX = /Current HP: 7/;
const CURRENT_HP_REGEX = /Current HP/;
// Mock persistence — no localStorage interaction
vi.mock("../../persistence/encounter-storage.js", () => ({
@@ -257,6 +259,172 @@ describe("CombatantRow", () => {
});
});
describe("inline name editing", () => {
it("click rename → type new name → blur commits rename", async () => {
const user = userEvent.setup();
renderRow();
await user.click(screen.getByRole("button", { name: "Rename" }));
const input = screen.getByDisplayValue("Goblin");
await user.clear(input);
await user.type(input, "Hobgoblin");
await user.tab(); // blur
// The input should be gone, name committed
expect(screen.queryByDisplayValue("Hobgoblin")).not.toBeInTheDocument();
});
it("Escape cancels without renaming", async () => {
const user = userEvent.setup();
renderRow();
await user.click(screen.getByRole("button", { name: "Rename" }));
const input = screen.getByDisplayValue("Goblin");
await user.clear(input);
await user.type(input, "Changed");
await user.keyboard("{Escape}");
// Should revert to showing the original name
expect(screen.getByText("Goblin")).toBeInTheDocument();
});
});
describe("inline AC editing", () => {
it("click AC → type value → Enter commits", async () => {
const user = userEvent.setup();
renderRow({
combatant: {
id: combatantId("1"),
name: "Goblin",
ac: 13,
},
});
// Click the AC shield button
const acButton = screen.getByText("13").closest("button");
expect(acButton).not.toBeNull();
await user.click(acButton as HTMLElement);
const input = screen.getByDisplayValue("13");
await user.clear(input);
await user.type(input, "16");
await user.keyboard("{Enter}");
expect(screen.queryByDisplayValue("16")).not.toBeInTheDocument();
});
});
describe("inline max HP editing", () => {
it("click max HP → type value → blur commits", async () => {
const user = userEvent.setup();
renderRow({
combatant: {
id: combatantId("1"),
name: "Goblin",
maxHp: 10,
currentHp: 10,
},
});
// The max HP button shows "10" as muted text
const maxHpButton = screen
.getAllByText("10")
.find(
(el) => el.closest("button") && el.className.includes("text-muted"),
);
expect(maxHpButton).toBeDefined();
const maxHpBtn = (maxHpButton as HTMLElement).closest("button");
expect(maxHpBtn).not.toBeNull();
await user.click(maxHpBtn as HTMLElement);
const input = screen.getByDisplayValue("10");
await user.clear(input);
await user.type(input, "25");
await user.tab();
expect(screen.queryByDisplayValue("25")).not.toBeInTheDocument();
});
});
describe("inline initiative editing", () => {
it("click initiative → type value → Enter commits", async () => {
const user = userEvent.setup();
renderRow({
combatant: {
id: combatantId("1"),
name: "Goblin",
initiative: 15,
},
});
await user.click(screen.getByText("15"));
const input = screen.getByDisplayValue("15");
await user.clear(input);
await user.type(input, "20");
await user.keyboard("{Enter}");
expect(screen.queryByDisplayValue("20")).not.toBeInTheDocument();
});
it("clearing initiative and pressing Enter commits the edit", async () => {
const user = userEvent.setup();
renderRow({
combatant: {
id: combatantId("1"),
name: "Goblin",
initiative: 15,
},
});
await user.click(screen.getByText("15"));
const input = screen.getByDisplayValue("15");
await user.clear(input);
await user.keyboard("{Enter}");
// Input should be dismissed (editing mode exited)
expect(screen.queryByRole("textbox")).not.toBeInTheDocument();
});
});
describe("HP popover", () => {
it("clicking current HP opens the HP adjust popover", async () => {
const user = userEvent.setup();
renderRow({
combatant: {
id: combatantId("1"),
name: "Goblin",
maxHp: 10,
currentHp: 7,
},
});
const hpButton = screen.getByLabelText(CURRENT_HP_7_REGEX);
await user.click(hpButton);
// The popover should appear with damage/heal controls
expect(
screen.getByRole("button", { name: "Apply damage" }),
).toBeInTheDocument();
expect(
screen.getByRole("button", { name: "Apply healing" }),
).toBeInTheDocument();
});
it("HP section is absent when maxHp is undefined", () => {
renderRow({
combatant: {
id: combatantId("1"),
name: "Goblin",
},
});
expect(screen.queryByLabelText(CURRENT_HP_REGEX)).not.toBeInTheDocument();
});
});
describe("condition picker", () => {
it("clicking Add condition button opens the picker", async () => {
const user = userEvent.setup();
renderRow();
const addButton = screen.getByRole("button", {
name: "Add condition",
});
await user.click(addButton);
// Condition picker should render with condition options
expect(screen.getByText("Blinded")).toBeInTheDocument();
});
});
describe("temp HP display", () => {
it("shows +N when combatant has temp HP", () => {
renderRow({