diff --git a/app/game-frontend/e2e/inGameMenus.spec.ts b/app/game-frontend/e2e/inGameMenus.spec.ts index c2a6b351..54ab7cd7 100644 --- a/app/game-frontend/e2e/inGameMenus.spec.ts +++ b/app/game-frontend/e2e/inGameMenus.spec.ts @@ -63,6 +63,7 @@ type FixtureState = { joinConfig?: Record; createGeneralInputs?: Array>; mainTraits?: { personal: string; specialDomestic: string; specialWar: string }; + hiddenSeedLogText?: string; recentRecords?: { global: Array<{ id: number; text: string; createdAt?: string }>; general: Array<{ id: number; text: string; createdAt?: string }>; @@ -484,7 +485,10 @@ const install = async (page: Page, state: FixtureState) => { }); if (operation === 'general.getMyLog') { state.generalLogQueries = (state.generalLogQueries ?? 0) + 1; - return response({ type: 'generalAction', logs: [{ id: 1, text: '기록' }] }); + return response({ + type: 'generalAction', + logs: [{ id: 1, text: state.hiddenSeedLogText ?? '기록' }], + }); } if (operation === 'general.instantRetreat') { state.instantRetreatInputs?.push(jsonInput); @@ -560,7 +564,11 @@ const install = async (page: Page, state: FixtureState) => { ['generalHistory', 'battleDetail', 'battleResult', 'generalAction'].includes(jsonInput.type) ? jsonInput.type : 'generalAction'; - return response({ type, generalId: 7, logs: [{ id: 1, text: `${type} 감찰 기록` }] }); + return response({ + type, + generalId: 7, + logs: [{ id: 1, text: state.hiddenSeedLogText ?? `${type} 감찰 기록` }], + }); } return response({ ok: true }); }); @@ -676,7 +684,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 +712,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 +764,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 +782,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 +798,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( @@ -791,6 +827,80 @@ test('메인 장수 동향과 개인 전투 기록은 모두 21px 행 간격을 await persistParityArtifact(page, 'core-main-personal-battle-log-inline-mobile', mobileGeometry); }); +test('전투시드는 메인·내 정보·감찰부에서 숨긴 채 선택할 수 있다', async ({ page }) => { + const seedText = '(전투시드: 0123456789abcdef)'; + const logText = + '검증장수가 낙양으로 ' + + `진격합니다.${seedText}`; + const state: FixtureState = { + permission: 'head', + myset: 3, + settingMutations: [], + accessPages: [], + hiddenSeedLogText: logText, + recentRecords: { + global: [{ id: 18611, text: logText }], + general: [], + history: [], + }, + }; + await install(page, state); + + const inspectHiddenSeed = async (selector: string) => { + const seed = page.locator(selector); + await expect(seed).toHaveCount(1); + return seed.evaluate((element) => { + const range = document.createRange(); + range.selectNodeContents(element); + const selection = window.getSelection(); + selection?.removeAllRanges(); + selection?.addRange(range); + const style = getComputedStyle(element); + const rect = element.getBoundingClientRect(); + const result = { + text: element.textContent, + selectedText: selection?.toString(), + color: style.color, + fontSize: style.fontSize, + width: rect.width, + height: rect.height, + }; + selection?.removeAllRanges(); + return result; + }); + }; + const assertHiddenSeed = (result: Awaited>) => { + expect(result.text).toBe(seedText); + expect(result.selectedText).toBe(seedText); + expect(result.color).toBe('rgba(0, 0, 0, 0)'); + expect(result.fontSize).toBe('0px'); + expect(result.width).toBe(0); + expect(result.height).toBe(0); + }; + + for (const viewport of [ + { width: 1000, height: 900 }, + { width: 500, height: 900 }, + ]) { + await page.setViewportSize(viewport); + + await page.goto(''); + const mainSeed = await inspectHiddenSeed('[data-record-bucket="global"] .hidden_but_copyable'); + assertHiddenSeed(mainSeed); + await persistParityArtifact(page, `core-hidden-battle-seed-main-${viewport.width}`, mainSeed); + + await page.goto('my-page'); + const myPageSeed = await inspectHiddenSeed('.log-panel:first-child .hidden_but_copyable'); + assertHiddenSeed(myPageSeed); + await persistParityArtifact(page, `core-hidden-battle-seed-my-page-${viewport.width}`, myPageSeed); + + await page.goto('battle-center'); + const battleCenterSeed = await inspectHiddenSeed('[data-log-type="generalAction"] .hidden_but_copyable'); + assertHiddenSeed(battleCenterSeed); + await persistParityArtifact(page, `core-hidden-battle-seed-battle-center-${viewport.width}`, battleCenterSeed); + } +}); + test('메인 개인 기록의 공격·수비 시각은 Ref와 같은 90% 글자 크기로 표시한다', async ({ page }) => { const state: FixtureState = { permission: 'head', @@ -1099,6 +1209,66 @@ test('내 정보&설정 keeps desktop density and becomes a 390px horizontal-ide await persistParityArtifact(page, 'core-my-page-mobile', mobile); }); +test('내 정보&설정의 지난 플레이는 기본 탐색과 분리되어 오른쪽에 정렬된다', async ({ page }) => { + const state: FixtureState = { permission: 'head', myset: 3, settingMutations: [], accessPages: [] }; + await install(page, state); + + for (const viewport of [ + { name: 'desktop', width: 1000, height: 900 }, + { name: 'mobile', width: 390, height: 844 }, + ] as const) { + await page.setViewportSize({ width: viewport.width, height: viewport.height }); + await page.goto('my-page'); + + const pastPlaysLink = page.getByRole('link', { name: '지난 플레이' }); + await expect(pastPlaysLink).toHaveAttribute('href', `${gameBasePath}/past-plays`); + await pastPlaysLink.hover(); + await pastPlaysLink.focus(); + await expect(pastPlaysLink).toBeFocused(); + + const geometry = await page.locator('.title-row').evaluate((element) => { + const title = element.getBoundingClientRect(); + const actions = element.querySelector('.title-actions')!.getBoundingClientRect(); + const navigation = element.querySelector('.navigation-actions')!.getBoundingClientRect(); + const back = element.querySelector('.navigation-actions a')!.getBoundingClientRect(); + const refresh = element.querySelector('.navigation-actions button')!.getBoundingClientRect(); + const past = element.querySelector('.past-plays-link')!.getBoundingClientRect(); + const pastStyle = getComputedStyle(element.querySelector('.past-plays-link')!); + return { + title: { left: title.left, right: title.right }, + actions: { left: actions.left, right: actions.right }, + navigation: { left: navigation.left, right: navigation.right }, + back: { top: back.top, right: back.right }, + refresh: { top: refresh.top, right: refresh.right }, + past: { top: past.top, left: past.left, right: past.right }, + pastStyle: { + cursor: pastStyle.cursor, + minHeight: pastStyle.minHeight, + backgroundColor: pastStyle.backgroundColor, + }, + scrollWidth: document.documentElement.scrollWidth, + }; + }); + + expect(geometry.actions.left).toBeCloseTo(geometry.title.left + 1, 0); + expect(geometry.actions.right).toBeCloseTo(geometry.title.right - 1, 0); + expect(geometry.navigation.left).toBeCloseTo(geometry.actions.left, 0); + expect(geometry.past.right).toBeCloseTo(geometry.actions.right, 0); + expect(geometry.past.left).toBeGreaterThan(geometry.navigation.right); + expect(geometry.back.top).toBeCloseTo(geometry.past.top, 0); + expect(geometry.refresh.top).toBeCloseTo(geometry.past.top, 0); + expect(geometry.pastStyle).toEqual({ + cursor: 'pointer', + minHeight: '34px', + backgroundColor: 'rgb(49, 95, 134)', + }); + expect(geometry.scrollWidth).toBe(viewport.width); + await persistParityArtifact(page, `core-my-page-past-plays-${viewport.name}`, geometry); + await pastPlaysLink.click(); + await expect(page).toHaveURL(new RegExp(`${gameBasePath}/past-plays$`, 'u')); + } +}); + for (const [label, failure] of [ ['daemon timeout', 'TIMEOUT'], ['engine transaction 오류', 'INTERNAL_SERVER_ERROR'], diff --git a/app/game-frontend/src/assets/main.css b/app/game-frontend/src/assets/main.css index 52aaefe7..d33af421 100644 --- a/app/game-frontend/src/assets/main.css +++ b/app/game-frontend/src/assets/main.css @@ -87,3 +87,39 @@ textarea { .small_war_log { display: inline-block; } + +/* + * Ref's common stylesheet keeps diagnostic seeds in the DOM so they remain + * copyable, while removing them from every normal log rendering surface. + * This marker can arrive through main, personal, audit, and history log APIs, + * so the display contract belongs to the app-wide legacy markup boundary. + */ +.hidden_but_copyable { + color: rgba(0, 0, 0, 0) !important; + font-size: 0; +} + +.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/app/game-frontend/src/components/main/GeneralRecordPanels.vue b/app/game-frontend/src/components/main/GeneralRecordPanels.vue index 7e756914..8d2bb70f 100644 --- a/app/game-frontend/src/components/main/GeneralRecordPanels.vue +++ b/app/game-frontend/src/components/main/GeneralRecordPanels.vue @@ -84,11 +84,6 @@ const unavailableText: Record = { border-bottom: 0; } -.log-line :deep(.hidden_but_copyable) { - color: transparent !important; - font-size: 0; -} - .empty { padding: 2px 8px; color: #999; diff --git a/app/game-frontend/src/views/MainView.vue b/app/game-frontend/src/views/MainView.vue index 0e5fa475..45c72071 100644 --- a/app/game-frontend/src/views/MainView.vue +++ b/app/game-frontend/src/views/MainView.vue @@ -763,11 +763,6 @@ button { vertical-align: top; } -.record-line :deep(.hidden_but_copyable) { - color: transparent !important; - font-size: 0; -} - .record-empty { color: #aaa; } diff --git a/app/game-frontend/src/views/MyPageView.vue b/app/game-frontend/src/views/MyPageView.vue index 7a51ab83..ae8f57d7 100644 --- a/app/game-frontend/src/views/MyPageView.vue +++ b/app/game-frontend/src/views/MyPageView.vue @@ -365,9 +365,13 @@ onMounted(() => {
내 정 보 - 지난 플레이 - 돌아가기 - +
+ + 지난 플레이 +
{{ error }}
@@ -675,6 +679,16 @@ onMounted(() => { height: 18px; letter-spacing: 0; } +.title-actions { + display: flex; + width: 100%; + align-items: flex-start; + justify-content: space-between; +} +.navigation-actions { + display: flex; + gap: 4px; +} .legacy-button, button, select, diff --git a/docs/frontend-legacy-parity.md b/docs/frontend-legacy-parity.md index a4e217cf..b97c1bc5 100644 --- a/docs/frontend-legacy-parity.md +++ b/docs/frontend-legacy-parity.md @@ -137,6 +137,27 @@ REF_PERSONAL_WAR_LOG_ARTIFACT_DIR=/path/to/ignored/artifacts \ `MENU_PARITY_ARTIFACT_DIR`를 지정하면 1200×900·500×900 screenshot과 computed style JSON을 남깁니다. +같은 Ref collector는 “진격합니다.” 전투 seed marker도 함께 렌더링하여 +desktop/mobile의 투명색, 0px 글자 크기, 0×0 rect와 selection text 보존을 +수집합니다. Core의 대응 test는 `inGameMenus.spec.ts`의 “전투시드는 메인·내 +정보·감찰부에서 숨긴 채 선택할 수 있다”이며 `PLAYWRIGHT_FRONTEND_MODE=production` +과 `MENU_PARITY_ARTIFACT_DIR`를 함께 지정하면 세 화면의 1000×900·500×900 +screenshot과 computed 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/packages/logic/src/actions/turn/general/che_NPC능동.ts b/packages/logic/src/actions/turn/general/che_NPC능동.ts index d033a289..085bc1e1 100644 --- a/packages/logic/src/actions/turn/general/che_NPC능동.ts +++ b/packages/logic/src/actions/turn/general/che_NPC능동.ts @@ -12,6 +12,7 @@ import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; +import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; import type { MapDefinition } from '@sammo-ts/logic/world/types.js'; import { normalizeLegacyIntegerArg, parseArgsWithSchema } from '../parseArgs.js'; @@ -68,11 +69,8 @@ export class ActionResolver< const destCityId = args.destCityId; const storedDestCityId = args.storedDestCityId ?? destCityId; - let destCityName = `도시(${destCityId})`; - if (context.map) { - const c = context.map.cities.find((ct) => ct.id === destCityId); - if (c) destCityName = c.name; - } + const destCityName = + context.map?.cities.find((city) => city.id === destCityId)?.name ?? '알 수 없는 도시'; const josaRo = JosaUtil.pick(destCityName, '로'); @@ -132,6 +130,11 @@ export class ActionDefinition< } } +export const actionContextBuilder: ActionContextBuilder = (base, options) => ({ + ...base, + map: options.map, +}); + export const commandSpec: GeneralTurnCommandSpec = { key: 'che_NPC능동', category: '특수', // Valid category? Legacy didn't specify category in static prop usually, handled by mapping. Defaulting to '특수'. diff --git a/packages/logic/src/actions/turn/general/che_강행.ts b/packages/logic/src/actions/turn/general/che_강행.ts index d5a1010c..680d5ddb 100644 --- a/packages/logic/src/actions/turn/general/che_강행.ts +++ b/packages/logic/src/actions/turn/general/che_강행.ts @@ -68,11 +68,8 @@ export class ActionResolver< const goldCost = develCost * 5; // Log destination - let destCityName = `도시(${destCityId})`; - if (context.map) { - const c = context.map.cities.find((ct) => ct.id === destCityId); - if (c) destCityName = c.name; - } + const destCityName = + context.map?.cities.find((city) => city.id === destCityId)?.name ?? '알 수 없는 도시'; const josaRo = JosaUtil.pick(destCityName, '로'); diff --git a/packages/logic/src/actions/turn/general/che_이동.ts b/packages/logic/src/actions/turn/general/che_이동.ts index 3d99052f..8ffcde56 100644 --- a/packages/logic/src/actions/turn/general/che_이동.ts +++ b/packages/logic/src/actions/turn/general/che_이동.ts @@ -68,11 +68,8 @@ export class ActionResolver< const cost = context.develCost ?? 0; - let destCityName = `도시(${destCityId})`; - if (context.map) { - const c = context.map.cities.find((ct) => ct.id === destCityId); - if (c) destCityName = c.name; - } + const destCityName = + context.map?.cities.find((city) => city.id === destCityId)?.name ?? '알 수 없는 도시'; const josaRo = JosaUtil.pick(destCityName, '로'); 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(); +} diff --git a/tools/frontend-legacy-parity/reference-personal-war-log-font.mjs b/tools/frontend-legacy-parity/reference-personal-war-log-font.mjs index 3d81d429..19f614c1 100644 --- a/tools/frontend-legacy-parity/reference-personal-war-log-font.mjs +++ b/tools/frontend-legacy-parity/reference-personal-war-log-font.mjs @@ -12,6 +12,8 @@ const css = await readFile(resolve(refRoot, 'dist_js/hwe_dynamic/vue/v_main.css' const records = [ '●10월:천귀병으로 ⓝ염행의 보병을 수비합니다. <1>12:54', '●10월:천귀병으로 ⓝ염행의 보병을 공격합니다. <1>12:55', + '검증장수가 낙양으로 진격합니다.' + + '(전투시드: 0123456789abcdef)', ]; const browser = await chromium.launch({ headless: true }); @@ -43,6 +45,29 @@ try { const time = spans.find((span) => /^\d{2}:\d{2}$/u.test(span.textContent ?? '')); const name = spans.find((span) => span.textContent === 'ⓝ염행'); const action = spans.find((span) => span.textContent === '수비' || span.textContent === '공격'); + const hiddenSeed = element.querySelector('.hidden_but_copyable'); + if (hiddenSeed instanceof HTMLElement) { + const range = document.createRange(); + range.selectNodeContents(hiddenSeed); + const selection = window.getSelection(); + selection?.removeAllRanges(); + selection?.addRange(range); + const hiddenSeedRect = hiddenSeed.getBoundingClientRect(); + const hiddenSeedStyle = getComputedStyle(hiddenSeed); + const result = { + text: hiddenSeed.textContent, + selectedText: selection?.toString(), + color: hiddenSeedStyle.color, + fontSize: hiddenSeedStyle.fontSize, + width: hiddenSeedRect.width, + height: hiddenSeedRect.height, + }; + selection?.removeAllRanges(); + return { + text: element.textContent, + hiddenSeed: result, + }; + } if ( !(time instanceof HTMLElement) || !(name instanceof HTMLElement) || @@ -62,6 +87,7 @@ try { timeFontSize: getComputedStyle(time).fontSize, nameFontSize: getComputedStyle(name).fontSize, actionFontSize: getComputedStyle(action).fontSize, + hiddenSeed: null, }; }) ); diff --git a/tools/integration-tests/test/turnCommandGeneralMatrix.integration.test.ts b/tools/integration-tests/test/turnCommandGeneralMatrix.integration.test.ts index fcadabed..7ace0651 100644 --- a/tools/integration-tests/test/turnCommandGeneralMatrix.integration.test.ts +++ b/tools/integration-tests/test/turnCommandGeneralMatrix.integration.test.ts @@ -421,6 +421,21 @@ integration('general command success matrix', () => { ignoredPathPatterns: ignoredLifecyclePaths, }) ).toEqual([]); + if (action === 'che_이동' || action === 'che_강행') { + const actionLogSuffix = action === 'che_이동' ? '이동했습니다.' : '강행했습니다.'; + expect( + semanticLogSignatures( + core.after.logs.filter((entry) => String(entry.text).includes(actionLogSuffix)) + ) + ).toEqual( + semanticLogSignatures( + addedReferenceLogs(reference.before, reference.after.logs).filter((entry) => + String(entry.text).includes(actionLogSuffix) + ) + ) + ); + expect(core.after.logs.some((entry) => String(entry.text).includes('도시('))).toBe(false); + } }, 120_000 ); @@ -564,6 +579,12 @@ integration('NPC active command boundary parity', () => { ignoredPathPatterns: ignoredLifecyclePaths, }) ).toEqual([]); + if (completed) { + expect(semanticLogSignatures(core.after.logs)).toEqual( + semanticLogSignatures(addedReferenceLogs(reference.before, reference.after.logs)) + ); + expect(core.after.logs.some((entry) => String(entry.text).includes('도시('))).toBe(false); + } }, 120_000 );