merge: 도시정보 폰트와 장수 표시를 반영
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