fix(BUG-4): updateDoc patch on activeDisplay (not setDoc replace)
All 5 storage.setDoc(activeDisplay, {...}, {merge:true}) →
storage.updateDoc(activeDisplay, {...}).
setDoc merge:true worked in prod (firebase honors merge) but ws adapter
+ mock ignore opts arg entirely → clobbers doc. updateDoc uses PATCH
across all adapters (firebase real updateDoc, ws PATCH endpoint, mock
merge). Consistent, no clobber.
Sites fixed:
- hidePlayerHp toggle
- startEncounter (set active ids)
- endEncounter (null active ids)
- deactivate active display
- activate new display
TDD: HideHpToggle.test RED first (assert updateDoc patch, impl still
setDoc → 0 calls found). GREEN after switch.
Char tests updated: Encounter.characterization (2) + Combat.characterization
(2) assert updateDoc on activeDisplay, not setDoc.
BUG-4: prod was already fixed (merge:true), test was RED due to mock
ignoring opts. Now all 3 adapters consistent via updateDoc.
This commit is contained in:
@@ -41,7 +41,7 @@ describe('Combat -> Firebase', () => {
|
||||
test('startEncounter: also sets activeDisplay to this encounter', async () => {
|
||||
await setupWithMonsters();
|
||||
await startCombatViaUI();
|
||||
const adCalls = findCallActiveDisplay('setDoc');
|
||||
const adCalls = findCallActiveDisplay('updateDoc');
|
||||
const last = adCalls[adCalls.length - 1];
|
||||
expect(last.data.activeCampaignId).toBeTruthy();
|
||||
expect(last.data.activeEncounterId).toBeTruthy();
|
||||
@@ -111,26 +111,26 @@ describe('Combat -> Firebase', () => {
|
||||
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 adCalls = findCallActiveDisplay('updateDoc');
|
||||
const last = adCalls[adCalls.length - 1];
|
||||
return last && last.data.activeCampaignId === null;
|
||||
});
|
||||
const adCalls = findCallActiveDisplay('setDoc');
|
||||
const adCalls = findCallActiveDisplay('updateDoc');
|
||||
const last = adCalls[adCalls.length - 1];
|
||||
expect(last.data).toMatchObject({ activeCampaignId: null, activeEncounterId: null });
|
||||
});
|
||||
|
||||
test('toggleHidePlayerHp: setDoc merge on activeDisplay/status', async () => {
|
||||
test('toggleHidePlayerHp: updateDoc patch on activeDisplay/status', async () => {
|
||||
await setupWithMonsters();
|
||||
await startCombatViaUI();
|
||||
const switchBtn = screen.getByRole('switch');
|
||||
fireEvent.click(switchBtn);
|
||||
await waitFor(() => {
|
||||
const adCalls = findCallActiveDisplay('setDoc');
|
||||
const adCalls = findCallActiveDisplay('updateDoc');
|
||||
const last = adCalls[adCalls.length - 1];
|
||||
return last && 'hidePlayerHp' in last.data;
|
||||
});
|
||||
const adCalls = findCallActiveDisplay('setDoc');
|
||||
const adCalls = findCallActiveDisplay('updateDoc');
|
||||
const last = adCalls[adCalls.length - 1];
|
||||
expect(last.data).toHaveProperty('hidePlayerHp');
|
||||
});
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('Encounter -> Firebase', () => {
|
||||
expect(call.path).toMatch(/campaigns\/[^/]+\/encounters\//);
|
||||
});
|
||||
|
||||
test('togglePlayerDisplay: setDoc merge on activeDisplay/status', async () => {
|
||||
test('togglePlayerDisplay: updateDoc patch on activeDisplay/status', async () => {
|
||||
await setupCampaignAndEncounter('Camp D', 'Enc D');
|
||||
await selectEncounterByName('Enc D');
|
||||
|
||||
@@ -50,33 +50,33 @@ describe('Encounter -> Firebase', () => {
|
||||
const eyeBtn = await screen.findByTitle('Activate for Player Display');
|
||||
fireEvent.click(eyeBtn);
|
||||
|
||||
await waitFor(() => findCall('setDoc', 'activeDisplay/status'));
|
||||
const call = findCall('setDoc', 'activeDisplay/status');
|
||||
// activeDisplay/status setDoc is called with merge option in App
|
||||
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: setDoc nulls active ids', async () => {
|
||||
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('setDoc', 'activeDisplay/status'));
|
||||
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('setDoc', 'activeDisplay/status');
|
||||
const calls = findCalls('updateDoc', 'activeDisplay/status');
|
||||
const last = calls[calls.length - 1];
|
||||
return last.data.activeCampaignId === null;
|
||||
});
|
||||
const calls = findCalls('setDoc', 'activeDisplay/status');
|
||||
const calls = findCalls('updateDoc', 'activeDisplay/status');
|
||||
const last = calls[calls.length - 1];
|
||||
expect(last.data).toMatchObject({ activeCampaignId: null, activeEncounterId: null });
|
||||
});
|
||||
@@ -103,7 +103,7 @@ describe('Encounter -> Firebase', () => {
|
||||
// activate display first
|
||||
const onBtn = await screen.findByTitle('Activate for Player Display');
|
||||
fireEvent.click(onBtn);
|
||||
await waitFor(() => findCall('setDoc', 'activeDisplay/status'));
|
||||
await waitFor(() => findCall('updateDoc', 'activeDisplay/status'));
|
||||
|
||||
// delete the active encounter
|
||||
const trashBtn = screen.getAllByTitle('Delete Encounter')[0];
|
||||
|
||||
@@ -50,14 +50,14 @@ describe('BUG-4: hide-player-HP toggle preserves activeDisplay', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
const writes = getAdapterCalls().filter(
|
||||
c => c.fn === 'setDoc' && c.path.includes('activeDisplay/status')
|
||||
c => c.fn === 'updateDoc' && 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');
|
||||
// patch must NOT clobber activeCampaignId/activeEncounterId.
|
||||
// BUG: setDoc replace writes only {hidePlayerHp:true} → clobbers.
|
||||
// Fix: updateDoc patch — other fields untouched.
|
||||
expect(last.patch.hidePlayerHp).toBe(true);
|
||||
}, { timeout: 3000 });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user