- design_profile_overlays.ts — 유토곡선 · 테이블 오버레이 · 유토 총괄 배지 만들기 - design_profile_viewport.ts — 가로 스크롤 다시 그림 · 휠 가로 스크롤 · 크기 바뀜 듣개(dispose 때 뗌) - 동작 그대로 · test_215_1 줄 수 기준 = 700(규칙) · 새 두 파일도 검사 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
154 lines
5.7 KiB
Python
154 lines
5.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""215-1 B05 종단 [자유지정] — 범위 고르고 클릭으로 계획선 잇기.
|
|
|
|
계산 · 상태(`A00_Common/design/design_profile_free.ts`)는 Node 헬퍼
|
|
(`helper_215_1_free_profile.cjs`)가 트랜스파일해 돌리고, 화면 배선(도구줄 단추 · 오버레이 ·
|
|
키 · 적용 길)은 소스로 본다.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
HERE = Path(__file__).resolve().parent
|
|
ROOT = HERE.parent.parent
|
|
|
|
|
|
def _read(relative: str) -> str:
|
|
return (ROOT / relative).read_text(encoding="utf-8")
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def out():
|
|
proc = subprocess.run(
|
|
["node", os.path.join(HERE, "helper_215_1_free_profile.cjs")],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=60,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
def test_높이_변환_세로_창_왕복(out):
|
|
assert out["yAt103_2"] == pytest.approx(121.6)
|
|
assert out["zBack"] == pytest.approx(103.2)
|
|
assert out["parsed"] == {"scale": 1.2, "offset": -20}
|
|
assert out["parsedNone"] == {"scale": 1, "offset": 0}
|
|
|
|
|
|
def test_세로선_붙음_범위_안만(out):
|
|
assert out["snap79"] == 80
|
|
assert out["snapOutside"] == 160
|
|
|
|
|
|
def test_범위_고르기_강조(out):
|
|
assert out["pendingEdges"] == [40]
|
|
assert out["rangePhase"] == "armed"
|
|
assert out["band"] == [40, 160]
|
|
assert out["stillArmed"] == "armed" # 가운데 클릭은 시작이 아님
|
|
|
|
|
|
def test_시작_이음_지금_계획고(out):
|
|
assert out["startPhase"] == "drawing"
|
|
assert out["startDot"] == {"chainage_m": 40, "elevation_m": pytest.approx(100.6)}
|
|
|
|
|
|
def test_클릭_점_세로선_붙음_높이(out):
|
|
assert out["point1"] == {"chainage_m": 80, "elevation_m": pytest.approx(103.46)}
|
|
assert out["dotsAfterBack"] == 2 # 뒤로 가는 클릭은 무시
|
|
|
|
|
|
def test_점선_미리_보기_기울기_글(out):
|
|
first, last = out["preview"]
|
|
assert (first["preview"], last["preview"]) == (False, True)
|
|
assert last["to"] == {"chainage_m": 120, "elevation_m": pytest.approx(101.2)}
|
|
assert first["grade"] == pytest.approx(7.15)
|
|
assert last["grade"] == pytest.approx(-5.65)
|
|
assert not first["over"] and not last["over"]
|
|
|
|
|
|
def test_기준_넘음_빨강(out):
|
|
assert out["steep"]["over"] is True
|
|
assert abs(out["steep"]["grade"]) > 14
|
|
|
|
|
|
def test_끝_이음과_적용_편집(out):
|
|
assert out["finishPreview"] == {"chainage_m": 160, "elevation_m": pytest.approx(102.4)}
|
|
assert out["appliedCount"] == 1 and out["closed"] == 1 and out["afterPhase"] == "range"
|
|
assert out["offsetKeys"] == ["100.000", "120.000", "140.000", "60.000", "80.000"]
|
|
pvi = out["pvi"]
|
|
assert pvi["40"] == pytest.approx(out["ground"]["40"]) # 양 끝 그대로
|
|
assert pvi["160"] == pytest.approx(out["ground"]["160"])
|
|
assert pvi["80"] == pytest.approx(103.46)
|
|
assert pvi["120"] == pytest.approx(101.2)
|
|
assert pvi["60"] == pytest.approx((100.6 + 103.46) / 2) # 점 사이 = 직선
|
|
assert pvi["140"] == pytest.approx((101.2 + 102.4) / 2)
|
|
assert out["outsideUntouched"] is True
|
|
|
|
|
|
def test_종단곡선_자동(out):
|
|
assert out["radiiKeys"] == ["120.000", "80.000"]
|
|
assert out["r80"] == pytest.approx(out["expectR80"])
|
|
assert out["curve80L"] == pytest.approx(20) # 정책 기본 길이 L
|
|
|
|
|
|
def test_Enter_끝_거꾸로_시작(out):
|
|
assert out["reverseAnchor"] == 160
|
|
assert out["enter"] is True and out["enterApplied"] == 1
|
|
pvi = out["enterPvi"]
|
|
assert pvi["40"] == pytest.approx(100.6) and pvi["160"] == pytest.approx(102.4)
|
|
assert pvi["100"] == pytest.approx(104)
|
|
assert pvi["60"] == pytest.approx(100.6 + (104 - 100.6) / 3, abs=1e-5)
|
|
|
|
|
|
def test_Backspace_와_Esc_취소(out):
|
|
assert out["afterBackspace"] == [40, 80]
|
|
assert out["backspaceToArmed"] == "armed"
|
|
assert out["escape"] is True
|
|
assert out["escApplied"] == 0 and out["escClosed"] == 1
|
|
assert out["escPhase"] == "range" and out["escDots"] == 0
|
|
assert out["enterOutsideDrawing"] is False
|
|
|
|
|
|
def test_도구줄_직선화_오른쪽_자유지정():
|
|
tools = _read("B05_Profile/B05_Profile_UI_Profile_Tools.ts")
|
|
assert '"자유지정"' in tools
|
|
assert "wrap.append(reset, undo, redo, straighten, free, shift, up, down)" in tools
|
|
assert 'className = "b05-route-profile__tool"' in tools # 같은 단추 양식
|
|
|
|
|
|
def test_적용은_직선화와_같은_길_키_배선():
|
|
panel = _read("B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts")
|
|
assert "apply: (next) => ctx.applyEdits(next)" in panel
|
|
assert 'tools.mode() === "free" && free.key(key)' in panel
|
|
profile = _read("A00_Common/design/design_profile.ts")
|
|
# applyEdits = 캐시(store.replace) + 되돌리기 기록 — 자동저장 없음
|
|
assert "store.replace(next);\n history.record();" in profile
|
|
render = _read("B05_Profile/B05_Profile_UI_Profile_Render.ts")
|
|
assert "ctx.tools.mountOverlay(" in render
|
|
|
|
|
|
def test_오버레이_점선_빨강_글():
|
|
view = _read("A00_Common/design/design_profile_free_view.ts")
|
|
assert "is-preview" in view and "is-over" in view
|
|
assert "grade_pct.toFixed(1)}%" in view
|
|
css = _read("B05_Profile/B05_Profile_UI_Style_Table.css")
|
|
assert ".b05-profile-freemark__line {" in css and "stroke-dasharray: 6 4" in css
|
|
assert ".b05-profile-freemark__line.is-over" in css
|
|
|
|
|
|
def test_줄_수_늘리지_않음():
|
|
for relative in (
|
|
"A00_Common/design/design_profile.ts",
|
|
"A00_Common/design/design_profile_free.ts",
|
|
"A00_Common/design/design_profile_free_view.ts",
|
|
"A00_Common/design/design_profile_overlays.ts",
|
|
"A00_Common/design/design_profile_viewport.ts",
|
|
):
|
|
assert len(_read(relative).splitlines()) <= 700 # 소스 700줄 초과 금지(213-3)
|