수정: 인사 명령에서 유저장을 먼저 정렬
This commit is contained in:
@@ -175,6 +175,7 @@ const inputOptions = {
|
|||||||
{
|
{
|
||||||
value: 3,
|
value: 3,
|
||||||
label: '여포NPC (업)',
|
label: '여포NPC (업)',
|
||||||
|
npcState: 2,
|
||||||
gold: 3000,
|
gold: 3000,
|
||||||
rice: 500,
|
rice: 500,
|
||||||
crew: 1500,
|
crew: 1500,
|
||||||
@@ -199,6 +200,7 @@ const inputOptions = {
|
|||||||
{
|
{
|
||||||
value: 3,
|
value: 3,
|
||||||
label: '여포NPC (청룡대 · 업)',
|
label: '여포NPC (청룡대 · 업)',
|
||||||
|
npcState: 2,
|
||||||
crew: 1500,
|
crew: 1500,
|
||||||
troopId: 2,
|
troopId: 2,
|
||||||
description: '금 3,000 · 쌀 500 · 병력 1,500',
|
description: '금 3,000 · 쌀 500 · 병력 1,500',
|
||||||
@@ -208,6 +210,7 @@ const inputOptions = {
|
|||||||
{
|
{
|
||||||
value: 3,
|
value: 3,
|
||||||
label: '여포NPC (아국 · 업)',
|
label: '여포NPC (아국 · 업)',
|
||||||
|
npcState: 2,
|
||||||
availableNow: true,
|
availableNow: true,
|
||||||
crew: 1500,
|
crew: 1500,
|
||||||
troopId: 2,
|
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');
|
let generalList = form.getByTestId('general-target-list');
|
||||||
await expect(form.locator('#command-arg-destGeneralId option')).toHaveText([
|
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(
|
await expect(generalList.locator('.target-option').filter({ hasText: '여포NPC' }).locator('strong')).toHaveCSS(
|
||||||
'color',
|
'color',
|
||||||
'rgb(0, 255, 255)'
|
'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 expect(generalList).not.toContainText('탑승 부대');
|
await expect(generalList).not.toContainText('탑승 부대');
|
||||||
await form.getByRole('button', { name: '쌀', exact: true }).click();
|
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();
|
await generalList.locator('.target-option').filter({ hasText: '여포NPC' }).click();
|
||||||
const awardResponse = page.waitForResponse((response) => response.url().includes('turns.reserved.setNationBulk'));
|
const awardResponse = page.waitForResponse((response) => response.url().includes('turns.reserved.setNationBulk'));
|
||||||
await picker.getByRole('button', { name: '입력', exact: true }).click();
|
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');
|
generalList = form.getByTestId('general-target-list');
|
||||||
await expect(form.locator('#command-arg-destGeneralId option')).toHaveText([
|
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 expect(generalList).not.toContainText('탑승 부대');
|
await expect(generalList).not.toContainText('탑승 부대');
|
||||||
await page.goto('/che/chief-center');
|
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();
|
await picker.getByRole('button', { name: /부대 탈퇴 지시/ }).click();
|
||||||
form = picker.getByTestId('command-argument-form');
|
form = picker.getByTestId('command-argument-form');
|
||||||
generalList = form.getByTestId('general-target-list');
|
generalList = form.getByTestId('general-target-list');
|
||||||
await expect(generalList.locator('.target-option strong').first()).toHaveText('여포NPC (아국 · 업)');
|
await expect(generalList.locator('.target-option strong')).toHaveText([
|
||||||
await expect(generalList.locator('.target-option').first().locator('.target-state')).toHaveText('우선 대상');
|
'장수 (아국 · 업)',
|
||||||
await expect(generalList.locator('.target-option').nth(1).locator('.target-state')).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.goto('/che/chief-center');
|
||||||
await page.getByRole('button', { name: '5턴 명령 입력', exact: true }).click();
|
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 });
|
await page.setViewportSize({ width: 500, height: 900 });
|
||||||
const mobilePersonnelCases = [
|
const mobilePersonnelCases = [
|
||||||
{ turn: 5, command: '포상', labels: ['관우 (업)', '여포NPC (업)', '장수 (업)'] },
|
{ turn: 5, command: '포상', labels: ['관우 (업)', '장수 (업)', '여포NPC (업)'] },
|
||||||
{ turn: 6, command: '몰수', labels: ['장수 (업)', '여포NPC (업)', '관우 (업)'] },
|
{ turn: 6, command: '몰수', labels: ['장수 (업)', '관우 (업)', '여포NPC (업)'] },
|
||||||
{
|
{
|
||||||
turn: 7,
|
turn: 7,
|
||||||
command: '발령',
|
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 NationColorSelect from './NationColorSelect.vue';
|
||||||
import { commandArgumentPresentation, resolveCommandArgumentMapTarget } from '../command/commandArgumentPresentation';
|
import { commandArgumentPresentation, resolveCommandArgumentMapTarget } from '../command/commandArgumentPresentation';
|
||||||
import { commandCityOptions } from '../command/commandArgumentOptions';
|
import { commandCityOptions } from '../command/commandArgumentOptions';
|
||||||
|
import { sortCommandGeneralOptions } from '../command/commandGeneralOptions';
|
||||||
import {
|
import {
|
||||||
commandArgumentFieldContract,
|
commandArgumentFieldContract,
|
||||||
shouldPreserveCommandArgumentValue,
|
shouldPreserveCommandArgumentValue,
|
||||||
@@ -44,18 +45,7 @@ const visibleFields = computed(() => props.fields.filter((entry) => entry.kind !
|
|||||||
const amountPreset = computed(() => props.options.amountPresets?.[props.commandKey]);
|
const amountPreset = computed(() => props.options.amountPresets?.[props.commandKey]);
|
||||||
|
|
||||||
const sortGeneralOptions = (options: CommandOption[]): CommandOption[] => {
|
const sortGeneralOptions = (options: CommandOption[]): CommandOption[] => {
|
||||||
const result = [...options];
|
return sortCommandGeneralOptions(props.commandKey, options, values.isGold !== false);
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const optionsFor = (field: CommandInputField): CommandOption[] => {
|
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]
|
||||||
|
);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user