// BUG-4 repro: toggling hidePlayerHp must not clobber activeDisplay doc. // setDoc = replace would clobber. setDoc({merge:true}) patches only — matches main. // Toggling hide-HP writes {hidePlayerHp:X} via setDoc{merge} → activeCampaignId + activeEncounterId preserved. // Display keeps reading them → stays active. // Regression: if caller swaps to plain setDoc (no merge) or updateDoc-throws-on-missing, re-breaks. import React from 'react'; import { render, waitFor, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import App from '../App'; import { MOCK_DB } from '../__mocks__/firebase/_mock-db'; import { getAdapterCalls, resetAdapterCalls } from '../storage/firebase'; import { selectCampaignByName } from './testHelpers'; function seedAdminWithActiveEncounter() { const campaignPath = 'artifacts/ttrpg-initiative-tracker-default/public/data/campaigns/c1'; const encounterPath = 'artifacts/ttrpg-initiative-tracker-default/public/data/campaigns/c1/encounters/e1'; const activeDisplayPath = 'artifacts/ttrpg-initiative-tracker-default/public/data/activeDisplay/status'; MOCK_DB.set(campaignPath, { name: 'Camp', playerDisplayBackgroundUrl: '' }); MOCK_DB.set(encounterPath, { name: 'Enc', participants: [], isStarted: true }); // active encounter set, HP NOT hidden MOCK_DB.set(activeDisplayPath, { activeCampaignId: 'c1', activeEncounterId: 'e1', hidePlayerHp: false, }); } describe('BUG-4: hide-player-HP toggle preserves activeDisplay', () => { beforeEach(() => { window.history.replaceState({}, '', '/'); global.alert = jest.fn(); resetAdapterCalls(); }); test('toggling hidePlayerHp does NOT clear activeCampaignId/activeEncounterId', async () => { seedAdminWithActiveEncounter(); render(); // wait for admin to mount + load active display await waitFor(() => screen.getByText('Camp'), { timeout: 3000 }); await selectCampaignByName('Camp'); // find the hide-player-HP toggle (role switch) const toggle = await screen.findByRole('switch', { name: /hide player hp/i }, { timeout: 3000 }); // toggle ON fireEvent.click(toggle); await waitFor(() => { const writes = getAdapterCalls().filter( c => c.fn === 'setDoc' && c.path.includes('activeDisplay/status') ); expect(writes.length).toBeGreaterThan(0); const last = writes[writes.length - 1]; // merge flag MUST be present — else plain setDoc clobbers other fields. expect(last.opts && last.opts.merge).toBe(true); // patch must NOT clobber activeCampaignId/activeEncounterId. // BUG: setDoc replace writes only {hidePlayerHp:true} → clobbers. // Fix: setDoc{merge} patch — other fields untouched. expect(last.data.hidePlayerHp).toBe(true); }, { timeout: 3000 }); }); });