feat(B08): 거리이정표 상자 모달 제목 · 상자 안 장소 그림(254-3)

- 상자 누름 모달 제목 = 「거래처 — 이름」 · 「기준점 — 이름」(표 머리 글 항목 정보 없음)
- 장소 그림(256-1 place_images) = 상자 글 아래 132 × 88 · 그림 없으면 자리 없음 · 키 바뀔 때만 다시 받음
- 시험 = test_254_3 새로 · 244_6 · 244_10 node 입력 = stdin(명령 줄 길이)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
This commit is contained in:
2026-10-11 14:12:24 +09:00
co-authored by Claude Opus 5.5
parent 5f31458967
commit 41f68b8178
7 changed files with 138 additions and 6 deletions
@@ -59,9 +59,10 @@ console.log(JSON.stringify({ injected, hint: hint.textContent, boxHidden: box.hi
"""
)
src = IMPORTS.sub("", _read(LAYOUT)) + script
# 254 — 글이 길어 명령 줄(Windows 32767자)을 넘지 않게 stdin 으로 넘김
result = subprocess.run(
["node", "--experimental-strip-types", "--no-warnings",
"--input-type=module-typescript", "-e", src],
"--input-type=module-typescript", "-"], input=src,
capture_output=True, text=True, encoding="utf-8", timeout=60,
) # fmt: skip
assert result.returncode == 0, result.stderr
@@ -27,9 +27,10 @@ def _node(script):
IMPORTS.sub("", _read(rel))
for rel in ("A00_Common/b_svg_zoom_pan.ts", "B08_Quantity/B08_Quantity_Milestone_Layout.ts")
)
# 254 — 글이 길어 명령 줄(Windows 32767자)을 넘지 않게 stdin 으로 넘김
result = subprocess.run(
["node", "--experimental-strip-types", "--no-warnings",
"--input-type=module-typescript", "-e", src + "\n" + script],
"--input-type=module-typescript", "-"], input=src + "\n" + script,
capture_output=True, text=True, encoding="utf-8", timeout=60,
) # fmt: skip
assert result.returncode == 0, result.stderr
@@ -0,0 +1,81 @@
"""254-3 B08 거리이정표 상자 모달 제목 · 장소 그림(사용자 162-6 · 8).
① 상자 누름 모달 제목 = 「거래처 — 이름」 · 「기준점 — 이름」(표 머리 글 「항목 정보」 없음)
② 장소 그림(256-1 `place_images`) = 상자 안 글 아래 작게 · 그림 없으면 자리 없음(상자 높이 그대로)
"""
import json
import re
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
B08 = "B08_Quantity/B08_Quantity_"
def _read(rel):
return (ROOT / rel).read_text(encoding="utf-8")
def _node(expr):
src = IMPORTS.sub("", _read(B08 + "Milestone_Layout.ts"))
result = subprocess.run(
["node", "--experimental-strip-types", "--no-warnings",
"--input-type=module-typescript", "-"],
input=src + f"\nconsole.log(JSON.stringify({expr}));",
capture_output=True, text=True, encoding="utf-8", timeout=60,
) # fmt: skip
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)
DATA = {
"site": {"mid_station_m": None, "mid_road": "비포장"},
"bases": [{"id": "B", "name": "병곡리", "address": "거창읍", "dir": None, "to": "site",
"legs": [{"road": None, "km": 0.6}], "linked": True}],
"routes": [
{"id": "g", "name": "거창군청", "address": "거창읍", "dir": "N", "to": "B",
"legs": [{"road": None, "km": 23.8}],
"rows": [{"item": "장비", "dest": "start", "note": ""}]},
{"id": "k", "name": "김천역", "address": "", "dir": "E", "to": "site",
"legs": [], "rows": [{"item": "", "dest": "start", "note": ""}]},
],
} # fmt: skip
def test_장소_그림_상자_안_글_아래():
d = json.dumps(DATA, ensure_ascii=False)
plain, with_pic = _node(
f"[layoutMilestone({d}, null, 0.5), layoutMilestone({d}, null, 0.5, new Set(['B', 'g']))]"
)
a = {b["id"]: b for b in plain["boxes"]}
b = {b["id"]: b for b in with_pic["boxes"]}
pic = _node("PIC")
assert pic == {"w": 132, "h": 88} # 서버 480 × 320 과 같은 3:2 · 상자 안쪽 폭
# 그림 없음 = 자리 없음(높이 그대로 · pic 없음)
assert all("pic" not in box for box in a.values())
assert "pic" not in b["k"] and b["k"]["h"] == a["k"]["h"]
for bid in ("B", "g"):
box, before = b[bid], a[bid]
assert box["h"] == before["h"] + pic["h"] + 8 # 그림 + 아래 여백만큼 늘림
p = box["pic"]
# 안쪽 여백 안 · 글 아래(글 자리 = 그림 위 높이 = 그림 없을 때 높이)
assert p["x"] == box["x"] + 8 and p["x"] + p["w"] == box["x"] + box["w"] - 8
assert p["y"] - box["y"] == before["h"] and p["y"] + p["h"] == box["y"] + box["h"] - 8
def test_모달_제목_화면_그림():
modal = _read(B08 + "UI_Milestone_Modal.ts")
assert "`거래처 — ${route!.name" in modal and "`기준점 — ${base.name" in modal
assert "항목 정보 —" not in modal
ui = _read(B08 + "UI_Milestone_Diagram.ts")
assert "placeImageBlobUrl(projectId, id)" in ui and "placeImageKey(id)" in ui
assert 'pic.setAttribute("href", url)' in ui and "() => pic.remove()" in ui
assert "if (!got || got.key !== key)" in ui # 키(이름#시각) 바뀔 때만 다시 받음
assert "const th = box.pic ? box.pic.y - box.y : box.h;" in ui # 글 = 그림 위 가운데
css = _read(B08 + "UI_Milestone.css")
pic = css[css.index(".b08-milestone__pic {") :]
assert "pointer-events: none;" in pic[: pic.index("}")] # 누름 · 끌기 = 상자 몫
for name in ("Milestone_Layout.ts", "UI_Milestone_Diagram.ts", "UI_Milestone_Modal.ts"):
assert len(_read(B08 + name).splitlines()) <= 700, name