Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jonfdxn1Xit9bdkNJpE4G
120 lines
4.6 KiB
Python
120 lines
4.6 KiB
Python
"""255-2 거리이정표 경로 탭 — 시점 끌기 = 길찾기용 덮개(site.start_pin · 노선 그대로) ·
|
|
표지 옮기면 그 자리에 닿는 구간(나가는 · 들어오는) 모두 다시 길찾기 · 열 때 사슬 구간 모두 선 ·
|
|
실패 안내 · 끌기는 그림 캡처 안 부름."""
|
|
|
|
import json
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
if str(ROOT) not in sys.path:
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from B08_Quantity import B08_Quantity_Router_Milestone as ms # noqa: E402
|
|
|
|
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
|
|
STUBS = """
|
|
let cache = null;
|
|
const sent = [];
|
|
const API_BASE_URL = "";
|
|
const showToast = () => undefined;
|
|
const readState = () => cache;
|
|
const writeState = (_key, d) => { cache = d; };
|
|
const clearState = () => { cache = null; };
|
|
const request = async (url, opts) => {
|
|
if (url.endsWith("/quantity/milestone"))
|
|
return { data: DATA, site: { length_m: 1000, mid_default_m: 500, start_lonlat: [127, 35],
|
|
mid_lonlat: null }, images: {}, auto_images: {} };
|
|
sent.push(JSON.parse(opts.body));
|
|
return { km: 1.5, reason: null, path: [] };
|
|
};
|
|
"""
|
|
|
|
|
|
def _read(rel):
|
|
return (ROOT / rel).read_text(encoding="utf-8")
|
|
|
|
|
|
def _place(pid, to, lon, lat, **more):
|
|
return {"id": pid, "name": pid, "address": "", "dir": None, "lon": lon, "lat": lat,
|
|
"legs": [], "to": to, **more} # fmt: skip
|
|
|
|
|
|
DATA = {
|
|
"site": {"mid_station_m": None, "mid_road": "비포장"},
|
|
"bases": [_place("b1", "site", 127.5, 35.5), _place("b2", "b1", 127.6, 35.6)],
|
|
"routes": [
|
|
_place("r1", "site", 127.2, 35.2, rows=[]),
|
|
_place("r2", "b1", 127.3, 35.3, rows=[]),
|
|
_place("r3", "b2", 127.4, 35.4, rows=[]),
|
|
_place("r4", "b1", 127.1, 35.1, rows=[], linked=False),
|
|
],
|
|
}
|
|
|
|
|
|
def _run(script):
|
|
src = "".join(
|
|
IMPORTS.sub("", _read(f"B08_Quantity/B08_Quantity_{n}.ts"))
|
|
for n in ("Milestone_Calc", "Milestone_Layout", "UI_Milestone_State")
|
|
)
|
|
code = f"const DATA = {json.dumps(DATA)};\n{STUBS}\n{src}\n{script}"
|
|
result = subprocess.run(
|
|
["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript"],
|
|
input=code, capture_output=True, text=True, encoding="utf-8", timeout=60,
|
|
) # fmt: skip
|
|
assert result.returncode == 0, result.stderr
|
|
return json.loads(result.stdout)
|
|
|
|
|
|
def test_시점_덮개_길찾기():
|
|
got = _run("""
|
|
await loadMilestone("p");
|
|
await findSegmentKm("p", "r1", false);
|
|
const d = liveMilestone("p");
|
|
d.site.start_pin = { lon: 128.25, lat: 36.5 };
|
|
putMilestone("p", d);
|
|
await findSegmentKm("p", "r1", false);
|
|
await findSegmentKm("p", "b1", false);
|
|
console.log(JSON.stringify({ sent, start: startPoint(liveMilestone("p")) }));
|
|
""")
|
|
dest = [s["destination"] for s in got["sent"]]
|
|
assert dest[0] == {"lon": 127, "lat": 35} # 덮개 없음 = 노선 첫 점
|
|
assert dest[1] == dest[2] == {"lon": 128.25, "lat": 36.5} # 거래처 · 기준점 → 시점 = 덮개
|
|
assert got["start"] == [128.25, 36.5]
|
|
|
|
|
|
def test_닿는_구간_모두():
|
|
got = _run("""
|
|
console.log(JSON.stringify(["start", "b1", "b2", "r1", "r3"].map((k) => touchingSegments(DATA, k))));
|
|
""")
|
|
start, b1, b2, r1, r3 = got
|
|
assert start == ["b1", "r1"] # 시점에 들어오는 구간
|
|
assert b1 == ["b1", "b2", "r2"] # 나가는 구간 + 들어오는 경로들(안 이은 r4 빠짐)
|
|
assert b2 == ["b2", "r3"]
|
|
assert r1 == ["r1"] and r3 == ["r3"]
|
|
|
|
|
|
def test_서버_모델_시점_덮개():
|
|
got = ms.Milestone.model_validate({"site": {"start_pin": {"lon": 127.5, "lat": 35.5}}})
|
|
assert got.model_dump()["site"]["start_pin"] == {"lon": 127.5, "lat": 35.5}
|
|
assert ms.Milestone().model_dump()["site"]["start_pin"] is None
|
|
with pytest.raises(ValidationError):
|
|
ms.Milestone.model_validate({"site": {"start_pin": {"lon": 500, "lat": 35}}})
|
|
|
|
|
|
def test_경로_탭_끌기_다시_길찾기():
|
|
route = _read("B08_Quantity/B08_Quantity_UI_Milestone_Route.ts")
|
|
assert "movable: true" in route and 'movable: key !== "start"' not in route
|
|
assert "fresh.site.start_pin = { lon, lat };" in route
|
|
assert "await refind(touchingSegments(fresh, key));" in route
|
|
assert "findSegmentKm(projectId, id, false)" in route # 끌기 · 열기 = 그림 캡처 없음
|
|
assert "[routeId, ...chain].filter((id) => !segmentPath(id).length)" in route # 열 때 모든 구간
|
|
assert "status.hidden = !bad.length;" in route # 실패 안내
|
|
calc = _read("B08_Quantity/B08_Quantity_Milestone_Calc.ts")
|
|
assert "start_pin?: { lon: number; lat: number } | null;" in calc
|