diff --git a/app/game-frontend/e2e/inGameMenus.spec.ts b/app/game-frontend/e2e/inGameMenus.spec.ts index c2a6b351..07acdded 100644 --- a/app/game-frontend/e2e/inGameMenus.spec.ts +++ b/app/game-frontend/e2e/inGameMenus.spec.ts @@ -676,7 +676,7 @@ test('메인 카드의 국가·수도·관직·계급·병종은 Ref 출력명 await expect(page.locator('.main-page')).not.toContainText('che_'); }); -test('메인 장수 동향과 개인 전투 기록은 모두 21px 행 간격을 유지한다', async ({ page }) => { +test('메인 장수 동향과 개인 전투 기록은 Ref 행 간격·색상·글자 크기를 유지한다', async ({ page }) => { const state: FixtureState = { permission: 'head', myset: 3, @@ -704,13 +704,15 @@ test('메인 장수 동향과 개인 전투 기록은 모두 21px 행 간격을 id: 18608, text: '◆186년 9월:
' + - '귀병 ' + - 'Administrator ' + - '0(-2209) ' + + '귀병 ' + + 'Administrator ' + + '0' + + '(-2209) ' + ' ' + - '1361' + - '(-5539) 기병 ' + - 'ⓝ뇌동
', + '1361' + + '(-5539) ' + + '기병 ' + + 'ⓝ뇌동', createdAt: '2026-01-01T03:54:00.000Z', }, ], @@ -754,6 +756,16 @@ test('메인 장수 동향과 개인 전투 기록은 모두 21px 행 간격을 const battle = element.querySelector('.small_war_log'); if (!battle) throw new Error('전투 요약 markup을 찾지 못했습니다.'); const battleRect = battle.getBoundingClientRect(); + const requireElement = (selector: string): HTMLElement => { + const target = element.querySelector(selector); + if (!target) throw new Error(`전투 요약 요소를 찾지 못했습니다: ${selector}`); + return target; + }; + const diamond = requireElement('span[style*="skyblue"]'); + const namePlate = requireElement('.me .name_plate'); + const nameCover = requireElement('.me .name_plate_cover'); + const crewPlate = requireElement('.me .crew_plate'); + const arrow = requireElement('.war_type_defense'); return { line: { top: lineRect.top, height: lineRect.height }, battle: { @@ -762,6 +774,14 @@ test('메인 장수 동향과 개인 전투 기록은 모두 21px 행 간격을 display: getComputedStyle(battle).display, }, lineHeight: getComputedStyle(element).lineHeight, + styles: { + diamondColor: getComputedStyle(diamond).color, + namePlateFontSize: getComputedStyle(namePlate).fontSize, + nameCoverColor: getComputedStyle(nameCover).color, + crewPlateColor: getComputedStyle(crewPlate).color, + crewPlateFontSize: getComputedStyle(crewPlate).fontSize, + defenseArrowColor: getComputedStyle(arrow).color, + }, }; }); }; @@ -770,6 +790,14 @@ test('메인 장수 동향과 개인 전투 기록은 모두 21px 행 간격을 expect(geometry.line.height).toBe(21); expect(geometry.battle.height).toBe(21); expect(geometry.battle.top).toBeCloseTo(geometry.line.top, 0); + expect(geometry.styles).toEqual({ + diamondColor: 'rgb(135, 206, 235)', + namePlateFontSize: '10.5px', + nameCoverColor: 'rgb(255, 255, 0)', + crewPlateColor: 'rgb(255, 69, 0)', + crewPlateFontSize: '12.6px', + defenseArrowColor: 'rgb(255, 0, 255)', + }); }; const desktopGeometry = await inspect( diff --git a/app/game-frontend/src/assets/main.css b/app/game-frontend/src/assets/main.css index 52aaefe7..2a589172 100644 --- a/app/game-frontend/src/assets/main.css +++ b/app/game-frontend/src/assets/main.css @@ -87,3 +87,28 @@ textarea { .small_war_log { display: inline-block; } + +.small_war_log .war_type_attack { + color: cyan; +} + +.small_war_log .war_type_defense { + color: magenta; +} + +.small_war_log .war_type_siege { + color: white; +} + +.small_war_log .name_plate { + font-size: 0.75em; +} + +.small_war_log .name_plate_cover { + color: yellow; +} + +.small_war_log .crew_plate { + color: orangered; + font-size: 90%; +} diff --git a/docs/frontend-legacy-parity.md b/docs/frontend-legacy-parity.md index a4e217cf..4e88fbb1 100644 --- a/docs/frontend-legacy-parity.md +++ b/docs/frontend-legacy-parity.md @@ -137,6 +137,20 @@ REF_PERSONAL_WAR_LOG_ARTIFACT_DIR=/path/to/ignored/artifacts \ `MENU_PARITY_ARTIFACT_DIR`를 지정하면 1200×900·500×900 screenshot과 computed style JSON을 남깁니다. +개인 전투 결과 요약의 Ref 색상·글자 크기는 실제 `small_war_log` class 구조와 +빌드 CSS를 사용하는 별도 정적 Chromium fixture로 재현합니다. 방어 화살표, +장수명 괄호, 병력 수치와 병종·장수/병력 글자 크기를 desktop/mobile에서 +수집하며 live session·DB writer 검증과는 구분합니다. + +```sh +REF_SAM_ROOT=/path/to/ref/sam \ +REF_PERSONAL_BATTLE_LOG_ARTIFACT_DIR=/path/to/ignored/artifacts \ + node tools/frontend-legacy-parity/reference-personal-battle-log-colors.mjs +``` + +대응하는 Core 검증은 `inGameMenus.spec.ts`의 “개인 전투 기록” test이며 같은 +viewport에서 한 줄 geometry와 computed 색상·글자 크기를 함께 검사합니다. + To refresh the PHP ranking evidence after building the ignored reference webpack assets, run: diff --git a/tools/frontend-legacy-parity/reference-personal-battle-log-colors.mjs b/tools/frontend-legacy-parity/reference-personal-battle-log-colors.mjs new file mode 100644 index 00000000..d02ae3e5 --- /dev/null +++ b/tools/frontend-legacy-parity/reference-personal-battle-log-colors.mjs @@ -0,0 +1,85 @@ +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import { resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; + +import { chromium } from '@playwright/test'; + +const refRoot = resolve(process.env.REF_SAM_ROOT ?? '/home/letrhee/sam_rebuild/ref/sam'); +const artifactDir = process.env.REF_PERSONAL_BATTLE_LOG_ARTIFACT_DIR; +const formatterUrl = pathToFileURL(resolve(refRoot, 'hwe/ts/utilGame/formatLog.ts')).href; +const { formatLog } = await import(formatterUrl); +const css = await readFile(resolve(refRoot, 'dist_js/hwe_dynamic/vue/v_main.css'), 'utf8'); +const record = + '◆188년 2월:
' + + '남귀 ' + + '운영자 ' + + '0' + + '(-4404) ' + + ' ' + + '555' + + '(-6845) ' + + '기병 ' + + 'ⓝ독야청정
'; + +const browser = await chromium.launch({ headless: true }); +try { + const context = await browser.newContext({ + colorScheme: 'dark', + deviceScaleFactor: 1, + locale: 'ko-KR', + timezoneId: 'UTC', + }); + const page = await context.newPage(); + await page.setContent( + `` + + `
` + + `
개인 기록
${formatLog(record)}
` + + `
`, + { waitUntil: 'networkidle' } + ); + await page.evaluate(() => document.fonts.ready); + + for (const viewport of [ + { name: 'desktop', width: 1200, height: 900 }, + { name: 'mobile', width: 500, height: 900 }, + ]) { + await page.setViewportSize(viewport); + const measurement = await page.locator('.fixture-line').evaluate((element) => { + const requireElement = (selector) => { + const target = element.querySelector(selector); + if (!(target instanceof HTMLElement)) throw new Error(`Ref 전투기록 요소를 찾지 못했습니다: ${selector}`); + return target; + }; + const rect = element.getBoundingClientRect(); + const battle = requireElement('.small_war_log'); + const battleRect = battle.getBoundingClientRect(); + return { + text: element.textContent, + line: { height: rect.height, fontSize: getComputedStyle(element).fontSize }, + battle: { height: battleRect.height, display: getComputedStyle(battle).display }, + styles: { + diamondColor: getComputedStyle(requireElement('span[style*="skyblue"]')).color, + namePlateFontSize: getComputedStyle(requireElement('.me .name_plate')).fontSize, + nameCoverColor: getComputedStyle(requireElement('.me .name_plate_cover')).color, + crewPlateColor: getComputedStyle(requireElement('.me .crew_plate')).color, + crewPlateFontSize: getComputedStyle(requireElement('.me .crew_plate')).fontSize, + defenseArrowColor: getComputedStyle(requireElement('.war_type_defense')).color, + }, + }; + }); + const output = { viewport, measurement }; + console.log(JSON.stringify(output)); + if (artifactDir) { + await mkdir(artifactDir, { recursive: true }); + await Promise.all([ + page.screenshot({ path: resolve(artifactDir, `ref-personal-battle-log-colors-${viewport.name}.png`) }), + writeFile( + resolve(artifactDir, `ref-personal-battle-log-colors-${viewport.name}.json`), + `${JSON.stringify(output, null, 2)}\n` + ), + ]); + } + } +} finally { + await browser.close(); +}