merge: 최신 main의 발령 작업을 대상 지도 브랜치에 통합
This commit is contained in:
@@ -1872,6 +1872,61 @@ test('shows a map and target details for every city or nation argument chief com
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('prioritizes own cities for assignment while retaining other map targets', async ({ page }) => {
|
||||||
|
const assignmentTable = structuredClone(commandTable);
|
||||||
|
assignmentTable.inputOptions.cities = [
|
||||||
|
{ value: 2, label: '허창 (적국)', description: '적국 · 예주 · 대도시' },
|
||||||
|
{ value: 3, label: '단양 (무주)' },
|
||||||
|
{ value: 1, label: '업 (아국)' },
|
||||||
|
];
|
||||||
|
|
||||||
|
await install(page, false, assignmentTable);
|
||||||
|
await page.setViewportSize({ width: 1200, height: 900 });
|
||||||
|
await page.goto('/che/chief-center');
|
||||||
|
await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
|
||||||
|
const picker = page.getByTestId('command-picker');
|
||||||
|
await picker.getByRole('button', { name: /^(?:국가:)?인사$/, exact: true }).click();
|
||||||
|
await picker.getByRole('button', { name: /발령/ }).click();
|
||||||
|
|
||||||
|
const form = picker.getByTestId('command-argument-form');
|
||||||
|
const citySelect = form.locator('#command-arg-destCityId');
|
||||||
|
await expect(form.getByTestId('command-argument-map')).toBeVisible();
|
||||||
|
await expect(citySelect.locator('option')).toHaveText(['업 (아국)', '허창 (적국)', '단양 (무주)']);
|
||||||
|
await expect(citySelect).toHaveValue('1');
|
||||||
|
|
||||||
|
await form.getByTestId('command-argument-map').locator('.city-base').nth(1).click();
|
||||||
|
await expect(citySelect).toHaveValue('2');
|
||||||
|
await expect(form.getByTestId('command-map-selection-status')).toContainText('선택 도시허창');
|
||||||
|
await expect(page).toHaveURL(/\/che\/chief-center$/);
|
||||||
|
await form.screenshot({ path: test.info().outputPath('chief-assignment-own-city-priority.png') });
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 500, height: 900 });
|
||||||
|
await page.goto('/che/chief-center');
|
||||||
|
await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
|
||||||
|
const mobilePicker = page.getByTestId('command-picker');
|
||||||
|
await mobilePicker.getByRole('button', { name: /^(?:국가:)?인사$/, exact: true }).click();
|
||||||
|
await mobilePicker.getByRole('button', { name: /발령/ }).click();
|
||||||
|
|
||||||
|
const mobileForm = mobilePicker.getByTestId('command-argument-form');
|
||||||
|
const mobileMap = mobileForm.getByTestId('command-argument-map');
|
||||||
|
const mobileCitySelect = mobileForm.locator('#command-arg-destCityId');
|
||||||
|
await expect(mobileMap).toBeVisible();
|
||||||
|
await expect(mobileCitySelect.locator('option')).toHaveText(['업 (아국)', '허창 (적국)', '단양 (무주)']);
|
||||||
|
const mobileGeometry = await mobilePicker.evaluate((element) => ({
|
||||||
|
width: element.getBoundingClientRect().width,
|
||||||
|
overflow: element.scrollWidth - element.clientWidth,
|
||||||
|
}));
|
||||||
|
expect(mobileGeometry).toEqual({ width: 500, overflow: 0 });
|
||||||
|
const mapGeometry = await mobileMap.locator('.map-area').evaluate((element) => {
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
return { width: rect.width, height: rect.height };
|
||||||
|
});
|
||||||
|
expect(mapGeometry.width / mapGeometry.height).toBeCloseTo(7 / 5, 2);
|
||||||
|
await mobileMap.screenshot({ path: test.info().outputPath('chief-assignment-map-mobile.png') });
|
||||||
|
await mobileCitySelect.scrollIntoViewIfNeeded();
|
||||||
|
await mobilePicker.screenshot({ path: test.info().outputPath('chief-assignment-own-city-priority-mobile.png') });
|
||||||
|
});
|
||||||
|
|
||||||
test('prioritizes current nation targets while preserving every choice', async ({ page }) => {
|
test('prioritizes current nation targets while preserving every choice', async ({ page }) => {
|
||||||
await install(page);
|
await install(page);
|
||||||
await page.setViewportSize({ width: 1200, height: 900 });
|
await page.setViewportSize({ width: 1200, height: 900 });
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type { CommandMapData, CommandOption } from './types';
|
||||||
|
|
||||||
|
export const commandCityOptions = (
|
||||||
|
commandKey: string,
|
||||||
|
options: readonly CommandOption[],
|
||||||
|
mapData?: CommandMapData | null
|
||||||
|
): CommandOption[] => {
|
||||||
|
if (commandKey !== 'che_발령' || typeof mapData?.myNation !== 'number') return [...options];
|
||||||
|
|
||||||
|
const nationByCityId = new Map(mapData.cityList.map(([cityId, , , nationId]) => [cityId, nationId]));
|
||||||
|
return options
|
||||||
|
.map((option, index) => ({ option, index }))
|
||||||
|
.sort((left, right) => {
|
||||||
|
const leftOwned =
|
||||||
|
typeof left.option.value === 'number' && nationByCityId.get(left.option.value) === mapData.myNation;
|
||||||
|
const rightOwned =
|
||||||
|
typeof right.option.value === 'number' && nationByCityId.get(right.option.value) === mapData.myNation;
|
||||||
|
return Number(rightOwned) - Number(leftOwned) || left.index - right.index;
|
||||||
|
})
|
||||||
|
.map(({ option }) => option);
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
import { computed, reactive, watch, type CSSProperties } from 'vue';
|
import { computed, reactive, watch, type CSSProperties } from 'vue';
|
||||||
import MapViewer from './MapViewer.vue';
|
import MapViewer from './MapViewer.vue';
|
||||||
import { commandArgumentPresentation, resolveCommandArgumentMapTarget } from '../command/commandArgumentPresentation';
|
import { commandArgumentPresentation, resolveCommandArgumentMapTarget } from '../command/commandArgumentPresentation';
|
||||||
|
import { commandCityOptions } from '../command/commandArgumentOptions';
|
||||||
import {
|
import {
|
||||||
commandArgumentFieldContract,
|
commandArgumentFieldContract,
|
||||||
shouldPreserveCommandArgumentValue,
|
shouldPreserveCommandArgumentValue,
|
||||||
@@ -64,6 +65,9 @@ const optionsFor = (field: CommandInputField): CommandOption[] => {
|
|||||||
if (field.optionSource === 'nations') {
|
if (field.optionSource === 'nations') {
|
||||||
return props.options.nationTargets?.[props.commandKey] ?? props.options.nations;
|
return props.options.nationTargets?.[props.commandKey] ?? props.options.nations;
|
||||||
}
|
}
|
||||||
|
if (field.optionSource === 'cities') {
|
||||||
|
return commandCityOptions(props.commandKey, props.options.cities, props.mapData);
|
||||||
|
}
|
||||||
if (field.optionSource === 'items') {
|
if (field.optionSource === 'items') {
|
||||||
return props.options.items[String(values.itemType ?? '')] ?? [];
|
return props.options.items[String(values.itemType ?? '')] ?? [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import test from 'node:test';
|
||||||
|
|
||||||
|
import { commandCityOptions } from '../src/components/command/commandArgumentOptions.ts';
|
||||||
|
import type { CommandMapData, CommandOption } from '../src/components/command/types.ts';
|
||||||
|
|
||||||
|
const cities: CommandOption[] = [
|
||||||
|
{ value: 20, label: '적국 도시' },
|
||||||
|
{ value: 30, label: '아국 도시 둘' },
|
||||||
|
{ value: 40, label: '공백지' },
|
||||||
|
{ value: 10, label: '아국 도시 하나' },
|
||||||
|
];
|
||||||
|
const mapData: CommandMapData = {
|
||||||
|
year: 200,
|
||||||
|
month: 1,
|
||||||
|
startYear: 180,
|
||||||
|
cityList: [
|
||||||
|
[10, 8, 0, 1, 1, 1],
|
||||||
|
[20, 7, 0, 2, 2, 1],
|
||||||
|
[30, 6, 0, 1, 3, 1],
|
||||||
|
[40, 5, 0, 0, 4, 1],
|
||||||
|
],
|
||||||
|
nationList: [
|
||||||
|
[1, '아국', '#008000', 10],
|
||||||
|
[2, '적국', '#800000', 20],
|
||||||
|
],
|
||||||
|
myCity: 10,
|
||||||
|
myNation: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
void test('발령은 아국 도시를 먼저 두고 적국과 공백지를 원래 순서로 보존한다', () => {
|
||||||
|
const sorted = commandCityOptions('che_발령', cities, mapData);
|
||||||
|
|
||||||
|
assert.deepEqual(
|
||||||
|
sorted.map((option) => option.value),
|
||||||
|
[30, 10, 20, 40]
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
cities.map((option) => option.value),
|
||||||
|
[20, 30, 40, 10],
|
||||||
|
'공용 입력 option은 변경하지 않는다'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('다른 도시 대상 명령의 순서는 바꾸지 않는다', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
commandCityOptions('che_출병', cities, mapData).map((option) => option.value),
|
||||||
|
[20, 30, 40, 10]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
void test('지도 국가 정보가 아직 없으면 발령의 기존 option 순서를 유지한다', () => {
|
||||||
|
assert.deepEqual(
|
||||||
|
commandCityOptions('che_발령', cities, null).map((option) => option.value),
|
||||||
|
[20, 30, 40, 10]
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user