merge: 최종 최신 main을 지도 계절 전환에 반영한다
This commit is contained in:
@@ -210,7 +210,44 @@ const install = async (
|
||||
}
|
||||
if (operation === 'general.me') return response(generalContext);
|
||||
if (operation === 'world.getMap') return response(mapFixture);
|
||||
if (operation === 'turns.getCommandTable') return response({ general: [], nation: [] });
|
||||
if (operation === 'turns.getCommandTable')
|
||||
return response({
|
||||
general: [
|
||||
{
|
||||
category: '군사',
|
||||
values: [
|
||||
{
|
||||
key: 'che_징병',
|
||||
name: '징병',
|
||||
reqArg: true,
|
||||
status: 'needsInput',
|
||||
possible: true,
|
||||
inputFields: [],
|
||||
},
|
||||
{
|
||||
key: 'che_화계',
|
||||
name: '화계',
|
||||
reqArg: true,
|
||||
status: 'needsInput',
|
||||
possible: true,
|
||||
inputFields: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
nation: [],
|
||||
inputOptions: {
|
||||
cities: [{ value: 1, label: '업 (아국)' }],
|
||||
nations: [],
|
||||
generals: [],
|
||||
crewTypes: [{ value: 1, label: '보병' }],
|
||||
armTypes: [],
|
||||
nationTypes: [],
|
||||
colors: [],
|
||||
items: {},
|
||||
recruitment: null,
|
||||
},
|
||||
});
|
||||
if (operation === 'turns.reserved.getGeneral' || operation === 'turns.reserved.getNation') {
|
||||
return response({ turns: [], revision: 0 });
|
||||
}
|
||||
@@ -373,7 +410,12 @@ const install = async (
|
||||
crew: 500,
|
||||
train: 90,
|
||||
atmos: 90,
|
||||
turns: denseCurrentCity ? ['징병', '훈련'] : ['징병'],
|
||||
turns: denseCurrentCity
|
||||
? [
|
||||
{ action: 'che_징병', args: { crewType: 1, amount: 300 } },
|
||||
{ action: 'che_화계', args: { destCityId: 1 } },
|
||||
]
|
||||
: [{ action: 'che_징병', args: { crewType: 1, amount: 300 } }],
|
||||
},
|
||||
...(denseCurrentCity
|
||||
? Array.from({ length: 12 }, (_, index) => ({
|
||||
@@ -1082,8 +1124,10 @@ test('current-city wraps dense general names and only shrinks reserved turns', a
|
||||
const rows = page.locator('.generals tbody tr');
|
||||
const reservedTurns = rows.nth(0).locator('.turns');
|
||||
const npcTurns = rows.nth(1).locator('.turns');
|
||||
await expect(reservedTurns).toContainText('1 : 징병');
|
||||
await expect(reservedTurns).toContainText('2 : 훈련');
|
||||
await expect(reservedTurns).toContainText('1 : 【보병】 300명 징병');
|
||||
await expect(reservedTurns).toContainText('2 : 【업】에 화계실행');
|
||||
await expect(reservedTurns.locator('.turn-line').nth(0)).toHaveAttribute('title', '【보병】 300명 징병');
|
||||
await expect(reservedTurns.locator('.turn-line').nth(1)).toHaveAttribute('title', '【업】에 화계실행');
|
||||
await expect(reservedTurns).toHaveClass(/turns--reserved/);
|
||||
await expect(npcTurns).toHaveText('NPC 장수');
|
||||
await expect(npcTurns).not.toHaveClass(/turns--reserved/);
|
||||
|
||||
@@ -2218,7 +2218,43 @@ test('main cards and command input stay inside their Ref-sized grid slots', asyn
|
||||
await selectedMenu.evaluate((element) => ((element as HTMLDetailsElement).open = false));
|
||||
await expect(selectedMenu).not.toHaveAttribute('open', '');
|
||||
|
||||
await page.locator('[data-main-target="commands"] .select-command').click();
|
||||
const selectCommand = page.locator('[data-main-target="commands"] .select-command');
|
||||
await expect(selectCommand).toHaveClass(/legacy-button--info/u);
|
||||
await page.mouse.move(1, 1);
|
||||
const measureSelectCommand = () =>
|
||||
selectCommand.evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = getComputedStyle(element);
|
||||
return {
|
||||
top: rect.top,
|
||||
bottom: rect.bottom,
|
||||
height: rect.height,
|
||||
marginTop: style.marginTop,
|
||||
borderBottomWidth: style.borderBottomWidth,
|
||||
borderRadius: style.borderRadius,
|
||||
backgroundColor: style.backgroundColor,
|
||||
};
|
||||
});
|
||||
const selectDefault = await measureSelectCommand();
|
||||
expect(selectDefault).toMatchObject({
|
||||
height: 34,
|
||||
marginTop: '0px',
|
||||
borderBottomWidth: '4px',
|
||||
borderRadius: '5.25px',
|
||||
backgroundColor: 'rgb(52, 152, 219)',
|
||||
});
|
||||
await selectCommand.hover();
|
||||
const selectHover = await measureSelectCommand();
|
||||
expect(selectHover).toMatchObject({ height: 33, marginTop: '1px', borderBottomWidth: '3px' });
|
||||
expect(selectHover.bottom).toBeCloseTo(selectDefault.bottom, 2);
|
||||
const selectBox = await selectCommand.boundingBox();
|
||||
if (!selectBox) throw new Error('select command control is not measurable');
|
||||
await page.mouse.move(selectBox.x + selectBox.width / 2, selectBox.y + selectBox.height / 2);
|
||||
await page.mouse.down();
|
||||
const selectActive = await measureSelectCommand();
|
||||
expect(selectActive).toMatchObject({ height: 32, marginTop: '2px', borderBottomWidth: '2px' });
|
||||
expect(selectActive.bottom).toBeCloseTo(selectDefault.bottom, 2);
|
||||
await page.mouse.up();
|
||||
const picker = page.getByTestId('command-picker');
|
||||
await expect(picker).toBeVisible();
|
||||
// The trigger can end up directly above a newly opened category button.
|
||||
|
||||
@@ -205,6 +205,86 @@ test('nation generals keeps the 1000px legacy grid and redacted member columns',
|
||||
await expect(page.locator('#nation-general-list')).toContainText('?');
|
||||
});
|
||||
|
||||
test('nation generals top controls share fixed Lumen state geometry on desktop and mobile', async ({
|
||||
page,
|
||||
}, testInfo) => {
|
||||
await install(page);
|
||||
const evidence: Record<string, unknown> = {};
|
||||
|
||||
for (const viewport of [
|
||||
{ width: 1200, height: 900 },
|
||||
{ width: 500, height: 900 },
|
||||
]) {
|
||||
await page.setViewportSize(viewport);
|
||||
await page.goto('nation/generals');
|
||||
await expect(page.locator('#nation-general-list')).toBeVisible();
|
||||
const controls = [
|
||||
page.getByRole('button', { name: '돌아가기' }),
|
||||
page.getByRole('button', { name: '갱신' }),
|
||||
page.getByRole('button', { name: '보기 모드⌄' }),
|
||||
page.getByRole('button', { name: '열 선택⌄' }),
|
||||
];
|
||||
const viewportEvidence: Record<string, unknown> = {};
|
||||
|
||||
for (const control of controls) {
|
||||
const label = (await control.textContent())?.trim() ?? 'unknown';
|
||||
await expect(control).toHaveClass(/legacy-button--fixed-height/u);
|
||||
const measure = () =>
|
||||
control.evaluate((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const style = getComputedStyle(element);
|
||||
return {
|
||||
top: rect.top,
|
||||
bottom: rect.bottom,
|
||||
height: rect.height,
|
||||
marginTop: style.marginTop,
|
||||
borderBottomWidth: style.borderBottomWidth,
|
||||
borderRadius: style.borderRadius,
|
||||
backgroundColor: style.backgroundColor,
|
||||
fontFamily: style.fontFamily,
|
||||
fontSize: style.fontSize,
|
||||
};
|
||||
});
|
||||
await page.mouse.move(viewport.width - 1, viewport.height - 1);
|
||||
const base = await measure();
|
||||
expect(base).toMatchObject({
|
||||
height: 32,
|
||||
marginTop: '0px',
|
||||
borderBottomWidth: '4px',
|
||||
borderRadius: '5.25px',
|
||||
fontSize: '14px',
|
||||
});
|
||||
expect(base.fontFamily).toContain('Pretendard');
|
||||
|
||||
await control.hover();
|
||||
const hover = await measure();
|
||||
expect(hover).toMatchObject({ height: 31, marginTop: '1px', borderBottomWidth: '3px' });
|
||||
expect(hover.bottom).toBeCloseTo(base.bottom, 2);
|
||||
|
||||
const box = await control.boundingBox();
|
||||
if (!box) throw new Error(`${label} control is not measurable`);
|
||||
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
await page.mouse.down();
|
||||
const active = await measure();
|
||||
expect(active).toMatchObject({ height: 30, marginTop: '2px', borderBottomWidth: '2px' });
|
||||
expect(active.bottom).toBeCloseTo(base.bottom, 2);
|
||||
await page.mouse.move(viewport.width - 1, viewport.height - 1);
|
||||
await page.mouse.up();
|
||||
viewportEvidence[label] = { default: base, hover, active };
|
||||
}
|
||||
evidence[`${viewport.width}x${viewport.height}`] = viewportEvidence;
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath(`nation-general-buttons-${viewport.width}.png`),
|
||||
fullPage: true,
|
||||
});
|
||||
}
|
||||
|
||||
await testInfo.attach('nation-general-button-geometry', {
|
||||
body: JSON.stringify(evidence, null, 2),
|
||||
contentType: 'application/json',
|
||||
});
|
||||
});
|
||||
|
||||
test('nation generals restores Ref group, saved view, sort, and Korean search behavior', async ({ page }, testInfo) => {
|
||||
await install(page);
|
||||
await page.setViewportSize({ width: 1200, height: 900 });
|
||||
@@ -405,17 +485,20 @@ test('secret office renders five Ref-style command briefs and the forbidden erro
|
||||
'5 : 휴식',
|
||||
]);
|
||||
await expect(commandRows.nth(2)).toHaveAttribute('title', '【다른장수】에게 쌀 200을 증여');
|
||||
const geometry = await page.locator('#secret-general-list .turns').first().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,
|
||||
};
|
||||
});
|
||||
const geometry = await page
|
||||
.locator('#secret-general-list .turns')
|
||||
.first()
|
||||
.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);
|
||||
|
||||
@@ -395,7 +395,10 @@ test('join refresh shows the assigned preliminary group immediately with accessi
|
||||
await refresh.focus();
|
||||
await expect(refresh).toBeFocused();
|
||||
await refresh.hover();
|
||||
await expect(refresh).toHaveCSS('filter', 'brightness(1.25)');
|
||||
await expect(refresh).toHaveCSS('filter', 'none');
|
||||
await expect(refresh).toHaveCSS('height', '43px');
|
||||
await expect(refresh).toHaveCSS('margin-top', '1px');
|
||||
await expect(refresh).toHaveCSS('border-bottom-width', '3px');
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390);
|
||||
});
|
||||
|
||||
@@ -602,7 +605,11 @@ test('mobile betting rankings use tabs and keep dedicated icons beside general n
|
||||
await expect(dialog.getByText('예상 환수금 280')).toBeVisible();
|
||||
await dialog.getByLabel('베팅 금액').selectOption('50');
|
||||
await expect(dialog.getByText('예상 환수금 1,400')).toBeVisible();
|
||||
await persistScreenshot(page, 'tournament-betting-dialog-mobile', testInfo.outputPath('betting-dialog-mobile.webp'));
|
||||
await persistScreenshot(
|
||||
page,
|
||||
'tournament-betting-dialog-mobile',
|
||||
testInfo.outputPath('betting-dialog-mobile.webp')
|
||||
);
|
||||
await dialog.getByRole('button', { name: '베팅 등록' }).click();
|
||||
await expect(dialog).not.toBeVisible();
|
||||
await expect(page.getByRole('status')).toHaveText('베팅이 등록되었습니다.');
|
||||
|
||||
Reference in New Issue
Block a user