fix: 도시정보 장수 표시와 폰트를 바로잡음
메인 화면과 같은 Pretendard 14px을 사용하고 장수 이름 목록의 줄바꿈을 허용한다. 실제 예약 턴에만 작은 글씨를 적용해 NPC 장수 표기는 본문 크기로 유지하며 Chromium 회귀 검증을 추가한다.
This commit is contained in:
@@ -160,7 +160,8 @@ const install = async (
|
|||||||
mode: 'member' | 'wanderer' | 'admin' = 'member',
|
mode: 'member' | 'wanderer' | 'admin' = 'member',
|
||||||
trade: number | null = 100,
|
trade: number | null = 100,
|
||||||
globalNationCount = 2,
|
globalNationCount = 2,
|
||||||
mapFixture = map
|
mapFixture = map,
|
||||||
|
denseCurrentCity = false
|
||||||
) => {
|
) => {
|
||||||
await page.addInitScript((profile) => {
|
await page.addInitScript((profile) => {
|
||||||
localStorage.setItem('sammo-game-token', 'ga_info');
|
localStorage.setItem('sammo-game-token', 'ga_info');
|
||||||
@@ -372,8 +373,32 @@ const install = async (
|
|||||||
crew: 500,
|
crew: 500,
|
||||||
train: 90,
|
train: 90,
|
||||||
atmos: 90,
|
atmos: 90,
|
||||||
turns: ['징병'],
|
turns: denseCurrentCity ? ['징병', '훈련'] : ['징병'],
|
||||||
},
|
},
|
||||||
|
...(denseCurrentCity
|
||||||
|
? Array.from({ length: 12 }, (_, index) => ({
|
||||||
|
id: index + 2,
|
||||||
|
name: `NPC장수이름이긴${index + 1}`,
|
||||||
|
npcState: 2,
|
||||||
|
picture: null,
|
||||||
|
imageServer: 0,
|
||||||
|
nationId: 1,
|
||||||
|
nationName: '아국',
|
||||||
|
leadership: 60,
|
||||||
|
strength: 60,
|
||||||
|
intelligence: 60,
|
||||||
|
injury: 0,
|
||||||
|
officerLevel: 1,
|
||||||
|
leadershipBonus: 0,
|
||||||
|
defenceTrain: 80,
|
||||||
|
crewTypeId: 1,
|
||||||
|
crewTypeName: '보병',
|
||||||
|
crew: 500,
|
||||||
|
train: 90,
|
||||||
|
atmos: 90,
|
||||||
|
turns: [],
|
||||||
|
}))
|
||||||
|
: []),
|
||||||
],
|
],
|
||||||
forceSummary: {
|
forceSummary: {
|
||||||
enemyCrew: 0,
|
enemyCrew: 0,
|
||||||
@@ -407,7 +432,7 @@ test('four legacy menu pages keep the 1000px desktop table contract', async ({ p
|
|||||||
['nation/info', '.legacy-info-page', '14px', 'Pretendard', 'collapse'],
|
['nation/info', '.legacy-info-page', '14px', 'Pretendard', 'collapse'],
|
||||||
['nation/cities', '.nation-cities-page', '14px', 'Pretendard', 'collapse'],
|
['nation/cities', '.nation-cities-page', '14px', 'Pretendard', 'collapse'],
|
||||||
['global-info', '.global-page', '14px', 'Pretendard', 'collapse'],
|
['global-info', '.global-page', '14px', 'Pretendard', 'collapse'],
|
||||||
['current-city', '.city-page', '16px', 'Times New Roman', 'separate'],
|
['current-city', '.city-page', '14px', 'Pretendard', 'separate'],
|
||||||
] as const) {
|
] as const) {
|
||||||
await go(page, path);
|
await go(page, path);
|
||||||
await expect(page.locator(selector)).toBeVisible();
|
await expect(page.locator(selector)).toBeVisible();
|
||||||
@@ -774,8 +799,8 @@ test('current-city exposes own general details to a member and admin fixture', a
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
expect(legacyGeometry.selector).toMatchObject({ width: 400, height: 19 });
|
expect(legacyGeometry.selector).toMatchObject({ width: 400, height: 19 });
|
||||||
expect(legacyGeometry.stats).toEqual({ x: 100, y: 178, width: 1000, height: 136 });
|
expect(legacyGeometry.stats).toEqual({ x: 100, y: 165.375, width: 1000, height: 106.9375 });
|
||||||
expect(legacyGeometry.generals).toMatchObject({ x: 88, y: 332, width: 1024 });
|
expect(legacyGeometry.generals).toMatchObject({ x: 88, y: 290.3125, width: 1024 });
|
||||||
expect(legacyGeometry.titleAlign).toBe('start');
|
expect(legacyGeometry.titleAlign).toBe('start');
|
||||||
expect(legacyGeometry.icon).toMatchObject({ width: 64, height: 64, naturalWidth: 64, naturalHeight: 64 });
|
expect(legacyGeometry.icon).toMatchObject({ width: 64, height: 64, naturalWidth: 64, naturalHeight: 64 });
|
||||||
if (artifactRoot) {
|
if (artifactRoot) {
|
||||||
@@ -834,6 +859,67 @@ test('current-city exposes own general details to a member and admin fixture', a
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('current-city wraps dense general names and only shrinks reserved turns', async ({ page }) => {
|
||||||
|
await install(page, 'member', 100, 2, map, true);
|
||||||
|
for (const viewport of [
|
||||||
|
{ name: 'desktop', width: 1200, height: 900 },
|
||||||
|
{ name: 'mobile', width: 500, height: 900 },
|
||||||
|
]) {
|
||||||
|
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
||||||
|
await go(page, 'current-city');
|
||||||
|
|
||||||
|
const pageStyle = await page.locator('.city-page').evaluate((element) => {
|
||||||
|
const style = getComputedStyle(element);
|
||||||
|
return { fontFamily: style.fontFamily, fontSize: style.fontSize };
|
||||||
|
});
|
||||||
|
expect(pageStyle.fontFamily).toContain('Pretendard');
|
||||||
|
expect(pageStyle.fontSize).toBe('14px');
|
||||||
|
|
||||||
|
const names = page.locator('.general-names');
|
||||||
|
await expect(names).toHaveCSS('white-space', 'normal');
|
||||||
|
const nameLineCount = await names.locator('span').evaluateAll((elements) => {
|
||||||
|
const tops = elements.map((element) => Math.round(element.getBoundingClientRect().top));
|
||||||
|
return new Set(tops).size;
|
||||||
|
});
|
||||||
|
expect(nameLineCount).toBeGreaterThan(1);
|
||||||
|
|
||||||
|
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).toHaveClass(/turns--reserved/);
|
||||||
|
await expect(npcTurns).toHaveText('NPC 장수');
|
||||||
|
await expect(npcTurns).not.toHaveClass(/turns--reserved/);
|
||||||
|
|
||||||
|
const turnFontSizes = await Promise.all([
|
||||||
|
reservedTurns.evaluate((element) => getComputedStyle(element).fontSize),
|
||||||
|
npcTurns.evaluate((element) => getComputedStyle(element).fontSize),
|
||||||
|
]);
|
||||||
|
expect(Number.parseFloat(turnFontSizes[0])).toBeLessThan(Number.parseFloat(pageStyle.fontSize));
|
||||||
|
expect(turnFontSizes[1]).toBe(pageStyle.fontSize);
|
||||||
|
|
||||||
|
const reservedLineTops = await reservedTurns
|
||||||
|
.locator('.turn-line')
|
||||||
|
.evaluateAll((elements) => elements.map((element) => Math.round(element.getBoundingClientRect().top)));
|
||||||
|
expect(new Set(reservedLineTops).size).toBe(2);
|
||||||
|
|
||||||
|
if (artifactRoot) {
|
||||||
|
await mkdir(artifactRoot, { recursive: true });
|
||||||
|
await writeFile(
|
||||||
|
resolve(artifactRoot, `core-current-city-dense-${viewport.name}.json`),
|
||||||
|
`${JSON.stringify({ pageStyle, nameLineCount, turnFontSizes, reservedLineTops }, null, 2)}\n`,
|
||||||
|
'utf8'
|
||||||
|
);
|
||||||
|
await page.screenshot({
|
||||||
|
path: resolve(artifactRoot, `core-current-city-dense-${viewport.name}.png`),
|
||||||
|
fullPage: true,
|
||||||
|
animations: 'disabled',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('current-city renders a missing merchant rate with the legacy dash and percent text', async ({ page }) => {
|
test('current-city renders a missing merchant rate with the legacy dash and percent text', async ({ page }) => {
|
||||||
await install(page, 'member', null);
|
await install(page, 'member', null);
|
||||||
await page.setViewportSize({ width: 1200, height: 900 });
|
await page.setViewportSize({ width: 1200, height: 900 });
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ const generalImage = (general: General): string => resolveGeneralIconUrl(general
|
|||||||
<td>{{ general.crew ?? '?' }}</td>
|
<td>{{ general.crew ?? '?' }}</td>
|
||||||
<td>{{ general.train ?? '?' }}</td>
|
<td>{{ general.train ?? '?' }}</td>
|
||||||
<td>{{ general.atmos ?? '?' }}</td>
|
<td>{{ general.atmos ?? '?' }}</td>
|
||||||
<td class="turns">
|
<td class="turns" :class="{ 'turns--reserved': general.turns.length > 0 }">
|
||||||
<template v-if="general.turns.length">
|
<template v-if="general.turns.length">
|
||||||
<span v-for="(turn, index) in general.turns" :key="index" class="turn-line"
|
<span v-for="(turn, index) in general.turns" :key="index" class="turn-line"
|
||||||
>{{ index + 1 }} : {{ turn }}</span
|
>{{ index + 1 }} : {{ turn }}</span
|
||||||
@@ -321,8 +321,8 @@ const generalImage = (general: General): string => resolveGeneralIconUrl(general
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="legacy-banner">
|
<td class="legacy-banner">
|
||||||
삼국지 모의전투 HiDCHe / KOEI의 이미지를 사용, 응용하였습니다 / 제작 :
|
삼국지 모의전투 HiDCHe / KOEI의 이미지를 사용, 응용하였습니다 / 제작 : HideD(hided62@gmail.com)
|
||||||
HideD(hided62@gmail.com) /
|
/
|
||||||
<a href="https://sam.hided.net/wiki/hidche/credit" target="_blank" rel="noreferrer">Credit</a>
|
<a href="https://sam.hided.net/wiki/hidche/credit" target="_blank" rel="noreferrer">Credit</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -335,9 +335,9 @@ const generalImage = (general: General): string => resolveGeneralIconUrl(general
|
|||||||
.city-page {
|
.city-page {
|
||||||
width: 1000px;
|
width: 1000px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
font-family: 'Times New Roman', serif;
|
font-family: var(--sammo-font-sans);
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
line-height: normal;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
.legacy-table {
|
.legacy-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -419,6 +419,7 @@ const generalImage = (general: General): string => resolveGeneralIconUrl(general
|
|||||||
}
|
}
|
||||||
.general-names {
|
.general-names {
|
||||||
text-align: left !important;
|
text-align: left !important;
|
||||||
|
white-space: normal !important;
|
||||||
}
|
}
|
||||||
.unknown {
|
.unknown {
|
||||||
color: gray;
|
color: gray;
|
||||||
@@ -447,7 +448,7 @@ const generalImage = (general: General): string => resolveGeneralIconUrl(general
|
|||||||
height: 64px;
|
height: 64px;
|
||||||
object-fit: fill;
|
object-fit: fill;
|
||||||
}
|
}
|
||||||
.turns {
|
.turns--reserved {
|
||||||
font-size: x-small;
|
font-size: x-small;
|
||||||
}
|
}
|
||||||
.turn-line {
|
.turn-line {
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ storage, route guards, and image loading.
|
|||||||
| gateway Kakao OTP | `index.php#modalOTP` | 동일 문구·500px modal, desktop/mobile geometry와 색상·typography, password/OAuth 진입, autofocus·focus-visible·active·disabled·오류 재시도·session 저장 |
|
| gateway Kakao OTP | `index.php#modalOTP` | 동일 문구·500px modal, desktop/mobile geometry와 색상·typography, password/OAuth 진입, autofocus·focus-visible·active·disabled·오류 재시도·session 저장 |
|
||||||
| game login hand-off | unauthenticated `hwe/index.php` redirect | `/che/login` delegates to `/gateway/` |
|
| game login hand-off | unauthenticated `hwe/index.php` redirect | `/che/login` delegates to `/gateway/` |
|
||||||
| troop | `hwe/v_troop.php` | existing `app/game-frontend/e2e/troop.spec.ts` desktop/mobile geometry and interaction suite |
|
| troop | `hwe/v_troop.php` | existing `app/game-frontend/e2e/troop.spec.ts` desktop/mobile geometry and interaction suite |
|
||||||
| current city | `hwe/b_currentCity.php` | ref-specific 16px Times New Roman, 1000px summary/1024px general tables, 400px selector, 64px icon, nation title color, force summary, actor/spy/admin redaction, and map-click query navigation |
|
| current city | `hwe/b_currentCity.php` | main-page Pretendard 14px, wrapping general-name summary, small reserved-turn lines but normal-size NPC labels, 1000px summary/1024px general tables, 400px selector, 64px icon, nation title color, force summary, actor/spy/admin redaction, and map-click query navigation |
|
||||||
| best general | `hwe/a_bestGeneral.php` | authenticated 500/1000px ranking and unique-item grids, user/NPC switch, 100/64px cell/image geometry, title/button computed styles, retained-data API error |
|
| best general | `hwe/a_bestGeneral.php` | authenticated 500/1000px ranking and unique-item grids, user/NPC switch, 100/64px cell/image geometry, title/button computed styles, retained-data API error |
|
||||||
| hall of fame | `hwe/a_hallOfFame.php` | public 500/1000px container, 100px ranking cells, 64px natural image, title/button/select computed styles, scenario switch and retained-data API error |
|
| hall of fame | `hwe/a_hallOfFame.php` | public 500/1000px container, 100px ranking cells, 64px natural image, title/button/select computed styles, scenario switch and retained-data API error |
|
||||||
| yearbook | `hwe/v_history.php` | 1000px 700+300 desktop grid, 500px stacked grid, month navigation, legacy textures, success and API-error flows |
|
| yearbook | `hwe/v_history.php` | 1000px 700+300 desktop grid, 500px stacked grid, month navigation, legacy textures, success and API-error flows |
|
||||||
@@ -167,6 +167,14 @@ and is never written to the artifact. The matching core fixture is
|
|||||||
`app/game-frontend/e2e/inGameInfo.spec.ts`, which writes its computed DOM and
|
`app/game-frontend/e2e/inGameInfo.spec.ts`, which writes its computed DOM and
|
||||||
screenshot only when `CITY_PARITY_ARTIFACT_DIR` is set.
|
screenshot only when `CITY_PARITY_ARTIFACT_DIR` is set.
|
||||||
|
|
||||||
|
Ref's standalone current-city document inherits the browser's 16px Times face
|
||||||
|
because its page-local includes do not load the in-game Pretendard baseline.
|
||||||
|
Core intentionally follows the requested main-page 14px Pretendard typography
|
||||||
|
instead. The Ref row contract still applies to commands: only a non-NPC
|
||||||
|
general's reserved turns use `general_turn_text`/`x-small`; `NPC 장수`, foreign
|
||||||
|
nation, and wanderer labels stay at the table's normal font size. The summary
|
||||||
|
general-name cell must remain wrappable when many generals share a city.
|
||||||
|
|
||||||
징병·모병의 Ref 화면은 다음 collector로 1000/500px DOM, 이미지 natural size,
|
징병·모병의 Ref 화면은 다음 collector로 1000/500px DOM, 이미지 natural size,
|
||||||
불가능 병종 toggle과 hover/focus를 수집합니다. 기본 모드는 현재 Ref session을
|
불가능 병종 toggle과 hover/focus를 수집합니다. 기본 모드는 현재 Ref session을
|
||||||
사용합니다. 비교 계정이 없는 환경에서는 `REF_STATIC_FIXTURE=1`로 Ref가 빌드한
|
사용합니다. 비교 계정이 없는 환경에서는 `REF_STATIC_FIXTURE=1`로 Ref가 빌드한
|
||||||
|
|||||||
Reference in New Issue
Block a user