feat: add sortie formation history
This commit is contained in:
@@ -36,6 +36,7 @@ try {
|
||||
normalizeCampaignSortieFormationPresets,
|
||||
normalizeCampaignSortieItemAssignments,
|
||||
normalizeCampaignSortiePerformanceSnapshot,
|
||||
normalizeCampaignSortieReviewSnapshot,
|
||||
resetCampaignState,
|
||||
saveCampaignState,
|
||||
setCampaignState,
|
||||
@@ -256,6 +257,49 @@ try {
|
||||
normalizeCampaignSortiePerformanceSnapshot({ selectedUnitIds: [], unitStats: [] }) === undefined,
|
||||
`Expected sortie performance snapshots to stay capped and empty snapshots to be discarded: ${JSON.stringify(cappedSortiePerformance)}`
|
||||
);
|
||||
const normalizedSortieReview = normalizeCampaignSortieReviewSnapshot(
|
||||
{
|
||||
version: 1,
|
||||
score: '88.9',
|
||||
grade: 'D',
|
||||
activeBondCount: '999',
|
||||
enemyCount: '1000',
|
||||
sourcePresetIds: ['siege', 'elite', 'siege', 'ghost', 'mobile', 7],
|
||||
sortieItemAssignments: { 'liu-bei': { bean: 2 } }
|
||||
},
|
||||
normalizedSortiePerformance
|
||||
);
|
||||
assert(
|
||||
normalizedSortieReview?.version === 1 &&
|
||||
normalizedSortieReview.score === 88 &&
|
||||
normalizedSortieReview.grade === 'A' &&
|
||||
normalizedSortieReview.activeBondCount === 96 &&
|
||||
normalizedSortieReview.enemyCount === 96 &&
|
||||
JSON.stringify(normalizedSortieReview.sourcePresetIds) === JSON.stringify(['elite', 'mobile', 'siege']) &&
|
||||
!Object.prototype.hasOwnProperty.call(normalizedSortieReview, 'sortieItemAssignments'),
|
||||
`Expected sortie reviews to clamp evaluator inputs, derive grade from score, canonicalize known preset ids, and exclude unrelated fields: ${JSON.stringify(normalizedSortieReview)}`
|
||||
);
|
||||
assert(
|
||||
normalizeCampaignSortieReviewSnapshot({ ...normalizedSortieReview, version: 2 }, normalizedSortiePerformance) === undefined &&
|
||||
normalizeCampaignSortieReviewSnapshot({ score: 88, grade: 'A', sourcePresetIds: ['elite'] }, normalizedSortiePerformance) === undefined &&
|
||||
normalizeCampaignSortieReviewSnapshot(normalizedSortieReview) === undefined &&
|
||||
normalizeCampaignSortieReviewSnapshot(normalizedSortieReview, {
|
||||
selectedUnitIds: ['liu-bei'],
|
||||
formationAssignments: {},
|
||||
unitStats: []
|
||||
}) === undefined,
|
||||
'Expected sortie reviews with unsupported/missing nested versions or missing/incomplete performance snapshots to be discarded'
|
||||
);
|
||||
const sortieReviewGradeBoundaries = [[90, 'S'], [75, 'A'], [60, 'B'], [45, 'C'], [44, 'D']];
|
||||
assert(
|
||||
sortieReviewGradeBoundaries.every(([score, grade]) => (
|
||||
normalizeCampaignSortieReviewSnapshot(
|
||||
{ ...normalizedSortieReview, score, grade: 'D' },
|
||||
normalizedSortiePerformance
|
||||
)?.grade === grade
|
||||
)),
|
||||
`Expected persisted sortie review grades to match evaluator boundaries: ${JSON.stringify(sortieReviewGradeBoundaries)}`
|
||||
);
|
||||
|
||||
storage.clear();
|
||||
storage.set(
|
||||
@@ -413,6 +457,14 @@ try {
|
||||
formationAssignments: { [performanceUnit.id]: 'front' },
|
||||
unitStats: [sortieStatsFixture(performanceUnit.id, { hpBefore: performanceUnit.maxHp, maxHpBefore: performanceUnit.maxHp, damageDealt: 24 })]
|
||||
},
|
||||
sortieReview: {
|
||||
version: 1,
|
||||
score: 88,
|
||||
grade: 'A',
|
||||
activeBondCount: 2,
|
||||
enemyCount: 4,
|
||||
sourcePresetIds: ['elite', 'mobile']
|
||||
},
|
||||
createdAt: '2026-07-03T09:05:00.000Z'
|
||||
};
|
||||
resetCampaignState();
|
||||
@@ -420,29 +472,51 @@ try {
|
||||
performanceReport.sortiePerformance.selectedUnitIds.push('ghost-unit');
|
||||
performanceReport.sortiePerformance.formationAssignments[performanceUnit.id] = 'support';
|
||||
performanceReport.sortiePerformance.unitStats[0].damageDealt = 999;
|
||||
performanceReport.sortieReview.score = 1;
|
||||
performanceReport.sortieReview.grade = 'D';
|
||||
performanceReport.sortieReview.sourcePresetIds.push('siege');
|
||||
const clonedPerformanceSettlement = getCampaignState();
|
||||
const clonedReportPerformance = clonedPerformanceSettlement.firstBattleReport?.sortiePerformance;
|
||||
const clonedHistoryPerformance = clonedPerformanceSettlement.battleHistory[firstScenario.id]?.sortiePerformance;
|
||||
const clonedReportReview = clonedPerformanceSettlement.firstBattleReport?.sortieReview;
|
||||
const clonedHistoryReview = clonedPerformanceSettlement.battleHistory[firstScenario.id]?.sortieReview;
|
||||
assert(
|
||||
JSON.stringify(clonedReportPerformance?.selectedUnitIds) === JSON.stringify([performanceUnit.id]) &&
|
||||
clonedReportPerformance.formationAssignments[performanceUnit.id] === 'front' &&
|
||||
clonedReportPerformance.unitStats[0].damageDealt === 24 &&
|
||||
JSON.stringify(clonedHistoryPerformance?.selectedUnitIds) === JSON.stringify([performanceUnit.id]) &&
|
||||
clonedHistoryPerformance.formationAssignments[performanceUnit.id] === 'front' &&
|
||||
clonedHistoryPerformance.unitStats[0].damageDealt === 24,
|
||||
`Expected battle report settlement to deep-clone nested sortie performance input: ${JSON.stringify(clonedPerformanceSettlement)}`
|
||||
clonedHistoryPerformance.unitStats[0].damageDealt === 24 &&
|
||||
clonedReportReview?.score === 88 &&
|
||||
clonedReportReview.grade === 'A' &&
|
||||
JSON.stringify(clonedReportReview.sourcePresetIds) === JSON.stringify(['elite', 'mobile']) &&
|
||||
clonedHistoryReview?.score === 88 &&
|
||||
clonedHistoryReview.grade === 'A' &&
|
||||
JSON.stringify(clonedHistoryReview.sourcePresetIds) === JSON.stringify(['elite', 'mobile']),
|
||||
`Expected battle report settlement to deep-clone nested sortie performance and review input: ${JSON.stringify(clonedPerformanceSettlement)}`
|
||||
);
|
||||
clonedReportPerformance.selectedUnitIds.push('detached-unit');
|
||||
clonedReportPerformance.formationAssignments[performanceUnit.id] = 'reserve';
|
||||
clonedReportPerformance.unitStats[0].damageDealt = 777;
|
||||
clonedReportReview.score = 2;
|
||||
clonedReportReview.grade = 'D';
|
||||
clonedReportReview.sourcePresetIds.push('siege');
|
||||
const unmutatedPerformanceSettlement = getCampaignState();
|
||||
assert(
|
||||
clonedHistoryPerformance.unitStats[0].damageDealt === 24 &&
|
||||
clonedHistoryReview.score === 88 &&
|
||||
clonedHistoryReview.grade === 'A' &&
|
||||
JSON.stringify(clonedHistoryReview.sourcePresetIds) === JSON.stringify(['elite', 'mobile']) &&
|
||||
JSON.stringify(unmutatedPerformanceSettlement.firstBattleReport?.sortiePerformance?.selectedUnitIds) === JSON.stringify([performanceUnit.id]) &&
|
||||
unmutatedPerformanceSettlement.firstBattleReport?.sortiePerformance?.formationAssignments[performanceUnit.id] === 'front' &&
|
||||
unmutatedPerformanceSettlement.firstBattleReport?.sortiePerformance?.unitStats[0].damageDealt === 24 &&
|
||||
unmutatedPerformanceSettlement.battleHistory[firstScenario.id]?.sortiePerformance?.unitStats[0].damageDealt === 24,
|
||||
`Expected returned report, history, and persisted sortie performance snapshots to remain deeply detached: ${JSON.stringify(unmutatedPerformanceSettlement)}`
|
||||
unmutatedPerformanceSettlement.battleHistory[firstScenario.id]?.sortiePerformance?.unitStats[0].damageDealt === 24 &&
|
||||
unmutatedPerformanceSettlement.firstBattleReport?.sortieReview?.score === 88 &&
|
||||
unmutatedPerformanceSettlement.firstBattleReport?.sortieReview?.grade === 'A' &&
|
||||
JSON.stringify(unmutatedPerformanceSettlement.firstBattleReport?.sortieReview?.sourcePresetIds) === JSON.stringify(['elite', 'mobile']) &&
|
||||
unmutatedPerformanceSettlement.battleHistory[firstScenario.id]?.sortieReview?.score === 88 &&
|
||||
JSON.stringify(unmutatedPerformanceSettlement.battleHistory[firstScenario.id]?.sortieReview?.sourcePresetIds) === JSON.stringify(['elite', 'mobile']),
|
||||
`Expected returned report, history, and persisted sortie performance/review snapshots to remain deeply detached: ${JSON.stringify(unmutatedPerformanceSettlement)}`
|
||||
);
|
||||
resetCampaignState();
|
||||
const reserveDrillTemplate = campaignRecruitUnits.find((unit) => unit.id === 'jian-yong');
|
||||
@@ -501,8 +575,10 @@ try {
|
||||
);
|
||||
assert(
|
||||
repeatedSettlement.firstBattleReport?.sortiePerformance === undefined &&
|
||||
repeatedSettlement.battleHistory[firstScenario.id]?.sortiePerformance === undefined,
|
||||
`Expected legacy reports without sortie performance to remain compatible in both latest report and history: ${JSON.stringify(repeatedSettlement)}`
|
||||
repeatedSettlement.battleHistory[firstScenario.id]?.sortiePerformance === undefined &&
|
||||
repeatedSettlement.firstBattleReport?.sortieReview === undefined &&
|
||||
repeatedSettlement.battleHistory[firstScenario.id]?.sortieReview === undefined,
|
||||
`Expected legacy reports without sortie performance/review to remain compatible in both latest report and history: ${JSON.stringify(repeatedSettlement)}`
|
||||
);
|
||||
assert(
|
||||
repeatedSettlement.firstBattleReport?.campaignRewards?.unlocks[0]?.battleId === 'second-battle-yellow-turban-pursuit' &&
|
||||
@@ -1132,6 +1208,15 @@ try {
|
||||
],
|
||||
sortieItemAssignments: { 'liu-bei': { bean: 2 } }
|
||||
},
|
||||
sortieReview: {
|
||||
version: 1,
|
||||
score: '88.9',
|
||||
grade: 'D',
|
||||
activeBondCount: '999',
|
||||
enemyCount: '1000',
|
||||
sourcePresetIds: ['siege', 'elite', 'siege', 'ghost', 'mobile', 7],
|
||||
itemAssignments: { 'liu-bei': { bean: 2 } }
|
||||
},
|
||||
itemRewards: ['Bean', 'Bean', 'Bean -2', 'Bean +0', 10, 'x'.repeat(97)],
|
||||
campaignRewards: {
|
||||
supplies: [' Bean ', 'Bean', 'Bean -2', 'Bean +0', 20, 'x'.repeat(97)],
|
||||
@@ -1196,6 +1281,7 @@ try {
|
||||
`Expected malformed report fields to be normalized without discarding the report: ${JSON.stringify(malformedReport)}`
|
||||
);
|
||||
const malformedReportPerformance = malformedReport.firstBattleReport?.sortiePerformance;
|
||||
const malformedReportReview = malformedReport.firstBattleReport?.sortieReview;
|
||||
assert(
|
||||
JSON.stringify(malformedReportPerformance?.selectedUnitIds) === JSON.stringify(['liu-bei']) &&
|
||||
JSON.stringify(malformedReportPerformance?.formationAssignments) === JSON.stringify({ 'liu-bei': 'front' }) &&
|
||||
@@ -1211,6 +1297,10 @@ try {
|
||||
!Object.prototype.hasOwnProperty.call(malformedReportPerformance, 'sortieItemAssignments'),
|
||||
`Expected malformed report sortie performance to normalize numbers, dedupe entries, filter stale roster ids, and exclude supplies: ${JSON.stringify(malformedReportPerformance)}`
|
||||
);
|
||||
assert(
|
||||
malformedReportReview === undefined,
|
||||
`Expected report reviews to be discarded when roster recovery removes part of their recorded sortie: ${JSON.stringify(malformedReportReview)}`
|
||||
);
|
||||
|
||||
storage.clear();
|
||||
storage.set(
|
||||
@@ -1345,6 +1435,15 @@ try {
|
||||
],
|
||||
itemAssignments: { 'liu-bei': { bean: 2 } }
|
||||
},
|
||||
sortieReview: {
|
||||
version: 1,
|
||||
score: '101.2',
|
||||
grade: 'D',
|
||||
activeBondCount: '999',
|
||||
enemyCount: '-4',
|
||||
sourcePresetIds: ['mobile', 'elite', 'mobile', 'ghost'],
|
||||
sortieItemAssignments: { 'liu-bei': { bean: 2 } }
|
||||
},
|
||||
reserveTraining: [
|
||||
{
|
||||
unitId: 'guan-yu',
|
||||
@@ -1445,6 +1544,7 @@ try {
|
||||
`Expected nested battle history progress arrays to filter invalid entries and normalize numbers: ${JSON.stringify(malformedHistory)}`
|
||||
);
|
||||
const malformedHistoryPerformance = malformedHistory.battleHistory['first-battle-zhuo-commandery']?.sortiePerformance;
|
||||
const malformedHistoryReview = malformedHistory.battleHistory['first-battle-zhuo-commandery']?.sortieReview;
|
||||
assert(
|
||||
JSON.stringify(malformedHistoryPerformance?.selectedUnitIds) === JSON.stringify(['liu-bei']) &&
|
||||
JSON.stringify(malformedHistoryPerformance?.formationAssignments) === JSON.stringify({ 'liu-bei': 'front' }) &&
|
||||
@@ -1460,6 +1560,10 @@ try {
|
||||
!Object.prototype.hasOwnProperty.call(malformedHistoryPerformance, 'itemAssignments'),
|
||||
`Expected malformed historical sortie performance to normalize and filter roster references without persisting supplies: ${JSON.stringify(malformedHistoryPerformance)}`
|
||||
);
|
||||
assert(
|
||||
malformedHistoryReview === undefined,
|
||||
`Expected historical reviews to be discarded when roster recovery removes part of their recorded sortie: ${JSON.stringify(malformedHistoryReview)}`
|
||||
);
|
||||
|
||||
storage.clear();
|
||||
storage.set(campaignStorageKey, JSON.stringify({ version: 99, step: 'sixty-sixth-camp' }));
|
||||
@@ -1544,7 +1648,7 @@ try {
|
||||
`Expected explicitly loaded corrupted slot timestamp to be normalized: ${JSON.stringify(corruptedSlot)}`
|
||||
);
|
||||
|
||||
console.log('Verified campaign save normalization, reward resettlement, camp reward idempotence, malformed-field/roster-equipment/bond-growth/bond-roster/report-reference/sortie-roster/sortie-preset/sortie-performance/latest-history/detail recovery, sortie performance legacy/deep-clone safety, timestamp-safe history/slot recovery, unknown-step/report/history recovery, unsupported-version rejection, and slotted fallback.');
|
||||
console.log('Verified campaign save normalization, reward resettlement, camp reward idempotence, malformed-field/roster-equipment/bond-growth/bond-roster/report-reference/sortie-roster/sortie-preset/sortie-performance/sortie-review/latest-history/detail recovery, sortie performance/review legacy/deep-clone safety, timestamp-safe history/slot recovery, unknown-step/report/history recovery, unsupported-version rejection, and slotted fallback.');
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
|
||||
@@ -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