feat: add sortie formation history
This commit is contained in:
@@ -132,6 +132,28 @@ try {
|
||||
sameJsonValue(firstResultSave.slot1?.battleHistory?.['first-battle-zhuo-commandery']?.sortiePerformance, firstResultPerformance),
|
||||
`Expected the first formation result snapshot to synchronize across report, history, current, and slot-1 saves: ${JSON.stringify(firstResultSave)}`
|
||||
);
|
||||
const expectedFirstSortieReview = {
|
||||
version: 1,
|
||||
score: firstResultState.sortieEvaluation.score,
|
||||
grade: firstResultState.sortieEvaluation.grade,
|
||||
activeBondCount: firstResultState.sortieEvaluation.activeBondCount,
|
||||
enemyCount: firstResultState.units.filter((unit) => unit.faction === 'enemy').length,
|
||||
sourcePresetIds: []
|
||||
};
|
||||
assert(
|
||||
sameJsonValue(firstResultSave.current?.firstBattleReport?.sortieReview, expectedFirstSortieReview) &&
|
||||
sameJsonValue(firstResultSave.slot1?.firstBattleReport?.sortieReview, expectedFirstSortieReview) &&
|
||||
sameJsonValue(firstResultSave.current?.battleHistory?.['first-battle-zhuo-commandery']?.sortieReview, expectedFirstSortieReview) &&
|
||||
sameJsonValue(firstResultSave.slot1?.battleHistory?.['first-battle-zhuo-commandery']?.sortieReview, expectedFirstSortieReview) &&
|
||||
firstResultSave.current.firstBattleReport.sortieReview.sourcePresetIds.length === 0,
|
||||
`Expected the first persisted sortie review to match the live grade, score, enemies, bonds, and empty preset attribution everywhere: ${JSON.stringify({
|
||||
expected: expectedFirstSortieReview,
|
||||
currentReport: firstResultSave.current?.firstBattleReport?.sortieReview,
|
||||
slotReport: firstResultSave.slot1?.firstBattleReport?.sortieReview,
|
||||
currentHistory: firstResultSave.current?.battleHistory?.['first-battle-zhuo-commandery']?.sortieReview,
|
||||
slotHistory: firstResultSave.slot1?.battleHistory?.['first-battle-zhuo-commandery']?.sortieReview
|
||||
})}`
|
||||
);
|
||||
|
||||
const firstEvaluationTogglePoint = await readBattleResultEvaluationControlPoint(page, 'toggle');
|
||||
await page.mouse.click(firstEvaluationTogglePoint.x, firstEvaluationTogglePoint.y);
|
||||
@@ -218,6 +240,7 @@ try {
|
||||
);
|
||||
|
||||
const firstCampEvaluation = firstCampProbe.state?.reportFormationEvaluation;
|
||||
const firstCampHistory = firstCampProbe.state?.reportFormationHistory;
|
||||
const firstCampEvaluationSaveBefore = await readCampaignSave(page);
|
||||
assert(
|
||||
firstCampEvaluation?.available === true &&
|
||||
@@ -229,6 +252,24 @@ try {
|
||||
firstCampEvaluation.toggleButtonBounds,
|
||||
`Expected the first camp report to expose the persisted battle formation evaluation: ${JSON.stringify(firstCampEvaluation)}`
|
||||
);
|
||||
assert(
|
||||
firstCampHistory?.available === true &&
|
||||
firstCampHistory.open === false &&
|
||||
firstCampHistory.recordCount === 1 &&
|
||||
firstCampHistory.page === 0 &&
|
||||
firstCampHistory.pageCount === 1 &&
|
||||
firstCampHistory.records?.length === 1 &&
|
||||
firstCampHistory.records[0].battleId === 'first-battle-zhuo-commandery' &&
|
||||
firstCampHistory.records[0].grade === expectedFirstSortieReview.grade &&
|
||||
firstCampHistory.records[0].score === expectedFirstSortieReview.score &&
|
||||
sameJsonValue(firstCampHistory.records[0].sourcePresetIds, expectedFirstSortieReview.sourcePresetIds) &&
|
||||
firstCampHistory.selectedBattleId === 'first-battle-zhuo-commandery' &&
|
||||
firstCampHistory.best?.battleId === 'first-battle-zhuo-commandery' &&
|
||||
firstCampHistory.loadableBest?.battleId === 'first-battle-zhuo-commandery' &&
|
||||
firstCampHistory.loadUndoAvailable === false &&
|
||||
firstCampHistory.toggleButtonBounds,
|
||||
`Expected the first camp report to expose one closed, persisted formation-history record and its best loadable sortie: ${JSON.stringify(firstCampHistory)}`
|
||||
);
|
||||
const firstCampEvaluationTogglePoint = await readCampReportFormationEvaluationControlPoint(page, 'toggle');
|
||||
await page.mouse.click(firstCampEvaluationTogglePoint.x, firstCampEvaluationTogglePoint.y);
|
||||
await page.waitForFunction(
|
||||
@@ -267,6 +308,338 @@ try {
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
|
||||
const firstCampHistoryMutationFixture = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
const campaign = scene?.campaign;
|
||||
const snapshot = campaign?.battleHistory?.['first-battle-zhuo-commandery']?.sortiePerformance;
|
||||
const targetId = snapshot?.selectedUnitIds?.find((unitId) => scene?.selectedSortieUnitIds?.includes(unitId));
|
||||
const historicalRole = targetId ? snapshot?.formationAssignments?.[targetId] : undefined;
|
||||
const divergentRole = ['front', 'flank', 'support', 'reserve'].find((role) => role !== historicalRole);
|
||||
if (
|
||||
!scene ||
|
||||
!campaign ||
|
||||
!snapshot?.selectedUnitIds?.length ||
|
||||
!targetId ||
|
||||
!historicalRole ||
|
||||
!divergentRole ||
|
||||
typeof scene.persistSortieSelection !== 'function' ||
|
||||
typeof scene.render !== 'function'
|
||||
) {
|
||||
return {
|
||||
ready: false,
|
||||
targetId: targetId ?? null,
|
||||
historicalRole: historicalRole ?? null,
|
||||
divergentRole: divergentRole ?? null
|
||||
};
|
||||
}
|
||||
|
||||
const before = {
|
||||
selectedUnitIds: [...scene.selectedSortieUnitIds],
|
||||
formationAssignments: { ...scene.sortieFormationAssignments },
|
||||
itemAssignments: structuredClone(scene.sortieItemAssignments ?? {}),
|
||||
focusedUnitId: scene.sortieFocusedUnitId,
|
||||
planFeedback: scene.sortiePlanFeedback,
|
||||
rosterScroll: scene.sortieRosterScroll
|
||||
};
|
||||
scene.sortieFormationAssignments = {
|
||||
...scene.sortieFormationAssignments,
|
||||
[targetId]: divergentRole
|
||||
};
|
||||
scene.persistSortieSelection();
|
||||
scene.render();
|
||||
return {
|
||||
ready: true,
|
||||
targetId,
|
||||
historicalRole,
|
||||
divergentRole,
|
||||
before,
|
||||
divergent: {
|
||||
selectedUnitIds: [...scene.selectedSortieUnitIds],
|
||||
formationAssignments: { ...scene.sortieFormationAssignments },
|
||||
itemAssignments: structuredClone(scene.sortieItemAssignments ?? {})
|
||||
}
|
||||
};
|
||||
});
|
||||
assert(
|
||||
firstCampHistoryMutationFixture.ready === true &&
|
||||
firstCampHistoryMutationFixture.historicalRole !== firstCampHistoryMutationFixture.divergentRole &&
|
||||
firstCampHistoryMutationFixture.divergent?.formationAssignments?.[firstCampHistoryMutationFixture.targetId] ===
|
||||
firstCampHistoryMutationFixture.divergentRole,
|
||||
`Expected a deterministic current-formation mismatch before exercising best-history loading: ${JSON.stringify(firstCampHistoryMutationFixture)}`
|
||||
);
|
||||
const firstCampHistorySaveBeforeOpen = await readCampaignSave(page);
|
||||
assert(
|
||||
sameJsonValue(
|
||||
firstCampHistorySaveBeforeOpen.current?.selectedSortieUnitIds,
|
||||
firstCampHistoryMutationFixture.divergent.selectedUnitIds
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistorySaveBeforeOpen.current?.sortieFormationAssignments,
|
||||
firstCampHistoryMutationFixture.divergent.formationAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistorySaveBeforeOpen.current?.sortieItemAssignments,
|
||||
firstCampHistoryMutationFixture.divergent.itemAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistorySaveBeforeOpen.slot1?.selectedSortieUnitIds,
|
||||
firstCampHistoryMutationFixture.divergent.selectedUnitIds
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistorySaveBeforeOpen.slot1?.sortieFormationAssignments,
|
||||
firstCampHistoryMutationFixture.divergent.formationAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistorySaveBeforeOpen.slot1?.sortieItemAssignments,
|
||||
firstCampHistoryMutationFixture.divergent.itemAssignments
|
||||
),
|
||||
`Expected the deliberate history-load mismatch to synchronize across current and slot-1 saves: ${JSON.stringify(firstCampHistorySaveBeforeOpen)}`
|
||||
);
|
||||
|
||||
const firstCampHistoryTogglePoint = await readCampReportFormationHistoryControlPoint(page, 'toggle');
|
||||
await page.mouse.click(firstCampHistoryTogglePoint.x, firstCampHistoryTogglePoint.y);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.reportFormationHistory?.open === true,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const firstCampHistoryOpenProbe = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
return {
|
||||
history: window.__HEROS_DEBUG__?.camp()?.reportFormationHistory,
|
||||
texts: (scene?.reportFormationHistoryObjects ?? [])
|
||||
.filter((object) => object?.type === 'Text')
|
||||
.map((object) => object.text)
|
||||
};
|
||||
});
|
||||
const firstCampHistorySaveOpen = await readCampaignSave(page);
|
||||
const firstCampHistoryLoadProjection = firstCampHistoryOpenProbe.history?.loadableBest;
|
||||
assert(
|
||||
firstCampHistoryOpenProbe.history?.open === true &&
|
||||
firstCampHistoryOpenProbe.history.recordCount === 1 &&
|
||||
firstCampHistoryOpenProbe.history.comparison?.matchesCurrent === false &&
|
||||
firstCampHistoryOpenProbe.history.closeButtonBounds &&
|
||||
firstCampHistoryOpenProbe.history.bestButtonBounds &&
|
||||
firstCampHistoryOpenProbe.history.loadBestButtonBounds &&
|
||||
firstCampHistoryOpenProbe.history.undoButtonBounds === null &&
|
||||
sameJsonValue(firstCampHistoryLoadProjection?.projectedSelectedUnitIds, firstResultPerformance.selectedUnitIds) &&
|
||||
firstResultPerformance.selectedUnitIds.every(
|
||||
(unitId) => firstCampHistoryLoadProjection?.projectedFormationAssignments?.[unitId] === firstResultPerformance.formationAssignments[unitId]
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryLoadProjection?.projectedItemAssignments,
|
||||
firstCampHistoryMutationFixture.divergent.itemAssignments
|
||||
) &&
|
||||
firstCampHistoryOpenProbe.texts.includes('편성 전적표') &&
|
||||
firstCampHistoryOpenProbe.texts.includes('최고 편성') &&
|
||||
firstCampHistoryOpenProbe.texts.includes('현재 출전안 vs 최고 편성') &&
|
||||
sameJsonValue(firstCampHistorySaveOpen, firstCampHistorySaveBeforeOpen),
|
||||
`Expected opening the first camp formation history to remain non-destructive and expose best/load controls: ${JSON.stringify(firstCampHistoryOpenProbe)}`
|
||||
);
|
||||
await page.screenshot({ path: `${screenshotDir}/rc-first-camp-formation-history.png`, fullPage: true });
|
||||
await assertCanvasPainted(page, 'first camp formation history');
|
||||
|
||||
const firstCampHistoryBestPoint = await readCampReportFormationHistoryControlPoint(page, 'best');
|
||||
await page.mouse.click(firstCampHistoryBestPoint.x, firstCampHistoryBestPoint.y);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.reportFormationHistory?.selectedBattleId === 'first-battle-zhuo-commandery',
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const firstCampHistorySaveAfterBest = await readCampaignSave(page);
|
||||
assert(
|
||||
sameJsonValue(firstCampHistorySaveAfterBest, firstCampHistorySaveBeforeOpen),
|
||||
`Expected selecting the best history card to leave campaign saves untouched: ${JSON.stringify(firstCampHistorySaveAfterBest)}`
|
||||
);
|
||||
|
||||
const firstCampHistoryLoadBestPoint = await readCampReportFormationHistoryControlPoint(page, 'loadBest');
|
||||
await page.mouse.click(firstCampHistoryLoadBestPoint.x, firstCampHistoryLoadBestPoint.y);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.reportFormationHistory?.loadUndoAvailable === true,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const firstCampHistoryAppliedState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
const firstCampHistoryAppliedSave = await readCampaignSave(page);
|
||||
assert(
|
||||
sameJsonValue(firstCampHistoryAppliedState?.selectedSortieUnitIds, firstCampHistoryLoadProjection.projectedSelectedUnitIds) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryAppliedState?.sortieFormationAssignments,
|
||||
firstCampHistoryLoadProjection.projectedFormationAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryAppliedState?.sortieItemAssignments,
|
||||
firstCampHistoryLoadProjection.projectedItemAssignments
|
||||
) &&
|
||||
firstCampHistoryAppliedState?.reportFormationHistory?.comparison?.matchesCurrent === true &&
|
||||
firstCampHistoryAppliedState?.reportFormationHistory?.loadUndoAvailable === true &&
|
||||
firstCampHistoryAppliedState?.reportFormationHistory?.undoButtonBounds &&
|
||||
firstCampHistoryAppliedState?.reportFormationHistory?.feedback?.includes('최고 편성 적용') &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryAppliedSave.current?.selectedSortieUnitIds,
|
||||
firstCampHistoryLoadProjection.projectedSelectedUnitIds
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryAppliedSave.slot1?.selectedSortieUnitIds,
|
||||
firstCampHistoryLoadProjection.projectedSelectedUnitIds
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryAppliedSave.current?.sortieFormationAssignments,
|
||||
firstCampHistoryAppliedState.sortieFormationAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryAppliedSave.slot1?.sortieFormationAssignments,
|
||||
firstCampHistoryAppliedState.sortieFormationAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryAppliedSave.current?.sortieItemAssignments,
|
||||
firstCampHistoryLoadProjection.projectedItemAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryAppliedSave.slot1?.sortieItemAssignments,
|
||||
firstCampHistoryLoadProjection.projectedItemAssignments
|
||||
) &&
|
||||
sameJsonValue(firstCampHistoryAppliedSave.current?.firstBattleReport?.sortieReview, expectedFirstSortieReview) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryAppliedSave.current?.battleHistory?.['first-battle-zhuo-commandery']?.sortieReview,
|
||||
expectedFirstSortieReview
|
||||
) &&
|
||||
sameJsonValue(firstCampHistoryAppliedSave.slot1?.firstBattleReport?.sortieReview, expectedFirstSortieReview) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryAppliedSave.slot1?.battleHistory?.['first-battle-zhuo-commandery']?.sortieReview,
|
||||
expectedFirstSortieReview
|
||||
),
|
||||
`Expected one best-history click to apply persisted names and roles, preserve common supplies, and keep the stored review immutable: ${JSON.stringify({
|
||||
state: firstCampHistoryAppliedState,
|
||||
save: firstCampHistoryAppliedSave
|
||||
})}`
|
||||
);
|
||||
|
||||
const firstCampHistoryUndoPoint = await readCampReportFormationHistoryControlPoint(page, 'undo');
|
||||
await page.mouse.click(firstCampHistoryUndoPoint.x, firstCampHistoryUndoPoint.y);
|
||||
await page.waitForFunction(
|
||||
(fixture) => {
|
||||
const state = window.__HEROS_DEBUG__?.camp();
|
||||
return state?.reportFormationHistory?.loadUndoAvailable === false &&
|
||||
JSON.stringify(state?.selectedSortieUnitIds) === JSON.stringify(fixture.divergent.selectedUnitIds) &&
|
||||
JSON.stringify(state?.sortieFormationAssignments) === JSON.stringify(fixture.divergent.formationAssignments) &&
|
||||
JSON.stringify(state?.sortieItemAssignments) === JSON.stringify(fixture.divergent.itemAssignments);
|
||||
},
|
||||
firstCampHistoryMutationFixture,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const firstCampHistoryUndoneState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
const firstCampHistoryUndoneSave = await readCampaignSave(page);
|
||||
assert(
|
||||
firstCampHistoryUndoneState?.reportFormationHistory?.open === true &&
|
||||
firstCampHistoryUndoneState.reportFormationHistory.loadUndoAvailable === false &&
|
||||
firstCampHistoryUndoneState.reportFormationHistory.undoButtonBounds === null &&
|
||||
firstCampHistoryUndoneState.reportFormationHistory.comparison?.matchesCurrent === false &&
|
||||
firstCampHistoryUndoneState.reportFormationHistory.feedback?.includes('되돌리고') &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryUndoneSave.current?.selectedSortieUnitIds,
|
||||
firstCampHistoryMutationFixture.divergent.selectedUnitIds
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryUndoneSave.current?.sortieFormationAssignments,
|
||||
firstCampHistoryMutationFixture.divergent.formationAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryUndoneSave.current?.sortieItemAssignments,
|
||||
firstCampHistoryMutationFixture.divergent.itemAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryUndoneSave.slot1?.selectedSortieUnitIds,
|
||||
firstCampHistoryMutationFixture.divergent.selectedUnitIds
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryUndoneSave.slot1?.sortieFormationAssignments,
|
||||
firstCampHistoryMutationFixture.divergent.formationAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryUndoneSave.slot1?.sortieItemAssignments,
|
||||
firstCampHistoryMutationFixture.divergent.itemAssignments
|
||||
) &&
|
||||
sameJsonValue(firstCampHistoryUndoneSave.current?.firstBattleReport?.sortieReview, expectedFirstSortieReview) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryUndoneSave.current?.battleHistory?.['first-battle-zhuo-commandery']?.sortieReview,
|
||||
expectedFirstSortieReview
|
||||
),
|
||||
`Expected the real history undo control to restore the exact prior names, roles, and supplies without touching the battle review: ${JSON.stringify({
|
||||
state: firstCampHistoryUndoneState,
|
||||
save: firstCampHistoryUndoneSave
|
||||
})}`
|
||||
);
|
||||
|
||||
const firstCampHistoryClosePoint = await readCampReportFormationHistoryControlPoint(page, 'close');
|
||||
await page.mouse.click(firstCampHistoryClosePoint.x, firstCampHistoryClosePoint.y);
|
||||
await page.waitForFunction(
|
||||
() => window.__HEROS_DEBUG__?.camp()?.reportFormationHistory?.open === false,
|
||||
undefined,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const firstCampHistoryClosedState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
assert(
|
||||
firstCampHistoryClosedState?.reportFormationHistory?.recordCount === 1 &&
|
||||
firstCampHistoryClosedState.reportFormationHistory.open === false &&
|
||||
firstCampHistoryClosedState.reportFormationHistory.closeButtonBounds === null &&
|
||||
firstCampHistoryClosedState.reportFormationHistory.toggleButtonBounds,
|
||||
`Expected the real history close control to return to the first camp report: ${JSON.stringify(firstCampHistoryClosedState?.reportFormationHistory)}`
|
||||
);
|
||||
|
||||
await page.evaluate((before) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
if (!scene || typeof scene.persistSortieSelection !== 'function' || typeof scene.render !== 'function') {
|
||||
throw new Error('Expected CampScene runtime methods while restoring the first-camp history fixture.');
|
||||
}
|
||||
scene.selectedSortieUnitIds = [...before.selectedUnitIds];
|
||||
scene.sortieFormationAssignments = { ...before.formationAssignments };
|
||||
scene.sortieItemAssignments = structuredClone(before.itemAssignments);
|
||||
scene.sortieFocusedUnitId = before.focusedUnitId;
|
||||
scene.sortiePlanFeedback = before.planFeedback;
|
||||
scene.sortieRosterScroll = before.rosterScroll;
|
||||
scene.persistSortieSelection();
|
||||
scene.render();
|
||||
}, firstCampHistoryMutationFixture.before);
|
||||
await page.waitForFunction(
|
||||
(before) => {
|
||||
const state = window.__HEROS_DEBUG__?.camp();
|
||||
return JSON.stringify(state?.selectedSortieUnitIds) === JSON.stringify(before.selectedUnitIds) &&
|
||||
JSON.stringify(state?.sortieFormationAssignments) === JSON.stringify(before.formationAssignments) &&
|
||||
JSON.stringify(state?.sortieItemAssignments) === JSON.stringify(before.itemAssignments);
|
||||
},
|
||||
firstCampHistoryMutationFixture.before,
|
||||
{ timeout: 30000 }
|
||||
);
|
||||
const firstCampHistoryRestoredSave = await readCampaignSave(page);
|
||||
assert(
|
||||
sameJsonValue(
|
||||
firstCampHistoryRestoredSave.current?.selectedSortieUnitIds,
|
||||
firstCampHistoryMutationFixture.before.selectedUnitIds
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryRestoredSave.current?.sortieFormationAssignments,
|
||||
firstCampHistoryMutationFixture.before.formationAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryRestoredSave.current?.sortieItemAssignments,
|
||||
firstCampHistoryMutationFixture.before.itemAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryRestoredSave.slot1?.selectedSortieUnitIds,
|
||||
firstCampHistoryMutationFixture.before.selectedUnitIds
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryRestoredSave.slot1?.sortieFormationAssignments,
|
||||
firstCampHistoryMutationFixture.before.formationAssignments
|
||||
) &&
|
||||
sameJsonValue(
|
||||
firstCampHistoryRestoredSave.slot1?.sortieItemAssignments,
|
||||
firstCampHistoryMutationFixture.before.itemAssignments
|
||||
),
|
||||
`Expected the RC-only mismatch fixture to restore the original first-camp sortie before continuing: ${JSON.stringify(firstCampHistoryRestoredSave)}`
|
||||
);
|
||||
|
||||
const firstCampSupplyMutationFixture = await page.evaluate(() => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
const state = window.__HEROS_DEBUG__?.camp();
|
||||
@@ -1396,6 +1769,9 @@ try {
|
||||
const midResultState = await page.evaluate(() => window.__HEROS_DEBUG__?.battle());
|
||||
const midResultSaveBeforeUpdate = await readCampaignSave(page);
|
||||
const midResultPerformance = midResultState?.sortieEvaluation?.snapshot;
|
||||
const midResultSortieReviewBeforePresetUpdate = structuredClone(
|
||||
midResultSaveBeforeUpdate.current?.battleHistory?.[midCampNextBattleId]?.sortieReview
|
||||
);
|
||||
const midContributorPerformance = midResultPerformance?.unitStats?.find((stats) => stats.unitId === midResultFixture.contributorId);
|
||||
const midBattleRoleById = Object.fromEntries(
|
||||
(midNextBattleProbe?.deployedAllyPositions ?? []).map((unit) => [unit.id, unit.formationRole])
|
||||
@@ -1438,6 +1814,20 @@ try {
|
||||
mobile: midResultSaveBeforeUpdate.current?.sortieFormationPresets?.mobile
|
||||
})}`
|
||||
);
|
||||
assert(
|
||||
midResultSortieReviewBeforePresetUpdate?.version === 1 &&
|
||||
midResultSortieReviewBeforePresetUpdate.score === midResultState?.sortieEvaluation?.score &&
|
||||
midResultSortieReviewBeforePresetUpdate.grade === midResultState?.sortieEvaluation?.grade &&
|
||||
midResultSortieReviewBeforePresetUpdate.activeBondCount === midResultState?.sortieEvaluation?.activeBondCount &&
|
||||
midResultSortieReviewBeforePresetUpdate.enemyCount === midNextBattleProbe.units.filter((unit) => unit.faction === 'enemy').length &&
|
||||
sameJsonValue(midResultSaveBeforeUpdate.current?.firstBattleReport?.sortieReview, midResultSortieReviewBeforePresetUpdate) &&
|
||||
sameJsonValue(midResultSaveBeforeUpdate.slot1?.firstBattleReport?.sortieReview, midResultSortieReviewBeforePresetUpdate) &&
|
||||
sameJsonValue(midResultSaveBeforeUpdate.slot1?.battleHistory?.[midCampNextBattleId]?.sortieReview, midResultSortieReviewBeforePresetUpdate),
|
||||
`Expected the mid-campaign result to persist one canonical sortie review before any result-screen preset update: ${JSON.stringify({
|
||||
review: midResultSortieReviewBeforePresetUpdate,
|
||||
evaluation: midResultState?.sortieEvaluation
|
||||
})}`
|
||||
);
|
||||
|
||||
const midEvaluationTogglePoint = await readBattleResultEvaluationControlPoint(page, 'toggle');
|
||||
await page.mouse.click(midEvaluationTogglePoint.x, midEvaluationTogglePoint.y);
|
||||
@@ -1515,6 +1905,10 @@ try {
|
||||
sameJsonValue(updatedMobileResultSave.slot1?.sortieItemAssignments, midResultSaveBeforeUpdate.slot1?.sortieItemAssignments) &&
|
||||
sameJsonValue(updatedMobileResultSave.current?.firstBattleReport?.sortiePerformance, midResultPerformance) &&
|
||||
sameJsonValue(updatedMobileResultSave.slot1?.battleHistory?.[midCampNextBattleId]?.sortiePerformance, midResultPerformance) &&
|
||||
sameJsonValue(updatedMobileResultSave.current?.firstBattleReport?.sortieReview, midResultSortieReviewBeforePresetUpdate) &&
|
||||
sameJsonValue(updatedMobileResultSave.slot1?.firstBattleReport?.sortieReview, midResultSortieReviewBeforePresetUpdate) &&
|
||||
sameJsonValue(updatedMobileResultSave.current?.battleHistory?.[midCampNextBattleId]?.sortieReview, midResultSortieReviewBeforePresetUpdate) &&
|
||||
sameJsonValue(updatedMobileResultSave.slot1?.battleHistory?.[midCampNextBattleId]?.sortieReview, midResultSortieReviewBeforePresetUpdate) &&
|
||||
sameJsonValue(updatedMobileResultSave.current?.sortieFormationPresets?.elite, midResultSaveBeforeUpdate.current?.sortieFormationPresets?.elite) &&
|
||||
sameJsonValue(updatedMobileResultSave.current?.sortieFormationPresets?.siege, midResultSaveBeforeUpdate.current?.sortieFormationPresets?.siege),
|
||||
`Expected result-to-preset update to leave the active sortie, supplies, report snapshot, and other presets unchanged: ${JSON.stringify({
|
||||
@@ -1543,6 +1937,7 @@ try {
|
||||
const midCampReviewState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
const midCampReviewSaveBefore = await readCampaignSave(page);
|
||||
const midCampFormationEvaluation = midCampReviewState?.reportFormationEvaluation;
|
||||
const midCampMobileCard = midCampFormationEvaluation?.presetCards?.find((card) => card.id === 'mobile');
|
||||
const midCampSiegeCard = midCampFormationEvaluation?.presetCards?.find((card) => card.id === 'siege');
|
||||
assert(
|
||||
midCampFormationEvaluation?.available === true &&
|
||||
@@ -1551,8 +1946,9 @@ try {
|
||||
midCampFormationEvaluation.grade === updatedMobileResultState?.sortieEvaluation?.grade &&
|
||||
midCampFormationEvaluation.score === updatedMobileResultState?.sortieEvaluation?.score &&
|
||||
sameJsonValue(midCampFormationEvaluation.snapshot, midResultPerformance) &&
|
||||
midCampFormationEvaluation.sourcePresetIds?.includes('mobile') === true &&
|
||||
midCampFormationEvaluation.sourcePresetIds?.includes('siege') === false &&
|
||||
sameJsonValue(midCampFormationEvaluation.sourcePresetIds, midResultSortieReviewBeforePresetUpdate.sourcePresetIds) &&
|
||||
midCampMobileCard?.saved === true &&
|
||||
midCampMobileCard.matching === true &&
|
||||
midCampSiegeCard?.saved === true &&
|
||||
midCampSiegeCard.matching === false &&
|
||||
midCampFormationEvaluation.toggleButtonBounds &&
|
||||
@@ -1605,7 +2001,10 @@ try {
|
||||
const pendingCampSiegeUpdateState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
const pendingCampSiegeUpdateSave = await readCampaignSave(page);
|
||||
assert(
|
||||
pendingCampSiegeUpdateState?.reportFormationEvaluation?.sourcePresetIds?.includes('siege') === false &&
|
||||
sameJsonValue(
|
||||
pendingCampSiegeUpdateState?.reportFormationEvaluation?.sourcePresetIds,
|
||||
midResultSortieReviewBeforePresetUpdate.sourcePresetIds
|
||||
) &&
|
||||
pendingCampSiegeUpdateState?.reportFormationEvaluation?.presetCards?.find((card) => card.id === 'siege')?.matching === false &&
|
||||
sameJsonValue(pendingCampSiegeUpdateSave, midCampReviewSaveBefore),
|
||||
`Expected the first camp siege update click to request confirmation without mutating either save: ${JSON.stringify({
|
||||
@@ -1620,7 +2019,7 @@ try {
|
||||
const evaluation = window.__HEROS_DEBUG__?.camp()?.reportFormationEvaluation;
|
||||
return evaluation?.open === true &&
|
||||
evaluation?.overwriteConfirmId === null &&
|
||||
evaluation?.sourcePresetIds?.includes('siege') &&
|
||||
evaluation?.presetCards?.find((card) => card.id === 'siege')?.matching === true &&
|
||||
evaluation?.feedback?.includes('갱신 완료');
|
||||
}, undefined, { timeout: 30000 });
|
||||
const updatedCampSiegeState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
|
||||
@@ -1645,6 +2044,10 @@ try {
|
||||
sameJsonValue(updatedCampSiegeSave.slot1?.sortieItemAssignments, midCampReviewSaveBefore.slot1?.sortieItemAssignments) &&
|
||||
sameJsonValue(updatedCampSiegeSave.current?.firstBattleReport?.sortiePerformance, midResultPerformance) &&
|
||||
sameJsonValue(updatedCampSiegeSave.slot1?.battleHistory?.[midCampNextBattleId]?.sortiePerformance, midResultPerformance) &&
|
||||
sameJsonValue(updatedCampSiegeSave.current?.firstBattleReport?.sortieReview, midResultSortieReviewBeforePresetUpdate) &&
|
||||
sameJsonValue(updatedCampSiegeSave.slot1?.firstBattleReport?.sortieReview, midResultSortieReviewBeforePresetUpdate) &&
|
||||
sameJsonValue(updatedCampSiegeSave.current?.battleHistory?.[midCampNextBattleId]?.sortieReview, midResultSortieReviewBeforePresetUpdate) &&
|
||||
sameJsonValue(updatedCampSiegeSave.slot1?.battleHistory?.[midCampNextBattleId]?.sortieReview, midResultSortieReviewBeforePresetUpdate) &&
|
||||
sameJsonValue(updatedCampSiegeSave.current?.sortieFormationPresets?.mobile, midCampReviewSaveBefore.current?.sortieFormationPresets?.mobile) &&
|
||||
sameJsonValue(updatedCampSiegeSave.current?.sortieFormationPresets?.elite, midCampReviewSaveBefore.current?.sortieFormationPresets?.elite) &&
|
||||
JSON.stringify(updatedCampSiegeState?.selectedSortieUnitIds) === JSON.stringify(midCampReviewState?.selectedSortieUnitIds) &&
|
||||
@@ -2078,6 +2481,50 @@ async function readCampReportFormationEvaluationControlPoint(page, control, pres
|
||||
return probe;
|
||||
}
|
||||
|
||||
async function readCampReportFormationHistoryControlPoint(page, control) {
|
||||
const probe = await page.evaluate((control) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
const history = window.__HEROS_DEBUG__?.camp()?.reportFormationHistory;
|
||||
const canvas = document.querySelector('canvas');
|
||||
const bounds = control === 'toggle'
|
||||
? history?.toggleButtonBounds
|
||||
: control === 'close'
|
||||
? history?.closeButtonBounds
|
||||
: control === 'best'
|
||||
? history?.bestButtonBounds
|
||||
: control === 'loadBest'
|
||||
? history?.loadBestButtonBounds
|
||||
: history?.undoButtonBounds;
|
||||
if (!scene || !canvas || !bounds) {
|
||||
return {
|
||||
ready: false,
|
||||
control,
|
||||
historyOpen: history?.open ?? false,
|
||||
loadUndoAvailable: history?.loadUndoAvailable ?? false,
|
||||
bounds: bounds ?? null
|
||||
};
|
||||
}
|
||||
|
||||
const canvasBounds = canvas.getBoundingClientRect();
|
||||
const centerX = bounds.x + bounds.width / 2;
|
||||
const centerY = bounds.y + bounds.height / 2;
|
||||
return {
|
||||
ready: true,
|
||||
control,
|
||||
historyOpen: history?.open ?? false,
|
||||
loadUndoAvailable: history?.loadUndoAvailable ?? false,
|
||||
bounds,
|
||||
x: canvasBounds.left + centerX * canvasBounds.width / scene.scale.width,
|
||||
y: canvasBounds.top + centerY * canvasBounds.height / scene.scale.height
|
||||
};
|
||||
}, control);
|
||||
assert(
|
||||
probe.ready === true && Number.isFinite(probe.x) && Number.isFinite(probe.y),
|
||||
`Expected a visible Phaser camp formation-history ${control} control with a canvas-scaled click point: ${JSON.stringify(probe)}`
|
||||
);
|
||||
return probe;
|
||||
}
|
||||
|
||||
async function readCampContentTextControlPoint(page, label, occurrence = 0) {
|
||||
const probe = await page.evaluate(({ label, occurrence }) => {
|
||||
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
|
||||
@@ -2249,6 +2696,7 @@ async function seedCampaignSave(page, options) {
|
||||
createdAt: now
|
||||
};
|
||||
delete report.sortiePerformance;
|
||||
delete report.sortieReview;
|
||||
const settlement = {
|
||||
battleId: report.battleId,
|
||||
battleTitle: report.battleTitle,
|
||||
|
||||
Reference in New Issue
Block a user