From edd962922e4a8c28129a8783d76d428ca91f3539 Mon Sep 17 00:00:00 2001 From: hided62 Date: Thu, 27 Aug 2026 11:45:00 +0000 Subject: [PATCH] =?UTF-8?q?fix(game-ui):=20=EC=A4=91=EA=B0=84=20=ED=8F=AD?= =?UTF-8?q?=20=EC=A7=80=EB=8F=84=20=EB=B9=84=EC=9C=A8=EC=9D=84=20=EB=B3=B4?= =?UTF-8?q?=EC=A1=B4=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/game-frontend/e2e/mainNavigation.spec.ts | 96 +++++++++++++++---- .../src/components/main/MapViewer.vue | 8 +- 2 files changed, 78 insertions(+), 26 deletions(-) diff --git a/app/game-frontend/e2e/mainNavigation.spec.ts b/app/game-frontend/e2e/mainNavigation.spec.ts index 1aa67dbd..0457dbce 100644 --- a/app/game-frontend/e2e/mainNavigation.spec.ts +++ b/app/game-frontend/e2e/mainNavigation.spec.ts @@ -3098,33 +3098,87 @@ test('main layout and map use the same mobile/desktop boundary', async ({ page } validMapImages: true, }; await installFixture(page, state); - await page.setViewportSize({ width: 1000, height: 900 }); + const geometryByViewport: Record = {}; + await page.setViewportSize({ width: 1023, height: 900 }); await waitForMain(page); - await expect(page.locator('.layout-desktop [data-main-target="map"] .map-area')).toBeVisible(); - expect( - await page - .locator('.layout-desktop [data-main-target="map"] .map-area') - .evaluate((element) => element.getBoundingClientRect().width) - ).toBe(700); - await page.screenshot({ path: testInfo.outputPath('screen-mode-desktop-1000.png'), fullPage: true }); + const inspectMapGeometry = (layout: 'desktop' | 'mobile') => + page.locator(`.layout-${layout} [data-main-target="map"] .map-area`).evaluate((element) => { + const rect = element.getBoundingClientRect(); + const background = element.querySelector('.map-background-image')?.getBoundingClientRect(); + const road = element.querySelector('.map-bgroad')?.getBoundingClientRect(); + const city = element.querySelector('.city-base'); + return { + width: rect.width, + height: rect.height, + backgroundWidth: background?.width ?? 0, + backgroundHeight: background?.height ?? 0, + roadWidth: road?.width ?? 0, + roadHeight: road?.height ?? 0, + cityLeft: city ? Number.parseFloat(city.style.left) : 0, + cityTop: city ? Number.parseFloat(city.style.top) : 0, + }; + }); + let expectedCityPositionRatio: { left: number; top: number } | null = null; - await page.setViewportSize({ width: 940, height: 900 }); - await expect(page.locator('.layout-desktop')).toBeVisible(); - await expect(page.locator('.layout-desktop [data-main-target="map"] .map-area')).toBeVisible(); - expect( - await page - .locator('.layout-desktop [data-main-target="map"] .map-area') - .evaluate((element) => element.getBoundingClientRect().width) - ).toBeGreaterThan(500); + for (const viewportWidth of [1023, 1000, 960, 940]) { + await page.setViewportSize({ width: viewportWidth, height: 900 }); + await expect(page.locator('.layout-desktop')).toBeVisible(); + await expect + .poll(async () => { + const geometry = await inspectMapGeometry('desktop'); + return { + mapRatioMatches: Math.abs(geometry.width / geometry.height - 7 / 5) < 0.0001, + backgroundMatches: + Math.abs(geometry.backgroundWidth - geometry.width) < 0.01 && + Math.abs(geometry.backgroundHeight - geometry.height) < 0.01, + roadMatches: + Math.abs(geometry.roadWidth - geometry.width) < 0.01 && + Math.abs(geometry.roadHeight - geometry.height) < 0.01, + }; + }) + .toEqual({ mapRatioMatches: true, backgroundMatches: true, roadMatches: true }); + const measured = await inspectMapGeometry('desktop'); + expect(measured.width).toBeGreaterThan(500); + expect(measured.width).toBeLessThanOrEqual(700); + expect(measured.width / measured.height).toBeCloseTo(7 / 5, 4); + expect(measured.backgroundWidth).toBeCloseTo(measured.width, 4); + expect(measured.backgroundHeight).toBeCloseTo(measured.height, 4); + expect(measured.roadWidth).toBeCloseTo(measured.width, 4); + expect(measured.roadHeight).toBeCloseTo(measured.height, 4); + const cityPositionRatio = { + left: measured.cityLeft / measured.width, + top: measured.cityTop / measured.height, + }; + if (expectedCityPositionRatio) { + expect(cityPositionRatio.left).toBeCloseTo(expectedCityPositionRatio.left, 4); + expect(cityPositionRatio.top).toBeCloseTo(expectedCityPositionRatio.top, 4); + } else { + expectedCityPositionRatio = cityPositionRatio; + } + geometryByViewport[String(viewportWidth)] = measured; + if (viewportWidth === 940 || viewportWidth === 1000) { + await page.screenshot({ + path: testInfo.outputPath(`screen-mode-desktop-${viewportWidth}.png`), + fullPage: true, + }); + } + } await page.setViewportSize({ width: 939, height: 900 }); await expect(page.locator('.layout-mobile')).toBeVisible(); await expect(page.locator('.layout-mobile [data-main-target="map"] .map-area')).toBeVisible(); - expect( - await page - .locator('.layout-mobile [data-main-target="map"] .map-area') - .evaluate((element) => element.getBoundingClientRect().width) - ).toBe(500); + const mobileGeometry = await inspectMapGeometry('mobile'); + expect(mobileGeometry.width).toBe(500); + expect(mobileGeometry.width / mobileGeometry.height).toBeCloseTo(7 / 5, 4); + expect(mobileGeometry.backgroundWidth).toBeCloseTo(mobileGeometry.width, 4); + expect(mobileGeometry.backgroundHeight).toBeCloseTo(mobileGeometry.height, 4); + expect(mobileGeometry.cityLeft / mobileGeometry.width).toBeCloseTo(expectedCityPositionRatio?.left ?? 0, 4); + expect(mobileGeometry.cityTop / mobileGeometry.height).toBeCloseTo(expectedCityPositionRatio?.top ?? 0, 4); + geometryByViewport['939'] = mobileGeometry; + await writeFile( + testInfo.outputPath('screen-mode-map-aspect-ratio.json'), + `${JSON.stringify(geometryByViewport, null, 2)}\n` + ); await page.screenshot({ path: testInfo.outputPath('screen-mode-mobile-939.png'), fullPage: true }); }); diff --git a/app/game-frontend/src/components/main/MapViewer.vue b/app/game-frontend/src/components/main/MapViewer.vue index 5de4e844..200962c4 100644 --- a/app/game-frontend/src/components/main/MapViewer.vue +++ b/app/game-frontend/src/components/main/MapViewer.vue @@ -200,13 +200,11 @@ const dynamicCityById = computed(() => { }); const mapScale = computed(() => { - if (isWide.value && !props.fitContainer) { - return 1; - } + const preferredScale = props.fitContainer || isWide.value ? 1 : SMALL_MAP_SCALE; if (mapBodyWidth.value <= 0) { - return SMALL_MAP_SCALE; + return preferredScale; } - return Math.min(props.fitContainer ? 1 : SMALL_MAP_SCALE, mapBodyWidth.value / BASE_MAP_WIDTH); + return Math.min(preferredScale, mapBodyWidth.value / BASE_MAP_WIDTH); }); const effectiveDetailMode = computed(() => props.detailMode ?? storeDetailMode.value);