Refactor App.tsx from god component to context-based architecture
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,11 +5,23 @@ 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);
|
||||
// Mock the context module
|
||||
vi.mock("../../contexts/encounter-context.js", () => ({
|
||||
useEncounterContext: vi.fn(),
|
||||
}));
|
||||
|
||||
function renderNav(overrides: Partial<Encounter> = {}) {
|
||||
import { useEncounterContext } from "../../contexts/encounter-context.js";
|
||||
import { TurnNavigation } from "../turn-navigation.js";
|
||||
|
||||
const mockUseEncounterContext = vi.mocked(useEncounterContext);
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
function mockContext(overrides: Partial<Encounter> = {}) {
|
||||
const encounter: Encounter = {
|
||||
combatants: [
|
||||
{ id: combatantId("1"), name: "Goblin" },
|
||||
@@ -20,14 +32,38 @@ function renderNav(overrides: Partial<Encounter> = {}) {
|
||||
...overrides,
|
||||
};
|
||||
|
||||
return render(
|
||||
<TurnNavigation
|
||||
encounter={encounter}
|
||||
onAdvanceTurn={vi.fn()}
|
||||
onRetreatTurn={vi.fn()}
|
||||
onClearEncounter={vi.fn()}
|
||||
/>,
|
||||
const value = {
|
||||
encounter,
|
||||
advanceTurn: vi.fn(),
|
||||
retreatTurn: vi.fn(),
|
||||
clearEncounter: vi.fn(),
|
||||
isEmpty: encounter.combatants.length === 0,
|
||||
hasCreatureCombatants: false,
|
||||
canRollAllInitiative: false,
|
||||
addCombatant: vi.fn(),
|
||||
removeCombatant: vi.fn(),
|
||||
editCombatant: vi.fn(),
|
||||
setInitiative: vi.fn(),
|
||||
setHp: vi.fn(),
|
||||
adjustHp: vi.fn(),
|
||||
setAc: vi.fn(),
|
||||
toggleCondition: vi.fn(),
|
||||
toggleConcentration: vi.fn(),
|
||||
addFromBestiary: vi.fn(),
|
||||
addFromPlayerCharacter: vi.fn(),
|
||||
makeStore: vi.fn(),
|
||||
events: [],
|
||||
};
|
||||
|
||||
mockUseEncounterContext.mockReturnValue(
|
||||
value as ReturnType<typeof useEncounterContext>,
|
||||
);
|
||||
return value;
|
||||
}
|
||||
|
||||
function renderNav(overrides: Partial<Encounter> = {}) {
|
||||
mockContext(overrides);
|
||||
return render(<TurnNavigation />);
|
||||
}
|
||||
|
||||
describe("TurnNavigation", () => {
|
||||
@@ -49,7 +85,7 @@ describe("TurnNavigation", () => {
|
||||
|
||||
it("does not render an em dash between round and name", () => {
|
||||
const { container } = renderNav();
|
||||
expect(container.textContent).not.toContain("—");
|
||||
expect(container.textContent).not.toContain("\u2014");
|
||||
});
|
||||
|
||||
it("round badge and combatant name are siblings in the center area", () => {
|
||||
@@ -60,69 +96,27 @@ describe("TurnNavigation", () => {
|
||||
});
|
||||
|
||||
it("updates the round badge when round changes", () => {
|
||||
const { rerender } = render(
|
||||
<TurnNavigation
|
||||
encounter={{
|
||||
combatants: [{ id: combatantId("1"), name: "Goblin" }],
|
||||
activeIndex: 0,
|
||||
roundNumber: 2,
|
||||
}}
|
||||
onAdvanceTurn={vi.fn()}
|
||||
onRetreatTurn={vi.fn()}
|
||||
onClearEncounter={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
mockContext({ roundNumber: 2 });
|
||||
const { rerender } = render(<TurnNavigation />);
|
||||
expect(screen.getByText("R2")).toBeInTheDocument();
|
||||
|
||||
rerender(
|
||||
<TurnNavigation
|
||||
encounter={{
|
||||
combatants: [{ id: combatantId("1"), name: "Goblin" }],
|
||||
activeIndex: 0,
|
||||
roundNumber: 3,
|
||||
}}
|
||||
onAdvanceTurn={vi.fn()}
|
||||
onRetreatTurn={vi.fn()}
|
||||
onClearEncounter={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
mockContext({ roundNumber: 3 });
|
||||
rerender(<TurnNavigation />);
|
||||
expect(screen.getByText("R3")).toBeInTheDocument();
|
||||
expect(screen.queryByText("R2")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the next combatant name when turn advances", () => {
|
||||
const { rerender } = render(
|
||||
<TurnNavigation
|
||||
encounter={{
|
||||
combatants: [
|
||||
{ id: combatantId("1"), name: "Goblin" },
|
||||
{ id: combatantId("2"), name: "Conjurer" },
|
||||
],
|
||||
activeIndex: 0,
|
||||
roundNumber: 1,
|
||||
}}
|
||||
onAdvanceTurn={vi.fn()}
|
||||
onRetreatTurn={vi.fn()}
|
||||
onClearEncounter={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
const combatants = [
|
||||
{ id: combatantId("1"), name: "Goblin" },
|
||||
{ id: combatantId("2"), name: "Conjurer" },
|
||||
];
|
||||
mockContext({ combatants, activeIndex: 0 });
|
||||
const { rerender } = render(<TurnNavigation />);
|
||||
expect(screen.getByText("Goblin")).toBeInTheDocument();
|
||||
|
||||
rerender(
|
||||
<TurnNavigation
|
||||
encounter={{
|
||||
combatants: [
|
||||
{ id: combatantId("1"), name: "Goblin" },
|
||||
{ id: combatantId("2"), name: "Conjurer" },
|
||||
],
|
||||
activeIndex: 1,
|
||||
roundNumber: 1,
|
||||
}}
|
||||
onAdvanceTurn={vi.fn()}
|
||||
onRetreatTurn={vi.fn()}
|
||||
onClearEncounter={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
mockContext({ combatants, activeIndex: 1 });
|
||||
rerender(<TurnNavigation />);
|
||||
expect(screen.getByText("Conjurer")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user