From c9d3763b2820043aaf319338aa60d27d8c531fcf Mon Sep 17 00:00:00 2001 From: hided62 Date: Mon, 14 Sep 2026 14:35:26 +0000 Subject: [PATCH] =?UTF-8?q?=ED=95=9C=EA=B8=80=20=EC=A1=B0=ED=95=A9=20?= =?UTF-8?q?=EC=A4=91=EC=97=90=EB=8F=84=20=ED=84=B4=20=EB=8C=80=EC=83=81=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=EC=9D=84=20=EB=94=94=EB=B0=94=EC=9A=B4?= =?UTF-8?q?=EC=8A=A4=EB=A1=9C=20=EA=B0=B1=EC=8B=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../e2e/commandArguments.spec.ts | 67 ++++++++++++++++++- .../components/main/CommandArgumentForm.vue | 54 ++++++++++++--- 2 files changed, 112 insertions(+), 9 deletions(-) diff --git a/app/game-frontend/e2e/commandArguments.spec.ts b/app/game-frontend/e2e/commandArguments.spec.ts index 333efafb..aee2d438 100644 --- a/app/game-frontend/e2e/commandArguments.spec.ts +++ b/app/game-frontend/e2e/commandArguments.spec.ts @@ -3715,7 +3715,7 @@ for (const width of [1200, 390]) { await results.getByRole('button').click(); await expect(form.locator('#command-arg-destNationId')).toHaveValue('3'); await input.fill('적국'); - await expect(results.locator('button')).toHaveCount(1); + await expect(results.locator('button strong')).toHaveText(['적국']); await results.getByRole('button').click(); await expect(form.locator('#command-arg-destNationId')).toHaveValue('2'); await input.press('Escape'); @@ -3771,3 +3771,68 @@ test('gift search indexes nullable names without matching the displayed no-troop await search.fill('피곤'); await expect(results.locator('button')).toHaveCount(3); }); + +test('search reflects an active Korean IME composition after debounce without committing it', async ({ + page, +}, testInfo) => { + const targets = buildRefGeneralTargetOptions({ + actorId: 1, + actorNationId: 1, + generals: [ + { id: 1, name: '선녀', nationId: 1, cityId: 1, troopId: 0, npcState: 0, officerLevel: 5 }, + { id: 2, name: '손권', nationId: 1, cityId: 1, troopId: 0, npcState: 0, officerLevel: 5 }, + { id: 3, name: '관우', nationId: 1, cityId: 1, troopId: 0, npcState: 0, officerLevel: 5 }, + ], + nationNames: new Map([[1, '피곤']]), + cityNames: new Map([[1, '업']]), + }); + await install(page, false, { + ...commandTable, + general: [{ category: '인사', values: [buildGeneralCommand('che_증여', '증여')] }], + inputOptions: { ...inputOptions, ...targets }, + }); + await page.goto(gamePath('/')); + await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click(); + const picker = page.getByTestId('command-picker'); + await picker.getByRole('button', { name: /증여/ }).click(); + const form = picker.getByTestId('command-argument-form'); + await form.getByRole('button', { name: '검색 꺼짐', exact: true }).click(); + const input = form.locator('input[type=search]'); + const results = form.getByTestId('general-target-list'); + await input.focus(); + await input.evaluate((element) => { + element.setAttribute('data-composition-ends', '0'); + element.addEventListener('compositionend', () => + element.setAttribute( + 'data-composition-ends', + String(Number(element.getAttribute('data-composition-ends')) + 1) + ) + ); + }); + const cdp = await page.context().newCDPSession(page); + await cdp.send('Input.imeSetComposition', { text: 'ㅅ', selectionStart: 1, selectionEnd: 1 }); + await expect(results.locator('button strong')).toHaveText(['선녀 (피곤 · 업)', '손권 (피곤 · 업)']); + await cdp.send('Input.imeSetComposition', { text: 'ㅅㄴ', selectionStart: 2, selectionEnd: 2 }); + await expect(input).toHaveValue('ㅅㄴ'); + await expect(results.locator('button strong')).toHaveText(['선녀 (피곤 · 업)']); + await expect(input).toBeFocused(); + await expect(input).toHaveAttribute('data-composition-ends', '0'); + // IME 확정 Enter를 검색창 닫기로 가로채지 않는다. + await input.dispatchEvent('keydown', { key: 'Enter', isComposing: true, keyCode: 229 }); + await expect(input).toBeFocused(); + await expect(input).toHaveAttribute('data-composition-ends', '0'); + await form.screenshot({ path: testInfo.outputPath('active-ime-search.png') }); + await writeFile(testInfo.outputPath('active-ime-search.html'), await form.evaluate((element) => element.outerHTML)); + await cdp.send('Input.insertText', { text: 'ㅅㄴ' }); + await expect(input).toHaveAttribute('data-composition-ends', '1'); + await form.getByRole('button', { name: '지우기', exact: true }).click(); + await expect(results.locator('button')).toHaveCount(3); + await input.fill('ㄱㅇ'); + await form.getByRole('button', { name: '검색 켜짐', exact: true }).click(); + await page.waitForTimeout(250); // 예약된 디바운스가 OFF 이후 재적용되지 않는지 확인한다. + await expect(results.locator('button')).toHaveCount(3); + await form.getByRole('button', { name: '검색 꺼짐', exact: true }).click(); + await expect(input).toHaveValue(''); + await expect(results.locator('button')).toHaveCount(3); + await cdp.detach(); +}); diff --git a/app/game-frontend/src/components/main/CommandArgumentForm.vue b/app/game-frontend/src/components/main/CommandArgumentForm.vue index 41f95cdf..54c9e3d5 100644 --- a/app/game-frontend/src/components/main/CommandArgumentForm.vue +++ b/app/game-frontend/src/components/main/CommandArgumentForm.vue @@ -1,5 +1,5 @@