Merge remote-tracking branch 'origin/main' into fix/recruitment-fullscreen-mobile-20260813
This commit is contained in:
@@ -465,6 +465,7 @@ test('enters general and nation command arguments and sends exact values', async
|
||||
const requests = await install(page);
|
||||
await page.setViewportSize({ width: 1200, height: 900 });
|
||||
await page.goto('/');
|
||||
await expect(page.getByTestId('current-city-marker')).toHaveCount(0);
|
||||
|
||||
await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
|
||||
await page.getByTestId('command-picker').getByRole('button', { name: /화계/ }).click();
|
||||
@@ -474,6 +475,12 @@ test('enters general and nation command arguments and sends exact values', async
|
||||
await expect(form.getByTestId('command-argument-guidance')).toContainText('선택한 도시에 화계를 실행합니다.');
|
||||
await expect(form.getByTestId('command-map-target-summary')).toContainText('현재 도시에서 0칸');
|
||||
const commandMap = form.getByTestId('command-argument-map');
|
||||
const currentCityMarker = commandMap.getByTestId('current-city-marker');
|
||||
const selectionStatus = form.getByTestId('command-map-selection-status');
|
||||
await expect(currentCityMarker).toHaveText('현재');
|
||||
await expect(currentCityMarker).toHaveAttribute('aria-label', '현재 도시 업');
|
||||
await expect(selectionStatus).toContainText('현재 도시업');
|
||||
await expect(selectionStatus).toContainText('선택 도시업');
|
||||
const mapCities = commandMap.locator('.city-base');
|
||||
await expect(mapCities).toHaveCount(2);
|
||||
await expect(commandMap.locator('.city-bg')).toHaveCount(2);
|
||||
@@ -515,11 +522,17 @@ test('enters general and nation command arguments and sends exact values', async
|
||||
await mapCities.nth(1).click();
|
||||
await expect(form.locator('select')).toHaveValue('2');
|
||||
await expect(form.getByTestId('command-map-target-summary')).toContainText('현재 도시에서 1칸');
|
||||
await expect(selectionStatus).toContainText('현재 도시업');
|
||||
await expect(selectionStatus).toContainText('선택 도시허창');
|
||||
await expect(currentCityMarker).toHaveAttribute('aria-label', '현재 도시 업');
|
||||
await expect(mapCities.nth(0)).toHaveClass(/mine/);
|
||||
await expect(mapCities.nth(1)).toHaveClass(/selected/);
|
||||
await mapCities.nth(1).hover();
|
||||
expect(await mapCities.nth(1).evaluate((element) => getComputedStyle(element).cursor)).toBe('pointer');
|
||||
await mapCities.nth(1).focus();
|
||||
await expect(mapCities.nth(1)).toBeFocused();
|
||||
await expect(page).toHaveURL(/\/$/);
|
||||
await commandMap.screenshot({ path: test.info().outputPath('main-city-current-marker-desktop.png') });
|
||||
const mapGeometry = await form.getByTestId('command-argument-map').evaluate((element) => {
|
||||
const area = element.querySelector<HTMLElement>('.map-area')!;
|
||||
const rect = area.getBoundingClientRect();
|
||||
@@ -759,6 +772,9 @@ test('uses the map to choose a nation target in the chief command window', async
|
||||
await expect(form.getByTestId('command-argument-guidance')).toContainText('초반 제한');
|
||||
await form.getByTestId('command-argument-map').locator('.city-base').nth(1).click();
|
||||
await expect(form.locator('select')).toHaveValue('2');
|
||||
await expect(form.getByTestId('command-map-selection-status')).toContainText('현재 도시업');
|
||||
await expect(form.getByTestId('command-map-selection-status')).toContainText('선택 국가적국');
|
||||
await expect(form.getByTestId('current-city-marker')).toHaveAttribute('aria-label', '현재 도시 업');
|
||||
await expect(form.getByTestId('command-map-target-summary')).toContainText('수도 허창 · 도시 1개');
|
||||
await expect(page).toHaveURL(/\/che\/chief-center$/);
|
||||
await page.screenshot({ path: test.info().outputPath('chief-nation-map-option.png'), fullPage: true });
|
||||
@@ -771,6 +787,16 @@ test('fits the city map option window inside the Ref-compatible 500px mobile pag
|
||||
await page.getByRole('button', { name: '1턴 명령 입력', exact: true }).click();
|
||||
const picker = page.getByTestId('command-picker');
|
||||
await picker.getByRole('button', { name: /화계/ }).click();
|
||||
const mobileForm = picker.getByTestId('command-argument-form');
|
||||
const mobileMap = mobileForm.getByTestId('command-argument-map');
|
||||
const mobileCities = mobileMap.locator('.city-base');
|
||||
await mobileCities.nth(1).click();
|
||||
await expect(mobileForm.locator('select')).toHaveValue('2');
|
||||
await expect(mobileForm.getByTestId('command-map-selection-status')).toContainText('현재 도시업');
|
||||
await expect(mobileForm.getByTestId('command-map-selection-status')).toContainText('선택 도시허창');
|
||||
await expect(mobileMap.getByTestId('current-city-marker')).toHaveAttribute('aria-label', '현재 도시 업');
|
||||
await expect(mobileCities.nth(0)).toHaveClass(/mine/);
|
||||
await expect(mobileCities.nth(1)).toHaveClass(/selected/);
|
||||
const geometry = await picker.evaluate((element) => {
|
||||
const map = element.querySelector<HTMLElement>('[data-testid="command-argument-map"] .map-area')!;
|
||||
const pickerRect = element.getBoundingClientRect();
|
||||
@@ -782,6 +808,19 @@ test('fits the city map option window inside the Ref-compatible 500px mobile pag
|
||||
pickerScrollWidth: element.scrollWidth,
|
||||
mapWidth: mapRect.width,
|
||||
mapHeight: mapRect.height,
|
||||
marker: (() => {
|
||||
const marker = element.querySelector<HTMLElement>('[data-testid="current-city-marker"]')!;
|
||||
const rect = marker.getBoundingClientRect();
|
||||
const style = getComputedStyle(marker);
|
||||
return {
|
||||
left: rect.left,
|
||||
right: rect.right,
|
||||
top: rect.top,
|
||||
bottom: rect.bottom,
|
||||
pointerEvents: style.pointerEvents,
|
||||
borderColor: style.borderColor,
|
||||
};
|
||||
})(),
|
||||
};
|
||||
});
|
||||
expect(geometry.pickerX).toBeGreaterThanOrEqual(0);
|
||||
@@ -790,6 +829,12 @@ test('fits the city map option window inside the Ref-compatible 500px mobile pag
|
||||
expect(geometry.pickerScrollWidth).toBeLessThanOrEqual(geometry.pickerWidth);
|
||||
expect(geometry.mapWidth).toBeGreaterThan(470);
|
||||
expect(geometry.mapHeight / geometry.mapWidth).toBeCloseTo(5 / 7, 2);
|
||||
expect(geometry.marker.left).toBeGreaterThanOrEqual(0);
|
||||
expect(geometry.marker.right).toBeLessThanOrEqual(500);
|
||||
expect(geometry.marker.top).toBeGreaterThanOrEqual(0);
|
||||
expect(geometry.marker.bottom).toBeLessThanOrEqual(900);
|
||||
expect(geometry.marker.pointerEvents).toBe('none');
|
||||
expect(geometry.marker.borderColor).toBe('rgb(130, 207, 255)');
|
||||
await page.screenshot({ path: test.info().outputPath('main-city-map-option-mobile.png'), fullPage: true });
|
||||
});
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ type NavigationFixture = {
|
||||
currentYear?: number;
|
||||
currentMonth?: number;
|
||||
scenarioTitle?: string;
|
||||
lastExecuted?: string | null;
|
||||
latestVote?: { id: number; title: string; hasVoted: boolean } | null;
|
||||
globalRecords?: Array<{ id: number; text: string }>;
|
||||
generalRecords?: Array<{ id: number; text: string }>;
|
||||
@@ -423,7 +424,7 @@ const installFixture = async (page: Page, state: NavigationFixture) => {
|
||||
onlineNations: '위(1)',
|
||||
onlineGenerals: '메뉴검증장수',
|
||||
nationNotice: '<p>국가 방침</p>',
|
||||
lastExecuted: null,
|
||||
lastExecuted: state.lastExecuted === undefined ? '2026-08-13T00:05:06.000Z' : state.lastExecuted,
|
||||
latestVote:
|
||||
state.latestVote === undefined
|
||||
? { id: 9, title: '메뉴 설문', hasVoted: false }
|
||||
@@ -567,6 +568,10 @@ const persistArtifact = async (page: Page, name: string) => {
|
||||
globalPopup: describe('#mobile-global-menu'),
|
||||
nationPopup: describe('#mobile-nation-menu'),
|
||||
quickPopup: describe('#mobile-quick-menu'),
|
||||
activityStatus: describe('.activity-status'),
|
||||
executionStatus: describe('.execution-status'),
|
||||
tournamentStatus: describe('.tournament-status'),
|
||||
voteStatus: describe('.vote-status'),
|
||||
commandMenu: describe('.reserved-command-editor details[open] .menu-items'),
|
||||
commandDividers: [...document.querySelectorAll<HTMLElement>('.reserved-command-editor details[open] .menu-divider')].map(
|
||||
(element) => {
|
||||
@@ -599,6 +604,7 @@ test('desktop menus preserve ref columns, prefix-safe routes, and controlled dro
|
||||
stage: 1,
|
||||
npcMode: 1,
|
||||
scenarioTitle: '메인 화면 검증 시나리오',
|
||||
lastExecuted: '2026-08-13T00:05:06.000Z',
|
||||
generalMeCalls: 0,
|
||||
operations: [],
|
||||
};
|
||||
@@ -615,31 +621,35 @@ test('desktop menus preserve ref columns, prefix-safe routes, and controlled dro
|
||||
await expect(page.getByRole('heading', { name: '메인 화면 검증 시나리오', exact: true })).toHaveCount(1);
|
||||
await expect(page.locator('.game-shell__subtitle')).toHaveText('185년 1월 · 턴 10분');
|
||||
await expect(page.locator('.game-shell__subtitle')).not.toContainText('메인 화면 검증 시나리오');
|
||||
await expect(page.locator('.execution-status')).toHaveText('동작 시각: 08-13 09:05');
|
||||
await expect(page.locator('.tournament-status')).toHaveText('토너먼트: 참가 모집중');
|
||||
await expect(page.locator('.vote-status')).toHaveText('설문: 메뉴 설문');
|
||||
const headerStatusGeometry = await page.locator('.main-page').evaluate((element) => {
|
||||
const title = element.querySelector<HTMLElement>('.game-shell__title');
|
||||
const subtitle = element.querySelector<HTMLElement>('.game-shell__subtitle');
|
||||
const activity = element.querySelector<HTMLElement>('.activity-status');
|
||||
const execution = element.querySelector<HTMLElement>('.execution-status');
|
||||
const tournament = element.querySelector<HTMLElement>('.tournament-status');
|
||||
const survey = element.querySelector<HTMLElement>('.vote-status');
|
||||
if (!title || !subtitle || !activity || !tournament || !survey) {
|
||||
if (!title || !subtitle || !activity || !execution || !tournament || !survey) {
|
||||
throw new Error('main header status geometry is incomplete');
|
||||
}
|
||||
return {
|
||||
title: title.getBoundingClientRect().toJSON(),
|
||||
subtitle: subtitle.getBoundingClientRect().toJSON(),
|
||||
activity: activity.getBoundingClientRect().toJSON(),
|
||||
execution: execution.getBoundingClientRect().toJSON(),
|
||||
tournament: tournament.getBoundingClientRect().toJSON(),
|
||||
survey: survey.getBoundingClientRect().toJSON(),
|
||||
activityColumns: getComputedStyle(activity).gridTemplateColumns,
|
||||
};
|
||||
});
|
||||
expect(headerStatusGeometry.subtitle.y).toBeGreaterThanOrEqual(headerStatusGeometry.title.bottom);
|
||||
expect(headerStatusGeometry.activity.width).toBeCloseTo(666.67, 0);
|
||||
expect(headerStatusGeometry.activity.width).toBeCloseTo(1000, 0);
|
||||
expect(headerStatusGeometry.execution.width).toBeCloseTo(333.33, 0);
|
||||
expect(headerStatusGeometry.tournament.width).toBeCloseTo(333.33, 0);
|
||||
expect(headerStatusGeometry.survey.width).toBeCloseTo(333.33, 0);
|
||||
expect(headerStatusGeometry.activityColumns.split(' ')).toHaveLength(2);
|
||||
expect(headerStatusGeometry.activityColumns.split(' ')).toHaveLength(3);
|
||||
const tournamentStatusLink = page.locator('.tournament-status a');
|
||||
const surveyStatusLink = page.locator('.vote-status a');
|
||||
await tournamentStatusLink.hover();
|
||||
@@ -1217,6 +1227,7 @@ test('the 939/940 boundary switches to the Ref-style 500px single document', asy
|
||||
stage: 6,
|
||||
npcMode: 1,
|
||||
scenarioTitle: '모바일 검증 시나리오',
|
||||
lastExecuted: null,
|
||||
latestVote: null,
|
||||
generalMeCalls: 0,
|
||||
operations: [],
|
||||
@@ -1238,25 +1249,27 @@ test('the 939/940 boundary switches to the Ref-style 500px single document', asy
|
||||
await page.setViewportSize({ width: 500, height: 900 });
|
||||
await expect(page.getByRole('heading', { name: '모바일 검증 시나리오', exact: true })).toHaveCount(1);
|
||||
await expect(page.locator('.game-shell__subtitle')).toHaveText('185년 1월 · 턴 10분');
|
||||
await expect(page.locator('.execution-status')).toHaveText('동작 시각: 기록 없음');
|
||||
await expect(page.locator('.tournament-status')).toHaveText('토너먼트: 베팅 진행중');
|
||||
await expect(page.locator('.vote-status')).toHaveText('설문: 진행 중인 설문 없음');
|
||||
const activityGeometry = await page.locator('.activity-status').evaluate((element) => {
|
||||
const execution = element.querySelector<HTMLElement>('.execution-status');
|
||||
const tournament = element.querySelector<HTMLElement>('.tournament-status');
|
||||
const survey = element.querySelector<HTMLElement>('.vote-status');
|
||||
if (!tournament || !survey) throw new Error('activity status is incomplete');
|
||||
if (!execution || !tournament || !survey) throw new Error('activity status is incomplete');
|
||||
return {
|
||||
width: element.getBoundingClientRect().width,
|
||||
executionWidth: execution.getBoundingClientRect().width,
|
||||
tournamentWidth: tournament.getBoundingClientRect().width,
|
||||
surveyWidth: survey.getBoundingClientRect().width,
|
||||
columns: getComputedStyle(element).gridTemplateColumns,
|
||||
};
|
||||
});
|
||||
expect(activityGeometry).toMatchObject({
|
||||
width: 500,
|
||||
tournamentWidth: 250,
|
||||
surveyWidth: 250,
|
||||
columns: '250px 250px',
|
||||
});
|
||||
expect(activityGeometry.width).toBe(500);
|
||||
expect(activityGeometry.executionWidth).toBeCloseTo(166.67, 0);
|
||||
expect(activityGeometry.tournamentWidth).toBeCloseTo(166.67, 0);
|
||||
expect(activityGeometry.surveyWidth).toBeCloseTo(166.67, 0);
|
||||
expect(activityGeometry.columns.split(' ')).toHaveLength(3);
|
||||
await expect
|
||||
.poll(() =>
|
||||
page
|
||||
|
||||
Reference in New Issue
Block a user