"""PLAN 137-2 · 137-3 — B06 보라 선 고름: 직선 조각 · 가림 · 치수 · 구역 · 판정 선 · 페이지 한 칸. 짝: `B06_Section/B06_Section_UI_Cross_Design_Pick.ts` · `A00_Common/b_polygon_clip.ts`(B07 에서 옮김) · `B06_Section_UI_Cross_View.ts`(배선 · 하나만). 실제 TS 를 `helper_137_design_pick.cjs` 가 Node 로 돌린다. """ from __future__ import annotations import json import os import subprocess import pytest HERE = os.path.dirname(__file__) ROOT = os.path.join(HERE, "..", "..") pytestmark = pytest.mark.skipif( not os.path.isdir(os.path.join(ROOT, "config", "node_modules")), reason="프론트엔드 의존성 미설치", ) @pytest.fixture(scope="module") def out() -> dict: proc = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", os.path.join(HERE, "helper_137_design_pick.cjs")], capture_output=True, text=True, encoding="utf-8", timeout=60, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) def _src(rel: str) -> str: with open(os.path.join(ROOT, rel), encoding="utf-8") as f: return f.read() def test_같은_방향_토막은_직선_조각_하나(out) -> None: """0.5 m 토막 25개 → 성토 사면 · 노면 둘(가운데 꺾임) · 측구 · 절토 사면 = 다섯 · 거꾸로 그린 토막도 합침.""" assert out["merge"]["segments"] == 25 assert out["merge"]["pieces"] == [[-6.5, -2.5], [-2.5, 0], [0, 2.5], [2.5, 3], [3, 6]] assert out["mergeStructure"] == 2 # 구조물 선은 설계선과 안 합침 def test_벽에_가린_부분은_빼고_보이는_조각만(out) -> None: assert out["occlude"]["pieces"] == [[3, 4], [4.5, 6]] assert out["occlude"]["covered"] == 0 def test_치수_사면길이_수평_높이_구배(out) -> None: fill = out["measure"]["fill"] assert (fill["horizontalM"], fill["heightM"], fill["grade"]) == (3, 2, "1:1.50") assert abs(fill["lengthM"] - 13**0.5) < 1e-9 assert out["measure"]["road"]["grade"] == "2.0%" # 노면 = % assert out["measure"]["flatSlope"] == "0.0%" # 두 줄(151-1) — 1줄 = 길이 · 수평 · 2줄 = 높이 · 구배 assert out["label"]["fill"] == "사면길이 3.61m · 수평 3.00m\n높이 2.00m · 1:1.50" assert out["label"]["road"] == "길이 2.50m · 수평 2.50m\n높이 0.05m · 2.0%" def test_구역_도로_절토_성토(out) -> None: zone = out["zone"] assert zone["road"] == {"zone": "road", "side": "left"} assert zone["roadEdge"] == "road" assert zone["ditch"] == {"zone": "cut", "side": "left"} # 측구 = 절토 assert zone["cutSlope"] == {"zone": "cut", "side": "left"} assert zone["fillSlope"] == {"zone": "fill", "side": "right"} assert zone["structure"] == "fill" # 구조물 트림 선 = 성토 assert zone["rightCutDitch"] == "cut" assert zone["bothFill"] == "fill" assert zone["pieceZones"] == ["fill", "road", "road", "cut", "cut"] def test_실제_배관_측점_조각과_판정_선(out) -> None: real = out["real"] assert real["pieces"] < real["designLines"] # 토막이 조각으로 합쳐짐 assert real["structurePieces"] > 0 # 구조물 트림 선도 고름 대상 assert real["hits"] == real["pieces"] assert real["wallCount"] == 2 and real["insideWall"] == 0 # 벽 안 조각 없음 assert real["hitsBelowStructure"] is True # 구조물 판정면이 위 assert real["picked"] == [0] and real["stopped"] is True # 카드 고름으로 안 번짐 assert real["activeCount"] == 1 and real["activeIndex"] == 1 assert real["labelOn"]["hidden"] is False and real["labelOn"]["text"].startswith("사면길이 ") assert real["labelOff"] == {"hidden": True, "text": ""} assert set(real["zones"]) <= {"road", "cut", "fill"} def test_페이지_한_칸_고름(out) -> None: assert out["slot"] == {"a1": 2, "a2": None, "b": 1, "cleared": None} def test_카드_배선_하나만() -> None: """137-3 — 선을 고르면 벽 · 본체 · 면적을 풀고 카드도 고름 · 다른 고름은 선을 풂.""" view = _src("B06_Section/B06_Section_UI_Cross_View.ts") assert "const pickLine = (index: number): void => {" in view assert "if (!isSelected) onSelect(section.station_id);" in view assert view.count("dropLine();") >= 4 # 본체 · 벽 · 카드 풂 · 면적 assert "setLineActive(pickedLineAt(section.station_id));" in view # 다시 그려도 되살림 assert "zoomBar.prepend(linePick.label);" in view # 치수 = [+ − ⤢] 왼쪽 assert "chartWrap.append(linePick.panel);" in view # 사면 이동 패널(151-3) css = _src("B06_Section/B06_Section_UI_Style_Cross_Areas.css") assert ".b06-chart__design-hit.is-active {" in css def test_치수_두_줄_좁은_카드는_표_아래로() -> None: """151-1 — 줄바꿈 글자를 살리고 오른쪽 맞춤 · 좁은 카드(3열 등)는 표 높이 아래로 내려 표를 안 덮음.""" css = _src("B06_Section/B06_Section_UI_Style_Cross_Controls.css") dims = css.split(".b06-cross-card__line-dims {", 1)[1].split("}", 1)[0] assert "white-space: pre-line;" in dims and "text-align: right;" in dims assert "container-type: inline-size;" in css narrow = css.split("@container (max-width: 620px) {", 1)[1] assert "top: calc(var(--spacing-8) + var(--b06-areas-height, 3.6rem));" in narrow def test_폴리곤_자르기는_공용_한_벌() -> None: b07 = _src("B07_DesignDetail/B07_DesignDetail_Cross_Overlay.ts") assert "function clipSegment(" not in b07 and "function pointInPolygon(" not in b07 assert 'from "../A00_Common/b_polygon_clip"' in b07