diff --git a/app/game-api/src/turns/commandTable.ts b/app/game-api/src/turns/commandTable.ts index 8a5dabe2..16324c85 100644 --- a/app/game-api/src/turns/commandTable.ts +++ b/app/game-api/src/turns/commandTable.ts @@ -825,7 +825,6 @@ const buildGroups = ( entries: CommandEntry[], ctx: ConstraintContext, view: StateView, - includeTurnDuration = false, env: CommandEnv, scope: 'general' | 'nation', currentYearMonth: number @@ -840,7 +839,8 @@ const buildGroups = ( baseAvailability.status === 'blocked' || baseAvailability.status === 'unknown' ? baseAvailability : (evaluateCooldown(entry.definition, scope, ctx, view, currentYearMonth) ?? baseAvailability); - const turnDurationText = includeTurnDuration ? getTurnDurationText(entry.definition) : undefined; + // 개인·국가 모두 실행 정의의 준비 턴과 마지막 실행 턴을 표시한다. + const turnDurationText = getTurnDurationText(entry.definition); const costText = getCommandCostText(entry, ctx, view, env); const value: TurnCommandAvailability = { key: entry.definition.key, @@ -951,7 +951,6 @@ export const buildTurnCommandTable = async (options: { projectCommandGroups(generalEntries, generalGroups ?? REF_GENERAL_COMMAND_GROUPS), ctx, view, - false, env, 'general', currentYearMonth @@ -960,7 +959,6 @@ export const buildTurnCommandTable = async (options: { projectCommandGroups(nationEntries, nationGroups ?? REF_NATION_COMMAND_GROUPS), ctx, view, - true, env, 'nation', currentYearMonth diff --git a/app/game-api/test/commandTable.test.ts b/app/game-api/test/commandTable.test.ts index 3e0af34c..abf0886f 100644 --- a/app/game-api/test/commandTable.test.ts +++ b/app/game-api/test/commandTable.test.ts @@ -211,9 +211,18 @@ describe('buildTurnCommandTable', () => { che_의병모집: '3턴', che_피장파장: '2턴', }); - expect(table.general.flatMap(({ values }) => values)).not.toContainEqual( - expect.objectContaining({ turnDurationText: expect.any(String) }) - ); + expect( + Object.fromEntries( + table.general + .flatMap(({ values }) => values) + .filter(({ turnDurationText }) => turnDurationText) + .map(({ key, turnDurationText }) => [key, turnDurationText]) + ) + ).toEqual({ + che_은퇴: '2턴', + che_내정특기초기화: '2턴', + che_전투특기초기화: '2턴', + }); const generalCosts = Object.fromEntries( table.general.flatMap(({ values }) => values.map(({ key, costText }) => [key, costText])) ); @@ -245,7 +254,7 @@ describe('buildTurnCommandTable', () => { availableGeneralCommand: { 개인: ['휴식'], 내정: ['che_물자조달'], - 군사: ['cr_맹훈련'], + 군사: ['cr_맹훈련', 'che_전투태세'], }, availableChiefCommand: { 휴식: ['휴식'], @@ -264,7 +273,9 @@ describe('buildTurnCommandTable', () => { '휴식', 'che_물자조달', 'cr_맹훈련', + 'che_전투태세', ]); + expect(findCommand(table, 'che_전투태세')).toMatchObject({ turnDurationText: '4턴' }); expect( Object.fromEntries(table.nation.flatMap(({ values }) => values.map(({ key, costText }) => [key, costText]))) ).toMatchObject({ @@ -309,10 +320,12 @@ describe('buildTurnCommandTable', () => { }); expect(findCommand(table, 'che_내정특기초기화')).toMatchObject({ + turnDurationText: '2턴', possible: true, status: 'available', }); expect(findCommand(table, 'che_전투특기초기화')).toMatchObject({ + turnDurationText: '2턴', possible: false, status: 'blocked', reason: '1턴 더 기다려야 합니다', diff --git a/app/game-frontend/e2e/commandArguments.spec.ts b/app/game-frontend/e2e/commandArguments.spec.ts index d656281c..a0404c9b 100644 --- a/app/game-frontend/e2e/commandArguments.spec.ts +++ b/app/game-frontend/e2e/commandArguments.spec.ts @@ -1514,6 +1514,65 @@ test('shows speciality reset cooldowns and reserves special user commands from t await picker.screenshot({ path: test.info().outputPath('special-user-commands-mobile-500.png') }); }); +test('shows general command durations from metadata on desktop and mobile', async ({ page }) => { + const requests = await install(page, false, { + general: [ + { + category: '개인', + values: [ + buildSimpleCommand('che_휴식', '휴식'), + buildSimpleCommand('che_내정특기초기화', '내정 특기 초기화', '2턴'), + buildSimpleCommand('che_전투특기초기화', '전투 특기 초기화', '2턴'), + buildSimpleCommand('che_전투태세', '전투태세', '4턴'), + buildSimpleCommand('fixture_future', '미래 명령', '7턴'), + buildSimpleCommand('fixture_dynamic', '대상별 명령', '1+거리×2턴'), + ], + }, + ], + nation: [], + inputOptions, + }); + for (const width of [1200, 500]) { + await page.setViewportSize({ width, height: 900 }); + await page.goto(gamePath('/')); + await page.reload(); + await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click(); + const picker = page.getByTestId('command-picker'); + await expect(picker.locator('.command-duration')).toHaveText(['/2턴', '/2턴', '/4턴', '/7턴', '/1+거리×2턴']); + await expect( + picker.getByRole('button', { name: '휴식', exact: true }).locator('.command-duration') + ).toHaveCount(0); + await page.evaluate(() => document.fonts.ready); + const reset = picker.getByRole('button', { name: '내정 특기 초기화 /2턴', exact: true }); + await reset.hover(); + await reset.focus(); + await expect(reset).toBeFocused(); + await page.mouse.down(); + await expect(reset.locator('.command-duration')).toBeVisible(); + const geometry = await picker.locator('.command-item').evaluateAll((buttons) => + buttons.map((button) => ({ + text: button.textContent, + rect: button.getBoundingClientRect().toJSON(), + overflow: button.scrollWidth - button.clientWidth, + fontSize: getComputedStyle(button).fontSize, + })) + ); + expect(geometry.every(({ overflow, rect }) => overflow <= 0 && rect.height >= 35)).toBe(true); + expect(await picker.evaluate((element) => element.scrollWidth - element.clientWidth)).toBeLessThanOrEqual(0); + await writeFile(test.info().outputPath(`general-duration-${width}.json`), JSON.stringify(geometry, null, 2)); + await writeFile( + test.info().outputPath(`general-duration-${width}.html`), + await picker.evaluate((element) => element.outerHTML) + ); + await picker.screenshot({ path: test.info().outputPath(`general-duration-${width}.png`) }); + await page.mouse.up(); + await expect(page.locator('[data-command-scope="general"] .action-column > div').first()).toHaveText( + '내정 특기 초기화' + ); + } + expect(JSON.stringify(requests)).toContain('"action":"che_내정특기초기화","args":{}'); +}); + test('shows every Ref chief command in the exact category and command order', async ({ page }) => { await install(page, false, refChiefCommandTable); await page.setViewportSize({ width: 1200, height: 900 });