diff --git a/apps/web/src/__tests__/app-integration.test.tsx b/apps/web/src/__tests__/app-integration.test.tsx
index 97a0e29..366e0e5 100644
--- a/apps/web/src/__tests__/app-integration.test.tsx
+++ b/apps/web/src/__tests__/app-integration.test.tsx
@@ -143,7 +143,6 @@ describe("App integration", () => {
await addCombatant(user, "Ogre", { maxHp: "59" });
// Verify HP displays — currentHp and maxHp both show "59"
- expect(screen.getByText("/")).toBeInTheDocument();
const hpButton = screen.getByRole("button", {
name: "Current HP: 59 (healthy)",
});
diff --git a/apps/web/src/components/__tests__/combatant-row.test.tsx b/apps/web/src/components/__tests__/combatant-row.test.tsx
index f16b7af..fccb223 100644
--- a/apps/web/src/components/__tests__/combatant-row.test.tsx
+++ b/apps/web/src/components/__tests__/combatant-row.test.tsx
@@ -12,9 +12,8 @@ import { PLAYER_COLOR_HEX } from "../player-icon-map.js";
const TEMP_HP_REGEX = /^\+\d/;
// Mock persistence — no localStorage interaction
-const mockLoadEncounter = vi.fn<() => unknown>(() => null);
vi.mock("../../persistence/encounter-storage.js", () => ({
- loadEncounter: () => mockLoadEncounter(),
+ loadEncounter: () => null,
saveEncounter: () => {},
}));
@@ -126,14 +125,14 @@ describe("CombatantRow", () => {
expect(nameContainer).not.toBeNull();
});
- it("shows '--' for current HP when no maxHp is set", () => {
+ it("shows 'Max' placeholder when no maxHp is set", () => {
renderRow({
combatant: {
id: combatantId("1"),
name: "Goblin",
},
});
- expect(screen.getByLabelText("No HP set")).toBeInTheDocument();
+ expect(screen.getByText("Max")).toBeInTheDocument();
});
it("shows concentration icon when isConcentrating is true", () => {
@@ -245,11 +244,6 @@ describe("CombatantRow", () => {
tempHp: 8,
isConcentrating: true,
};
- mockLoadEncounter.mockReturnValueOnce({
- combatants: [combatant],
- activeIndex: 0,
- roundNumber: 1,
- });
const { rerender, container } = renderRow({ combatant });
// Temp HP absorbs all damage, currentHp unchanged
rerender(
@@ -265,20 +259,15 @@ describe("CombatantRow", () => {
describe("temp HP display", () => {
it("shows +N when combatant has temp HP", () => {
- const combatant = {
- id: combatantId("1"),
- name: "Goblin",
- maxHp: 20,
- currentHp: 15,
- tempHp: 5,
- };
- // Provide encounter with tempHp so hasTempHp is true
- mockLoadEncounter.mockReturnValueOnce({
- combatants: [combatant],
- activeIndex: 0,
- roundNumber: 1,
+ renderRow({
+ combatant: {
+ id: combatantId("1"),
+ name: "Goblin",
+ maxHp: 20,
+ currentHp: 15,
+ tempHp: 5,
+ },
});
- renderRow({ combatant });
expect(screen.getByText("+5")).toBeInTheDocument();
});
@@ -295,19 +284,15 @@ describe("CombatantRow", () => {
});
it("temp HP display uses cyan color", () => {
- const combatant = {
- id: combatantId("1"),
- name: "Goblin",
- maxHp: 20,
- currentHp: 15,
- tempHp: 8,
- };
- mockLoadEncounter.mockReturnValueOnce({
- combatants: [combatant],
- activeIndex: 0,
- roundNumber: 1,
+ renderRow({
+ combatant: {
+ id: combatantId("1"),
+ name: "Goblin",
+ maxHp: 20,
+ currentHp: 15,
+ tempHp: 8,
+ },
});
- renderRow({ combatant });
const tempHpEl = screen.getByText("+8");
expect(tempHpEl.className).toContain("text-cyan-400");
});
diff --git a/apps/web/src/components/combatant-row.tsx b/apps/web/src/components/combatant-row.tsx
index 764ac35..c9537f0 100644
--- a/apps/web/src/components/combatant-row.tsx
+++ b/apps/web/src/components/combatant-row.tsx
@@ -172,7 +172,12 @@ function MaxHpDisplay({
@@ -183,35 +188,20 @@ function ClickableHp({
currentHp,
maxHp,
tempHp,
- hasTempHp,
onAdjust,
onSetTempHp,
- dimmed,
}: Readonly<{
currentHp: number | undefined;
maxHp: number | undefined;
tempHp: number | undefined;
- hasTempHp: boolean;
onAdjust: (delta: number) => void;
onSetTempHp: (value: number) => void;
- dimmed?: boolean;
}>) {
const [popoverOpen, setPopoverOpen] = useState(false);
const status = deriveHpStatus(currentHp, maxHp);
if (maxHp === undefined) {
- return (
-
- --
-
- );
+ return null;
}
return (
@@ -221,24 +211,17 @@ function ClickableHp({
onClick={() => setPopoverOpen(true)}
aria-label={`Current HP: ${currentHp}${tempHp ? ` (+${tempHp} temp)` : ""} (${status})`}
className={cn(
- "inline-block h-7 min-w-[3ch] text-center font-medium text-sm tabular-nums leading-7 transition-colors hover:text-hover-neutral",
+ "inline-block h-7 min-w-[3ch] text-center font-medium text-sm leading-7 transition-colors hover:text-hover-neutral",
status === "bloodied" && "text-amber-400",
status === "unconscious" && "text-red-400",
status === "healthy" && "text-foreground",
- dimmed && "opacity-50",
)}
>
{currentHp}
- {!!hasTempHp && (
-
- {tempHp ? `+${tempHp}` : ""}
+ {!!tempHp && (
+
+ +{tempHp}
)}
{!!popoverOpen && (
@@ -463,7 +446,6 @@ export function CombatantRow({
setHp,
adjustHp,
setTempHp,
- hasTempHp,
setAc,
toggleCondition,
toggleConcentration,
@@ -615,29 +597,26 @@ export function CombatantRow({
{/* HP */}
-
+
adjustHp(id, delta)}
onSetTempHp={(value) => setTempHp(id, value)}
- dimmed={dimmed}
/>
{maxHp !== undefined && (
-
- /
-
+ /
)}
-
- setHp(id, v)} />
-
+ setHp(id, v)} />
{/* Actions */}