Fix PF2e stat block senses and attack trait rendering
All checks were successful
CI / check (push) Successful in 2m20s
CI / build-image (push) Successful in 17s

- Format senses with type (imprecise/precise) and range in feet,
  and strip {@ability} tags (e.g. tremorsense)
- Strip angle-bracket dice notation in attack traits (<d8> → d8)
- Fix existing weakness/resistance tests to nest under defenses
- Fix non-null assertions in 5e bestiary adapter tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lukas
2026-04-07 12:23:08 +02:00
parent 8dbff66ce1
commit 65e4db153b
3 changed files with 124 additions and 30 deletions

View File

@@ -6,7 +6,8 @@ import {
} from "../bestiary-adapter.js";
/** Flatten segments to a single string for simple text assertions. */
function flatText(trait: TraitBlock): string {
function flatText(trait: TraitBlock | undefined): string {
if (!trait) return "";
return trait.segments
.map((s) =>
s.type === "text"
@@ -88,11 +89,11 @@ describe("normalizeBestiary", () => {
expect(c.senses).toBe("Darkvision 60 ft.");
expect(c.languages).toBe("Common, Goblin");
expect(c.actions).toHaveLength(1);
expect(flatText(c.actions![0])).toContain("Melee Attack Roll:");
expect(flatText(c.actions![0])).not.toContain("{@");
expect(flatText(c.actions?.[0])).toContain("Melee Attack Roll:");
expect(flatText(c.actions?.[0])).not.toContain("{@");
expect(c.bonusActions).toHaveLength(1);
expect(flatText(c.bonusActions![0])).toContain("Disengage");
expect(flatText(c.bonusActions![0])).not.toContain("{@");
expect(flatText(c.bonusActions?.[0])).toContain("Disengage");
expect(flatText(c.bonusActions?.[0])).not.toContain("{@");
});
it("normalizes a creature with legendary actions", () => {
@@ -347,9 +348,9 @@ describe("normalizeBestiary", () => {
const creatures = normalizeBestiary(raw);
const bite = creatures[0].actions?.[0];
expect(flatText(bite!)).toContain("Melee Weapon Attack:");
expect(flatText(bite!)).not.toContain("mw");
expect(flatText(bite!)).not.toContain("{@");
expect(flatText(bite)).toContain("Melee Weapon Attack:");
expect(flatText(bite)).not.toContain("mw");
expect(flatText(bite)).not.toContain("{@");
});
it("handles fly speed with hover condition", () => {
@@ -438,14 +439,15 @@ describe("normalizeBestiary", () => {
};
const creatures = normalizeBestiary(raw);
const trait = creatures[0].traits![0];
expect(trait.name).toBe("Confusing Burble");
expect(trait.segments).toHaveLength(2);
expect(trait.segments[0]).toEqual({
const trait = creatures[0].traits?.[0];
expect(trait).toBeDefined();
expect(trait?.name).toBe("Confusing Burble");
expect(trait?.segments).toHaveLength(2);
expect(trait?.segments[0]).toEqual({
type: "text",
value: expect.stringContaining("d4"),
});
expect(trait.segments[1]).toEqual({
expect(trait?.segments[1]).toEqual({
type: "list",
items: [
{ label: "1-2", text: "The creature does nothing." },
@@ -498,8 +500,9 @@ describe("normalizeBestiary", () => {
};
const creatures = normalizeBestiary(raw);
const trait = creatures[0].traits![0];
expect(trait.segments[1]).toEqual({
const trait = creatures[0].traits?.[0];
expect(trait).toBeDefined();
expect(trait?.segments[1]).toEqual({
type: "list",
items: [
{ label: "1", text: "Nothing happens." },