fix(game-ui): 암행부 예약 명령 요약을 복원
암행부 API가 앞 5턴의 action과 args를 함께 반환하도록 수정한다.\n개인턴·사령부 공용 formatter를 암행부와 세력도시 연동 표에 적용하고 Chromium 회귀를 추가한다.
This commit is contained in:
@@ -76,6 +76,37 @@ const cities = [
|
||||
},
|
||||
] as const;
|
||||
|
||||
const commandTable = {
|
||||
general: [
|
||||
{
|
||||
category: '내정',
|
||||
values: [
|
||||
{
|
||||
key: 'che_농지개간',
|
||||
name: '농지 개간',
|
||||
reqArg: false,
|
||||
status: 'available',
|
||||
possible: true,
|
||||
inputFields: [],
|
||||
},
|
||||
{ key: 'che_훈련', name: '훈련', reqArg: false, status: 'available', possible: true, inputFields: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
nation: [],
|
||||
inputOptions: {
|
||||
cities: cities.map((city) => ({ value: city.id, label: city.name })),
|
||||
nations: [{ value: 1, label: '위' }],
|
||||
generals: [],
|
||||
crewTypes: [{ value: 1, label: '보병' }],
|
||||
armTypes: [],
|
||||
nationTypes: [],
|
||||
colors: [],
|
||||
items: {},
|
||||
recruitment: null,
|
||||
},
|
||||
};
|
||||
|
||||
const overviewFixture = (state: FixtureState) => ({
|
||||
me: { id: state.role === 'head' ? 20 : 21, officerLevel: state.role === 'head' ? 5 : 1 },
|
||||
nation: {
|
||||
@@ -161,7 +192,10 @@ const secretGeneral = (id: number, name: string, cityId: number, overrides: Reco
|
||||
atmos: 90,
|
||||
killTurn: 7,
|
||||
turnTime: '2026-01-01T01:02:00.000Z',
|
||||
reservedCommands: ['농지 개간', '훈련'],
|
||||
reservedCommands: [
|
||||
{ action: 'che_농지개간', args: {} },
|
||||
{ action: 'che_훈련', args: {} },
|
||||
],
|
||||
...overrides,
|
||||
});
|
||||
|
||||
@@ -292,6 +326,7 @@ const install = async (page: Page, state: FixtureState): Promise<void> => {
|
||||
: response(secretFixture());
|
||||
}
|
||||
if (operation === 'nation.getPersonnelInfo') return response(personnelFixture(state));
|
||||
if (operation === 'turns.getCommandTable') return response(commandTable);
|
||||
if (operation === 'nation.appoint') {
|
||||
const input = requestInput(route, index);
|
||||
state.appointmentInputs.push({
|
||||
|
||||
@@ -4,6 +4,35 @@ import { gameProfile, gameTrpcRoute } from './gameTestPaths.js';
|
||||
const response = (data: unknown) => ({ result: { data } });
|
||||
const operations = (route: Route) =>
|
||||
decodeURIComponent(new URL(route.request().url()).pathname.split('/trpc/')[1] ?? '').split(',');
|
||||
const commandTable = {
|
||||
general: [
|
||||
{
|
||||
category: '전체',
|
||||
values: [
|
||||
{ key: '휴식', name: '휴식', reqArg: false, status: 'available', possible: true, inputFields: [] },
|
||||
{ key: 'che_이동', name: '이동', reqArg: true, status: 'available', possible: true, inputFields: [] },
|
||||
{ key: 'che_징병', name: '징병', reqArg: true, status: 'available', possible: true, inputFields: [] },
|
||||
{ key: 'che_증여', name: '증여', reqArg: true, status: 'available', possible: true, inputFields: [] },
|
||||
{ key: 'che_화계', name: '화계', reqArg: true, status: 'available', possible: true, inputFields: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
nation: [],
|
||||
inputOptions: {
|
||||
cities: [{ value: 1, label: '업 (위)' }],
|
||||
nations: [{ value: 1, label: '위' }],
|
||||
generals: [
|
||||
{ value: 1, label: '테스트장수 (위 · 업)' },
|
||||
{ value: 2, label: '다른장수 (위 · 업)' },
|
||||
],
|
||||
crewTypes: [{ value: 1, label: '보병' }],
|
||||
armTypes: [],
|
||||
nationTypes: [],
|
||||
colors: [],
|
||||
items: {},
|
||||
recruitment: null,
|
||||
},
|
||||
};
|
||||
const general = {
|
||||
id: 1,
|
||||
name: '테스트장수',
|
||||
@@ -112,11 +141,18 @@ const install = async (page: Page, secretAllowed = true) => {
|
||||
atmos: 90,
|
||||
killTurn: 7,
|
||||
turnTime: '2026-01-01T01:02:00.000Z',
|
||||
reservedCommands: ['징병', '훈련'],
|
||||
reservedCommands: [
|
||||
{ action: 'che_이동', args: { destCityId: 1 } },
|
||||
{ action: 'che_징병', args: { crewType: 1, amount: 300 } },
|
||||
{ action: 'che_증여', args: { destGeneralId: 2, isGold: false, amount: 200 } },
|
||||
{ action: 'che_화계', args: { destCityId: 1 } },
|
||||
{ action: '휴식', args: {} },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (operation === 'turns.getCommandTable') return response(commandTable);
|
||||
return { error: { message: `unhandled ${operation}`, data: { code: 'BAD_REQUEST' } } };
|
||||
});
|
||||
await route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(results) });
|
||||
@@ -311,7 +347,7 @@ test('nation generals filter buttons open Ref operator menus and apply compound
|
||||
await page.screenshot({ path: testInfo.outputPath('core-mobile-filter-menu.png'), fullPage: true });
|
||||
});
|
||||
|
||||
test('both pages preserve the legacy 1000px overflow contract at 500px', async ({ page }) => {
|
||||
test('both pages preserve the legacy 1000px overflow contract at 500px', async ({ page }, testInfo) => {
|
||||
await install(page);
|
||||
await page.setViewportSize({ width: 500, height: 900 });
|
||||
for (const path of ['nation/generals', 'nation/secret']) {
|
||||
@@ -319,18 +355,60 @@ test('both pages preserve the legacy 1000px overflow contract at 500px', async (
|
||||
await expect(
|
||||
page.locator(path.endsWith('secret') ? '#secret-general-list' : '#nation-general-list')
|
||||
).toBeVisible();
|
||||
expect(await page.locator('main').evaluate((el) => el.getBoundingClientRect().width)).toBe(1000);
|
||||
const fixedWidthElement = path.endsWith('secret')
|
||||
? page.locator('.secret-page .title').first()
|
||||
: page.locator('.general-page');
|
||||
expect(await fixedWidthElement.evaluate((el) => el.getBoundingClientRect().width)).toBe(1000);
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeGreaterThanOrEqual(1000);
|
||||
if (path.endsWith('secret')) {
|
||||
await page.screenshot({ path: testInfo.outputPath('secret-command-brief-mobile-500.png'), fullPage: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('secret office renders summary, turns, and the forbidden error flow', async ({ page }) => {
|
||||
test('secret office renders five Ref-style command briefs and the forbidden error flow', async ({ page }, testInfo) => {
|
||||
await install(page);
|
||||
await page.setViewportSize({ width: 1200, height: 900 });
|
||||
await page.goto('nation/secret');
|
||||
await expect(page.locator('.summary')).toContainText('전체 금');
|
||||
await expect(page.locator('#secret-general-list')).toContainText('1 : 징병');
|
||||
expect(await page.locator('.secret-page').evaluate((el) => el.getBoundingClientRect().width)).toBe(1000);
|
||||
const commandRows = page.locator('#secret-general-list .turns div');
|
||||
await expect(commandRows).toHaveCount(5);
|
||||
await expect(commandRows).toHaveText([
|
||||
'1 : 【업】으로 이동',
|
||||
'2 : 【보병】 300명 징병',
|
||||
'3 : 【다른장수】에게 쌀 200을 증여',
|
||||
'4 : 【업】에 화계실행',
|
||||
'5 : 휴식',
|
||||
]);
|
||||
await expect(commandRows.nth(2)).toHaveAttribute('title', '【다른장수】에게 쌀 200을 증여');
|
||||
const geometry = await page.locator('#secret-general-list .turns').evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = getComputedStyle(element);
|
||||
return {
|
||||
width: rect.width,
|
||||
height: rect.height,
|
||||
fontSize: style.fontSize,
|
||||
textAlign: style.textAlign,
|
||||
horizontalOverflow: element.scrollWidth - element.clientWidth,
|
||||
};
|
||||
});
|
||||
expect(geometry.width).toBeGreaterThanOrEqual(190);
|
||||
expect(geometry.width).toBeLessThanOrEqual(230);
|
||||
expect(geometry.height).toBeGreaterThanOrEqual(60);
|
||||
expect(geometry).toMatchObject({ fontSize: '11px', textAlign: 'left', horizontalOverflow: 0 });
|
||||
const titleBox = await page.locator('.secret-page .title').first().boundingBox();
|
||||
const listBox = await page.locator('#secret-general-list').boundingBox();
|
||||
expect(titleBox?.width).toBe(1000);
|
||||
expect(listBox?.width).toBe(974);
|
||||
await testInfo.attach('secret-command-brief-geometry', {
|
||||
body: JSON.stringify(
|
||||
{ viewport: { width: 1200, height: 900 }, titleBox, listBox, commandCell: geometry },
|
||||
null,
|
||||
2
|
||||
),
|
||||
contentType: 'application/json',
|
||||
});
|
||||
await page.screenshot({ path: testInfo.outputPath('secret-command-brief-desktop-1200.png'), fullPage: true });
|
||||
|
||||
await page.unroute(gameTrpcRoute);
|
||||
await install(page, false);
|
||||
|
||||
Reference in New Issue
Block a user