From b888f89a8448072c01fe4a574fb353444106c0b5 Mon Sep 17 00:00:00 2001 From: hided62 Date: Sat, 15 Aug 2026 18:07:32 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=9E=A5=EC=88=98=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20=EB=B2=84=ED=8A=BC=EA=B3=BC=20=EC=84=B1=EA=B3=B5=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=EC=9D=84=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 능력치 빠른 설정을 큰 녹색 버튼으로 표시하고 장수 생성 성공 뒤 확인 대화상자를 거쳐 이동하도록 수정한다. --- app/game-frontend/e2e/joinLayout.spec.ts | 112 +++++++++++++++++++++-- app/game-frontend/src/views/JoinView.vue | 28 +++++- 2 files changed, 129 insertions(+), 11 deletions(-) diff --git a/app/game-frontend/e2e/joinLayout.spec.ts b/app/game-frontend/e2e/joinLayout.spec.ts index c336ed87..337fe9b9 100644 --- a/app/game-frontend/e2e/joinLayout.spec.ts +++ b/app/game-frontend/e2e/joinLayout.spec.ts @@ -8,6 +8,8 @@ const operationNames = (route: Route) => type FixtureState = { mapRequests: number; generalRequests: number; + created?: boolean; + createRequests?: number; }; const installFixture = async (page: Page, state: FixtureState): Promise => { @@ -28,7 +30,12 @@ const installFixture = async (page: Page, state: FixtureState): Promise => const results = operations.map((operation) => { if (operation === 'auth.status') return response({ ok: true }); if (operation === 'lobby.info') { - return response({ myGeneral: null, year: 180, month: 4, turnTerm: 5 }); + return response({ + myGeneral: state.created ? { id: 9, name: '생성장수' } : null, + year: 180, + month: 4, + turnTerm: 5, + }); } if (operation === 'join.getConfig') { return response({ @@ -133,6 +140,14 @@ const installFixture = async (page: Page, state: FixtureState): Promise => }, ]); } + if (operation === 'join.createGeneral') { + state.created = true; + state.createRequests = (state.createRequests ?? 0) + 1; + return response({ generalId: 9 }); + } + if (operation === 'world.getState') { + return response({ config: { npcMode: 0 } }); + } return response({}); }); await route.fulfill({ @@ -158,12 +173,48 @@ test('prioritizes core general fields and keeps context and inheritance progress await expect(page.getByLabel('성격')).toBeVisible(); await expect(page.locator('.create-form').getByLabel('통솔')).toHaveValue('55'); const statActions = page.getByRole('group', { name: '능력치 빠른 설정' }); - await expect(statActions.getByRole('button')).toHaveText([ - '랜덤형', - '통솔무력형', - '통솔지력형', - '무력지력형', - ]); + await expect(statActions.getByRole('button')).toHaveText(['랜덤형', '통솔무력형', '통솔지력형', '무력지력형']); + const randomButton = statActions.getByRole('button', { name: '랜덤형', exact: true }); + const defaultButtonStyle = await randomButton.evaluate((element) => { + const rect = element.getBoundingClientRect(); + const style = getComputedStyle(element); + return { + height: rect.height, + backgroundColor: style.backgroundColor, + color: style.color, + fontSize: style.fontSize, + fontWeight: style.fontWeight, + cursor: style.cursor, + }; + }); + expect(defaultButtonStyle).toEqual({ + height: 40, + backgroundColor: 'rgb(0, 88, 44)', + color: 'rgb(255, 255, 255)', + fontSize: '14px', + fontWeight: '700', + cursor: 'pointer', + }); + await randomButton.hover(); + await expect + .poll(() => randomButton.evaluate((element) => getComputedStyle(element).backgroundColor)) + .toBe('rgb(0, 109, 55)'); + await page.screenshot({ path: testInfo.outputPath('join-stat-actions-hover-desktop.png'), fullPage: true }); + await randomButton.focus(); + await expect(randomButton).toBeFocused(); + await expect.poll(() => randomButton.evaluate((element) => getComputedStyle(element).outlineStyle)).toBe('solid'); + const randomButtonBox = await randomButton.boundingBox(); + expect(randomButtonBox).not.toBeNull(); + await page.mouse.move( + randomButtonBox!.x + randomButtonBox!.width / 2, + randomButtonBox!.y + randomButtonBox!.height / 2 + ); + await page.mouse.down(); + await expect + .poll(() => randomButton.evaluate((element) => getComputedStyle(element).backgroundColor)) + .toBe('rgb(0, 69, 35)'); + await page.screenshot({ path: testInfo.outputPath('join-stat-actions-active-desktop.png'), fullPage: true }); + await page.mouse.up(); const setRandomValues = async (values: number[]) => { await page.evaluate((nextValues) => { let index = 0; @@ -268,8 +319,55 @@ test('keeps the primary creation flow readable without horizontal overflow on mo flowWidth: 366, tabsWidth: 352, }); + const mobileStatButtons = await page + .getByRole('group', { name: '능력치 빠른 설정' }) + .getByRole('button') + .evaluateAll((buttons) => + buttons.map((button) => { + const rect = button.getBoundingClientRect(); + return { width: rect.width, height: rect.height }; + }) + ); + expect(mobileStatButtons).toHaveLength(4); + expect(mobileStatButtons.every(({ width, height }) => width >= 160 && height === 40)).toBe(true); await expect(page.locator('.advanced-options')).not.toHaveAttribute('open'); await page.getByRole('tab', { name: '임관 권유' }).focus(); await expect(page.getByRole('tab', { name: '임관 권유' })).toBeFocused(); await page.screenshot({ path: testInfo.outputPath('join-layout-mobile.png'), fullPage: true }); }); + +test('shows the creation success dialog exactly once before navigating home', async ({ page }, testInfo) => { + const state: FixtureState = { mapRequests: 0, generalRequests: 0 }; + await installFixture(page, state); + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto('join'); + + await page.locator('.create-form').getByRole('button', { name: '장수 생성', exact: true }).click(); + + const successDialog = page.getByRole('alertdialog', { name: '완료' }); + await expect(successDialog).toHaveCount(1); + await expect(successDialog).toContainText('장수를 생성했습니다!'); + await expect(successDialog.getByRole('button', { name: '확인' })).toBeFocused(); + await expect(page).toHaveURL(/\/join(?:\?.*)?$/); + expect(state.createRequests).toBe(1); + + const dialogGeometry = await successDialog.evaluate((element) => { + const rect = element.getBoundingClientRect(); + return { + left: rect.left, + right: window.innerWidth - rect.right, + width: rect.width, + documentWidth: document.documentElement.scrollWidth, + }; + }); + expect(dialogGeometry.left).toBeGreaterThanOrEqual(12); + expect(dialogGeometry.right).toBeGreaterThanOrEqual(12); + expect(dialogGeometry.width).toBeLessThanOrEqual(366); + expect(dialogGeometry.documentWidth).toBe(390); + await page.screenshot({ path: testInfo.outputPath('join-create-success-dialog-mobile.png'), fullPage: true }); + + await successDialog.getByRole('button', { name: '확인' }).click(); + await expect(successDialog).toHaveCount(0); + await expect(page).toHaveURL(new RegExp(`${gameBasePath}/?$`)); + expect(state.createRequests).toBe(1); +}); diff --git a/app/game-frontend/src/views/JoinView.vue b/app/game-frontend/src/views/JoinView.vue index ee5eac72..3b7968bf 100644 --- a/app/game-frontend/src/views/JoinView.vue +++ b/app/game-frontend/src/views/JoinView.vue @@ -494,6 +494,7 @@ const submitJoin = async () => { clientRequestId: pending.clientRequestId, }); clearPendingJoin(pending); + await showDialog({ kind: 'success', message: '장수를 생성했습니다!' }); await session.refreshGeneralStatus(); if (session.hasGeneral) { await router.push({ name: 'home' }); @@ -1378,14 +1379,32 @@ onUnmounted(() => { .stat-actions { display: flex; flex-wrap: wrap; - gap: 6px; + gap: 8px; margin-top: 12px; } .stat-actions button { - border: 1px solid rgba(201, 164, 90, 0.3); - padding: 4px 8px; - font-size: 0.75rem; + flex: 1 1 140px; + min-height: 40px; + border: 1px solid #004f28; + border-radius: 3px; + background: #00582c; + padding: 8px 14px; + color: #fff; + font-size: 0.875rem; + font-weight: 700; + line-height: 1.2; + cursor: pointer; +} + +.stat-actions button:hover { + border-color: #005f30; + background: #006d37; +} + +.stat-actions button:active { + border-color: #003d1f; + background: #004523; } .stat-summary { @@ -1503,6 +1522,7 @@ onUnmounted(() => { .context-tabs button:focus-visible, .advanced-options > summary:focus-visible, +.stat-actions button:focus-visible, .form-actions button:focus-visible { outline: 2px solid #f0d58f; outline-offset: -2px;