feat: add protected manual save records
This commit is contained in:
@@ -43,6 +43,7 @@ const campaignGeneration = 4;
|
||||
const staleGeneration = 3;
|
||||
const campaignKey = 'heros-web:campaign-state';
|
||||
const campaignSlotKey = `${campaignKey}:slot-1`;
|
||||
const manualSaveSlotKey = 'heros-web:manual-save:slot-1';
|
||||
const battleBaseKey = `heros-web:battle:${battleId}`;
|
||||
const battleSlotKey = `${battleBaseKey}:slot-1`;
|
||||
const legacyBattleKey = 'heros-web:first-battle-state';
|
||||
@@ -373,9 +374,16 @@ try {
|
||||
`${renderer}: removal refusal escaped the intended battle keys.`
|
||||
);
|
||||
|
||||
await page.evaluate(() => {
|
||||
window.__HEROS_BATTLE_GENERATION_FIXTURE__
|
||||
?.restoreRemovalInterceptor?.();
|
||||
});
|
||||
|
||||
const beforeControl = await page.evaluate(
|
||||
({
|
||||
requestedBattleSlotKey,
|
||||
requestedCampaignSlotKey,
|
||||
requestedManualSaveSlotKey,
|
||||
requestedCampaignGeneration
|
||||
}) => {
|
||||
const scene = window.__HEROS_DEBUG__?.scene('BattleScene');
|
||||
@@ -389,7 +397,28 @@ try {
|
||||
requestedBattleSlotKey,
|
||||
JSON.stringify(fixtureState.valid)
|
||||
);
|
||||
const campaign = JSON.parse(
|
||||
window.localStorage.getItem(
|
||||
requestedCampaignSlotKey
|
||||
) ?? 'null'
|
||||
);
|
||||
if (!campaign) {
|
||||
throw new Error('The campaign fixture is unavailable for the protected manual control.');
|
||||
}
|
||||
window.localStorage.setItem(
|
||||
requestedManualSaveSlotKey,
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
slot: 1,
|
||||
savedAt: fixtureState.valid.savedAt,
|
||||
context: 'battle',
|
||||
campaign,
|
||||
battle: fixtureState.valid
|
||||
})
|
||||
);
|
||||
const accepted = scene.readBattleSaveState?.(1, true);
|
||||
const acceptedManual =
|
||||
scene.readManualBattleSaveState?.(1, true);
|
||||
const loadDisabled =
|
||||
scene.isMapMenuActionDisabled?.('load') === true;
|
||||
scene.showSaveSlotPanel?.('load');
|
||||
@@ -404,6 +433,8 @@ try {
|
||||
accepted?.battleResumeGeneration ?? null,
|
||||
acceptedTurnNumber:
|
||||
accepted?.turnNumber ?? null,
|
||||
acceptedManualTurnNumber:
|
||||
acceptedManual?.turnNumber ?? null,
|
||||
loadDisabled,
|
||||
panelMode: panel?.saveSlotPanelMode ?? null,
|
||||
confirmationMode:
|
||||
@@ -415,6 +446,8 @@ try {
|
||||
},
|
||||
{
|
||||
requestedBattleSlotKey: battleSlotKey,
|
||||
requestedCampaignSlotKey: campaignSlotKey,
|
||||
requestedManualSaveSlotKey: manualSaveSlotKey,
|
||||
requestedCampaignGeneration: campaignGeneration
|
||||
}
|
||||
);
|
||||
@@ -428,6 +461,10 @@ try {
|
||||
beforeControl.acceptedTurnNumber,
|
||||
fixture.validTurnNumber
|
||||
);
|
||||
assert.equal(
|
||||
beforeControl.acceptedManualTurnNumber,
|
||||
fixture.validTurnNumber
|
||||
);
|
||||
assert.equal(beforeControl.loadDisabled, false);
|
||||
assert.equal(beforeControl.panelMode, 'load');
|
||||
assert.equal(beforeControl.confirmationMode, 'load-confirm');
|
||||
@@ -437,29 +474,70 @@ try {
|
||||
const scene = window.__HEROS_DEBUG__?.scene('BattleScene');
|
||||
scene?.activateTurnPromptPrimaryAction?.();
|
||||
});
|
||||
await page.waitForFunction(
|
||||
({
|
||||
expectedBattleId,
|
||||
expectedTurnNumber,
|
||||
expectedMarkerHp
|
||||
}) => {
|
||||
const battle = window.__HEROS_DEBUG__?.battle?.();
|
||||
return (
|
||||
battle?.battleId === expectedBattleId &&
|
||||
battle?.turnNumber === expectedTurnNumber &&
|
||||
battle?.activeFaction === 'ally' &&
|
||||
battle?.phase === 'idle' &&
|
||||
battle?.units?.find((unit) => unit.id === 'liu-bei')?.hp ===
|
||||
expectedMarkerHp
|
||||
);
|
||||
},
|
||||
{
|
||||
expectedBattleId: battleId,
|
||||
expectedTurnNumber: fixture.validTurnNumber,
|
||||
expectedMarkerHp: fixture.validMarkerHp
|
||||
},
|
||||
{ timeout: 90000 }
|
||||
);
|
||||
try {
|
||||
await page.waitForFunction(
|
||||
({
|
||||
expectedBattleId,
|
||||
expectedTurnNumber,
|
||||
expectedMarkerHp
|
||||
}) => {
|
||||
const battle = window.__HEROS_DEBUG__?.battle?.();
|
||||
return (
|
||||
battle?.battleId === expectedBattleId &&
|
||||
battle?.turnNumber === expectedTurnNumber &&
|
||||
battle?.activeFaction === 'ally' &&
|
||||
battle?.phase === 'idle' &&
|
||||
battle?.units?.find((unit) => unit.id === 'liu-bei')?.hp ===
|
||||
expectedMarkerHp
|
||||
);
|
||||
},
|
||||
{
|
||||
expectedBattleId: battleId,
|
||||
expectedTurnNumber: fixture.validTurnNumber,
|
||||
expectedMarkerHp: fixture.validMarkerHp
|
||||
},
|
||||
{ timeout: 90000 }
|
||||
);
|
||||
} catch (error) {
|
||||
const restoreDiagnostics = await page.evaluate(
|
||||
({
|
||||
requestedCampaignSlotKey,
|
||||
requestedBattleSlotKey,
|
||||
requestedManualSaveSlotKey
|
||||
}) => ({
|
||||
battle:
|
||||
window.__HEROS_DEBUG__?.battle?.() ?? null,
|
||||
campaign: JSON.parse(
|
||||
window.localStorage.getItem(
|
||||
requestedCampaignSlotKey
|
||||
) ?? 'null'
|
||||
),
|
||||
battleSave: JSON.parse(
|
||||
window.localStorage.getItem(
|
||||
requestedBattleSlotKey
|
||||
) ?? 'null'
|
||||
),
|
||||
manualSave: JSON.parse(
|
||||
window.localStorage.getItem(
|
||||
requestedManualSaveSlotKey
|
||||
) ?? 'null'
|
||||
)
|
||||
}),
|
||||
{
|
||||
requestedCampaignSlotKey: campaignSlotKey,
|
||||
requestedBattleSlotKey: battleSlotKey,
|
||||
requestedManualSaveSlotKey: manualSaveSlotKey
|
||||
}
|
||||
);
|
||||
throw new Error(
|
||||
`${renderer}: protected manual restore did not become ready: ${JSON.stringify({
|
||||
restoreDiagnostics,
|
||||
pageErrors,
|
||||
consoleErrors
|
||||
})}`,
|
||||
{ cause: error }
|
||||
);
|
||||
}
|
||||
|
||||
const restored = await page.evaluate(
|
||||
({
|
||||
@@ -687,6 +765,16 @@ try {
|
||||
},
|
||||
`${renderer}: ally action autosave`
|
||||
);
|
||||
assert.equal(
|
||||
actionAutosave.autosave.indicatorVisible,
|
||||
true,
|
||||
`${renderer}: a successful ally-action autosave did not expose visible save feedback.`
|
||||
);
|
||||
assert.match(
|
||||
actionAutosave.autosave.indicatorText ?? '',
|
||||
/자동 저장 완료/,
|
||||
`${renderer}: the success save indicator was not clearly labeled.`
|
||||
);
|
||||
|
||||
const unsafeAutosaves = [];
|
||||
for (const [
|
||||
@@ -1099,6 +1187,16 @@ try {
|
||||
rollbackProbe.autosave.lastSavedAt,
|
||||
rollbackBaseline.state.savedAt
|
||||
);
|
||||
assert.equal(
|
||||
rollbackProbe.autosave.indicatorVisible,
|
||||
true,
|
||||
`${renderer}: a failed autosave did not expose a persistent visible warning.`
|
||||
);
|
||||
assert.match(
|
||||
rollbackProbe.autosave.indicatorText ?? '',
|
||||
/자동 저장 실패/,
|
||||
`${renderer}: the failed autosave warning was not clearly labeled.`
|
||||
);
|
||||
assert.equal(pageErrors.length, 0, pageErrors.join('\n'));
|
||||
assert.equal(consoleErrors.length, 0, consoleErrors.join('\n'));
|
||||
|
||||
@@ -1313,6 +1411,15 @@ function assertAutosaveDebugState(
|
||||
),
|
||||
`${label}.lastSavedAt is invalid.`
|
||||
);
|
||||
assert(
|
||||
typeof autosave.indicatorVisible === 'boolean',
|
||||
`${label}.indicatorVisible must be boolean.`
|
||||
);
|
||||
assert(
|
||||
autosave.indicatorText === null ||
|
||||
typeof autosave.indicatorText === 'string',
|
||||
`${label}.indicatorText is invalid.`
|
||||
);
|
||||
}
|
||||
|
||||
function assertAutosaveWrite(
|
||||
|
||||
Reference in New Issue
Block a user