From a5dec1394a400160c456c8ff1deb461e01947ca1 Mon Sep 17 00:00:00 2001 From: hided62 Date: Sun, 23 Aug 2026 17:27:23 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=98=88=EC=95=BD=20=EC=98=A4=ED=94=88?= =?UTF-8?q?=20=EA=B3=B5=EC=A7=80=EB=A5=BC=20=EA=B0=84=EA=B2=B0=ED=95=98?= =?UTF-8?q?=EA=B2=8C=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../e2e/lobby-game-auth.spec.ts | 81 ++++++--- app/gateway-frontend/src/views/LobbyView.vue | 161 +++++++++++------- 2 files changed, 162 insertions(+), 80 deletions(-) diff --git a/app/gateway-frontend/e2e/lobby-game-auth.spec.ts b/app/gateway-frontend/e2e/lobby-game-auth.spec.ts index d9aa1fad..24d57871 100644 --- a/app/gateway-frontend/e2e/lobby-game-auth.spec.ts +++ b/app/gateway-frontend/e2e/lobby-game-auth.spec.ts @@ -502,7 +502,7 @@ test('does not contact a STOPPED game runtime and labels it inaccessible', async await page.screenshot({ path: testInfo.outputPath('gateway-stopped-profile-lobby.png'), fullPage: true }); }); -test('shows a complete upcoming reset announcement without contacting the stopped runtime', async ({ +test('shows a compact upcoming reset announcement without contacting the stopped runtime', async ({ page, }, testInfo) => { const gameOperations = await installFixture(page, { @@ -531,36 +531,65 @@ test('shows a complete upcoming reset announcement without contacting the stoppe await page.goto('lobby'); const row = page.locator('tbody tr').filter({ hasText: '체섭' }); const announcement = row.getByTestId('upcoming-reset-announcement'); - const autorun = announcement.locator('.copyable-autorun'); - const tooltip = announcement.locator('.copyable-autorun-detail'); - await expect(row.getByTestId('upcoming-reset-phase')).toHaveText('오픈 예정 · 빌드 대기'); - await expect(row.getByTestId('upcoming-reset-scheduled-at')).toHaveText('- 초기화 시작 : 2026-08-27 14:00:00 -'); + const marker = announcement.getByTestId('upcoming-reset-options-marker'); + const swatch = announcement.getByTestId('upcoming-reset-options-swatch'); + const tooltip = announcement.getByTestId('upcoming-reset-options-tooltip'); + await expect(row.getByTestId('upcoming-reset-phase')).toHaveCount(0); + await expect(row.getByTestId('upcoming-reset-scheduled-at')).toHaveCount(0); await expect(row.getByTestId('upcoming-reset-preopen-at')).toHaveText('- 가오픈 일시 : 2026-08-27 14:30:00 -'); await expect(row.getByTestId('upcoming-reset-open-at')).toHaveText('- 오픈 일시 : 2026-08-27 20:00:00 -'); - await expect(row.getByTestId('upcoming-reset-scenario-announcement')).toHaveText( - '【가상】황건적의 난 60분 턴 서버' - ); - expect((await announcement.textContent())?.replace(/\s+/g, ' ')).toContain( - '(상성 설정:가상), (빙의 여부:가능), (최대 스탯:70), (기타 설정:랜덤 임관, 자율행동[내정, 출병, 24시간 유효])' + await expect(row.getByTestId('upcoming-reset-scenario-title')).toHaveText('【가상】황건적의 난'); + await expect( + row.getByTestId('upcoming-reset-scenario-announcement').locator(':scope > .text-green-400') + ).toHaveText('60분 턴 서버'); + expect( + (await announcement.evaluate((element) => (element as HTMLElement).innerText)).replace(/\s+/g, ' ').trim() + ).toBe( + '- 가오픈 일시 : 2026-08-27 14:30:00 - - 오픈 일시 : 2026-08-27 20:00:00 - 【가상】황건적의 난 60분 턴 서버' ); + await expect(tooltip).toBeHidden(); + await expect(marker).toHaveAttribute('aria-label', '초기화 옵션 보기'); + await expect(swatch).toHaveCSS('background-color', 'rgb(217, 119, 6)'); await expect(row).not.toContainText('서버 중지 · 접근 불가'); + await expect(row).not.toContainText('정보를 불러오는 중'); await expect(row.getByRole('button', { name: '입장' })).toHaveCount(0); await expect(page.getByRole('tab', { name: 'hwe섭' })).toHaveCount(0); expect(gameOperations).toEqual([]); - const desktopGeometry = await announcement.evaluate((element) => ({ - announcement: element.getBoundingClientRect().toJSON(), - cell: element.parentElement?.getBoundingClientRect().toJSON(), - documentWidth: document.documentElement.scrollWidth, - viewportWidth: window.innerWidth, - })); + const desktopGeometry = await announcement.evaluate((element) => { + const marker = element.querySelector('[data-testid="upcoming-reset-options-marker"]'); + const swatch = element.querySelector('[data-testid="upcoming-reset-options-swatch"]'); + if (!marker || !swatch) throw new Error('expected upcoming reset option marker'); + return { + announcement: element.getBoundingClientRect().toJSON(), + cell: element.parentElement?.getBoundingClientRect().toJSON(), + marker: marker.getBoundingClientRect().toJSON(), + swatch: swatch.getBoundingClientRect().toJSON(), + documentWidth: document.documentElement.scrollWidth, + viewportWidth: window.innerWidth, + }; + }); expect(desktopGeometry.announcement.left).toBeGreaterThanOrEqual(desktopGeometry.cell?.left ?? 0); expect(desktopGeometry.announcement.right).toBeLessThanOrEqual(desktopGeometry.cell?.right ?? 0); - await autorun.hover(); + expect(desktopGeometry.marker.width).toBe(18); + expect(desktopGeometry.marker.height).toBe(18); + expect(desktopGeometry.swatch.width).toBe(8); + expect(desktopGeometry.swatch.height).toBe(8); + await marker.hover(); await expect(tooltip).toBeVisible(); - await autorun.focus(); - await expect(autorun).toBeFocused(); - await expect(autorun).toHaveCSS('outline-width', '2px'); + await expect(tooltip).toContainText('초기화 옵션'); + await expect(tooltip).toContainText('상성 설정: 가상 · 빙의 여부: 가능 · 최대 스탯: 70'); + await expect(tooltip).toContainText('기타 설정: 랜덤 임관 · 자율행동: 내정, 출병, 24시간 유효'); + await expect(tooltip).not.toContainText('초기화 시작'); + await expect(tooltip).not.toContainText('빌드 대기'); + const desktopTooltipGeometry = await tooltip.evaluate((element) => element.getBoundingClientRect().toJSON()); + expect(desktopTooltipGeometry.left).toBeGreaterThanOrEqual(8); + expect(desktopTooltipGeometry.right).toBeLessThanOrEqual(desktopGeometry.viewportWidth - 8); + expect(desktopTooltipGeometry.top).toBeGreaterThanOrEqual(desktopGeometry.cell?.top ?? 0); + expect(desktopTooltipGeometry.bottom).toBeLessThanOrEqual(desktopGeometry.cell?.bottom ?? 0); + await marker.focus(); + await expect(marker).toBeFocused(); + await expect(marker).toHaveCSS('outline-width', '2px'); await page.screenshot({ path: testInfo.outputPath('gateway-upcoming-reset-desktop.png'), fullPage: true }); await page.setViewportSize({ width: 390, height: 844 }); @@ -577,8 +606,12 @@ test('shows a complete upcoming reset announcement without contacting the stoppe expect(mobileGeometry.left).toBeGreaterThanOrEqual(0); expect(mobileGeometry.right).toBeLessThanOrEqual(mobileGeometry.viewportWidth); expect(mobileGeometry.documentWidth).toBe(mobileGeometry.viewportWidth); - await autorun.hover(); + await marker.hover(); await expect(tooltip).toBeVisible(); + const mobileTooltipGeometry = await tooltip.evaluate((element) => element.getBoundingClientRect().toJSON()); + expect(mobileTooltipGeometry.left).toBeGreaterThanOrEqual(8); + expect(mobileTooltipGeometry.right).toBeLessThanOrEqual(mobileGeometry.viewportWidth - 8); + expect(mobileTooltipGeometry.bottom).toBeLessThanOrEqual(844 - 8); await page.screenshot({ path: testInfo.outputPath('gateway-upcoming-reset-mobile.png'), fullPage: true }); }); @@ -603,8 +636,12 @@ test('keeps the announcement through the RESERVED handoff after the build comple await page.goto('lobby'); const row = page.locator('tbody tr').filter({ hasText: 'hwe섭' }); - await expect(row.getByTestId('upcoming-reset-phase')).toHaveText('오픈 준비 완료 · 가오픈 대기'); + const marker = row.getByTestId('upcoming-reset-options-marker'); + await expect(row.getByTestId('upcoming-reset-phase')).toHaveCount(0); await expect(row.getByTestId('upcoming-reset-scenario-title')).toHaveText('【가상】황건적의 난'); + await marker.hover(); + await expect(row.getByTestId('upcoming-reset-options-tooltip')).toContainText('초기화 옵션'); + await expect(row.getByTestId('upcoming-reset-options-tooltip')).not.toContainText('오픈 준비 완료'); await expect(row).not.toContainText('준 비 중 · 접근 불가'); await expect(row.getByRole('button', { name: '입장' })).toHaveCount(0); expect(gameOperations).toEqual([]); diff --git a/app/gateway-frontend/src/views/LobbyView.vue b/app/gateway-frontend/src/views/LobbyView.vue index 4b8a5205..c63087e9 100644 --- a/app/gateway-frontend/src/views/LobbyView.vue +++ b/app/gateway-frontend/src/views/LobbyView.vue @@ -135,12 +135,8 @@ const autorunDetailText = (autorun: LobbyInfo['autorunUser']): string => { }; const autorunTooltipId = (profileName: string, scope = 'current'): string => 'profile-autorun-' + scope + '-' + profileName.replaceAll(/[^a-zA-Z0-9_-]/g, '-'); -const upcomingResetPhaseText = (profile: LobbyProfile): string => { - if (profile.upcomingReset?.phase === 'DELAYED') return '준비 지연 · 일정 확인 중'; - if (profile.upcomingReset?.phase === 'READY') return '오픈 준비 완료 · 가오픈 대기'; - if (profile.upcomingReset?.phase === 'PREPARING') return '오픈 준비 중'; - return '오픈 예정 · 빌드 대기'; -}; +const upcomingResetOptionsTooltipId = (profileName: string): string => + 'upcoming-reset-options-' + profileName.replaceAll(/[^a-zA-Z0-9_-]/g, '-'); const isProfileRuntimeAvailable = (profile: LobbyProfile): boolean => profile.lifecycle.userAccessible; const unavailableProfileText = (profile: LobbyProfile): string => { if (!profile.lifecycle.dataInitialized) return '- DB 초기화 전 · 접근 불가 -'; @@ -513,17 +509,6 @@ const handleEnter = async (profile: LobbyProfile, targetPath: string) => { class="upcoming-reset-announcement" data-testid="upcoming-reset-announcement" > -
- {{ upcomingResetPhaseText(profile) }} -
-
- - 초기화 시작 : - {{ formatAnnouncementDate(profile.upcomingReset.scheduledAt) }} - -
- 가오픈 일시 : {{ formatAnnouncementDate(profile.upcomingReset.preopenAt) }} - @@ -538,38 +523,41 @@ const handleEnter = async (profile: LobbyProfile, targetPath: string) => { {{ profile.upcomingReset.turnTermMinutes }}분 턴 서버 -
-
- (상성 설정:{{ profile.upcomingReset.fictionMode }}), (빙의 여부:{{ - npcModeText(profile.upcomingReset.npcMode) - }}), (최대 스탯:{{ profile.upcomingReset.defaultStatTotal }}), (기타 - 설정:자율행동 + + [{{ - autorunDetailText(profile.upcomingReset.autorunUser) - }}]) + data-testid="upcoming-reset-options-tooltip" + > + 초기화 옵션 + 상성 설정: {{ profile.upcomingReset.fictionMode }} · 빙의 여부: + {{ npcModeText(profile.upcomingReset.npcMode) }} · 최대 스탯: + {{ profile.upcomingReset.defaultStatTotal }} + 기타 설정: {{ profile.upcomingReset.otherTextInfo || '없음' + }} + +
-