// test helpers: drive App UI to states. Used across characterization suites. import React from 'react'; import { render, screen, fireEvent, waitFor, within } from '@testing-library/react'; import App from '../App'; import { MOCK_DB } from '../__mocks__/firebase/_mock-db'; // Scoped container: the "Add Participants" section (avoids label clashes with CharacterManager). export function getParticipantForm() { const heading = screen.getByText('Add Participants'); // closest section/div wrapping the form let node = heading; for (let i = 0; i < 6; i++) { node = node.parentElement; if (!node) break; if (node.querySelector('form')) return node; } return heading.parentElement; } // Render app, wait for auth + campaign list. export async function renderApp() { window.history.replaceState({}, '', '/'); global.alert = jest.fn(); window.open = jest.fn(); const utils = render(); await waitFor(() => screen.getByRole('button', { name: /Create Campaign/i })); return utils; } // Open create-campaign modal, fill name, submit. Returns campaign id from recorded call. export async function createCampaignViaUI(name = 'Test Campaign') { fireEvent.click(screen.getByRole('button', { name: /Create Campaign/i })); await waitFor(() => screen.getByLabelText(/Campaign Name/i)); fireEvent.change(screen.getByLabelText(/Campaign Name/i), { target: { value: name } }); fireEvent.click(screen.getByRole('button', { name: /^Create$/i })); // wait for setDoc recorded with this name (latest match) const { getCalls } = require('../__mocks__/firebase/_mock-db'); await waitFor(() => getCalls().find(c => c.fn === 'setDoc' && c.path.includes('/campaigns/') && c.data.name === name)); const call = getCalls().filter(c => c.fn === 'setDoc' && c.path.includes('/campaigns/') && c.data.name === name).pop(); return call.path.split('/').pop(); // campaign id } // Click campaign card by name to select it. Returns selected campaign id. export async function selectCampaignByName(name) { const card = await waitFor(() => screen.getByText(name)); fireEvent.click(card); await waitFor(() => screen.getByText(/Managing:/i)); } // Open create-encounter modal, fill name, submit. Assumes campaign selected. export async function createEncounterViaUI(name = 'Test Encounter') { fireEvent.click(screen.getByRole('button', { name: /Create Encounter/i })); await waitFor(() => screen.getByLabelText(/Encounter Name/i)); fireEvent.change(screen.getByLabelText(/Encounter Name/i), { target: { value: name } }); fireEvent.click(screen.getByRole('button', { name: /^Create$/i })); const { getCalls } = require('../__mocks__/firebase/_mock-db'); await waitFor(() => getCalls().find(c => c.fn === 'setDoc' && c.path.includes('/encounters/') && c.data.name === name)); const call = getCalls().filter(c => c.fn === 'setDoc' && c.path.includes('/encounters/') && c.data.name === name).pop(); return call.path.split('/').pop(); } // Click encounter card by name. Assumes campaign selected. export async function selectEncounterByName(name) { const card = await waitFor(() => screen.getByText(name)); fireEvent.click(card); await waitFor(() => screen.getByText(/Managing Encounter:/i)); } // Add a monster participant via the ParticipantManager form. Assumes encounter selected. export async function addMonsterViaUI(name = 'Goblin', maxHp = 7, initMod = 2) { const form = within(getParticipantForm()); fireEvent.change(form.getByPlaceholderText('e.g., Dire Wolf'), { target: { value: name } }); fireEvent.change(form.getByLabelText(/Init Mod/i), { target: { value: String(initMod) } }); fireEvent.change(form.getByLabelText(/Max HP/i), { target: { value: String(maxHp) } }); fireEvent.click(form.getByRole('button', { name: /Add to Encounter/i })); const { getCalls } = require('../__mocks__/firebase/_mock-db'); await waitFor(() => { const calls = getCalls().filter(c => c.fn === 'updateDoc' && c.path.includes('/encounters/')); const last = calls[calls.length - 1]; return last && last.data.participants && last.data.participants.some(p => p.name === name); }); } // Full setup: app -> campaign -> encounter selected. export async function setupReady(campName = 'Camp', encName = 'Enc') { await renderApp(); await createCampaignViaUI(campName); await selectCampaignByName(campName); await createEncounterViaUI(encName); await selectEncounterByName(encName); } // Start combat. Assumes encounter selected with active participants. export async function startCombatViaUI() { fireEvent.click(screen.getByRole('button', { name: /Start Combat/i })); const { getCalls } = require('../__mocks__/firebase/_mock-db'); await waitFor(() => { const calls = getCalls().filter(c => c.fn === 'updateDoc' && c.path.includes('/encounters/')); const last = calls[calls.length - 1]; return last && last.data.isStarted === true; }); } export { MOCK_DB };