// Combat characterization. Lock updateDoc/setDoc patch for combat controls. import React from 'react'; import { screen, fireEvent, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; import { getCalls } from '../__mocks__/firebase/_mock-db'; import { setupReady, addMonsterViaUI, startCombatViaUI } from './testHelpers'; function findCallsEnc() { return getCalls().filter(c => c.fn === 'updateDoc' && c.path.includes('/encounters/')); } function lastEncCall() { const calls = findCallsEnc(); return calls[calls.length - 1]; } function findCallActiveDisplay(fn) { return getCalls().filter(c => c.fn === fn && c.path.includes('activeDisplay/status')); } async function setupWithMonsters(names = ['A', 'B', 'C']) { await setupReady('CombatCamp', 'CombatEnc'); for (const n of names) { await addMonsterViaUI(n, 20, Number(n.charCodeAt(0) % 10)); } } describe('Combat -> Firebase', () => { test('startEncounter: updateDoc sets isStarted/round/turn/current', async () => { await setupWithMonsters(); await startCombatViaUI(); const call = lastEncCall(); expect(call.data).toMatchObject({ isStarted: true, isPaused: false, round: 1, }); expect(call.data.currentTurnParticipantId).toBeTruthy(); expect(call.data.turnOrderIds).toHaveLength(3); }); test('startEncounter: also sets activeDisplay to this encounter', async () => { await setupWithMonsters(); await startCombatViaUI(); const adCalls = findCallActiveDisplay('setDoc'); const last = adCalls[adCalls.length - 1]; expect(last.data.activeCampaignId).toBeTruthy(); expect(last.data.activeEncounterId).toBeTruthy(); }); test('nextTurn: advances currentTurnParticipantId', async () => { await setupWithMonsters(); await startCombatViaUI(); const beforeId = lastEncCall().data.currentTurnParticipantId; fireEvent.click(screen.getByRole('button', { name: /Next Turn/i })); await waitFor(() => lastEncCall()?.data?.currentTurnParticipantId !== beforeId); expect(lastEncCall().data.currentTurnParticipantId).not.toBe(beforeId); }); test('nextTurn wrapping to round 1->2 increments round', async () => { await setupWithMonsters(['A', 'B']); await startCombatViaUI(); // advance through all participants to wrap fireEvent.click(screen.getByRole('button', { name: /Next Turn/i })); // A->B (or 2nd) await waitFor(() => lastEncCall()?.data?.currentTurnParticipantId); fireEvent.click(screen.getByRole('button', { name: /Next Turn/i })); // wrap await waitFor(() => lastEncCall()?.data?.round === 2); expect(lastEncCall().data.round).toBe(2); }); test('pause: updateDoc sets isPaused true', async () => { await setupWithMonsters(); await startCombatViaUI(); fireEvent.click(screen.getByRole('button', { name: /Pause Combat/i })); await waitFor(() => lastEncCall()?.data?.isPaused === true); expect(lastEncCall().data.isPaused).toBe(true); }); test('resume: updateDoc sets isPaused false + recomputes turnOrder', async () => { await setupWithMonsters(); await startCombatViaUI(); fireEvent.click(screen.getByRole('button', { name: /Pause Combat/i })); await waitFor(() => lastEncCall()?.data?.isPaused === true); fireEvent.click(screen.getByRole('button', { name: /Resume Combat/i })); await waitFor(() => lastEncCall()?.data?.isPaused === false); const call = lastEncCall(); expect(call.data.isPaused).toBe(false); expect(call.data.turnOrderIds).toHaveLength(3); }); test('endEncounter: updateDoc resets all combat state', async () => { await setupWithMonsters(); await startCombatViaUI(); fireEvent.click(screen.getByRole('button', { name: /End Combat/i })); fireEvent.click(await screen.findByRole('button', { name: /Confirm/i })); await waitFor(() => lastEncCall()?.data?.isStarted === false); const call = lastEncCall(); expect(call.data).toMatchObject({ isStarted: false, isPaused: false, round: 0, currentTurnParticipantId: null, turnOrderIds: [], }); }); test('endEncounter: clears activeDisplay', async () => { await setupWithMonsters(); await startCombatViaUI(); fireEvent.click(screen.getByRole('button', { name: /End Combat/i })); fireEvent.click(await screen.findByRole('button', { name: /Confirm/i })); await waitFor(() => { const adCalls = findCallActiveDisplay('setDoc'); const last = adCalls[adCalls.length - 1]; return last && last.data.activeCampaignId === null; }); const adCalls = findCallActiveDisplay('setDoc'); const last = adCalls[adCalls.length - 1]; expect(last.data).toMatchObject({ activeCampaignId: null, activeEncounterId: null }); }); test('toggleHidePlayerHp: setDoc merge on activeDisplay/status', async () => { await setupWithMonsters(); await startCombatViaUI(); const switchBtn = screen.getByRole('switch'); fireEvent.click(switchBtn); await waitFor(() => { const adCalls = findCallActiveDisplay('setDoc'); const last = adCalls[adCalls.length - 1]; return last && 'hidePlayerHp' in last.data; }); const adCalls = findCallActiveDisplay('setDoc'); const last = adCalls[adCalls.length - 1]; expect(last.data).toHaveProperty('hidePlayerHp'); }); });