// @vitest-environment jsdom import "@testing-library/jest-dom/vitest"; import type { Encounter } from "@initiative/domain"; import { combatantId } from "@initiative/domain"; import { cleanup, render, screen } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import { TurnNavigation } from "../turn-navigation"; afterEach(cleanup); function renderNav(overrides: Partial = {}) { const encounter: Encounter = { combatants: [ { id: combatantId("1"), name: "Goblin" }, { id: combatantId("2"), name: "Conjurer" }, ], activeIndex: 0, roundNumber: 1, ...overrides, }; return render( , ); } describe("TurnNavigation", () => { describe("US1: Round badge and combatant name", () => { it("renders the round badge with correct round number", () => { renderNav({ roundNumber: 3 }); expect(screen.getByText("R3")).toBeInTheDocument(); }); it("renders the combatant name separately from the round badge", () => { renderNav(); const badge = screen.getByText("R1"); const name = screen.getByText("Goblin"); expect(badge).toBeInTheDocument(); expect(name).toBeInTheDocument(); expect(badge).not.toBe(name); expect(badge.closest("[class]")).not.toBe(name.closest("[class]")); }); it("does not render an em dash between round and name", () => { const { container } = renderNav(); expect(container.textContent).not.toContain("—"); }); it("round badge and combatant name are siblings in the center area", () => { renderNav(); const badge = screen.getByText("R1"); const name = screen.getByText("Goblin"); expect(badge.parentElement).toBe(name.parentElement); }); it("updates the round badge when round changes", () => { const { rerender } = render( , ); expect(screen.getByText("R2")).toBeInTheDocument(); rerender( , ); expect(screen.getByText("R3")).toBeInTheDocument(); expect(screen.queryByText("R2")).not.toBeInTheDocument(); }); it("renders the next combatant name when turn advances", () => { const { rerender } = render( , ); expect(screen.getByText("Goblin")).toBeInTheDocument(); rerender( , ); expect(screen.getByText("Conjurer")).toBeInTheDocument(); }); }); describe("US2: Layout robustness", () => { it("applies truncation styles to long combatant names", () => { const longName = "Ancient Red Dragon Wyrm of the Northern Wastes and Beyond"; renderNav({ combatants: [{ id: combatantId("1"), name: longName }], }); const nameEl = screen.getByText(longName); expect(nameEl.className).toContain("truncate"); }); it("renders three-zone layout with a single-character name", () => { renderNav({ combatants: [{ id: combatantId("1"), name: "O" }], }); expect(screen.getByText("R1")).toBeInTheDocument(); expect(screen.getByText("O")).toBeInTheDocument(); expect( screen.getByRole("button", { name: "Previous turn" }), ).toBeInTheDocument(); expect( screen.getByRole("button", { name: "Next turn" }), ).toBeInTheDocument(); }); it("keeps all action buttons accessible regardless of name length", () => { const longName = "A".repeat(60); renderNav({ combatants: [{ id: combatantId("1"), name: longName }], }); expect( screen.getByRole("button", { name: "Previous turn" }), ).toBeInTheDocument(); expect( screen.getByRole("button", { name: "Next turn" }), ).toBeInTheDocument(); }); it("renders a 40-character name without truncation class issues", () => { const name40 = "A".repeat(40); renderNav({ combatants: [{ id: combatantId("1"), name: name40 }], }); const nameEl = screen.getByText(name40); expect(nameEl).toBeInTheDocument(); // The truncate class is applied but CSS only visually truncates if content overflows expect(nameEl.className).toContain("truncate"); }); }); describe("US3: No combatants state", () => { it("shows the round badge when there are no combatants", () => { renderNav({ combatants: [], roundNumber: 1 }); expect(screen.getByText("R1")).toBeInTheDocument(); }); it("shows 'No combatants' placeholder text", () => { renderNav({ combatants: [] }); expect(screen.getByText("No combatants")).toBeInTheDocument(); }); it("disables navigation buttons when there are no combatants", () => { renderNav({ combatants: [] }); expect( screen.getByRole("button", { name: "Previous turn" }), ).toBeDisabled(); expect(screen.getByRole("button", { name: "Next turn" })).toBeDisabled(); }); }); });