Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea17c5e26e | ||
|
|
23aa99ba0a |
@@ -139,6 +139,55 @@ describe('character writeback on endEncounter', () => {
|
|||||||
expect(fighterOld.defaultCurrentHp).toBe(30);
|
expect(fighterOld.defaultCurrentHp).toBe(30);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('writeback uses ctx.campaignPath when provided (firebase prefixed paths)', async () => {
|
||||||
|
const storage = makeMockStorage();
|
||||||
|
const players = [{ id: 'char1', name: 'Fighter', defaultMaxHp: 30, defaultAc: 17, defaultCurrentHp: 30 }];
|
||||||
|
const prefixed = 'artifacts/app1/public/data/campaigns/camp1';
|
||||||
|
storage._docs.set(prefixed, { id: 'camp1', name: 'Camp', players, syncCharacters: true });
|
||||||
|
const enc = makeEncounter();
|
||||||
|
const ctx = {
|
||||||
|
storage,
|
||||||
|
encounterPath: 'campaigns/camp1/encounters/enc1',
|
||||||
|
campaignPath: prefixed,
|
||||||
|
logPath: 'logs/log1',
|
||||||
|
logCollection: 'logs',
|
||||||
|
displayPath: 'activeDisplay/status',
|
||||||
|
};
|
||||||
|
await endEncounter(enc, ctx);
|
||||||
|
expect(storage._writes.find(w => w.path === 'campaigns/camp1')).toBeUndefined();
|
||||||
|
const campWrite = storage._writes.find(w => w.path === prefixed);
|
||||||
|
expect(campWrite).toBeDefined();
|
||||||
|
expect(campWrite.patch.players.find(p => p.id === 'char1').defaultMaxHp).toBe(28);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('writeback failure does not block ending combat', async () => {
|
||||||
|
const storage = makeMockStorage();
|
||||||
|
const players = [{ id: 'char1', name: 'Fighter', defaultMaxHp: 30, defaultAc: 17, defaultCurrentHp: 30 }];
|
||||||
|
storage._docs.set('campaigns/camp1', { id: 'camp1', name: 'Camp', players, syncCharacters: true });
|
||||||
|
storage.updateDoc = jest.fn(async (p, patch) => {
|
||||||
|
if (p === 'campaigns/camp1') throw new Error('No document to update');
|
||||||
|
storage._writes.push({ path: p, patch });
|
||||||
|
});
|
||||||
|
const enc = makeEncounter();
|
||||||
|
const ctx = {
|
||||||
|
storage,
|
||||||
|
encounterPath: 'campaigns/camp1/encounters/enc1',
|
||||||
|
encPath: 'campaigns/camp1/encounters/enc1',
|
||||||
|
logPath: 'logs',
|
||||||
|
logCollection: 'logs',
|
||||||
|
displayPath: 'activeDisplay/status',
|
||||||
|
};
|
||||||
|
const newEnc = await endEncounter(enc, ctx);
|
||||||
|
expect(newEnc.isStarted).toBe(false);
|
||||||
|
expect(newEnc.currentTurnParticipantId).toBe(null);
|
||||||
|
// participants are preserved — ending combat never clears the roster
|
||||||
|
expect(newEnc.participants).toHaveLength(3);
|
||||||
|
const encWrite = storage._writes.find(w => w.path === 'campaigns/camp1/encounters/enc1');
|
||||||
|
expect(encWrite).toBeDefined();
|
||||||
|
expect(encWrite.patch.isStarted).toBe(false);
|
||||||
|
expect(encWrite.patch.participants).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
test('writeback no-op if character missing from campaign', async () => {
|
test('writeback no-op if character missing from campaign', async () => {
|
||||||
const storage = makeMockStorage();
|
const storage = makeMockStorage();
|
||||||
storage._docs.set('campaigns/camp1', { id: 'camp1', name: 'Camp', players: [], syncCharacters: true });
|
storage._docs.set('campaigns/camp1', { id: 'camp1', name: 'Camp', players: [], syncCharacters: true });
|
||||||
|
|||||||
+11
-3
@@ -1129,11 +1129,15 @@ async function endEncounter(encounter, ctx) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Character writeback: sync maxHp/ac/currentHp to campaign players if enabled.
|
// Character writeback: sync maxHp/ac/currentHp to campaign players if enabled.
|
||||||
|
// ctx.campaignPath: adapter-specific doc path (firebase needs the full
|
||||||
|
// artifacts/{APP_ID}/public/data prefix); bare campaigns/{id} fallback for
|
||||||
|
// server/test adapters. Writeback failure must not block ending combat.
|
||||||
const wbCampaignId = encounter.campaignId || ctx.campaignId;
|
const wbCampaignId = encounter.campaignId || ctx.campaignId;
|
||||||
if ((wbCampaignId || ctx.campaign) && ctx.storage) {
|
if ((wbCampaignId || ctx.campaign) && ctx.storage) {
|
||||||
|
const campaignPath = ctx.campaignPath || `campaigns/${wbCampaignId}`;
|
||||||
let campaign;
|
let campaign;
|
||||||
try {
|
try {
|
||||||
campaign = ctx.campaign || await ctx.storage.getDoc(`campaigns/${wbCampaignId}`);
|
campaign = ctx.campaign || await ctx.storage.getDoc(campaignPath);
|
||||||
} catch { campaign = null; }
|
} catch { campaign = null; }
|
||||||
if (campaign && campaign.syncCharacters && Array.isArray(campaign.players)) {
|
if (campaign && campaign.syncCharacters && Array.isArray(campaign.players)) {
|
||||||
const charParticipants = (encounter.participants || []).filter(p => p.originalCharacterId);
|
const charParticipants = (encounter.participants || []).filter(p => p.originalCharacterId);
|
||||||
@@ -1153,8 +1157,12 @@ async function endEncounter(encounter, ctx) {
|
|||||||
return newPlayer;
|
return newPlayer;
|
||||||
});
|
});
|
||||||
if (changed) {
|
if (changed) {
|
||||||
await ctx.storage.updateDoc(`campaigns/${wbCampaignId}`, { players: updatedPlayers });
|
try {
|
||||||
log.undo.characterWriteback = oldValues;
|
await ctx.storage.updateDoc(campaignPath, { players: updatedPlayers });
|
||||||
|
log.undo.characterWriteback = oldValues;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`endEncounter: character writeback failed (${campaignPath}):`, err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -2281,8 +2281,9 @@ function InitiativeControls({ campaignId, encounter, encounterPath }) {
|
|||||||
try {
|
try {
|
||||||
const ctx = { ...buildCtx(encounterPath), campaignId };
|
const ctx = { ...buildCtx(encounterPath), campaignId };
|
||||||
if (campaignId) {
|
if (campaignId) {
|
||||||
|
ctx.campaignPath = getPath.campaign(campaignId);
|
||||||
try {
|
try {
|
||||||
ctx.campaign = await storage.getDoc(getPath.campaign(campaignId));
|
ctx.campaign = await storage.getDoc(ctx.campaignPath);
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
await endEncounter(encounter, ctx);
|
await endEncounter(encounter, ctx);
|
||||||
|
|||||||
Reference in New Issue
Block a user