// Encounter characterization. Lock setDoc path + payload on encounter actions. 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 { renderApp, createCampaignViaUI, selectCampaignByName, createEncounterViaUI, selectEncounterByName } from './testHelpers'; function findCall(fn, pathSub) { return getCalls().find(c => c.fn === fn && (pathSub ? c.path.includes(pathSub) : true)); } function findCalls(fn, pathSub) { return getCalls().filter(c => c.fn === fn && (pathSub ? c.path.includes(pathSub) : true)); } async function setupCampaignAndEncounter(campName, encName) { await renderApp(); await createCampaignViaUI(campName); await selectCampaignByName(campName); await createEncounterViaUI(encName); } describe('Encounter -> Firebase', () => { test('createEncounter: setDoc with encounter path + payload', async () => { await setupCampaignAndEncounter('Camp E', 'Boss Fight'); const call = findCall('setDoc', '/encounters/'); expect(call.path).toMatch(/encounters\/.+$/); expect(call.data).toMatchObject({ name: 'Boss Fight', participants: [], round: 0, currentTurnParticipantId: null, isStarted: false, isPaused: false, }); expect(call.data).toHaveProperty('createdAt'); }); test('createEncounter: path nested under campaign', async () => { await setupCampaignAndEncounter('Camp N', 'Enc N'); const call = findCall('setDoc', '/encounters/'); expect(call.path).toMatch(/campaigns\/[^/]+\/encounters\//); }); test('togglePlayerDisplay: updateDoc patch on activeDisplay/status', async () => { await setupCampaignAndEncounter('Camp D', 'Enc D'); await selectEncounterByName('Enc D'); // Eye button (icon-only, title attr) const eyeBtn = await screen.findByTitle('Activate for Player Display'); fireEvent.click(eyeBtn); await waitFor(() => findCall('updateDoc', 'activeDisplay/status')); const call = findCall('updateDoc', 'activeDisplay/status'); // BUG-4 fix: updateDoc patch, not setDoc replace (was clobbering fields) expect(call.data).toMatchObject({ activeCampaignId: expect.any(String), activeEncounterId: expect.any(String), }); }); test('togglePlayerDisplay off: updateDoc nulls active ids', async () => { await setupCampaignAndEncounter('Camp O', 'Enc O'); await selectEncounterByName('Enc O'); // turn ON const onBtn = await screen.findByTitle('Activate for Player Display'); fireEvent.click(onBtn); await waitFor(() => findCall('updateDoc', 'activeDisplay/status')); // turn OFF const offBtn = await screen.findByTitle('Deactivate for Player Display'); fireEvent.click(offBtn); await waitFor(() => { const calls = findCalls('updateDoc', 'activeDisplay/status'); const last = calls[calls.length - 1]; return last.data.activeCampaignId === null; }); const calls = findCalls('updateDoc', 'activeDisplay/status'); const last = calls[calls.length - 1]; expect(last.data).toMatchObject({ activeCampaignId: null, activeEncounterId: null }); }); test('deleteEncounter: deleteDoc on encounter path', async () => { await setupCampaignAndEncounter('Camp X', 'Enc X'); await selectEncounterByName('Enc X'); // trash icon on encounter card const trashBtn = screen.getAllByTitle('Delete Encounter')[0]; fireEvent.click(trashBtn); // confirm modal fireEvent.click(await screen.findByRole('button', { name: /Confirm/i })); await waitFor(() => findCall('deleteDoc', '/encounters/')); const del = findCall('deleteDoc', '/encounters/'); expect(del.path).toMatch(/campaigns\/[^/]+\/encounters\//); }); test('deleteEncounter clears activeDisplay if it was active', async () => { await setupCampaignAndEncounter('Camp A', 'Enc A'); await selectEncounterByName('Enc A'); // activate display first const onBtn = await screen.findByTitle('Activate for Player Display'); fireEvent.click(onBtn); await waitFor(() => findCall('updateDoc', 'activeDisplay/status')); // delete the active encounter const trashBtn = screen.getAllByTitle('Delete Encounter')[0]; fireEvent.click(trashBtn); fireEvent.click(await screen.findByRole('button', { name: /Confirm/i })); await waitFor(() => { const adCalls = findCalls('updateDoc', 'activeDisplay/status'); const last = adCalls[adCalls.length - 1]; return last.data.activeEncounterId === null; }); const adCalls = findCalls('updateDoc', 'activeDisplay/status'); const last = adCalls[adCalls.length - 1]; expect(last.data).toMatchObject({ activeCampaignId: null, activeEncounterId: null }); }); });