fix(game-ui): 감찰부 기록 배경과 모바일 표시를 바로잡음
중복 하단 메타를 제거하고 최근 전투를 연도 없는 독립 행으로 넓힌다. 기록 본문에 walnut 질감을 적용하고 고정 높이 잘림을 없애 모바일 전투 결과까지 표시한다. production Chromium 검증이 실제 로그 유형과 배경·기하를 확인하도록 감찰부 fixture와 assertion도 보강한다.
This commit is contained in:
@@ -327,9 +327,27 @@ const install = async (page: Page, state: FixtureState) => {
|
|||||||
const rawRequestBody: unknown = route.request().postData() ? route.request().postDataJSON() : {};
|
const rawRequestBody: unknown = route.request().postData() ? route.request().postDataJSON() : {};
|
||||||
const requestBody =
|
const requestBody =
|
||||||
rawRequestBody && typeof rawRequestBody === 'object' ? (rawRequestBody as Record<string, unknown>) : {};
|
rawRequestBody && typeof rawRequestBody === 'object' ? (rawRequestBody as Record<string, unknown>) : {};
|
||||||
|
const queryInputText = new URL(route.request().url()).searchParams.get('input');
|
||||||
|
let queryInput: Record<string, unknown> = {};
|
||||||
|
if (queryInputText) {
|
||||||
|
try {
|
||||||
|
const parsed: unknown = JSON.parse(queryInputText);
|
||||||
|
if (parsed && typeof parsed === 'object') {
|
||||||
|
queryInput = parsed as Record<string, unknown>;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
queryInput = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
const results = operations.map((operation, operationIndex) => {
|
const results = operations.map((operation, operationIndex) => {
|
||||||
const rawPayload =
|
const rawPayload =
|
||||||
requestBody[String(operationIndex)] ?? (operations.length === 1 ? requestBody : undefined);
|
requestBody[String(operationIndex)] ??
|
||||||
|
queryInput[String(operationIndex)] ??
|
||||||
|
(operations.length === 1
|
||||||
|
? Object.keys(requestBody).length > 0
|
||||||
|
? requestBody
|
||||||
|
: queryInput
|
||||||
|
: undefined);
|
||||||
const payload =
|
const payload =
|
||||||
rawPayload && typeof rawPayload === 'object' ? (rawPayload as TrpcRequestPayload) : undefined;
|
rawPayload && typeof rawPayload === 'object' ? (rawPayload as TrpcRequestPayload) : undefined;
|
||||||
const jsonInput =
|
const jsonInput =
|
||||||
@@ -537,10 +555,12 @@ const install = async (page: Page, state: FixtureState) => {
|
|||||||
return response(battleCenter(state));
|
return response(battleCenter(state));
|
||||||
}
|
}
|
||||||
if (operation === 'nation.getGeneralLog') {
|
if (operation === 'nation.getGeneralLog') {
|
||||||
const type = new URL(route.request().url()).searchParams.get('input')?.includes('generalAction')
|
const type =
|
||||||
? 'generalAction'
|
typeof jsonInput.type === 'string' &&
|
||||||
: operation;
|
['generalHistory', 'battleDetail', 'battleResult', 'generalAction'].includes(jsonInput.type)
|
||||||
return response({ type, generalId: 7, logs: [{ id: 1, text: '<Y>감찰 기록</>' }] });
|
? jsonInput.type
|
||||||
|
: 'generalAction';
|
||||||
|
return response({ type, generalId: 7, logs: [{ id: 1, text: `<Y>${type} 감찰 기록</>` }] });
|
||||||
}
|
}
|
||||||
return response({ ok: true });
|
return response({ ok: true });
|
||||||
});
|
});
|
||||||
@@ -1310,7 +1330,9 @@ test('감찰부 keeps the selector interaction and shows the permission error pa
|
|||||||
await expect(page.locator('.selector-row select').nth(1)).toHaveValue('8');
|
await expect(page.locator('.selector-row select').nth(1)).toHaveValue('8');
|
||||||
await page.getByRole('button', { name: '다음 ▶' }).click();
|
await page.getByRole('button', { name: '다음 ▶' }).click();
|
||||||
await expect(page.locator('.selector-row select').nth(1)).toHaveValue('7');
|
await expect(page.locator('.selector-row select').nth(1)).toHaveValue('7');
|
||||||
await expect(page.locator('.battle-general-name')).toContainText('검증장수 【 간의대부 | 건강 】');
|
await expect(page.locator('.battle-general-name')).toContainText('검증장수');
|
||||||
|
await expect(page.locator('.battle-general-name')).toContainText('간의대부');
|
||||||
|
await expect(page.locator('.battle-general-name')).toContainText('건강');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('계급29품관');
|
await expect(page.locator('.battle-general-extra')).toContainText('계급29품관');
|
||||||
await expect(page.locator('.battle-general-card')).toContainText('병종보병');
|
await expect(page.locator('.battle-general-card')).toContainText('병종보병');
|
||||||
await expect(page.locator('.battle-general-card')).not.toContainText('che_');
|
await expect(page.locator('.battle-general-card')).not.toContainText('che_');
|
||||||
@@ -1321,6 +1343,11 @@ test('감찰부 keeps the selector interaction and shows the permission error pa
|
|||||||
expect(battleImages[1]?.backgroundImage).toContain('/game/crewtype1.png');
|
expect(battleImages[1]?.backgroundImage).toContain('/game/crewtype1.png');
|
||||||
await expect(page.locator('.battle-general-card [role="progressbar"]')).toHaveCount(14);
|
await expect(page.locator('.battle-general-card [role="progressbar"]')).toHaveCount(14);
|
||||||
await expect(page.locator('.battle-general-card [aria-label*="1,275,975 (EX+)"]')).toHaveCount(5);
|
await expect(page.locator('.battle-general-card [aria-label*="1,275,975 (EX+)"]')).toHaveCount(5);
|
||||||
|
await expect(page.locator('.general-meta')).toHaveCount(0);
|
||||||
|
await expect(page.locator('.battle-general-extra__recent-value')).toHaveText('01-01 00:00');
|
||||||
|
await expect(page.locator('.battle-general-extra__recent-value')).not.toContainText('2026');
|
||||||
|
await expect(page.locator('.log-block')).toHaveCount(4);
|
||||||
|
await expect(page.locator('.log-block[data-log-type="battleResult"]')).toContainText('battleResult 감찰 기록');
|
||||||
expect(
|
expect(
|
||||||
await page
|
await page
|
||||||
.locator('.battle-general-card [role="progressbar"]')
|
.locator('.battle-general-card [role="progressbar"]')
|
||||||
@@ -1330,7 +1357,11 @@ test('감찰부 keeps the selector interaction and shows the permission error pa
|
|||||||
const geometry = await page.locator('.battle-page').evaluate((element) => {
|
const geometry = await page.locator('.battle-page').evaluate((element) => {
|
||||||
const selector = element.querySelector<HTMLElement>('.selector-row')!;
|
const selector = element.querySelector<HTMLElement>('.selector-row')!;
|
||||||
const controls = [...selector.children].map((child) => (child as HTMLElement).getBoundingClientRect());
|
const controls = [...selector.children].map((child) => (child as HTMLElement).getBoundingClientRect());
|
||||||
const logBlock = element.querySelector<HTMLElement>('.log-block')!.getBoundingClientRect();
|
const logBlocks = [...element.querySelectorAll<HTMLElement>('.log-block')];
|
||||||
|
const logBlock = logBlocks[0]!.getBoundingClientRect();
|
||||||
|
const recentLabel = element.querySelector<HTMLElement>('.battle-general-extra__recent-label')!;
|
||||||
|
const recentValue = element.querySelector<HTMLElement>('.battle-general-extra__recent-value')!;
|
||||||
|
const previousStat = recentLabel.previousElementSibling as HTMLElement;
|
||||||
return {
|
return {
|
||||||
width: element.getBoundingClientRect().width,
|
width: element.getBoundingClientRect().width,
|
||||||
fontSize: getComputedStyle(element).fontSize,
|
fontSize: getComputedStyle(element).fontSize,
|
||||||
@@ -1341,6 +1372,11 @@ test('감찰부 keeps the selector interaction and shows the permission error pa
|
|||||||
backgroundImage: getComputedStyle(element).backgroundImage,
|
backgroundImage: getComputedStyle(element).backgroundImage,
|
||||||
generalBackgroundImage: getComputedStyle(element.querySelector<HTMLElement>('.battle-general-card')!)
|
generalBackgroundImage: getComputedStyle(element.querySelector<HTMLElement>('.battle-general-card')!)
|
||||||
.backgroundImage,
|
.backgroundImage,
|
||||||
|
logBackgroundImages: logBlocks.map((block) => getComputedStyle(block).backgroundImage),
|
||||||
|
recentLabelTop: recentLabel.getBoundingClientRect().top,
|
||||||
|
recentValueTop: recentValue.getBoundingClientRect().top,
|
||||||
|
recentValueWidth: recentValue.getBoundingClientRect().width,
|
||||||
|
previousStatTop: previousStat.getBoundingClientRect().top,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
expect(geometry.width).toBe(1000);
|
expect(geometry.width).toBe(1000);
|
||||||
@@ -1352,16 +1388,41 @@ test('감찰부 keeps the selector interaction and shows the permission error pa
|
|||||||
expect(geometry.logBlockWidth).toBeCloseTo(500, 0);
|
expect(geometry.logBlockWidth).toBeCloseTo(500, 0);
|
||||||
expect(geometry.backgroundImage).toContain('back_walnut.jpg');
|
expect(geometry.backgroundImage).toContain('back_walnut.jpg');
|
||||||
expect(geometry.generalBackgroundImage).toContain('back_blue.jpg');
|
expect(geometry.generalBackgroundImage).toContain('back_blue.jpg');
|
||||||
|
expect(geometry.logBackgroundImages).toHaveLength(4);
|
||||||
|
expect(geometry.logBackgroundImages.every((background) => background.includes('back_walnut.jpg'))).toBe(true);
|
||||||
|
expect(geometry.recentLabelTop).toBeCloseTo(geometry.recentValueTop, 0);
|
||||||
|
expect(geometry.recentLabelTop).toBeGreaterThan(geometry.previousStatTop);
|
||||||
|
expect(geometry.recentValueWidth).toBeGreaterThan(400);
|
||||||
await persistParityArtifact(page, 'core-battle-center-desktop', geometry);
|
await persistParityArtifact(page, 'core-battle-center-desktop', geometry);
|
||||||
|
|
||||||
await page.setViewportSize({ width: 500, height: 900 });
|
await page.setViewportSize({ width: 500, height: 900 });
|
||||||
const mobileGeometry = await page.locator('.selector-row').evaluate((element) => ({
|
const mobileGeometry = await page.locator('.battle-page').evaluate((element) => {
|
||||||
columns: getComputedStyle(element).gridTemplateColumns,
|
const selector = element.querySelector<HTMLElement>('.selector-row')!;
|
||||||
controlWidths: [...element.children].map((child) => (child as HTMLElement).getBoundingClientRect().width),
|
const battleResult = element.querySelector<HTMLElement>('.log-block[data-log-type="battleResult"]')!;
|
||||||
}));
|
const footer = element.querySelector<HTMLElement>('.battle-footer')!;
|
||||||
|
const pageRect = element.getBoundingClientRect();
|
||||||
|
const resultRect = battleResult.getBoundingClientRect();
|
||||||
|
const footerRect = footer.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
columns: getComputedStyle(selector).gridTemplateColumns,
|
||||||
|
controlWidths: [...selector.children].map((child) => (child as HTMLElement).getBoundingClientRect().width),
|
||||||
|
pageHeight: pageRect.height,
|
||||||
|
pageOverflow: getComputedStyle(element).overflow,
|
||||||
|
resultBottomWithinPage: resultRect.bottom <= pageRect.bottom,
|
||||||
|
footerAfterResult: footerRect.top >= resultRect.bottom,
|
||||||
|
resultBackgroundImage: getComputedStyle(battleResult).backgroundImage,
|
||||||
|
};
|
||||||
|
});
|
||||||
expect(mobileGeometry.columns.split(' ')).toHaveLength(4);
|
expect(mobileGeometry.columns.split(' ')).toHaveLength(4);
|
||||||
expect(mobileGeometry.controlWidths[0]).toBeCloseTo(83.33, 0);
|
expect(mobileGeometry.controlWidths[0]).toBeCloseTo(83.33, 0);
|
||||||
expect(mobileGeometry.controlWidths[1]).toBeCloseTo(125, 0);
|
expect(mobileGeometry.controlWidths[1]).toBeCloseTo(125, 0);
|
||||||
|
expect(mobileGeometry.pageHeight).toBeGreaterThan(0);
|
||||||
|
expect(mobileGeometry.pageOverflow).toBe('visible');
|
||||||
|
expect(mobileGeometry.resultBottomWithinPage).toBe(true);
|
||||||
|
expect(mobileGeometry.footerAfterResult).toBe(true);
|
||||||
|
expect(mobileGeometry.resultBackgroundImage).toContain('back_walnut.jpg');
|
||||||
|
await page.locator('.log-block[data-log-type="battleResult"]').scrollIntoViewIfNeeded();
|
||||||
|
await expect(page.locator('.log-block[data-log-type="battleResult"]')).toBeInViewport();
|
||||||
await persistParityArtifact(page, 'core-battle-center-mobile', mobileGeometry);
|
await persistParityArtifact(page, 'core-battle-center-mobile', mobileGeometry);
|
||||||
|
|
||||||
await page.unrouteAll({ behavior: 'wait' });
|
await page.unrouteAll({ behavior: 'wait' });
|
||||||
|
|||||||
@@ -286,28 +286,26 @@ onMounted(() => {
|
|||||||
><strong>{{ selectedGeneral.battleStats.killCrew.toLocaleString('ko-KR') }}</strong>
|
><strong>{{ selectedGeneral.battleStats.killCrew.toLocaleString('ko-KR') }}</strong>
|
||||||
<span>피살</span
|
<span>피살</span
|
||||||
><strong>{{ selectedGeneral.battleStats.deathCrew.toLocaleString('ko-KR') }}</strong>
|
><strong>{{ selectedGeneral.battleStats.deathCrew.toLocaleString('ko-KR') }}</strong>
|
||||||
<span>최근 전투</span><strong>{{ selectedGeneral.recentWar || '-' }}</strong>
|
<span class="battle-general-extra__recent-label">최근 전투</span>
|
||||||
|
<strong class="battle-general-extra__recent-value">
|
||||||
|
{{
|
||||||
|
formatServerDateTime(selectedGeneral.recentWar, {
|
||||||
|
format: 'monthDayTime',
|
||||||
|
fallback: '-',
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
<LegacyGeneralProgress :general="selectedGeneral" :show-primary="false" />
|
<LegacyGeneralProgress :general="selectedGeneral" :show-primary="false" />
|
||||||
</template>
|
</template>
|
||||||
</GeneralBasicCard>
|
</GeneralBasicCard>
|
||||||
<div v-if="selectedGeneral" class="general-meta">
|
|
||||||
<div>
|
|
||||||
최근 턴:
|
|
||||||
{{
|
|
||||||
formatServerDateTime(selectedGeneral.turnTime, { format: 'hourMinute', fallback: '-' })
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<div>최근 전투: {{ selectedGeneral.recentWar || '-' }}</div>
|
|
||||||
<div>전투 횟수: {{ selectedGeneral.warnum }}</div>
|
|
||||||
</div>
|
|
||||||
</PanelCard>
|
</PanelCard>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stack">
|
<div class="stack">
|
||||||
<PanelCard title="장수 기록" subtitle="열전과 전투 기록">
|
<PanelCard title="장수 기록" subtitle="열전과 전투 기록">
|
||||||
<div class="log-grid">
|
<div class="log-grid">
|
||||||
<div v-for="type in logTypes" :key="type" class="log-block">
|
<div v-for="type in logTypes" :key="type" class="log-block" :data-log-type="type">
|
||||||
<div class="log-title">{{ logLabels[type] }}</div>
|
<div class="log-title">{{ logLabels[type] }}</div>
|
||||||
<SkeletonLines v-if="loading || logLoading" :lines="3" />
|
<SkeletonLines v-if="loading || logLoading" :lines="3" />
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -356,14 +354,6 @@ onMounted(() => {
|
|||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.general-meta {
|
|
||||||
margin: 0;
|
|
||||||
padding: 6px 8px;
|
|
||||||
color: #ccc;
|
|
||||||
display: grid;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.battle-general-extra {
|
.battle-general-extra {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(6, 1fr);
|
grid-template-columns: repeat(6, 1fr);
|
||||||
@@ -390,6 +380,15 @@ onMounted(() => {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.battle-general-extra > .battle-general-extra__recent-label {
|
||||||
|
grid-column: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.battle-general-extra > .battle-general-extra__recent-value {
|
||||||
|
grid-column: 2 / -1;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
.log-grid {
|
.log-grid {
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
@@ -397,7 +396,8 @@ onMounted(() => {
|
|||||||
.log-block {
|
.log-block {
|
||||||
border: 1px solid #666;
|
border: 1px solid #666;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: #000;
|
background-color: #302016;
|
||||||
|
background-image: var(--sammo-texture-walnut);
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -492,8 +492,8 @@ onMounted(() => {
|
|||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
height: 1268px;
|
height: auto;
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
.battle-top {
|
.battle-top {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
@@ -530,7 +530,6 @@ onMounted(() => {
|
|||||||
@media (max-width: 991px) {
|
@media (max-width: 991px) {
|
||||||
.battle-page {
|
.battle-page {
|
||||||
width: 500px;
|
width: 500px;
|
||||||
height: 1411px;
|
|
||||||
}
|
}
|
||||||
.battle-top {
|
.battle-top {
|
||||||
grid-template-columns: 89px 89px 1fr 0 0;
|
grid-template-columns: 89px 89px 1fr 0 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user