diff --git a/app/game-frontend/e2e/mainNavigation.spec.ts b/app/game-frontend/e2e/mainNavigation.spec.ts index 8ae3476a..772269e1 100644 --- a/app/game-frontend/e2e/mainNavigation.spec.ts +++ b/app/game-frontend/e2e/mainNavigation.spec.ts @@ -1356,6 +1356,7 @@ test('desktop menus preserve ref columns, prefix-safe routes, and controlled dro codeColor: codeStyle.color, codeFontFamily: codeStyle.fontFamily, viewportWidth: window.innerWidth, + viewportHeight: window.innerHeight, }; }); expect(versionGeometry.dialog.width).toBeLessThanOrEqual(versionGeometry.viewportWidth - 32); @@ -1364,11 +1365,18 @@ test('desktop menus preserve ref columns, prefix-safe routes, and controlled dro expect(versionGeometry.dialogBackground).toBe('rgb(32, 32, 32)'); expect(versionGeometry.dialogColor).toBe('rgb(255, 255, 255)'); expect(versionGeometry.codeColor).toBe('rgb(215, 215, 215)'); + expect( + Math.abs(versionGeometry.dialog.left + versionGeometry.dialog.width / 2 - versionGeometry.viewportWidth / 2) + ).toBeLessThanOrEqual(1); + expect( + Math.abs(versionGeometry.dialog.top + versionGeometry.dialog.height / 2 - versionGeometry.viewportHeight / 2) + ).toBeLessThanOrEqual(1); await writeFile( testInfo.outputPath('desktop-game-version-dialog.json'), `${JSON.stringify(versionGeometry, null, 2)}\n` ); await versionDialog.screenshot({ path: testInfo.outputPath('desktop-game-version-dialog.png') }); + await page.screenshot({ path: testInfo.outputPath('desktop-game-version-dialog-viewport.png') }); await versionDialog.getByRole('button', { name: '닫기' }).click(); await expect(versionDialog).toBeHidden(); @@ -1737,6 +1745,7 @@ test('the repeated bottom global menu opens upward on the mobile document', asyn dialog: dialog.getBoundingClientRect().toJSON(), code: code.getBoundingClientRect().toJSON(), viewportWidth: window.innerWidth, + viewportHeight: window.innerHeight, documentScrollWidth: document.documentElement.scrollWidth, }; }); @@ -1744,11 +1753,18 @@ test('the repeated bottom global menu opens upward on the mobile document', asyn expect(versionGeometry.code.left).toBeGreaterThanOrEqual(versionGeometry.dialog.left); expect(versionGeometry.code.right).toBeLessThanOrEqual(versionGeometry.dialog.right); expect(versionGeometry.documentScrollWidth).toBe(500); + expect( + Math.abs(versionGeometry.dialog.left + versionGeometry.dialog.width / 2 - versionGeometry.viewportWidth / 2) + ).toBeLessThanOrEqual(1); + expect( + Math.abs(versionGeometry.dialog.top + versionGeometry.dialog.height / 2 - versionGeometry.viewportHeight / 2) + ).toBeLessThanOrEqual(1); await writeFile( testInfo.outputPath('mobile-game-version-dialog.json'), `${JSON.stringify(versionGeometry, null, 2)}\n` ); await versionDialog.screenshot({ path: testInfo.outputPath('mobile-game-version-dialog.png') }); + await page.screenshot({ path: testInfo.outputPath('mobile-game-version-dialog-viewport.png') }); await persistArtifact(page, `${basePath.slice(1)}-mobile-bottom-dropup`); }); diff --git a/app/game-frontend/e2e/tournamentBracket.spec.ts b/app/game-frontend/e2e/tournamentBracket.spec.ts index 19fbaa4c..673fd0c5 100644 --- a/app/game-frontend/e2e/tournamentBracket.spec.ts +++ b/app/game-frontend/e2e/tournamentBracket.spec.ts @@ -1,5 +1,5 @@ import { expect, test, type Page, type Route } from '@playwright/test'; -import { mkdir, readFile } from 'node:fs/promises'; +import { mkdir, readFile, writeFile } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { gameProfile, gameTrpcRoute } from './gameTestPaths.js'; @@ -967,6 +967,28 @@ test('mobile betting rankings use tabs and keep dedicated icons beside general n await firstBetButton.click(); const dialog = page.getByRole('dialog', { name: '베팅하기' }); await expect(dialog).toBeVisible(); + const dialogGeometry = await dialog.evaluate((element) => ({ + dialog: element.getBoundingClientRect().toJSON(), + viewportWidth: window.innerWidth, + viewportHeight: window.innerHeight, + documentScrollWidth: document.documentElement.scrollWidth, + position: getComputedStyle(element).position, + })); + expect( + Math.abs(dialogGeometry.dialog.left + dialogGeometry.dialog.width / 2 - dialogGeometry.viewportWidth / 2) + ).toBeLessThanOrEqual(1); + expect( + Math.abs(dialogGeometry.dialog.top + dialogGeometry.dialog.height / 2 - dialogGeometry.viewportHeight / 2) + ).toBeLessThanOrEqual(1); + expect(dialogGeometry.dialog.left).toBeGreaterThanOrEqual(0); + expect(dialogGeometry.dialog.right).toBeLessThanOrEqual(dialogGeometry.viewportWidth); + expect(dialogGeometry.dialog.top).toBeGreaterThanOrEqual(0); + expect(dialogGeometry.dialog.bottom).toBeLessThanOrEqual(dialogGeometry.viewportHeight); + expect(dialogGeometry.documentScrollWidth).toBeLessThanOrEqual(dialogGeometry.viewportWidth); + expect(dialogGeometry.position).toBe('fixed'); + await writeFile(testInfo.outputPath('mobile-betting-dialog.json'), `${JSON.stringify(dialogGeometry, null, 2)}\n`); + await dialog.screenshot({ path: testInfo.outputPath('mobile-betting-dialog.png') }); + await page.screenshot({ path: testInfo.outputPath('mobile-betting-dialog-viewport.png') }); await expect(dialog.getByText('배당 28.00')).toBeVisible(); await expect(dialog.getByText('예상 환수금 280')).toBeVisible(); await dialog.getByLabel('베팅 금액').selectOption('50'); @@ -1043,6 +1065,28 @@ test('desktop betting presents icon-and-name cards and all four rankings without expect(firstCardCorner.rightOffset).toBeGreaterThanOrEqual(2); expect(firstCardCorner.rightOffset).toBeLessThanOrEqual(4); expect(firstCardCorner.contained).toBe(true); + const firstBetButton = page.getByRole('button', { name: '관우에게 베팅하기' }); + await firstBetButton.click(); + const dialog = page.getByRole('dialog', { name: '베팅하기' }); + await expect(dialog).toBeVisible(); + const dialogGeometry = await dialog.evaluate((element) => ({ + dialog: element.getBoundingClientRect().toJSON(), + viewportWidth: window.innerWidth, + viewportHeight: window.innerHeight, + position: getComputedStyle(element).position, + })); + expect( + Math.abs(dialogGeometry.dialog.left + dialogGeometry.dialog.width / 2 - dialogGeometry.viewportWidth / 2) + ).toBeLessThanOrEqual(1); + expect( + Math.abs(dialogGeometry.dialog.top + dialogGeometry.dialog.height / 2 - dialogGeometry.viewportHeight / 2) + ).toBeLessThanOrEqual(1); + expect(dialogGeometry.position).toBe('fixed'); + await writeFile(testInfo.outputPath('desktop-betting-dialog.json'), `${JSON.stringify(dialogGeometry, null, 2)}\n`); + await dialog.screenshot({ path: testInfo.outputPath('desktop-betting-dialog.png') }); + await page.screenshot({ path: testInfo.outputPath('desktop-betting-dialog-viewport.png') }); + await dialog.getByRole('button', { name: '취소' }).click(); + await expect(dialog).toBeHidden(); expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(1365); await persistScreenshot(page, 'tournament-ranking-desktop', testInfo.outputPath('tournament-ranking-desktop.webp')); }); diff --git a/app/game-frontend/src/assets/main.css b/app/game-frontend/src/assets/main.css index 4912c428..8af960f0 100644 --- a/app/game-frontend/src/assets/main.css +++ b/app/game-frontend/src/assets/main.css @@ -64,6 +64,17 @@ textarea { font: inherit; } +/* + * Tailwind's reset removes the native dialog's auto margins. Dialogs that are + * meant to float over the current screen opt back into explicit viewport + * centring instead of inheriting the document's 500px legacy canvas. + */ +.viewport-centered-dialog { + position: fixed; + inset: 0; + margin: auto; +} + /* * Firefox for Android may zoom to a focused search field. `manipulation` * suppresses that focus-only zoom while retaining ordinary pan and pinch zoom. diff --git a/app/game-frontend/src/views/BettingView.vue b/app/game-frontend/src/views/BettingView.vue index 8a5bcc42..cc752fbb 100644 --- a/app/game-frontend/src/views/BettingView.vue +++ b/app/game-frontend/src/views/BettingView.vue @@ -150,7 +150,12 @@ const placeBet = async () => { @request-bet="openBetDialog" /> - +

베팅하기

diff --git a/app/game-frontend/src/views/MainView.vue b/app/game-frontend/src/views/MainView.vue index 1136834b..b30f5ddc 100644 --- a/app/game-frontend/src/views/MainView.vue +++ b/app/game-frontend/src/views/MainView.vue @@ -585,7 +585,11 @@ watch( @quick="moveQuick" @action="handleNavigationAction" /> - +

게임 정보

{{ lobbyInfo?.scenarioTitle || 'Core2026' }}

삼국지 모의전투 Core2026