fix(game-ui): 증축 감축을 즉시 예약
This commit is contained in:
@@ -2227,6 +2227,39 @@ test('limits non-aggression end years to the Ref twenty-year window on desktop a
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('reserves argument-free expansion and reduction immediately on desktop and mobile', async ({
|
||||||
|
browser,
|
||||||
|
}, testInfo) => {
|
||||||
|
for (const viewport of [
|
||||||
|
{ name: 'desktop', width: 1200, height: 900 },
|
||||||
|
{ name: 'mobile', width: 500, height: 900 },
|
||||||
|
]) {
|
||||||
|
const context = await browser.newContext({ viewport });
|
||||||
|
const page = await context.newPage();
|
||||||
|
try {
|
||||||
|
const requests = await install(page);
|
||||||
|
await page.goto('/che/chief-center');
|
||||||
|
const editor = page.locator('[data-command-scope="nation"]');
|
||||||
|
|
||||||
|
for (const [turn, action] of ['증축', '감축'].entries()) {
|
||||||
|
await editor.getByRole('button', { name: `${turn + 1}턴 명령 입력`, exact: true }).click();
|
||||||
|
const picker = editor.getByTestId('command-picker');
|
||||||
|
await picker.getByRole('button', { name: /^(?:국가:)?특수$/ }).click();
|
||||||
|
await picker.getByRole('button', { name: new RegExp(action) }).click();
|
||||||
|
await expect(picker).toBeHidden();
|
||||||
|
await expect(editor.locator('.action-column > div').nth(turn)).toContainText(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
const serialized = JSON.stringify(requests);
|
||||||
|
expect(serialized).toContain('"action":"che_증축","args":{}');
|
||||||
|
expect(serialized).toContain('"action":"che_감축","args":{}');
|
||||||
|
await page.screenshot({ path: testInfo.outputPath(`chief-capital-commands-${viewport.name}.png`) });
|
||||||
|
} finally {
|
||||||
|
await context.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('shows a map and target details for every city or nation argument chief command except assignment', async ({
|
test('shows a map and target details for every city or nation argument chief command except assignment', async ({
|
||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -2277,20 +2310,6 @@ test('shows a map and target details for every city or nation argument chief com
|
|||||||
|
|
||||||
await page.screenshot({ path: test.info().outputPath('chief-command-target-map-details.png'), fullPage: true });
|
await page.screenshot({ path: test.info().outputPath('chief-command-target-map-details.png'), fullPage: true });
|
||||||
|
|
||||||
const targetMapPickerWidth = await page
|
|
||||||
.getByTestId('command-picker')
|
|
||||||
.evaluate((element) => element.getBoundingClientRect().width);
|
|
||||||
await page.goto('/che/chief-center');
|
|
||||||
await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
|
|
||||||
const capitalPicker = page.getByTestId('command-picker');
|
|
||||||
await capitalPicker.getByRole('button', { name: /^(?:국가:)?특수$/ }).click();
|
|
||||||
await capitalPicker.getByRole('button', { name: /증축/ }).click();
|
|
||||||
await expect(capitalPicker.getByTestId('command-argument-map')).toBeVisible();
|
|
||||||
const capitalMapPickerWidth = await capitalPicker.evaluate((element) => element.getBoundingClientRect().width);
|
|
||||||
expect(capitalMapPickerWidth).toBe(targetMapPickerWidth);
|
|
||||||
expect(capitalMapPickerWidth).toBeGreaterThanOrEqual(700);
|
|
||||||
await capitalPicker.screenshot({ path: test.info().outputPath('chief-capital-map-expanded-desktop.png') });
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 500, height: 900 });
|
await page.setViewportSize({ width: 500, height: 900 });
|
||||||
await page.goto('/che/chief-center');
|
await page.goto('/che/chief-center');
|
||||||
await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
|
await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
|
||||||
|
|||||||
@@ -250,9 +250,7 @@ const togglePicker = (turnIndex?: number) => {
|
|||||||
};
|
};
|
||||||
const selectCommand = (commandKey: string) => {
|
const selectCommand = (commandKey: string) => {
|
||||||
const table = props.commandTable;
|
const table = props.commandTable;
|
||||||
const command = table?.[props.scope]
|
const command = table?.[props.scope].flatMap((group) => group.values).find((entry) => entry.key === commandKey);
|
||||||
.flatMap((group) => group.values)
|
|
||||||
.find((entry) => entry.key === commandKey);
|
|
||||||
if (!table || !command) return;
|
if (!table || !command) return;
|
||||||
selectedCommand.value = command;
|
selectedCommand.value = command;
|
||||||
// Ref opens argument commands on a separate processing page. Keep the same
|
// Ref opens argument commands on a separate processing page. Keep the same
|
||||||
@@ -266,8 +264,7 @@ const selectCommand = (commandKey: string) => {
|
|||||||
};
|
};
|
||||||
commandArgs.value = {};
|
commandArgs.value = {};
|
||||||
commandArgsValid.value = !command.reqArg;
|
commandArgsValid.value = !command.reqArg;
|
||||||
const needsInformationalConfirmation = commandArgumentPresentation(command.key).mapTarget === 'capital';
|
if (!command.reqArg) submitCommand();
|
||||||
if (!command.reqArg && !needsInformationalConfirmation) submitCommand();
|
|
||||||
};
|
};
|
||||||
const submitCommand = () => {
|
const submitCommand = () => {
|
||||||
const command = selectedCommand.value;
|
const command = selectedCommand.value;
|
||||||
@@ -799,11 +796,7 @@ const clickOutsideMenu = (event: Event) => {
|
|||||||
@submit="submitCommand"
|
@submit="submitCommand"
|
||||||
/>
|
/>
|
||||||
<CommandArgumentForm
|
<CommandArgumentForm
|
||||||
v-else-if="
|
v-else-if="commandInputSnapshot && selectedCommand.reqArg"
|
||||||
commandInputSnapshot &&
|
|
||||||
(selectedCommand.reqArg ||
|
|
||||||
commandArgumentPresentation(selectedCommand.key).mapTarget === 'capital')
|
|
||||||
"
|
|
||||||
:command-key="selectedCommand.key"
|
:command-key="selectedCommand.key"
|
||||||
:fields="selectedCommand.inputFields"
|
:fields="selectedCommand.inputFields"
|
||||||
:options="commandInputSnapshot.options"
|
:options="commandInputSnapshot.options"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { CommandInputField } from './types';
|
import type { CommandInputField } from './types';
|
||||||
|
|
||||||
export type CommandArgumentMapTarget = 'city' | 'nation' | 'capital';
|
export type CommandArgumentMapTarget = 'city' | 'nation';
|
||||||
|
|
||||||
export type CommandArgumentPresentation = {
|
export type CommandArgumentPresentation = {
|
||||||
lines: string[];
|
lines: string[];
|
||||||
@@ -9,7 +9,6 @@ export type CommandArgumentPresentation = {
|
|||||||
|
|
||||||
const cityTarget = (lines: string[]): CommandArgumentPresentation => ({ lines, mapTarget: 'city' });
|
const cityTarget = (lines: string[]): CommandArgumentPresentation => ({ lines, mapTarget: 'city' });
|
||||||
const nationTarget = (lines: string[]): CommandArgumentPresentation => ({ lines, mapTarget: 'nation' });
|
const nationTarget = (lines: string[]): CommandArgumentPresentation => ({ lines, mapTarget: 'nation' });
|
||||||
const capitalTarget = (lines: string[]): CommandArgumentPresentation => ({ lines, mapTarget: 'capital' });
|
|
||||||
|
|
||||||
// Ref hwe/ts/processing의 명령별 안내를 예약 명령 옵션창에 맞게 옮긴다.
|
// Ref hwe/ts/processing의 명령별 안내를 예약 명령 옵션창에 맞게 옮긴다.
|
||||||
// 징병/모병은 별도 이관 범위이므로 이 표에 넣지 않는다.
|
// 징병/모병은 별도 이관 범위이므로 이 표에 넣지 않는다.
|
||||||
@@ -38,15 +37,6 @@ const PRESENTATIONS: Record<string, CommandArgumentPresentation> = {
|
|||||||
]),
|
]),
|
||||||
cr_인구이동: cityTarget(['현재 도시의 인구를 선택한 인접 도시로 이동합니다.']),
|
cr_인구이동: cityTarget(['현재 도시의 인구를 선택한 인접 도시로 이동합니다.']),
|
||||||
che_발령: cityTarget(['선택한 도시로 아국 장수를 발령합니다.', '아국 도시만 대상이 됩니다.']),
|
che_발령: cityTarget(['선택한 도시로 아국 장수를 발령합니다.', '아국 도시만 대상이 됩니다.']),
|
||||||
che_증축: capitalTarget([
|
|
||||||
'현재 수도를 증축해 인구·내정·성벽의 최대치를 높입니다.',
|
|
||||||
'지도에는 이번 명령의 대상인 현재 수도가 강조됩니다.',
|
|
||||||
]),
|
|
||||||
che_감축: capitalTarget([
|
|
||||||
'현재 수도를 감축해 인구·내정·성벽의 최대치를 낮추고 국고를 회수합니다.',
|
|
||||||
'지도에는 이번 명령의 대상인 현재 수도가 강조됩니다.',
|
|
||||||
]),
|
|
||||||
|
|
||||||
che_선전포고: nationTarget([
|
che_선전포고: nationTarget([
|
||||||
'선택한 국가에 선전포고합니다.',
|
'선택한 국가에 선전포고합니다.',
|
||||||
'고립되지 않은 아국 도시와 인접한 국가에만 가능하며 초반 제한의 영향을 받습니다.',
|
'고립되지 않은 아국 도시와 인접한 국가에만 가능하며 초반 제한의 영향을 받습니다.',
|
||||||
@@ -104,7 +94,6 @@ export const commandArgumentPresentation = (commandKey: string): CommandArgument
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 대상 지도는 명령명 목록이 아니라 API가 내린 실제 인자 계약을 우선한다.
|
* 대상 지도는 명령명 목록이 아니라 API가 내린 실제 인자 계약을 우선한다.
|
||||||
* 인자가 없는 증축·감축의 수도 확인 지도만 presentation의 명시적 target을 사용한다.
|
|
||||||
*/
|
*/
|
||||||
export const resolveCommandArgumentMapTarget = (
|
export const resolveCommandArgumentMapTarget = (
|
||||||
commandKey: string,
|
commandKey: string,
|
||||||
|
|||||||
@@ -191,10 +191,6 @@ const mapSelectedCityId = computed<number | null>(() => {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (mapTarget.value === 'capital') {
|
|
||||||
const myNation = props.mapData.myNation;
|
|
||||||
return props.mapData.nationList.find((entry) => entry[0] === myNation)?.[3] ?? null;
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -215,11 +211,6 @@ const selectedMapTargetName = computed(() => {
|
|||||||
if (typeof nationId !== 'number') return '-';
|
if (typeof nationId !== 'number') return '-';
|
||||||
return props.mapData?.nationList.find((nation) => nation[0] === nationId)?.[1] ?? '-';
|
return props.mapData?.nationList.find((nation) => nation[0] === nationId)?.[1] ?? '-';
|
||||||
}
|
}
|
||||||
if (mapTarget.value === 'capital') {
|
|
||||||
const cityId = mapSelectedCityId.value;
|
|
||||||
if (!cityId) return '-';
|
|
||||||
return props.mapLayout?.cityList.find((city) => city.id === cityId)?.name ?? '-';
|
|
||||||
}
|
|
||||||
return '-';
|
return '-';
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -272,14 +263,6 @@ const mapTargetSummary = computed(() => {
|
|||||||
const cityCount = props.mapData.cityList.filter((entry) => entry[3] === value).length;
|
const cityCount = props.mapData.cityList.filter((entry) => entry[3] === value).length;
|
||||||
return `${nation[1]} · 수도 ${capital?.name ?? '-'} · 도시 ${cityCount.toLocaleString()}개`;
|
return `${nation[1]} · 수도 ${capital?.name ?? '-'} · 도시 ${cityCount.toLocaleString()}개`;
|
||||||
}
|
}
|
||||||
if (mapTarget.value === 'capital' && mapSelectedCityId.value) {
|
|
||||||
const city = props.mapLayout.cityList.find((entry) => entry.id === mapSelectedCityId.value);
|
|
||||||
const dynamic = props.mapData.cityList.find((entry) => entry[0] === mapSelectedCityId.value);
|
|
||||||
if (!city) return '';
|
|
||||||
return `${city.name} · ${props.mapLayout.regionMap[dynamic?.[4] ?? city.region]} · ${
|
|
||||||
props.mapLayout.levelMap[dynamic?.[1] ?? city.level]
|
|
||||||
} · 현재 수도`;
|
|
||||||
}
|
|
||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -424,21 +407,17 @@ watch(
|
|||||||
:detail-mode="true"
|
:detail-mode="true"
|
||||||
:fit-container="true"
|
:fit-container="true"
|
||||||
:show-current-city-marker="true"
|
:show-current-city-marker="true"
|
||||||
:readonly="mapTarget === 'capital'"
|
|
||||||
@select-city="selectMapCity"
|
@select-city="selectMapCity"
|
||||||
/>
|
/>
|
||||||
<small v-if="mapTarget === 'capital'">현재 명령이 적용될 수도를 지도에서 확인하세요.</small>
|
<small>지도에서 도시를 클릭하거나 아래 목록에서 대상을 선택하세요.</small>
|
||||||
<small v-else>지도에서 도시를 클릭하거나 아래 목록에서 대상을 선택하세요.</small>
|
|
||||||
<div class="map-selection-status" aria-live="polite" data-testid="command-map-selection-status">
|
<div class="map-selection-status" aria-live="polite" data-testid="command-map-selection-status">
|
||||||
<span v-if="mapTarget !== 'capital'" class="current-city-status">
|
<span class="current-city-status">
|
||||||
<span class="status-key">현재 도시</span>
|
<span class="status-key">현재 도시</span>
|
||||||
<strong>{{ currentCityName }}</strong>
|
<strong>{{ currentCityName }}</strong>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="mapTarget !== 'capital'" aria-hidden="true">→</span>
|
<span aria-hidden="true">→</span>
|
||||||
<span class="selected-target-status">
|
<span class="selected-target-status">
|
||||||
<span class="status-key">{{
|
<span class="status-key">{{ mapTarget === 'nation' ? '선택 국가' : '선택 도시' }}</span>
|
||||||
mapTarget === 'nation' ? '선택 국가' : mapTarget === 'capital' ? '현재 수도' : '선택 도시'
|
|
||||||
}}</span>
|
|
||||||
<strong>{{ selectedMapTargetName }}</strong>
|
<strong>{{ selectedMapTargetName }}</strong>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ const nationCommands = [
|
|||||||
'che_물자원조',
|
'che_물자원조',
|
||||||
];
|
];
|
||||||
|
|
||||||
const capitalCommands = ['che_증축', 'che_감축'];
|
|
||||||
|
|
||||||
const otherArgumentCommands = [
|
const otherArgumentCommands = [
|
||||||
'che_증여',
|
'che_증여',
|
||||||
'che_헌납',
|
'che_헌납',
|
||||||
@@ -61,7 +59,7 @@ const otherArgumentCommands = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
void test('provides Ref-level guidance for every in-scope argument command', () => {
|
void test('provides Ref-level guidance for every in-scope argument command', () => {
|
||||||
const expected = [...cityCommands, ...nationCommands, ...capitalCommands, ...otherArgumentCommands].sort();
|
const expected = [...cityCommands, ...nationCommands, ...otherArgumentCommands].sort();
|
||||||
assert.deepEqual(presentedCommandKeys().sort(), expected);
|
assert.deepEqual(presentedCommandKeys().sort(), expected);
|
||||||
for (const commandKey of expected) {
|
for (const commandKey of expected) {
|
||||||
assert.ok(commandArgumentPresentation(commandKey).lines.join(' ').length >= 12, commandKey);
|
assert.ok(commandArgumentPresentation(commandKey).lines.join(' ').length >= 12, commandKey);
|
||||||
@@ -79,9 +77,6 @@ void test('marks the same city and nation target families that Ref renders with
|
|||||||
for (const commandKey of nationCommands) {
|
for (const commandKey of nationCommands) {
|
||||||
assert.equal(commandArgumentPresentation(commandKey).mapTarget, 'nation', commandKey);
|
assert.equal(commandArgumentPresentation(commandKey).mapTarget, 'nation', commandKey);
|
||||||
}
|
}
|
||||||
for (const commandKey of capitalCommands) {
|
|
||||||
assert.equal(commandArgumentPresentation(commandKey).mapTarget, 'capital', commandKey);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
void test('derives selection maps from the actual city and nation argument contract', () => {
|
void test('derives selection maps from the actual city and nation argument contract', () => {
|
||||||
@@ -97,7 +92,7 @@ void test('derives selection maps from the actual city and nation argument contr
|
|||||||
]),
|
]),
|
||||||
'nation'
|
'nation'
|
||||||
);
|
);
|
||||||
assert.equal(resolveCommandArgumentMapTarget('che_증축', []), 'capital');
|
assert.equal(resolveCommandArgumentMapTarget('che_증축', []), undefined);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
resolveCommandArgumentMapTarget('future_general_command', [
|
resolveCommandArgumentMapTarget('future_general_command', [
|
||||||
{ key: 'target', label: '장수', kind: 'select', required: true, optionSource: 'generals' },
|
{ key: 'target', label: '장수', kind: 'select', required: true, optionSource: 'generals' },
|
||||||
|
|||||||
Reference in New Issue
Block a user