Add PF2e persistent damage condition tags
All checks were successful
CI / check (push) Successful in 2m39s
CI / build-image (push) Successful in 19s

Persistent damage displayed as compact tags with damage type icon and
formula (e.g., Flame + "2d6"). Supports fire, bleed, acid, cold,
electricity, poison, and mental types. One instance per type, added via
sub-picker in the condition picker. PF2e only, persists across reload.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-04-11 12:09:31 +02:00
parent 09a801487d
commit 4b1c1deda2
20 changed files with 1257 additions and 111 deletions

View File

@@ -0,0 +1,76 @@
// @vitest-environment jsdom
import "@testing-library/jest-dom/vitest";
import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";
import { PersistentDamagePicker } from "../persistent-damage-picker.js";
afterEach(cleanup);
function renderPicker(
overrides: Partial<{
activeEntries: { type: string; formula: string }[];
onAdd: (damageType: string, formula: string) => void;
onClose: () => void;
}> = {},
) {
const onAdd = overrides.onAdd ?? vi.fn();
const onClose = overrides.onClose ?? vi.fn();
const result = render(
<PersistentDamagePicker
activeEntries={
(overrides.activeEntries as Parameters<
typeof PersistentDamagePicker
>[0]["activeEntries"]) ?? undefined
}
onAdd={onAdd as Parameters<typeof PersistentDamagePicker>[0]["onAdd"]}
onClose={onClose}
/>,
);
return { ...result, onAdd, onClose };
}
describe("PersistentDamagePicker", () => {
it("renders damage type dropdown and formula input", () => {
renderPicker();
expect(screen.getByRole("combobox")).toBeInTheDocument();
expect(screen.getByPlaceholderText("2d6")).toBeInTheDocument();
});
it("confirm button is disabled when formula is empty", () => {
renderPicker();
expect(
screen.getByRole("button", { name: "Add persistent damage" }),
).toBeDisabled();
});
it("submitting calls onAdd with selected type and formula", async () => {
const user = userEvent.setup();
const { onAdd } = renderPicker();
await user.type(screen.getByPlaceholderText("2d6"), "3d6");
await user.click(
screen.getByRole("button", { name: "Add persistent damage" }),
);
expect(onAdd).toHaveBeenCalledWith("fire", "3d6");
});
it("Enter in formula input confirms", async () => {
const user = userEvent.setup();
const { onAdd } = renderPicker();
await user.type(screen.getByPlaceholderText("2d6"), "2d6{Enter}");
expect(onAdd).toHaveBeenCalledWith("fire", "2d6");
});
it("pre-fills formula for existing active entry", async () => {
const user = userEvent.setup();
renderPicker({
activeEntries: [{ type: "fire", formula: "2d6" }],
});
expect(screen.getByPlaceholderText("2d6")).toHaveValue("2d6");
// Change type to one without active entry
await user.selectOptions(screen.getByRole("combobox"), "bleed");
expect(screen.getByPlaceholderText("2d6")).toHaveValue("");
});
});