From b84f31d0986a5957fecf696c2549915bc17fe6a2 Mon Sep 17 00:00:00 2001 From: hided62 Date: Mon, 24 Aug 2026 08:29:45 +0000 Subject: [PATCH] =?UTF-8?q?fix(game-ui):=20=EC=A7=80=EB=8F=84=20=ED=95=98?= =?UTF-8?q?=EB=8B=A8=20=EB=8F=84=EC=8B=9C=20=ED=88=B4=ED=8C=81=EC=9D=84=20?= =?UTF-8?q?=EC=9C=84=EB=A1=9C=20=EC=A0=84=ED=99=98=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 남만, 교지, 남해, 대처럼 지도 하단에 있는 도시의 툴팁이 지도 경계 밖으로 잘리지 않도록 실제 높이를 기준으로 세로 배치를 전환한다. 실제 CHE 좌표를 사용하는 Chromium 회귀 검증을 추가한다. --- app/game-frontend/e2e/inGameInfo.spec.ts | 46 ++++++++++++++++--- .../src/components/main/MapViewer.vue | 12 ++++- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/app/game-frontend/e2e/inGameInfo.spec.ts b/app/game-frontend/e2e/inGameInfo.spec.ts index e32182a9..c859f953 100644 --- a/app/game-frontend/e2e/inGameInfo.spec.ts +++ b/app/game-frontend/e2e/inGameInfo.spec.ts @@ -61,10 +61,10 @@ const castleFixtures = [ { id: 2, level: 1, layoutLevel: 8, x: 200, y: 100, width: 16, height: 15 }, { id: 3, level: 2, layoutLevel: 8, x: 300, y: 100, width: 20, height: 14 }, { id: 4, level: 3, layoutLevel: 8, x: 400, y: 100, width: 14, height: 14 }, - { id: 5, level: 4, layoutLevel: 8, x: 100, y: 220, width: 20, height: 15 }, - { id: 6, level: 5, layoutLevel: 8, x: 200, y: 220, width: 24, height: 16 }, - { id: 7, level: 6, layoutLevel: 8, x: 300, y: 220, width: 26, height: 18 }, - { id: 8, level: 7, layoutLevel: 8, x: 400, y: 220, width: 28, height: 20 }, + { id: 5, name: '남만', level: 4, layoutLevel: 8, x: 80, y: 455, width: 20, height: 15 }, + { id: 6, name: '교지', level: 5, layoutLevel: 8, x: 130, y: 480, width: 24, height: 16 }, + { id: 7, name: '남해', level: 6, layoutLevel: 8, x: 245, y: 480, width: 26, height: 18 }, + { id: 8, name: '대', level: 7, layoutLevel: 8, x: 450, y: 480, width: 28, height: 20 }, ] as const; const map = { result: true, @@ -82,9 +82,9 @@ const map = { }; const layout = { mapName: 'che', - cityList: castleFixtures.map(({ id, layoutLevel: level, x, y }) => ({ + cityList: castleFixtures.map(({ id, layoutLevel: level, x, y, ...fixture }) => ({ id, - name: id === 1 ? '업' : `성${id}`, + name: id === 1 ? '업' : 'name' in fixture ? fixture.name : `성${id}`, level, region: 1, x, @@ -550,6 +550,40 @@ test('map keeps desktop hover navigation and lets touch users choose one-tap or await expect(page.locator('.map-toggle-single-tap')).toHaveCount(0); await desktopCity.hover(); await expect(page.locator('.map-tooltip .tooltip-title')).toHaveText('【하북|특】업'); + + for (const cityName of ['남만', '교지', '남해', '대']) { + await page.getByRole('link', { name: cityName, exact: true }).hover(); + await expect(page.locator('.map-tooltip')).toBeVisible(); + const geometry = await page.locator('.map-area').evaluate((mapArea, expectedCityName) => { + const cityElement = Array.from(mapArea.querySelectorAll('.city-base')).find( + (element) => element.getAttribute('aria-label') === expectedCityName + ); + const tooltip = mapArea.querySelector('.map-tooltip'); + if (!cityElement || !tooltip) throw new Error(`Missing bottom-city hover geometry for ${expectedCityName}`); + const mapRect = mapArea.getBoundingClientRect(); + const cityRect = cityElement.getBoundingClientRect(); + const tooltipRect = tooltip.getBoundingClientRect(); + const controls = mapArea.querySelector('.map-controls'); + const tooltipStyle = getComputedStyle(tooltip); + return { + map: { top: mapRect.top, bottom: mapRect.bottom }, + city: { top: cityRect.top, bottom: cityRect.bottom }, + tooltip: { top: tooltipRect.top, bottom: tooltipRect.bottom, height: tooltipRect.height }, + tooltipZIndex: Number(tooltipStyle.zIndex), + controlsZIndex: controls ? Number(getComputedStyle(controls).zIndex) : null, + pointerEvents: tooltipStyle.pointerEvents, + }; + }, cityName); + expect(geometry.tooltip.top).toBeGreaterThanOrEqual(geometry.map.top); + expect(geometry.tooltip.bottom).toBeLessThanOrEqual(geometry.map.bottom); + expect(geometry.tooltip.bottom).toBeLessThan(geometry.city.top); + expect(geometry.tooltip.height).toBeGreaterThanOrEqual(32); + expect(geometry.tooltipZIndex).toBeGreaterThan(geometry.controlsZIndex ?? 0); + expect(geometry.pointerEvents).toBe('none'); + } + await page.screenshot({ path: testInfo.outputPath('desktop-map-bottom-tooltip.png'), fullPage: true }); + + await desktopCity.hover(); await desktopCity.click(); await expect(page).toHaveURL(/\/current-city\?cityId=1$/u); await page.goBack(); diff --git a/app/game-frontend/src/components/main/MapViewer.vue b/app/game-frontend/src/components/main/MapViewer.vue index 85c7ff03..d757caee 100644 --- a/app/game-frontend/src/components/main/MapViewer.vue +++ b/app/game-frontend/src/components/main/MapViewer.vue @@ -92,6 +92,8 @@ const BASE_MAP_WIDTH = 700; const BASE_MAP_HEIGHT = 500; const SMALL_MAP_SCALE = 5 / 7; const MAP_BACKGROUND_TRANSITION_MS = 480; +const TOOLTIP_FALLBACK_HEIGHT = 32; +const TOOLTIP_VERTICAL_OFFSET = 30; const decodedImageCache = new Map>(); const decodedImageElements = new Map(); @@ -146,6 +148,7 @@ const reduceMotion = useMediaQuery('(prefers-reduced-motion: reduce)'); const mapArea = ref(null); const mapBody = ref(null); const mapControls = ref(null); +const tooltipElement = ref(null); const mapOptionsOpen = ref(false); const mapOptionsMenuId = `map-options-${useId()}`; const { width: mapBodyWidth } = useElementSize(mapBody); @@ -568,10 +571,15 @@ const tooltipPosition = computed(() => { const width = 120; const offset = 10; const mapPixelWidth = BASE_MAP_WIDTH * mapScale.value; + const mapPixelHeight = BASE_MAP_HEIGHT * mapScale.value; + const tooltipHeight = tooltipElement.value?.offsetHeight ?? TOOLTIP_FALLBACK_HEIGHT; const left = elementX.value + width + offset > mapPixelWidth ? elementX.value - width - 5 : elementX.value + offset; + const belowTop = elementY.value + TOOLTIP_VERTICAL_OFFSET; + const top = + belowTop + tooltipHeight > mapPixelHeight ? elementY.value - tooltipHeight - TOOLTIP_VERTICAL_OFFSET : belowTop; return { left: `${Math.max(0, left)}px`, - top: `${elementY.value + 30}px`, + top: `${Math.max(0, top)}px`, }; }); @@ -698,7 +706,7 @@ const selectCity = (cityId: number) => { > 현재 -
+
{{ hoveredCityTitle }}
{{ hoveredCity.nationId > 0 ? hoveredCity.nationName : '' }}