Introduce a settings modal (opened from the kebab menu) with a rules edition selector for condition tooltip descriptions and a theme picker replacing the inline cycle button. About half the conditions have meaningful mechanical differences between editions. - Add description5e field to ConditionDefinition with 5e (2014) text - Add RulesEditionProvider context with localStorage persistence - Create SettingsModal with Conditions and Theme sections - Wire condition tooltips to edition-aware descriptions - Fix 6 inaccurate 5.5e condition descriptions - Update spec 003 with stories CC-3, CC-8 and FR-095–FR-102 Closes #12 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
73 lines
2.4 KiB
TypeScript
73 lines
2.4 KiB
TypeScript
// @vitest-environment jsdom
|
|
import "@testing-library/jest-dom/vitest";
|
|
|
|
import { CONDITION_DEFINITIONS, type ConditionId } from "@initiative/domain";
|
|
import { cleanup, render, screen } from "@testing-library/react";
|
|
import userEvent from "@testing-library/user-event";
|
|
import { createRef, type RefObject } from "react";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { RulesEditionProvider } from "../../contexts/index.js";
|
|
import { ConditionPicker } from "../condition-picker";
|
|
|
|
afterEach(cleanup);
|
|
|
|
function renderPicker(
|
|
overrides: Partial<{
|
|
activeConditions: readonly ConditionId[];
|
|
onToggle: (conditionId: ConditionId) => void;
|
|
onClose: () => void;
|
|
}> = {},
|
|
) {
|
|
const onToggle = overrides.onToggle ?? vi.fn();
|
|
const onClose = overrides.onClose ?? vi.fn();
|
|
const anchorRef = createRef<HTMLElement>() as RefObject<HTMLElement>;
|
|
const anchor = document.createElement("div");
|
|
document.body.appendChild(anchor);
|
|
(anchorRef as { current: HTMLElement }).current = anchor;
|
|
const result = render(
|
|
<RulesEditionProvider>
|
|
<ConditionPicker
|
|
anchorRef={anchorRef}
|
|
activeConditions={overrides.activeConditions ?? []}
|
|
onToggle={onToggle}
|
|
onClose={onClose}
|
|
/>
|
|
</RulesEditionProvider>,
|
|
);
|
|
return { ...result, onToggle, onClose };
|
|
}
|
|
|
|
describe("ConditionPicker", () => {
|
|
it("renders all condition definitions from domain", () => {
|
|
renderPicker();
|
|
for (const def of CONDITION_DEFINITIONS) {
|
|
expect(screen.getByText(def.label)).toBeInTheDocument();
|
|
}
|
|
});
|
|
|
|
it("active conditions are visually distinguished", () => {
|
|
renderPicker({ activeConditions: ["blinded"] });
|
|
const blindedButton = screen.getByText("Blinded").closest("button");
|
|
expect(blindedButton?.className).toContain("bg-card/50");
|
|
});
|
|
|
|
it("clicking a condition calls onToggle with that condition's ID", async () => {
|
|
const user = userEvent.setup();
|
|
const { onToggle } = renderPicker();
|
|
await user.click(screen.getByText("Poisoned"));
|
|
expect(onToggle).toHaveBeenCalledWith("poisoned");
|
|
});
|
|
|
|
it("non-active conditions render with muted styling", () => {
|
|
renderPicker({ activeConditions: [] });
|
|
const label = screen.getByText("Charmed");
|
|
expect(label.className).toContain("text-muted-foreground");
|
|
});
|
|
|
|
it("active condition labels use foreground color", () => {
|
|
renderPicker({ activeConditions: ["charmed"] });
|
|
const label = screen.getByText("Charmed");
|
|
expect(label.className).toContain("text-foreground");
|
|
});
|
|
});
|