수정: 인사 명령에서 유저장을 먼저 정렬

This commit is contained in:
2026-08-31 11:40:16 +00:00
parent 0bc4be9a13
commit bd826b1ff8
4 changed files with 113 additions and 22 deletions
+17 -10
View File
@@ -175,6 +175,7 @@ const inputOptions = {
{
value: 3,
label: '여포NPC (업)',
npcState: 2,
gold: 3000,
rice: 500,
crew: 1500,
@@ -199,6 +200,7 @@ const inputOptions = {
{
value: 3,
label: '여포NPC (청룡대 · 업)',
npcState: 2,
crew: 1500,
troopId: 2,
description: '금 3,000 · 쌀 500 · 병력 1,500',
@@ -208,6 +210,7 @@ const inputOptions = {
{
value: 3,
label: '여포NPC (아국 · 업)',
npcState: 2,
availableNow: true,
crew: 1500,
troopId: 2,
@@ -2513,10 +2516,10 @@ test('offers Ref amount presets and rich, command-specific general lists', async
let generalList = form.getByTestId('general-target-list');
await expect(form.locator('#command-arg-destGeneralId option')).toHaveText([
'관우 (업)',
'여포NPC (업)',
'장수 (업)',
'여포NPC (업)',
]);
await expect(generalList.locator('.target-option strong')).toHaveText(['관우 (업)', '여포NPC (업)', '장수 (업)']);
await expect(generalList.locator('.target-option strong')).toHaveText(['관우 (업)', '장수 (업)', '여포NPC (업)']);
await expect(generalList.locator('.target-option').filter({ hasText: '여포NPC' }).locator('strong')).toHaveCSS(
'color',
'rgb(0, 255, 255)'
@@ -2525,7 +2528,7 @@ test('offers Ref amount presets and rich, command-specific general lists', async
await expect(generalList).not.toContainText('아국');
await expect(generalList).not.toContainText('탑승 부대');
await form.getByRole('button', { name: '쌀', exact: true }).click();
await expect(generalList.locator('.target-option strong')).toHaveText(['장수 (업)', '여포NPC (업)', '관우 (업)']);
await expect(generalList.locator('.target-option strong')).toHaveText(['장수 (업)', '관우 (업)', '여포NPC (업)']);
await generalList.locator('.target-option').filter({ hasText: '여포NPC' }).click();
const awardResponse = page.waitForResponse((response) => response.url().includes('turns.reserved.setNationBulk'));
await picker.getByRole('button', { name: '입력', exact: true }).click();
@@ -2540,10 +2543,10 @@ test('offers Ref amount presets and rich, command-specific general lists', async
generalList = form.getByTestId('general-target-list');
await expect(form.locator('#command-arg-destGeneralId option')).toHaveText([
'장수 (업)',
'여포NPC (업)',
'관우 (업)',
'여포NPC (업)',
]);
await expect(generalList.locator('.target-option strong')).toHaveText(['장수 (업)', '여포NPC (업)', '관우 (업)']);
await expect(generalList.locator('.target-option strong')).toHaveText(['장수 (업)', '관우 (업)', '여포NPC (업)']);
await expect(generalList).not.toContainText('아국');
await expect(generalList).not.toContainText('탑승 부대');
await page.goto('/che/chief-center');
@@ -2574,9 +2577,13 @@ test('offers Ref amount presets and rich, command-specific general lists', async
await picker.getByRole('button', { name: /부대 탈퇴 지시/ }).click();
form = picker.getByTestId('command-argument-form');
generalList = form.getByTestId('general-target-list');
await expect(generalList.locator('.target-option strong').first()).toHaveText('여포NPC (아국 · 업)');
await expect(generalList.locator('.target-option').first().locator('.target-state')).toHaveText('우선 대상');
await expect(generalList.locator('.target-option').nth(1).locator('.target-state')).toHaveText('현재 불가');
await expect(generalList.locator('.target-option strong')).toHaveText([
'장수 (아국 · 업)',
'관우 (아국 · 업)',
'여포NPC (아국 · 업)',
]);
await expect(generalList.locator('.target-option').first().locator('.target-state')).toHaveText('현재 불가');
await expect(generalList.locator('.target-option').nth(2).locator('.target-state')).toHaveText('우선 대상');
await page.goto('/che/chief-center');
await page.getByRole('button', { name: '5턴 명령 입력', exact: true }).click();
@@ -2603,8 +2610,8 @@ test('offers Ref amount presets and rich, command-specific general lists', async
await page.setViewportSize({ width: 500, height: 900 });
const mobilePersonnelCases = [
{ turn: 5, command: '포상', labels: ['관우 (업)', '여포NPC (업)', '장수 (업)'] },
{ turn: 6, command: '몰수', labels: ['장수 (업)', '여포NPC (업)', '관우 (업)'] },
{ turn: 5, command: '포상', labels: ['관우 (업)', '장수 (업)', '여포NPC (업)'] },
{ turn: 6, command: '몰수', labels: ['장수 (업)', '관우 (업)', '여포NPC (업)'] },
{
turn: 7,
command: '발령',
@@ -0,0 +1,33 @@
import type { CommandOption } from './types';
const npcStateOf = (option: CommandOption): number => option.npcState ?? 0;
/** 장수 선택지는 유저장부터 NPC 종류순으로 묶고, 명령별 우선순위는 같은 종류 안에서만 적용한다. */
export const sortCommandGeneralOptions = (
commandKey: string,
options: readonly CommandOption[],
isGold: boolean
): CommandOption[] => {
const resourceKey = isGold ? 'gold' : 'rice';
return options
.map((option, index) => ({ option, index }))
.sort((left, right) => {
const typeOrder = npcStateOf(left.option) - npcStateOf(right.option);
if (typeOrder !== 0) return typeOrder;
if (commandKey === 'che_포상') {
const resourceOrder = (left.option[resourceKey] ?? 0) - (right.option[resourceKey] ?? 0);
if (resourceOrder !== 0) return resourceOrder;
}
if (commandKey === 'che_몰수') {
const resourceOrder = (right.option[resourceKey] ?? 0) - (left.option[resourceKey] ?? 0);
if (resourceOrder !== 0) return resourceOrder;
}
if (commandKey === 'che_부대탈퇴지시') {
const availabilityOrder = Number(right.option.availableNow) - Number(left.option.availableNow);
if (availabilityOrder !== 0) return availabilityOrder;
}
return left.index - right.index;
})
.map(({ option }) => option);
};
@@ -4,6 +4,7 @@ import MapViewer from './MapViewer.vue';
import NationColorSelect from './NationColorSelect.vue';
import { commandArgumentPresentation, resolveCommandArgumentMapTarget } from '../command/commandArgumentPresentation';
import { commandCityOptions } from '../command/commandArgumentOptions';
import { sortCommandGeneralOptions } from '../command/commandGeneralOptions';
import {
commandArgumentFieldContract,
shouldPreserveCommandArgumentValue,
@@ -44,18 +45,7 @@ const visibleFields = computed(() => props.fields.filter((entry) => entry.kind !
const amountPreset = computed(() => props.options.amountPresets?.[props.commandKey]);
const sortGeneralOptions = (options: CommandOption[]): CommandOption[] => {
const result = [...options];
const resourceKey = values.isGold === false ? 'rice' : 'gold';
if (props.commandKey === 'che_포상') {
return result.sort((left, right) => (left[resourceKey] ?? 0) - (right[resourceKey] ?? 0));
}
if (props.commandKey === 'che_몰수') {
return result.sort((left, right) => (right[resourceKey] ?? 0) - (left[resourceKey] ?? 0));
}
if (props.commandKey === 'che_부대탈퇴지시') {
return result.sort((left, right) => Number(right.availableNow) - Number(left.availableNow));
}
return result;
return sortCommandGeneralOptions(props.commandKey, options, values.isGold !== false);
};
const optionsFor = (field: CommandInputField): CommandOption[] => {
@@ -0,0 +1,61 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { sortCommandGeneralOptions } from '../src/components/command/commandGeneralOptions.ts';
import type { CommandOption } from '../src/components/command/types.ts';
const option = (value: number, npcState: number | undefined, fields: Partial<CommandOption> = {}): CommandOption => ({
value,
label: String(value),
npcState,
...fields,
});
void test('puts user generals before possessed and NPC types for general target commands', () => {
const options = [option(4, 3), option(3, 2), option(2, 1), option(1, 0), option(5, undefined)];
assert.deepEqual(
sortCommandGeneralOptions('che_발령', options, true).map(({ value }) => value),
[1, 5, 2, 3, 4]
);
assert.deepEqual(
options.map(({ value }) => value),
[4, 3, 2, 1, 5]
);
});
void test('keeps reward and confiscation resource order inside each general type', () => {
const options = [
option(3, 2, { gold: 300, rice: 500 }),
option(1, 0, { gold: 500, rice: 100 }),
option(4, 2, { gold: 100, rice: 900 }),
option(2, 0, { gold: 200, rice: 400 }),
];
assert.deepEqual(
sortCommandGeneralOptions('che_포상', options, true).map(({ value }) => value),
[2, 1, 4, 3]
);
assert.deepEqual(
sortCommandGeneralOptions('che_포상', options, false).map(({ value }) => value),
[1, 2, 3, 4]
);
assert.deepEqual(
sortCommandGeneralOptions('che_몰수', options, true).map(({ value }) => value),
[1, 2, 3, 4]
);
});
void test('keeps troop-exit availability order inside each general type', () => {
const options = [
option(3, 2, { availableNow: true }),
option(1, 0, { availableNow: false }),
option(4, 2, { availableNow: false }),
option(2, 0, { availableNow: true }),
];
assert.deepEqual(
sortCommandGeneralOptions('che_부대탈퇴지시', options, true).map(({ value }) => value),
[2, 1, 3, 4]
);
});