tests: BUG-4 RED locked (hide-HP clobbers activeDisplay), add write recorder
src/tests/HideHpToggle.test.js: renders App, selects campaign, toggles
hide-player-HP switch, asserts setDoc data includes activeCampaignId +
activeEncounterId. RED: data only {hidePlayerHp:true}, both clobbered.
Root cause proven with evidence (recorder):
setDoc(activeDisplay/status, {hidePlayerHp:true}, {merge:true})
data written = {hidePlayerHp:true} ONLY
activeCampaignId = undefined
activeEncounterId = undefined
setDoc = replace per contract. {merge:true} arg ignored. Toggle wipes
encounter pointer → DisplayView reads null → 'Game Session Paused'.
Fix: use updateDoc (patch), not setDoc.
src/storage/firebase.js: adapter recorder now captures setDoc + updateDoc
(data + opts). Was subscribe-only. Enables write-path assertions.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
// BUG-4 repro: toggling hidePlayerHp must not clobber activeDisplay doc.
|
||||
// setDoc = replace (contract). {merge:true} arg ignored.
|
||||
// Toggling hide-HP writes {hidePlayerHp:X} alone → activeCampaignId + activeEncounterId → null.
|
||||
// Display reads null → "Game Session Paused". Recover requires re-activating encounter.
|
||||
// Fix: use updateDoc (patch), not setDoc.
|
||||
|
||||
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(<App />);
|
||||
|
||||
// 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/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];
|
||||
// data written must include activeCampaignId AND activeEncounterId
|
||||
// BUG: writes only {hidePlayerHp:true}, clobbering them.
|
||||
expect(last.data.activeCampaignId).toBe('c1');
|
||||
expect(last.data.activeEncounterId).toBe('e1');
|
||||
}, { timeout: 3000 });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user