merge: 개인 전투기록 색상 복원 통합
This commit is contained in:
@@ -676,7 +676,7 @@ test('메인 카드의 국가·수도·관직·계급·병종은 Ref 출력명
|
|||||||
await expect(page.locator('.main-page')).not.toContainText('che_');
|
await expect(page.locator('.main-page')).not.toContainText('che_');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('메인 장수 동향과 개인 전투 기록은 모두 21px 행 간격을 유지한다', async ({ page }) => {
|
test('메인 장수 동향과 개인 전투 기록은 Ref 행 간격·색상·글자 크기를 유지한다', async ({ page }) => {
|
||||||
const state: FixtureState = {
|
const state: FixtureState = {
|
||||||
permission: 'head',
|
permission: 'head',
|
||||||
myset: 3,
|
myset: 3,
|
||||||
@@ -704,13 +704,15 @@ test('메인 장수 동향과 개인 전투 기록은 모두 21px 행 간격을
|
|||||||
id: 18608,
|
id: 18608,
|
||||||
text:
|
text:
|
||||||
'<S>◆</>186년 9월:<div class="small_war_log">' +
|
'<S>◆</>186년 9월:<div class="small_war_log">' +
|
||||||
'<span class="me"><span class="crew_type">귀병</span> ' +
|
'<span class="me"><span class="name_plate"><span class="crew_type">귀병</span> ' +
|
||||||
'<span class="name_plate_cover">【<span class="name">Administrator</span>】</span> ' +
|
'<span class="name_plate_cover">【<span class="name">Administrator</span>】</span></span> ' +
|
||||||
'<span class="remain_crew">0</span>(<span class="killed_crew">-2209</span>)</span> ' +
|
'<span class="crew_plate"><span class="remain_crew">0</span>' +
|
||||||
|
'<span class="killed_plate">(<span class="killed_crew">-2209</span>)</span></span></span> ' +
|
||||||
'<span class="war_type war_type_defense">←</span> ' +
|
'<span class="war_type war_type_defense">←</span> ' +
|
||||||
'<span class="you"><span class="remain_crew">1361</span>' +
|
'<span class="you"><span class="crew_plate"><span class="remain_crew">1361</span>' +
|
||||||
'(<span class="killed_crew">-5539</span>) <span class="crew_type">기병</span> ' +
|
'<span class="killed_plate">(<span class="killed_crew">-5539</span>)</span></span> ' +
|
||||||
'<span class="name_plate_cover">【<span class="name">ⓝ뇌동</span>】</span></span></div>',
|
'<span class="name_plate"><span class="crew_type">기병</span> ' +
|
||||||
|
'<span class="name_plate_cover">【<span class="name">ⓝ뇌동</span>】</span></span></span></div>',
|
||||||
createdAt: '2026-01-01T03:54:00.000Z',
|
createdAt: '2026-01-01T03:54:00.000Z',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -754,6 +756,16 @@ test('메인 장수 동향과 개인 전투 기록은 모두 21px 행 간격을
|
|||||||
const battle = element.querySelector<HTMLElement>('.small_war_log');
|
const battle = element.querySelector<HTMLElement>('.small_war_log');
|
||||||
if (!battle) throw new Error('전투 요약 markup을 찾지 못했습니다.');
|
if (!battle) throw new Error('전투 요약 markup을 찾지 못했습니다.');
|
||||||
const battleRect = battle.getBoundingClientRect();
|
const battleRect = battle.getBoundingClientRect();
|
||||||
|
const requireElement = (selector: string): HTMLElement => {
|
||||||
|
const target = element.querySelector<HTMLElement>(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 {
|
return {
|
||||||
line: { top: lineRect.top, height: lineRect.height },
|
line: { top: lineRect.top, height: lineRect.height },
|
||||||
battle: {
|
battle: {
|
||||||
@@ -762,6 +774,14 @@ test('메인 장수 동향과 개인 전투 기록은 모두 21px 행 간격을
|
|||||||
display: getComputedStyle(battle).display,
|
display: getComputedStyle(battle).display,
|
||||||
},
|
},
|
||||||
lineHeight: getComputedStyle(element).lineHeight,
|
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.line.height).toBe(21);
|
||||||
expect(geometry.battle.height).toBe(21);
|
expect(geometry.battle.height).toBe(21);
|
||||||
expect(geometry.battle.top).toBeCloseTo(geometry.line.top, 0);
|
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(
|
const desktopGeometry = await inspect(
|
||||||
|
|||||||
@@ -87,3 +87,28 @@ textarea {
|
|||||||
.small_war_log {
|
.small_war_log {
|
||||||
display: inline-block;
|
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%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -137,6 +137,20 @@ REF_PERSONAL_WAR_LOG_ARTIFACT_DIR=/path/to/ignored/artifacts \
|
|||||||
`MENU_PARITY_ARTIFACT_DIR`를 지정하면 1200×900·500×900 screenshot과 computed
|
`MENU_PARITY_ARTIFACT_DIR`를 지정하면 1200×900·500×900 screenshot과 computed
|
||||||
style JSON을 남깁니다.
|
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
|
To refresh the PHP ranking evidence after building the ignored reference
|
||||||
webpack assets, run:
|
webpack assets, run:
|
||||||
|
|
||||||
|
|||||||
@@ -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 =
|
||||||
|
'<S>◆</>188년 2월:<div class="small_war_log">' +
|
||||||
|
'<span class="me"><span class="name_plate"><span class="crew_type">남귀</span> ' +
|
||||||
|
'<span class="name_plate_cover">【<span class="name">운영자</span>】</span></span> ' +
|
||||||
|
'<span class="crew_plate"><span class="remain_crew">0</span>' +
|
||||||
|
'<span class="killed_plate">(<span class="killed_crew">-4404</span>)</span></span></span> ' +
|
||||||
|
'<span class="war_type war_type_defense">←</span> ' +
|
||||||
|
'<span class="you"><span class="crew_plate"><span class="remain_crew">555</span>' +
|
||||||
|
'<span class="killed_plate">(<span class="killed_crew">-6845</span>)</span></span> ' +
|
||||||
|
'<span class="name_plate"><span class="crew_type">기병</span> ' +
|
||||||
|
'<span class="name_plate_cover">【<span class="name">ⓝ독야청정</span>】</span></span></span></div>';
|
||||||
|
|
||||||
|
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(
|
||||||
|
`<!doctype html><html><head><style>${css}</style></head><body>` +
|
||||||
|
`<div id="container"><div class="RecordZone row gx-0"><div class="GeneralLog col col-12 col-lg-6">` +
|
||||||
|
`<div class="bg1 center s-border-tb title">개인 기록</div><div class="fixture-line">${formatLog(record)}</div>` +
|
||||||
|
`</div></div></div></body></html>`,
|
||||||
|
{ 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();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user