- 까닭: 폭 → 높이 계산은 설계유량(담당 유역)이 있어야 돎 — 유량 없는 지점은 아무 일도 안 일어나고 안내도 없었음 · 높이를 비우면 안 채움 - overflowSync(design_structure_ford_calc) — 폭 바꿈 = 필요 수심 · 높이 비움 · 그 아래 = 필요 수심 · 상자 끝 안내 「필요 수심 n 이상」 · 「설계유량 없음 — 수기 입력」 · 유량이 오면 안내 다시 그림 - 세월교 · 물넘이 폼이 같은 것을 씀 · Facility.ts 줄 줄임(697 → 686) - 시험 test_287 화면 길(폼 onChange 훅) — 유량 있음 · 없음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TteK8mfYZetk5P3bEZkyVZ
161 lines
7.5 KiB
Python
161 lines
7.5 KiB
Python
"""287 물넘이포장 후속(요구 183-2 · 4 · 6 · 7 · 8 · 9).
|
||
|
||
못박는 것
|
||
- 287-1 도수로/돌붙임 길이 ↔ 면적 — 길이 넣음 = 월류 폭 × 길이 · 비움 = 칸 기본값 · 월류 폭을 바꾸면
|
||
길이가 있을 때만 다시 셈 · 폭이 비면 물넘이 기본 폭.
|
||
- 287-3 월류 폭 → 높이 자동 = 세월교 한 벌(`fordHeightM` · 폼 `overflow(…, "slope")`) · 유량 없으면 손 입력.
|
||
- 287-5 물넘이포장 형상 누름 = 다른 시설과 같은 고름 창구(`toggleRevet("pipe")` → 폼 · 알약 · 측점선) ·
|
||
판정면은 보라 손잡이 아래(요구 185 ①) · 끌기(3 px 넘게 움직임) = 고름 아님(②) · 노면공 포장층
|
||
(범위 표식)은 형상 고름 밖(③) · 목록 · 알약 고름이 형상 강조를 따라옴.
|
||
- 287-2 · 287-4 콘크리트 윗면 = 설계 노면(서버 · TS 짝이 경사각도로 지대 낮은 쪽 한 경사 · 중심점이 그 위)
|
||
· 포장층 = 노면공 포장 형상 노견까지(노면공 항목과 안 섞임) · 월류 높이 점선 = 노면 위 같은 각도 ·
|
||
3D 노면을 파지 않음 · 설계 측구 없음.
|
||
|
||
진짜 TS 는 `helper_287_ford_pavement.cjs` 가 Node 로 돌림. 화면은 사용자 확인.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import subprocess
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
HERE = Path(__file__).resolve().parent
|
||
ROOT = HERE.parents[1]
|
||
if str(ROOT) not in sys.path:
|
||
sys.path.insert(0, str(ROOT))
|
||
|
||
pytestmark = pytest.mark.skipif(
|
||
not (ROOT / "config" / "node_modules" / "typescript").is_dir(),
|
||
reason="프론트엔드 의존성 미설치",
|
||
)
|
||
|
||
|
||
def _read(*parts: str) -> str:
|
||
return (ROOT.joinpath(*parts)).read_text(encoding="utf-8")
|
||
|
||
|
||
@pytest.fixture(scope="module")
|
||
def ts() -> dict:
|
||
proc = subprocess.run(
|
||
["node", str(HERE / "helper_287_ford_pavement.cjs")],
|
||
capture_output=True,
|
||
text=True,
|
||
encoding="utf-8",
|
||
timeout=60,
|
||
)
|
||
assert proc.returncode == 0, proc.stderr
|
||
return json.loads(proc.stdout)
|
||
|
||
|
||
def test_도수로_길이_면적(ts):
|
||
assert ts["areaByLength"] == "24" # 6 m × 4 m
|
||
assert ts["areaCleared"] == "30" # 길이 비움 = 기본값
|
||
assert ts["areaByWidth"] == "20" # 폭 8 m × 2.5 m
|
||
assert ts["areaWidthNoLength"] == "17" # 길이 없으면 폭을 바꿔도 그대로
|
||
assert ts["areaNoWidth"] == "30" # 폭 비움 = 기본 폭 10 m × 3 m
|
||
|
||
|
||
def test_물넘이_폼이_길이_면적을_잇는다():
|
||
form = _read("B05_Profile", "B05_Profile_UI_Drainage_Facility.ts")
|
||
assert "syncChuteArea(paveForm, c, item), paveFlowBox.onChange(c, item)" in form
|
||
|
||
|
||
def _pts(text: str) -> list[float]:
|
||
return [float(v) for pair in text.split() for v in pair.split(",")]
|
||
|
||
|
||
def test_포장층_노견까지_점선은_노면_위(ts):
|
||
assert ts["drew"] is True
|
||
# 포장층 하나 = 노견 끝(±3)까지 · 윗면 = 설계 노면 · 두께 0.2
|
||
assert len(ts["pavePoints"]) == 1
|
||
assert _pts(ts["pavePoints"][0]) == pytest.approx([3, 100.15, -3, 99.85, -3, 99.65, 3, 99.95])
|
||
# 월류 높이선 = 노면 + 0.4 · 같은 각도
|
||
assert _pts(ts["planPoints"][0]) == pytest.approx([3, 100.55, -3, 100.25])
|
||
assert ts["inflowLeft"] is True # 노면 높은 쪽 = 유입 · 도수로 띠 = 낮은 쪽
|
||
|
||
|
||
def test_3D_노면을_파지_않고_옛_파임_그림을_걷음():
|
||
station = _read("B05_Profile", "B05_Profile_UI_Corridor_Station.ts")
|
||
assert "fordDeckElevationAt" not in station
|
||
pave = _read("B06_Section", "B06_Section_UI_Cross_Ford_Pavement.ts")
|
||
assert "hideFordPavedRoad" not in pave and "fordDeckElevationAt" not in pave
|
||
view = _read("B06_Section", "B06_Section_UI_Cross_View.ts")
|
||
assert "hideFordPavedRoad" not in view
|
||
|
||
|
||
def test_월류_폭_넣으면_높이_자동_세월교_한_벌(ts):
|
||
assert ts["paveSlopeItems"] == ["angle", "width", "height"]
|
||
assert ts["heightByWidth"] and ts["heightByWidth"] > 0
|
||
assert ts["heightNoFlow"] is None
|
||
form = _read("B05_Profile", "B05_Profile_UI_Drainage_Facility.ts")
|
||
assert 'if (facility === "ford_pavement") paveFlowBox.fill();' in form
|
||
|
||
|
||
def test_형상_판정면은_보라_손잡이_아래(ts):
|
||
order = ts["shapeOrder"]
|
||
# 그림 → 형상 판정면 → 보라 손잡이 판정 → 구조물 판정면(위가 이김)
|
||
assert (
|
||
order.index("b06-chart__shape-hit")
|
||
< order.index("b06-chart__design-hit")
|
||
< order.index("b06-chart__culvert-revet-hit")
|
||
)
|
||
|
||
|
||
def test_형상_누름은_고름_끌기는_고름_아님(ts):
|
||
assert ts["picked"] == ["pipe-230.00"] # 누름 한 번만 고름 · 끌기는 안 셈
|
||
assert ts["clickStops"] is True and ts["dragStops"] is False # 끌기는 카드로 그대로
|
||
assert ts["pavedShapeIsPicked"] is False # 노면공 포장층은 형상 고름 밖(포장 고름 몫)
|
||
|
||
|
||
def test_형상_강조와_표식(ts):
|
||
assert ts["activeAfterSet"] is True
|
||
assert ts["activeAfterOther"] is False and ts["activeAfterSame"] is True
|
||
assert ts["fordTag"] == ["pipe-230.00"] # 포장층 표식 = 관 지점 알약 id
|
||
|
||
|
||
def test_보라_손잡이는_계획고만_형상_누름은_고름만():
|
||
view = _read("B06_Section", "B06_Section_UI_Cross_View.ts")
|
||
# 형상 누름 = 시설 고름 창구만(설계 · 계획고를 안 건드림) · 보라 선 고름보다 먼저 꽂음(손잡이가 위)
|
||
assert 'shapePickRoute(svg, () => toggleRevet("pipe"))' in view
|
||
assert view.index("attachShapePick(") < view.index("attachDesignLinePick({")
|
||
pick = _read("B06_Section", "B06_Section_UI_Cross_Shape_Pick.ts")
|
||
assert "onDesignChange" not in pick and "design_elevation" not in pick
|
||
assert '"b06-chart__culvert-revet-hit"' in pick # 구조물 판정면 앞(아래)에 꽂을 기준만
|
||
assert 'hit.setAttribute("class", "b06-chart__shape-hit")' in pick
|
||
# 보라 손잡이 판정 = 계획고 · 사면 이동 창구(설계선 고름 · 구조물 판정면 아래)
|
||
design_pick = _read("B06_Section", "B06_Section_UI_Cross_Design_Pick.ts")
|
||
assert 'el.classList.contains("b06-chart__culvert-revet-hit")' in design_pick
|
||
# 목록 · 알약 · 폼 고름 → 형상 강조
|
||
section_view = _read("B06_Section", "B06_Section_UI_Section_View.ts")
|
||
assert "highlightShapes(card, pickedMarkId);" in section_view
|
||
selection = _read("B06_Section", "B06_Section_UI_Page_Structures_Selection.ts")
|
||
assert "sectionAt.ford_pavement?.chainage_m" in selection
|
||
|
||
|
||
def test_월류_폭_높이_화면_길_유량_있음(ts):
|
||
got = ts["overflowFlow"]
|
||
assert got["afterWidth8"] == "0.26" # 폭 8 → 필요 수심(유량 14.58 ㎥/s)
|
||
assert got["afterClear"] == "0.26" # 높이 비움 → 다시 채움
|
||
assert got["afterLow"] == "0.26" # 필요 수심 아래 → 끌어올림
|
||
assert got["afterHigh"] == "0.9" # 올려 적은 값은 그대로
|
||
assert got["hint"] == ["필요 수심 0.26 이상"] and got["otherBoxHint"] == 0
|
||
assert got["emitted"] == 4
|
||
|
||
|
||
def test_월류_폭_높이_화면_길_유량_없음(ts):
|
||
got = ts["overflowNoFlow"]
|
||
assert got["afterWidth8"] == "0.05" and got["afterClear"] == "" # 손 입력 그대로
|
||
assert got["hint"] == ["설계유량 없음 — 수기 입력"]
|
||
|
||
|
||
def test_세월교_물넘이_한_벌_안내_훅():
|
||
form = _read("B05_Profile", "B05_Profile_UI_Drainage_Facility.ts")
|
||
flat = " ".join(form.split())
|
||
assert 'overflowSync( () => fordForm, "ford", () => fordFlow, emit, )' in flat
|
||
assert 'overflowSync( () => paveForm, "slope", () => fordFlow, emit, )' in flat
|
||
assert "...fordFlowBox.fields(c)" in form and "fields: paveFlowBox.fields," in form
|