Prompt file imported from yakuzadave/narrative_cards (
.github/prompts/write-tests.prompt.md). Fill in{{act}}before use. Copyright stays with the author.
Write tests for a feature in Narrative Cards following testing.instructions.md.
Input
Identify from the user's request (or ask):
- What to test — action name, component, or genre pack
- Test type — hook logic, component smoke test, or content validation
Determine the Correct Test File
| What | File |
|---|---|
| Hook action (game rule) | src/hooks/useGameState.test.ts |
| Game widget (component) | src/components/game/components.test.tsx |
| Genre pack content | src/data/gameContent.test.ts |
Read the target test file before writing new tests so you can:
- Match the existing describe block structure
- Reuse shared helpers (
startPlay,startSetup,withTooltip, etc.) - Avoid duplicating tests that already exist
Hook Tests — renderHook + act Pattern
describe("myAction", () => {
it("describes the expected game rule outcome", () => {
const result = startPlay();
act(() => result.current.myAction(/* args */));
expect(result.current.gameState.someField).toBe(expectedValue);
});
it("edge case: what happens at a boundary", () => {
const result = startPlay();
// set up state if needed, then assert
});
});
- Always wrap calls in
act() - Assert on
gameStatefields — not internal implementation - Cover at least: happy path + one edge case (boundary or invalid input)
Component Tests — Smoke Test Pattern
describe('MyWidget', () => {
it('renders without crashing', () => {
withTooltip(<MyWidget prop={value} />);
expect(screen.getByText('Expected text')).toBeInTheDocument();
});
it('calls callback when button clicked', () => {
const onAction = vi.fn();
withTooltip(<MyWidget onAction={onAction} />);
screen.getByRole('button', { name: /button label/i }).click();
expect(onAction).toHaveBeenCalledOnce();
});
});
Wrapper rule: wrap in <TooltipProvider> via withTooltip() if the component uses any Tooltip. Use render() directly otherwise.
Icon-only buttons: query by aria-label, not text content:
// ✅
screen.getByRole("button", { name: /increase tension/i });
// ❌
screen.getByRole("button", { name: /\+/ });
Content Tests — Genre Pack Validation
For a new or modified genre pack, add assertions to src/data/gameContent.test.ts:
describe("myPack", () => {
const pack = getGenrePack("my-pack-id");
it("exists", () => expect(pack).toBeDefined());
it("has at least 3 prompts per act", () => {
[1, 2, 3].forEach((act) => {
const count = pack!.scenePrompts.filter((p) => p.act === act).length;
expect(count, `Act {{act}} prompts`).toBeGreaterThanOrEqual(3);
});
});
it("covers all canonical move names", () => {
const CANONICAL = [
"Introduce",
"Complicate",
"Reveal",
"Callback",
"Resolve",
"Confront",
];
const moveNames = pack!.gameCards
.filter((c) => c.type === "move")
.map((c) => c.name);
CANONICAL.forEach((name) => expect(moveNames).toContain(name));
});
});
Regression Tests
If writing a test for a bug fix, structure it to:
- Reproduce the bug state (what was wrong)
- Apply the fix (call the action)
- Assert the correct post-fix state
Name it clearly: it('does not crash when deck is empty on draw', ...).
Verify
npm test
All tests must pass. Fix any failures before completing.
Output
Report:
- Test file edited
- New describe blocks and
itnames added - Any helpers added/reused
npm testresults (pass count)