fix(game-ui): 전투 통계의 의미별 행을 맞춘다

승률·승리·패배와 살상률·사살·피살을 각각 같은 행에 두고 빈 slot을 명시적으로 유지한다.

실제 Chromium 좌표로 내정보와 감찰부 행 배치를 검증하고 지난 플레이의 소수 둘째 자리 기대값도 현재 공통 계약에 맞춘다.
This commit is contained in:
2026-08-21 16:46:34 +00:00
parent d6ee667eb7
commit 5e196c07d2
3 changed files with 55 additions and 35 deletions
+24 -26
View File
@@ -38,6 +38,18 @@ const readGeneralPanelImages = async (panel: Locator) =>
})
);
const readGeneralSummaryRows = async (summary: Locator) =>
summary.evaluate((element) => {
const rows = new Map<number, string[]>();
for (const label of element.querySelectorAll<HTMLElement>(':scope > span')) {
const top = Math.round(label.getBoundingClientRect().top * 100) / 100;
const row = rows.get(top) ?? [];
row.push(label.textContent?.trim() ?? '');
rows.set(top, row);
}
return [...rows.values()];
});
type FixtureState = {
permission: 'head' | 'member';
myset: number;
@@ -1242,19 +1254,12 @@ test('내 정보&설정 keeps desktop density and becomes a 390px horizontal-ide
await expect(page.locator('.battle-general-extra')).toContainText('피살6,789');
await expect(page.locator('.battle-general-extra__recent-value')).toHaveText('01-01 00:00');
await expect(page.locator('.legacy-general-details')).toHaveCount(0);
await expect(page.locator('.battle-general-extra > span')).toHaveText([
'명성',
'계',
'전투',
'승리',
'패배',
'계략',
'사관',
'사살',
'피살',
'승률',
'살상률',
'최근 전투',
expect(await readGeneralSummaryRows(page.locator('.battle-general-extra'))).toEqual([
['명성', '계급', ''],
['전투', '계', '사관'],
['승률', '승리', '패배'],
['살상률', '사살', '피살'],
['최근 전투'],
]);
await expect(page.locator('.item-group')).toContainText('명마');
await expect(page.locator('#container')).not.toContainText('che_');
@@ -2043,19 +2048,12 @@ test('감찰부 keeps the selector interaction and shows the permission error pa
await expect(page.locator('.battle-general-extra')).toContainText('사관4년');
await expect(page.locator('.battle-general-extra')).toContainText('승률62.50%');
await expect(page.locator('.battle-general-extra')).toContainText('살상률181.84%');
await expect(page.locator('.battle-general-extra > span')).toHaveText([
'명성',
'계',
'전투',
'승리',
'패배',
'계략',
'사관',
'사살',
'피살',
'승률',
'살상률',
'최근 전투',
expect(await readGeneralSummaryRows(page.locator('.battle-general-extra'))).toEqual([
['명성', '계급', ''],
['전투', '계', '사관'],
['승률', '승리', '패배'],
['살상률', '사살', '피살'],
['최근 전투'],
]);
const battleImages = await readGeneralPanelImages(page.locator('.battle-general-card'));
expect(battleImages).toHaveLength(2);
+2 -2
View File
@@ -278,8 +278,8 @@ test('past plays is available without a current general and preserves desktop in
await expect(detailToggle).toHaveAttribute('aria-expanded', 'true');
await expect(page.locator('.archive-general-card')).toHaveAttribute('data-general-basic-card', '');
await expect(page.locator('.archive-general-card [role="progressbar"]')).toHaveCount(14);
await expect(page.locator('[data-general-battle-summary]')).toContainText('승률62.5%');
await expect(page.locator('[data-general-battle-summary]')).toContainText('살상률75.0%');
await expect(page.locator('[data-general-battle-summary]')).toContainText('승률62.50%');
await expect(page.locator('[data-general-battle-summary]')).toContainText('살상률75.00%');
const hallBattle = page.locator('[data-hall-battle-record]');
await expect(hallBattle).toContainText('명예의 전당 보존 기록');
await expect(hallBattle).toContainText('항목별 기록 시점이 서로 다를 수 있습니다.');
@@ -61,11 +61,12 @@ const killRate = computed(() => {
</div>
<template v-else>
<span>명성</span><strong>{{ numberText(summary.experience) }}</strong> <span>계급</span
><strong>{{ summary.dedicationText || '-' }}</strong> <span>전투</span
><strong>{{ summary.dedicationText || '-' }}</strong>
<span class="battle-general-extra__empty" aria-hidden="true"></span>
<strong class="battle-general-extra__empty" aria-hidden="true"></strong>
<span>전투</span
><strong>{{ numberText(summary.warnum) }}<template v-if="summary.warnum != null"></template></strong>
<span>승리</span><strong>{{ numberText(summary.wins) }}</strong> <span>패배</span
><strong>{{ numberText(summary.losses) }}</strong> <span>계략</span
><strong>{{ numberText(summary.strategies) }}</strong>
<span>계략</span><strong>{{ numberText(summary.strategies) }}</strong>
<template v-if="summary.serviceYears !== undefined">
<span>사관</span
><strong
@@ -73,11 +74,28 @@ const killRate = computed(() => {
}}<template v-if="summary.serviceYears != null"></template></strong
>
</template>
<template v-else>
<span class="battle-general-extra__empty" aria-hidden="true"></span>
<strong class="battle-general-extra__empty" aria-hidden="true"></strong>
</template>
<template v-if="showWinRate">
<span>승률</span><strong>{{ winRate }}</strong>
</template>
<template v-else>
<span class="battle-general-extra__empty" aria-hidden="true"></span>
<strong class="battle-general-extra__empty" aria-hidden="true"></strong>
</template>
<span>승리</span><strong>{{ numberText(summary.wins) }}</strong> <span>패배</span
><strong>{{ numberText(summary.losses) }}</strong>
<template v-if="showWinRate">
<span>살상률</span><strong>{{ killRate }}</strong>
</template>
<template v-else>
<span class="battle-general-extra__empty" aria-hidden="true"></span>
<strong class="battle-general-extra__empty" aria-hidden="true"></strong>
</template>
<span>사살</span><strong>{{ numberText(summary.killCrew) }}</strong> <span>피살</span
><strong>{{ numberText(summary.deathCrew) }}</strong>
<template v-if="showWinRate">
<span>승률</span><strong>{{ winRate }}</strong> <span>살상률</span><strong>{{ killRate }}</strong>
</template>
<span class="battle-general-extra__recent-label">최근 전투</span>
<strong class="battle-general-extra__recent-value">
{{ formatServerDateTime(summary.recentWar, { format: 'monthDayTime', fallback: '-' }) }}
@@ -113,6 +131,10 @@ const killRate = computed(() => {
white-space: nowrap;
}
.battle-general-extra > .battle-general-extra__empty {
color: transparent;
}
.battle-general-extra > .battle-general-extra__recent-label {
grid-column: 1;
}