"""193-1 B06 횡단 보라 계획선 이동 제약 — 갈래(도로 · 절토 · 성토)마다 축 · 방향. 화면 TS(`_Cross_Design_Pick`)를 흉내 DOM 으로 돌린 값(`helper_193_1_line_axis.cjs`) + 서버 재계산(노견 확장 = 그 쪽만 · 반대측 고정). """ import json import subprocess import sys from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] HELPER = Path(__file__).parent / "helper_193_1_line_axis.cjs" if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from B06_Section.B06_Section_Engine_Design import compute_cross_design # noqa: E402 node_only = pytest.mark.skipif( not (ROOT / "config" / "node_modules").is_dir(), reason="프론트엔드 의존성 미설치" ) @pytest.fixture(scope="module") def out() -> dict: done = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", str(HELPER)], cwd=str(ROOT), capture_output=True, text=True, encoding="utf-8" ) assert done.returncode == 0, done.stderr return json.loads(done.stdout) @node_only def test_갈래_판정(out: dict) -> None: assert out["axis"] == {"left": "x", "road": "y", "right": "x", "structure": "x"} assert out["cutZone"] == "cut" and out["cut"]["axis"] == "x" @node_only def test_성토측_바깥_가로만_중심_못_넘음(out: dict) -> None: fill = out["fill"] assert fill["upBlocked"] and fill["downBlocked"] # ▲▼ 가 종단 계획고로 새지 않음 assert fill["afterUpDown"] == {"left": 0, "right": 0} assert fill["out2"] == {"left": 0.2, "right": 0} # ← ← = 좌 바깥 0.2 · 우 그대로 assert fill["inward"] == {"left": 0, "right": 0} # 안쪽 30 걸음 = 0 에서 멈춤 assert fill["title"] == "사면 이동 (좌)" and fill["rowHidden"] is False assert fill["profileUp"] is False # 종단도를 눌렀으면 방향키는 종단도 몫 assert out["right"]["values"] == {"left": 0.1, "right": 0.1} # 우 사면 = 우 바깥만 @node_only def test_구조물_선_반대측_고정_한_벌(out: dict) -> None: """성토측 구조물 트림 선 = 그 쪽 노견째(벽은 노견 끝에 섬) · 반대측 0 그대로.""" assert out["structure"]["values"] == {"left": 0.1, "right": 0} assert out["structure"]["title"] == "사면 이동 (좌)" and out["structure"]["upBlocked"] @node_only def test_절토측_같은_규칙(out: dict) -> None: cut = out["cut"] assert cut["upBlocked"] assert cut["outward"] == {"left": 0.1, "right": 0} assert cut["inward"] == {"left": 0, "right": 0} @node_only def test_도로측_높이만_종단_함께(out: dict) -> None: road = out["road"] assert road["upPassed"] and road["downPassed"] # ▲▼ = 종단도 계획고(지금 길 그대로) assert road["leftBlocked"] and road["rightBlocked"] and road["same"] assert road["title"] == "노면 높이" and road["rowHidden"] is True assert "이동" not in road["label"] assert out["released"] == [False, False] # 고름 풀면 막지 않음 def test_종단도는_막힌_방향키를_건너뜀() -> None: src = (ROOT / "B05_Profile" / "B05_Profile_UI_Profile_Panel_Tools.ts").read_text( encoding="utf-8" ) assert "if (event.defaultPrevented) return;" in src assert "ctx.applyEdits(adjustStation(base, ctx.edits(), chainage, delta))" in src def _ground(level: float) -> list[dict]: return [{"offset_m": o / 2, "elevation_m": level, "valid": True} for o in range(-30, 31)] def test_서버_한쪽_이동_반대측_고정_바깥으로() -> None: samples = _ground(97.0) base = compute_cross_design(samples, 100.0, ground_type="soil", section_mode="both_fill") moved = compute_cross_design( samples, 100.0, ground_type="soil", section_mode="both_fill", shoulder_extend_left_m=0.7 ) right = lambda d: [p for p in d["design_line"] if p["offset_m"] < 0] # noqa: E731 assert right(moved) == right(base) # 반대측 고정 assert moved["road_edges"]["right"] == base["road_edges"]["right"] assert moved["road_edges"]["left"]["offset_m"] == pytest.approx( base["road_edges"]["left"]["offset_m"] + 0.7 ) toe = lambda d: max(p["offset_m"] for p in d["design_line"] if p["elevation_m"] > 97.0) # noqa: E731 assert toe(moved) > toe(base) # 사면째 바깥으로 def test_구조물은_노견_끝에_섬() -> None: """관 기슭막이 자리 = 설계 노견 끝(`road_edges` · 노견 확장 포함) — 사면과 한 벌로 감.""" src = (ROOT / "B06_Section" / "B06_Section_UI_Cross_Culvert_Geom.ts").read_text( encoding="utf-8" ) assert "const edges = section.design?.road_edges;" in src