"""399 B05 평면 구조물 측점 마커(요구 250-2) — 알약과 같은 목록 · 고름 = 공용 고름 · 정보 창(알약 벌룬) · 20 m 법선 · 끌기 · 방향키 = 노선 따라 정수 m 1 m 씩 · 시점 · 종점 밖 안 감 · 놓을 때 한 번 옮김(알약과 같은 길). TS 셈은 `helper_399_plan_structure_marks.cjs`(Node · 노선 셈 · 법선 · 걸음은 진짜). ./venv/Scripts/python.exe -m pytest resources/tester/test_399_plan_structure_marks.py -q """ import json import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] HELPER = Path(__file__).with_name("helper_399_plan_structure_marks.cjs") def _read(*parts: str) -> str: return (ROOT.joinpath(*parts)).read_text(encoding="utf-8") @pytest.fixture(scope="module") def got() -> dict: run = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", str(HELPER)], capture_output=True, text=True, encoding="utf-8", timeout=120, ) assert run.returncode == 0, run.stderr return json.loads(run.stdout) def test_마커_목록은_알약_목록(got) -> None: assert got["laneGotSource"] is True # 알약 레인과 같은 `laneStructures` assert got["plain"]["marks"] == [ {"color": "#e11d48", "at": [80, 0]}, # 40 m × 2 px · 종류 색 {"color": "#2563eb", "at": [160.34, 0]}, ] assert got["plain"]["normals"] == 0 and got["balloonsAfterPlain"] == 0 lane = _read("B05_Profile", "B05_Profile_UI_Structures_Marks.ts") assert "const structures = laneStructures(options.structures);" in lane marks = _read("B05_Profile", "B05_Profile_UI_RouteEdit_StructureMarks.ts") assert "laneStructures(source.marks())" in marks bridge = _read("B05_Profile", "B05_Profile_UI_Page_Structures.ts") assert "setStructures([...ownStructures, ...pipeMarks]);" in bridge assert "marks: (): StructureInstance[] => [...ownStructures, ...pipeMarks]," in bridge assert "planMarksChanged(deps.projectId);" in bridge def test_고름_정보_창_법선(got) -> None: pipe = got["pickedPipe"] assert pipe["ringAt"] == [[160.34, 0]] # 관 고름(누가거리만)도 그 마커 assert pipe["normal"] == [[160.34, -40], [160.34, 40]] # 좌우 20 m assert pipe["balloon"]["lines"] == ["이름 pipe pos 80.17", "옵션 배수관"] # 알약 벌룬 줄 assert pipe["balloon"]["color"] == "#2563eb" assert got["pickedStructureBalloon"] == ["이름 box pos 40", "옵션 BOX"] assert got["balloonsAfterRelease"] == 0 def test_공용_고름과_같은_길() -> None: view = _read("A00_Common", "design", "design_view_profile.ts") assert " onStructureSelect,\n onStructureMarkMove,\n" in view # 알약 콜백 = 같은 함수 assert "select: onStructureSelect," in view and "move: onStructureMarkMove," in view render = _read("B05_Profile", "B05_Profile_UI_RouteEdit_Render.ts") labels = render.index("drawContourLabels(context, scene, band, style);") nodes = render.index("scene.planned.forEach((vertex, index) => {") hover = render.index("drawPickHover(context, scene, line, style, NODE_R);") marks = render.index("drawStructureMarks(context, scene, line, style);") turned = render.index("context.restore(); // 회전 끝") assert labels < nodes < hover < marks < turned # 맨 위(409 — 교각점 · 손잡이 · 맴돔 위) def test_집기_차례는_재기_다음_맨_앞() -> None: pointer = _read("B05_Profile", "B05_Profile_UI_RouteEdit_Pointer.ts") measure = pointer.index("void params.measure.pick(px, py);") mark = pointer.index("if (marks.press(px, py)) {") release = pointer.index("marks.deselect();") node = pointer.index("if (hitNode >= 0) {") handle = pointer.index("} else if (dragHandle) {") tick = pointer.index('if (!pick || !("chainage" in pick)) return false;') assert measure < mark < release < node < handle < tick # 409 def test_끌기_1m_정수_놓을_때_한_번(got) -> None: assert got["pressMiss"] is False and got["pressA"] is True assert got["selects"] == ["A"] assert got["movesBeforeRelease"] == 0 # 끄는 동안은 미리보기만 assert got["dragMove"] == ["A", 43] # 3 m assert "2+0 → 2+3(+3 m) 옮김" in got["dragDoneStatus"] assert got["oldDecimalStay"] is True # 옛 소수 자리 눌렀다 떼기 = 안 옮김 assert got["oldDecimalMoved"] == ["pipe-80.17", 81] def test_노선_밖_안_감(got) -> None: assert got["pastEnd"] == ["A", 200] assert got["beforeStart"] == ["A", 0] assert got["logic"]["clamp"] == [0, 199] # 종점 199.6 → 199(정수 m 안쪽) assert got["logic"]["step"] == [81, 0, 199] def test_방향키_노선_따라_1m(got) -> None: assert got["keyRight"] == ["A", 41] assert got["keyLeft"] == ["A", 39] assert got["keyUpPerpendicular"] == ["A", 41] # 직각 = → · ↑ 가 + assert got["keyRightWestward"] == ["A", 39] # 노선이 서쪽으로 가면 → = 뒤로 assert got["keyWithNodePicked"] == 0 # 교각점 고름 = 교각점 방향키 몫 def test_관_목록이_늦게_와도_연달아_두_걸음(got) -> None: assert got["lagHeldAt"] == [[162, 0]] # 옮긴 자리 · 고름 그대로(목록은 아직 80.17) assert got["lagTwoSteps"] == [["pipe-80.17", 81], ["pipe-80.17", 82]] assert got["lagCaught"] == [[164, 0]] # 목록이 따라오면 목록 자리 assert got["keyNoPick"] == 0 def test_고른_마커_찾기(got) -> None: logic = got["logic"] assert logic["round"] == [41, 42] assert (logic["pickId"], logic["pickNear"], logic["pickFar"], logic["pickOther"]) == ( "A", "A", None, None, ) assert got["redrawnOnChange"] == 1 def test_편집기_줄_수_그대로() -> None: edit = _read("B05_Profile", "B05_Profile_UI_RouteEdit.ts") assert edit.count("\n") <= 695 for name in ( "B05_Profile_UI_RouteEdit_StructureMarks.ts", "B05_Profile_UI_RouteEdit_Render.ts", "B05_Profile_UI_RouteEdit_Pointer.ts", ): assert _read("B05_Profile", name).count("\n") <= 700