fix(game-ui): 메인 턴과 장수 상태 표시를 보완

This commit is contained in:
2026-08-29 03:30:50 +00:00
parent fa9fe71539
commit 26952fcdc7
11 changed files with 120 additions and 49 deletions
@@ -946,7 +946,7 @@ const install = async (
return requests;
};
test('offers the Ref repeat range for general turns while keeping the chief range', async ({ page }) => {
test('offers 12 repeat turns and six shift turns for general turns while keeping the chief range', async ({ page }) => {
await install(page);
await page.setViewportSize({ width: 1200, height: 900 });
await page.goto('/');
@@ -957,6 +957,11 @@ test('offers the Ref repeat range for general turns while keeping the chief rang
await expect(generalRepeat.locator('.menu-items > button')).toHaveText(
Array.from({ length: 12 }, (_, index) => `${index + 1}`)
);
const generalPull = generalEditor.locator('.bottom-shift-menu').first();
await generalPull.locator('summary').click();
await expect(generalPull.locator('.menu-items > button')).toHaveText(
Array.from({ length: 6 }, (_, index) => `${index + 1}`)
);
await page.setViewportSize({ width: 500, height: 900 });
const mobileGeneralEditor = page.locator('[data-command-scope="general"]:visible');
+31 -3
View File
@@ -1840,7 +1840,11 @@ test('tournament split main action follows recruitment, betting, finals, and tou
const main = nationMenu.locator('[data-navigation-id="tournament"]');
await expect(main).toHaveText(lifecycle.label);
await expect(main).toHaveAttribute('href', `${basePath}${lifecycle.route}`);
await expect(main).toHaveClass(/highlight/);
if (lifecycle.stage === 1 || lifecycle.stage === 6) {
await expect(main).toHaveClass(/highlight/);
} else {
await expect(main).not.toHaveClass(/highlight/);
}
const toggle = nationMenu.locator('[data-menu-id="tournament-betting"]');
await toggle.click();
@@ -1851,9 +1855,12 @@ test('tournament split main action follows recruitment, betting, finals, and tou
if (lifecycle.stage === 6) {
await expect(tournamentItem).not.toHaveClass(/highlight/);
await expect(bettingItem).toHaveClass(/highlight/);
} else {
} else if (lifecycle.stage === 1) {
await expect(tournamentItem).toHaveClass(/highlight/);
await expect(bettingItem).not.toHaveClass(/highlight/);
} else {
await expect(tournamentItem).not.toHaveClass(/highlight/);
await expect(bettingItem).not.toHaveClass(/highlight/);
}
await page.keyboard.press('Escape');
@@ -1865,9 +1872,12 @@ test('tournament split main action follows recruitment, betting, finals, and tou
if (lifecycle.stage === 6) {
await expect(mobileTournament).not.toHaveClass(/highlight/);
await expect(mobileBetting).toHaveClass(/highlight/);
} else {
} else if (lifecycle.stage === 1) {
await expect(mobileTournament).toHaveClass(/highlight/);
await expect(mobileBetting).not.toHaveClass(/highlight/);
} else {
await expect(mobileTournament).not.toHaveClass(/highlight/);
await expect(mobileBetting).not.toHaveClass(/highlight/);
}
await page.keyboard.press('Escape');
}
@@ -2323,6 +2333,12 @@ test('main general card uses local turn time and command clock tracks corrected
await expect(generalCard).toContainText('7분 남음');
await expect(generalCard).toContainText('백마대');
await expect(generalCard).toContainText('보통 120점(3)');
const leadershipProgress = generalCard.locator('[data-rich-tooltip="stat-leadership"]');
await leadershipProgress.hover();
const statTooltip = page.locator('.tippy-box[data-theme~="sammo-rich"]');
await expect(statTooltip).toBeVisible();
await expect(statTooltip).toContainText('통솔 성장');
await expect(statTooltip).toContainText('5 / 20');
const desktopGeometry = await title.evaluate((element) => {
const rect = element.getBoundingClientRect();
@@ -4239,6 +4255,18 @@ test('all main Lumen button families share the rounded pressed geometry', async
await page.mouse.up();
}
const generalPullMenu = page.locator('[data-main-target="commands"] .bottom-shift-menu').first();
await generalPullMenu.locator('summary').click();
await expect(generalPullMenu.locator('.menu-items > button')).toHaveText([
'1턴',
'2턴',
'3턴',
'4턴',
'5턴',
'6턴',
]);
await generalPullMenu.locator('summary').click();
state.permission = 0;
await page.locator('.main-turn-controls').getByRole('button', { name: '갱 신' }).click();
const disabledSecret = page.locator('.layout-desktop [data-navigation-id="secret-board"]');
@@ -32,6 +32,7 @@ const props = withDefaults(
storageKey: string;
editModeStorageKey?: string;
maxPushTurn?: number;
maxShiftTurn?: number;
compact?: boolean;
mobile?: boolean;
title?: string;
@@ -44,6 +45,7 @@ const props = withDefaults(
{
editModeStorageKey: undefined,
maxPushTurn: 6,
maxShiftTurn: 6,
compact: false,
mobile: false,
title: '',
@@ -498,7 +500,7 @@ const clickOutsideMenu = (event: Event) => {
<summary>당기기</summary>
<div class="menu-items">
<button
v-for="amount in props.maxPushTurn"
v-for="amount in props.maxShiftTurn"
:key="amount"
@click="
emit('shift', -amount);
@@ -513,7 +515,7 @@ const clickOutsideMenu = (event: Event) => {
<summary>미루기</summary>
<div class="menu-items">
<button
v-for="amount in props.maxPushTurn"
v-for="amount in props.maxShiftTurn"
:key="amount"
@click="
emit('shift', amount);
@@ -695,12 +697,38 @@ const clickOutsideMenu = (event: Event) => {
</div>
<div v-if="!props.compact" class="bottom-actions">
<button class="legacy-button legacy-button--secondary" type="button" @click="emit('shift', -1)">
당기기
</button>
<button class="legacy-button legacy-button--secondary" type="button" @click="emit('shift', 1)">
미루기
</button>
<details class="legacy-menu bottom-shift-menu">
<summary class="legacy-button legacy-button--secondary" role="button">당기기</summary>
<div class="menu-items">
<button
v-for="amount in props.maxShiftTurn"
:key="amount"
type="button"
@click="
emit('shift', -amount);
clickOutsideMenu($event);
"
>
{{ amount }}
</button>
</div>
</details>
<details class="legacy-menu bottom-shift-menu">
<summary class="legacy-button legacy-button--secondary" role="button">미루기</summary>
<div class="menu-items">
<button
v-for="amount in props.maxShiftTurn"
:key="amount"
type="button"
@click="
emit('shift', amount);
clickOutsideMenu($event);
"
>
{{ amount }}
</button>
</div>
</details>
<button class="legacy-button legacy-button--secondary" type="button" @click="expanded = !expanded">
{{ expanded ? '접기' : '펼치기' }}
</button>
@@ -831,7 +859,8 @@ const clickOutsideMenu = (event: Event) => {
}
.control-pad > button,
.clock,
.legacy-menu > summary {
.control-pad > .legacy-menu > summary,
.advanced-actions .legacy-menu > summary {
box-sizing: border-box;
min-height: 34px;
border: 0;
@@ -272,11 +272,19 @@ const specialText = computed(() => {
<strong class="stat-value" :style="{ color: injuryInfo.color }">
<span>{{ stat.value }}</span>
<span v-if="stat.bonus > 0" class="leadership-bonus">+{{ stat.bonus }}</span>
<span class="bar-cell" :data-stat-progress="stat.key">
<LegacyProgressBar
:percent="stat.percent"
:label="`${stat.label} 성장 ${stat.accumulated} / ${stat.limit}`"
/>
<span class="bar-cell">
<RichTooltip
:title="`${stat.label} 성장`"
:description="`${stat.accumulated} / ${stat.limit}`"
:test-id="`stat-${stat.key}`"
>
<span :data-stat-progress="stat.key">
<LegacyProgressBar
:percent="stat.percent"
:label="`${stat.label} 성장 ${stat.accumulated} / ${stat.limit}`"
/>
</span>
</RichTooltip>
</span>
</strong>
</template>
@@ -539,6 +547,12 @@ const specialText = computed(() => {
grid-column: 3;
}
.bar-cell :deep(.rich-tooltip-trigger),
.bar-cell [data-stat-progress] {
display: block;
width: 100%;
}
.bar-cell,
.experience-bar {
display: grid;
@@ -17,7 +17,7 @@ export const resolveTournamentMainPresentation = (
tournamentStage: number,
tournamentType: number | null
): TournamentMainPresentation => {
const active = tournamentStage > 0 || tournamentType !== null;
const active = tournamentStage === 1 || tournamentStage === 6;
const bettingActive = tournamentStage === 6;
const tournamentLabel = tournamentType === null ? undefined : tournamentLabels[tournamentType];
return {
@@ -3,14 +3,14 @@ import test from 'node:test';
import { resolveTournamentMainPresentation } from '../src/utils/tournamentNavigation.ts';
void test('routes every active tournament stage except betting to its type-specific tournament button', () => {
void test('routes every non-betting stage to its type-specific tournament button and highlights only registration', () => {
const expectedLabels = ['전력전', '통솔전', '일기토', '설전'];
for (const [type, expectedLabel] of expectedLabels.entries()) {
for (const stage of [1, 2, 3, 4, 5, 7, 8, 9, 10, 0]) {
const presentation = resolveTournamentMainPresentation(stage, type);
assert.equal(presentation.compactLabel, expectedLabel);
assert.equal(presentation.to, '/tournament');
assert.equal(presentation.active, true);
assert.equal(presentation.active, stage === 1);
}
}
});