From ba5f870b0662b2a3edf18c67e2d9adcb05b0d727 Mon Sep 17 00:00:00 2001 From: hided62 Date: Fri, 21 Aug 2026 01:26:51 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=EB=AA=A8=EB=B0=94=EC=9D=BC=20=EA=B3=A0?= =?UTF-8?q?=EA=B8=89=20=ED=84=B4=20=EB=93=9C=EB=9E=98=EA=B7=B8=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref DragSelect와 같이 활성 선택 영역에 touch-action none을 적용해 브라우저 스크롤의 pointercancel을 막는다. 일반·사령턴의 trusted mobile touch 선택과 일반 모드 스크롤 계약을 production Chromium으로 검증한다. --- .../e2e/commandArguments.spec.ts | 57 +++++++++++++++++++ .../src/components/command/DragSelect.vue | 4 +- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/app/game-frontend/e2e/commandArguments.spec.ts b/app/game-frontend/e2e/commandArguments.spec.ts index 4ca368ca..2d248099 100644 --- a/app/game-frontend/e2e/commandArguments.spec.ts +++ b/app/game-frontend/e2e/commandArguments.spec.ts @@ -3,6 +3,7 @@ import { readFile } from 'node:fs/promises'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { gameProfile, gameTrpcRoute } from './gameTestPaths.js'; +import { touchDrag } from './touchDrag.js'; const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../..'); const imageRoots = [ @@ -2302,6 +2303,62 @@ test('uses drag selection, clipboard paste, and a stored template in advanced mo await page.screenshot({ path: test.info().outputPath('advanced-command-editor.png'), fullPage: true }); }); +test('physical mobile touch drag selects general and nation turns in advanced mode', async ({ browser }, testInfo) => { + const configuredBaseUrl = testInfo.project.use.baseURL; + if (typeof configuredBaseUrl !== 'string') { + throw new Error('Playwright baseURL is required for the mobile touch contract'); + } + const context = await browser.newContext({ + baseURL: configuredBaseUrl, + viewport: { width: 390, height: 844 }, + screen: { width: 390, height: 844 }, + deviceScaleFactor: 1, + isMobile: true, + hasTouch: true, + colorScheme: 'dark', + }); + const mobilePage = await context.newPage(); + try { + await install(mobilePage); + await mobilePage.goto(configuredBaseUrl); + + const editor = mobilePage.locator('[data-command-scope="general"]'); + await expect(editor).toBeVisible(); + await expect(editor.locator('.date-column.drag-select')).toHaveCSS('touch-action', 'auto'); + await editor.getByRole('button', { name: '고급 모드', exact: true }).click(); + await expect(editor.locator('.index-column.drag-select')).toHaveCSS('touch-action', 'none'); + const cells = editor.locator('.index-column > button'); + await touchDrag(mobilePage, cells.nth(0), cells.nth(2), { targetYRatio: 0.9 }); + + await expect(editor.locator('.index-column > button.selected')).toHaveCount(3); + const dates = editor.locator('.date-column > div'); + await touchDrag(mobilePage, dates.nth(4), dates.nth(6), { targetYRatio: 0.9 }); + await expect + .poll(() => editor.locator('.index-column > button.selected').allTextContents()) + .toEqual(['5', '6', '7']); + await mobilePage.screenshot({ + path: testInfo.outputPath('advanced-general-command-editor-mobile-touch.png'), + fullPage: true, + }); + + await mobilePage.goto(new URL('chief-center', configuredBaseUrl).href); + const chiefEditor = mobilePage.locator('[data-command-scope="nation"]:visible'); + await expect(chiefEditor).toBeVisible(); + await expect(chiefEditor.locator('.date-column.drag-select')).toHaveCSS('touch-action', 'auto'); + await chiefEditor.getByRole('button', { name: '고급 모드', exact: true }).click(); + await expect(chiefEditor.locator('.index-column.drag-select')).toHaveCSS('touch-action', 'none'); + const chiefCells = chiefEditor.locator('.index-column > button'); + await touchDrag(mobilePage, chiefCells.nth(0), chiefCells.nth(2), { targetYRatio: 0.9 }); + await expect(chiefEditor.locator('.index-column > button.selected')).toHaveCount(3); + await mobilePage.screenshot({ + path: testInfo.outputPath('advanced-nation-command-editor-mobile-touch.png'), + fullPage: true, + }); + } finally { + await context.close(); + } +}); + test('keeps the shared main and chief shell geometry and interaction states', async ({ page }) => { const requests = await install(page); await page.setViewportSize({ width: 1000, height: 900 }); diff --git a/app/game-frontend/src/components/command/DragSelect.vue b/app/game-frontend/src/components/command/DragSelect.vue index 106fb386..686683f7 100644 --- a/app/game-frontend/src/components/command/DragSelect.vue +++ b/app/game-frontend/src/components/command/DragSelect.vue @@ -80,7 +80,9 @@ onBeforeUnmount(() => {