"""PLAN 250(사용자 149 · 150 · 152) — 거리이정표 도로 구분 = 선 자리 · 모달 정리 · 폭 · 지도 높이.""" import json import re import subprocess import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT)) from B08_Quantity.B08_Quantity_Milestone_Calc import ( # noqa: E402 compute_milestone, migrate_milestone, road_of, ) IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE) def _read(rel: str) -> str: return (ROOT / rel).read_text(encoding="utf-8") def _ts(expr: str): src = IMPORTS.sub("", _read("B08_Quantity/B08_Quantity_Milestone_Calc.ts")) out = subprocess.run( ["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript", "-e", src + f"\nconsole.log(JSON.stringify({expr}));"], capture_output=True, text=True, encoding="utf-8", timeout=60, ) # fmt: skip assert out.returncode == 0, out.stderr return json.loads(out.stdout) def test_도로_구분_선_자리_짝(): cases = [ (r, k) for r in (None, "2차포장", "1차포장", "포장", "비포장") for k in ("via", "base", "direct", "mid") ] py = [road_of(r, k) for r, k in cases] assert py == _ts(f"{json.dumps(cases)}.map(([r, k]) => roadOf(r, k))") assert road_of(None, "via") == "2차포장" and road_of("포장", "base") == "1차포장" assert road_of("2차포장", "direct") == "포장" and road_of("비포장", "via") == "비포장" def test_기준점_생기면_2차_1차_없으면_포장(): leg = lambda road, km: {"road": road, "km": km} # noqa: E731 route = {"id": "r", "name": "거창군청", "address": "", "via_base": True, "dir": None, "legs": [leg(None, 23.8)], "rows": [{"item": "장비", "dest": "mid", "note": ""}]} # fmt: skip data = {"site": {"mid_station_m": 800, "mid_road": "비포장"}, "base": {"enabled": True, "name": "병곡리", "address": "", "dir": None, "legs": [leg(None, 0.6)]}, "routes": [route]} # fmt: skip (a,) = compute_milestone(data, None) assert (a["s2"], a["s1"], a["pv"], a["un"], a["total"]) == (23.8, 0.6, 0, 0.8, 25.2) data["base"]["enabled"] = False (b,) = compute_milestone(data, None) assert (b["s2"], b["s1"], b["pv"], b["un"], b["total"]) == (0, 0, 23.8, 0.8, 24.6) def test_모달_상자_하나_항목_하나_수정_글(): modal = _read("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts") leg = _read("B08_Quantity/B08_Quantity_UI_Milestone_LegModal.ts") assert '"+ 줄"' not in modal and "same.rows" not in modal assert 'label: "고침"' not in modal + leg and modal.count('"수정"') >= 2 assert 'item.root.style.display = dest.root.style.display = k === "base"' in modal # 255-3 def test_폭_전체_지도_높이_한_값(): css = _read("B08_Quantity/B08_Quantity_UI_Milestone.css") block = css[css.index(".b08-milestone {") :] assert "width: 100%;" in block[: block.index("}")] assert ".b08-milestone > .ui-data-table .ui-data-table__table {\n width: 100%;" in css assert css.count("height: 480px;") == 1 and ".b08-milestone__map {" in css def test_153_지도_보기_유지_상자_색_끌어_옮김(): kmap = _read("B08_Quantity/B08_Quantity_UI_Milestone_KakaoMap.ts") assert ( "let fitted = false;" in kmap and "map.panTo(" in kmap ) # 처음 한 번만 맞춤 · 그 뒤 줌 그대로 css = _read("B08_Quantity/B08_Quantity_UI_Milestone.css") for kind, var in ( ("is-site", "--color-accent"), ("is-base", "--color-warning"), ("is-route", "--color-success"), ): block = css[css.index(f".b08-milestone__box.{kind} rect {{") :] assert var in block[: block.index("}")], kind ui = _read("B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts") assert 'if (box.kind === "route" || box.kind === "base") dragBox(g, box, shift);' in ui assert ( 'label: "방위 배치"' in ui and "fresh.routes.forEach((r) => (r.pos = null));" in ui ) # 159 이름 def test_153_끌어_놓은_자리_레이아웃(): src = IMPORTS.sub("", _read("B08_Quantity/B08_Quantity_Milestone_Layout.ts")) data = {"site": {"mid_station_m": None, "mid_road": "비포장"}, "base": {"enabled": True, "name": "기준", "address": "", "dir": "W", "legs": [], "pos": [500, -40]}, "routes": [{"id": "r", "name": "a", "address": "", "via_base": False, "dir": "N", "legs": [], "rows": [], "pos": [-700, 300]}]} # fmt: skip data = migrate_milestone(data) # 254 옛 꼴 → bases(옛 기준점 = b1) expr = f"layoutMilestone({json.dumps(data)}, {{}}, 0.5).boxes.filter((b) => b.id === 'b1' || b.id === 'r').map((b) => [b.id, b.x, b.y])" out = subprocess.run( ["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript", "-e", src + f"\nconsole.log(JSON.stringify({expr}));"], capture_output=True, text=True, encoding="utf-8", timeout=60, ) # fmt: skip assert out.returncode == 0, out.stderr assert sorted(json.loads(out.stdout)) == [["b1", 500, -40], ["r", -700, 300]] def test_154_연결_점_끌어_잇기(): ui = _read("B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts") # 254 기준점 상자에도 연결 점 assert ( 'if (box.kind === "route" || box.kind === "base") g.append(connectDot(box, shift));' in ui ) assert "route.to = t;" in ui # 254 놓은 기준점 id · 공사지 = "site" assert "route.legs = [{ road: null, km: null }];" in ui # 옛 구간 km 비움 assert "void findSegmentKm(projectId, route.id).then(toastKm);" in ui # 새 길찾기 한 번 assert "if (route.linked !== false && route.to === t) return;" in ui # 같은 연결 = 그대로 modal = _read("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts") assert ( 'createCheckbox("기준점 거침")' not in modal and "via_base: via.input.checked" not in modal ) css = _read("B08_Quantity/B08_Quantity_UI_Milestone.css") assert ".b08-milestone__connect {" in css and ".b08-milestone__box.is-drop-target rect {" in css def test_155_요소_추가_안_이은_거래처_수기_입력(): leg = lambda road, km, auto=None: {"road": road, "km": km, "auto_km": auto} # noqa: E731 route = {"id": "r", "name": "a", "address": "", "via_base": True, "dir": None, "linked": False, "legs": [leg(None, 5.0)], "rows": [{"item": "장비", "dest": "mid", "note": ""}]} # fmt: skip data = {"site": {"mid_station_m": 700, "mid_road": "비포장"}, "base": {"enabled": True, "name": "b", "address": "", "dir": None, "legs": [leg(None, 0.6)]}, "routes": [route]} # fmt: skip (a,) = compute_milestone(data, None) # 안 이음 = 구간 km 없음 · 중점 구간만 assert (a["s2"], a["s1"], a["un"], a["total"]) == (0, 0, 0.7, 0.7) assert _ts(f"computeMilestone({json.dumps(data)}, null)") == [a] route["linked"] = True (b,) = compute_milestone(data, None) assert (b["s2"], b["s1"], b["total"]) == (5.0, 0.6, 6.3) ui = _read("B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts") assert "route.linked = true;" in ui and 'label: "요소 추가"' in ui and "plus(box" not in ui modal = _read("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts") assert "linked: false," in modal and "export type PlaceTarget = { add: true } |" in modal leg_ui = _read("B08_Quantity/B08_Quantity_UI_Milestone_LegModal.ts") assert ( 'createCheckbox("수기 입력' in leg_ui and "target.manual = manual.input.checked;" in leg_ui ) state = _read("B08_Quantity/B08_Quantity_UI_Milestone_State.ts") assert "if (owner?.manual) return" in state # 수기 입력 = 카카오 길찾기 안 함 layout = _read("B08_Quantity/B08_Quantity_Milestone_Layout.ts") assert "if (!owner || owner.linked === false) return null;" in layout # 254 거래처 · 기준점 def test_157_도구_왼쪽_아래_경로_탭_지도_그림_변경_초기화(): css = _read("B08_Quantity/B08_Quantity_UI_Milestone.css") tools = css[css.index(".b08-milestone__tools {") :] assert "bottom: 8px;" in tools[: tools.index("}")] and "left: 8px;" in tools[: tools.index("}")] ui = _read("B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts") assert ( 'label: "요소 추가",\n size: "sm"' not in ui and 'label: "방위 배치",\n size: "sm"' not in ui ) route = _read("B08_Quantity/B08_Quantity_UI_Milestone_Route.ts") assert 'label: "그림 변경"' in route and 'label: "초기화"' in route assert "그림 지우기" not in route and "자동 그림으로 되돌리기" not in route.split("*/", 1)[1] assert ( "map.root.hidden = user;" in route and "editMap(projectId" in route ) # 그림 없으면 길찾기 지도 assert ".b08-milestone__route-head {" in css and "justify-content: space-between;" in css state = _read("B08_Quantity/B08_Quantity_UI_Milestone_State.ts") assert "export const hasUserImage" in state and "canRevertImage" not in state