feat: preview sortie lineup swaps

This commit is contained in:
2026-07-10 20:09:56 +09:00
parent 5bc2852882
commit fa57f4c1dd
2 changed files with 453 additions and 37 deletions

View File

@@ -399,18 +399,110 @@ try {
`Expected all 23 campaign officers to use loaded lightweight portrait cards: ${JSON.stringify(missingMidFormationPortraits)}`
);
await page.mouse.move(530, 260);
await page.waitForFunction(() => Boolean(window.__HEROS_DEBUG__?.camp()?.sortieHoveredUnitId), undefined, { timeout: 30000 });
const hoveredPortraitUnitId = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()?.sortieHoveredUnitId);
const midFormationSelectedBefore = [...(midFormationPortraitState?.selectedSortieUnitIds ?? [])];
const midFormationCampaignSelectedBefore = [...(midFormationPortraitState?.campaign?.selectedSortieUnitIds ?? [])];
await page.mouse.click(210, 447);
await page.waitForFunction(() => window.__HEROS_DEBUG__?.camp()?.sortieFocusedUnitId === 'ma-dai', undefined, { timeout: 30000 });
const swapFocusState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
assert(
JSON.stringify(swapFocusState?.selectedSortieUnitIds) === JSON.stringify(midFormationSelectedBefore) &&
JSON.stringify(swapFocusState?.campaign?.selectedSortieUnitIds) === JSON.stringify(midFormationCampaignSelectedBefore),
`Expected focusing Ma Dai to preserve the actual sortie roster: ${JSON.stringify(swapFocusState?.selectedSortieUnitIds)}`
);
await page.mouse.move(530, 544);
await page.waitForFunction(() => {
const state = window.__HEROS_DEBUG__?.camp();
return state?.sortieHoveredUnitId === 'guan-yu' && state?.sortieComparisonPreview?.source === 'hover-swap';
}, undefined, { timeout: 30000 });
const midFormationSwapPreviewProbe = await page.evaluate(() => {
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
const view = scene?.sortieComparisonPanelView;
return {
state: window.__HEROS_DEBUG__?.camp(),
panel: {
title: view?.title?.text ?? null,
headline: view?.headline?.text ?? null,
impact: view?.impact?.text ?? null,
detail: view?.detail?.text ?? null
}
};
});
const midFormationSwapPreviewState = midFormationSwapPreviewProbe.state;
const swapPreview = midFormationSwapPreviewState?.sortieComparisonPreview;
const expectedProjectedSwapIds = midFormationSelectedBefore.map((unitId) => unitId === 'ma-dai' ? 'guan-yu' : unitId);
assert(
midFormationPortraitState.sortiePortraitRoster.some(
(unit) => unit.id === hoveredPortraitUnitId && unit.portraitReady && unit.portraitTextureKey?.startsWith('portrait-card-')
(unit) => unit.id === swapPreview?.incomingUnitId && unit.portraitReady && unit.portraitTextureKey?.startsWith('portrait-card-')
),
`Expected portrait-card hover feedback for a mapped officer: ${JSON.stringify(hoveredPortraitUnitId)}`
`Expected portrait-card hover feedback for a mapped swap candidate: ${JSON.stringify(swapPreview?.incomingUnitId)}`
);
assert(
swapPreview?.mode === 'swap' &&
swapPreview?.incomingUnitId === 'guan-yu' &&
swapPreview?.outgoingUnitId === 'ma-dai' &&
JSON.stringify(swapPreview?.selectedUnitIds) === JSON.stringify(midFormationSelectedBefore) &&
JSON.stringify(swapPreview?.projectedSelectedUnitIds) === JSON.stringify(expectedProjectedSwapIds) &&
JSON.stringify(swapPreview?.current?.selectedUnitIds) === JSON.stringify(midFormationSelectedBefore) &&
JSON.stringify(swapPreview?.projected?.selectedUnitIds) === JSON.stringify(expectedProjectedSwapIds) &&
JSON.stringify(swapPreview?.comparison?.current?.selectedUnitIds) === JSON.stringify(midFormationSelectedBefore) &&
JSON.stringify(swapPreview?.comparison?.projected?.selectedUnitIds) === JSON.stringify(expectedProjectedSwapIds) &&
swapPreview?.metrics?.length === 4 &&
swapPreview.metrics.some((metric) => metric.delta !== 0) &&
swapPreview?.comparison?.lostRoles?.includes('flank') &&
swapPreview?.comparison?.activeBondDelta === 0 &&
swapPreview?.comparison?.gainedBonds?.length > 0 &&
swapPreview?.comparison?.lostBonds?.length > 0,
`Expected a meaningful non-destructive Ma Dai to Guan Yu swap preview: ${JSON.stringify(swapPreview)}`
);
assert(
midFormationSwapPreviewProbe.panel.title === '가상 교체 · 마대 → 관우' &&
midFormationSwapPreviewProbe.panel.headline?.includes('OUT 마대') &&
midFormationSwapPreviewProbe.panel.headline?.includes('IN 관우') &&
midFormationSwapPreviewProbe.panel.impact?.includes('공백 돌파') &&
midFormationSwapPreviewProbe.panel.detail?.includes('+맥성 북문') &&
midFormationSwapPreviewProbe.panel.detail?.includes('-산길과 말발굽') &&
midFormationSwapPreviewProbe.panel.detail?.includes('실제 편성 미변경'),
`Expected the visible comparison panel to render the swap preview: ${JSON.stringify(midFormationSwapPreviewProbe.panel)}`
);
assert(
JSON.stringify(midFormationSwapPreviewState?.selectedSortieUnitIds) === JSON.stringify(midFormationSelectedBefore) &&
JSON.stringify(midFormationSwapPreviewState?.campaign?.selectedSortieUnitIds) === JSON.stringify(midFormationCampaignSelectedBefore),
`Expected hover swap preview to preserve runtime and saved selections: ${JSON.stringify(midFormationSwapPreviewState?.selectedSortieUnitIds)}`
);
await page.screenshot({ path: `${screenshotDir}/rc-mid-camp-sortie-formation-hover.png`, fullPage: true });
await page.mouse.move(40, 710);
await page.waitForFunction(() => window.__HEROS_DEBUG__?.camp()?.sortieHoveredUnitId === null, undefined, { timeout: 30000 });
await page.waitForFunction(() => {
const state = window.__HEROS_DEBUG__?.camp();
return state?.sortieHoveredUnitId === null && state?.sortieComparisonPreview?.source === 'focus';
}, undefined, { timeout: 30000 });
const afterSwapPreviewProbe = await page.evaluate(() => {
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
const view = scene?.sortieComparisonPanelView;
return {
state: window.__HEROS_DEBUG__?.camp(),
panel: {
title: view?.title?.text ?? null,
headline: view?.headline?.text ?? null
}
};
});
const afterSwapPreviewState = afterSwapPreviewProbe.state;
assert(
JSON.stringify(afterSwapPreviewState?.selectedSortieUnitIds) === JSON.stringify(midFormationSelectedBefore) &&
JSON.stringify(afterSwapPreviewState?.campaign?.selectedSortieUnitIds) === JSON.stringify(midFormationCampaignSelectedBefore),
`Expected pointerout to restore focus preview without mutating selections: ${JSON.stringify(afterSwapPreviewState?.selectedSortieUnitIds)}`
);
assert(
afterSwapPreviewState?.sortieComparisonPreview?.source === 'focus' &&
afterSwapPreviewState?.sortieComparisonPreview?.mode === 'remove' &&
afterSwapPreviewState?.sortieComparisonPreview?.unitId === 'ma-dai' &&
afterSwapPreviewState?.sortieComparisonPreview?.incomingUnitId === null &&
afterSwapPreviewState?.sortieComparisonPreview?.outgoingUnitId === 'ma-dai' &&
afterSwapPreviewProbe.panel.title === '편성 변화 · 마대' &&
!afterSwapPreviewProbe.panel.headline?.includes('관우'),
`Expected pointerout to restore the visible Ma Dai focus preview: ${JSON.stringify(afterSwapPreviewProbe)}`
);
const midFormationFirstPage = await page.evaluate(() => window.__HEROS_DEBUG__?.camp()?.sortiePortraitRosterView);
await page.mouse.click(658, 196);