- 새 logic/B05_Profile_RouteEdit_Fit(unionArea · fitAreaToView) — 옛 _Visible 자리 - _RouteEdit_Cover 지움 · View · CurveBar · CSS 의 덮인 높이 따라가기 걷음(345-5 덧 · 덧 2) - 글판 · 도움말 접기(덧 3)는 그대로 - 시험: test_345_5b_plan_visible_area 3 고쳐 씀 · test_345_5c 자리 줄 고침 · 관련 86 통과 · typecheck 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
141 lines
6.2 KiB
Python
141 lines
6.2 KiB
Python
"""345-5 · 349-0 노선 편집 평면 처음 그림 · [맞춤] = 등고 영역 맞춤 · 뜬 종단 판은 덮이기만.
|
||
|
||
349-0(요구 234) — 판이 올라와도 다시 맞추지 않음 · 글판 · 잰 값 창 · 곡선 바는 판 따라 안 옮김 ·
|
||
처음 그림은 판이 없다고 보고 칸 전체 기준으로 등고 영역(등고선 범위 ∪ 노선)을 둘레 여유 두고 맞춤.
|
||
`logic/B05_Profile_RouteEdit_Fit.ts` 를 tsc 로 옮겨 node 로 돌림 · 배선 · CSS 는 글로 봄.
|
||
./venv/Scripts/python.exe -m pytest resources/tester/test_345_5b_plan_visible_area.py -q
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
import subprocess
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
B05 = ROOT / "B05_Profile"
|
||
TS_FILE = B05 / "logic" / "B05_Profile_RouteEdit_Fit.ts"
|
||
TSC = ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc"
|
||
|
||
# 칸 1200 × 900 px · 배율 1 도엽 사각형 1125 × 900(가로 37.5 px 여백)
|
||
_RUNNER = """
|
||
import { writeFileSync } from "node:fs";
|
||
import { AREA_FILL, fitAreaToView, unionArea } from "./B05_Profile/logic/B05_Profile_RouteEdit_Fit.js";
|
||
const mapRect = { x: 37.5, y: 0, width: 1125, height: 900 };
|
||
const at = (area) =>
|
||
fitAreaToView({ mapRect, width: 1200, height: 900, area, minScale: 0.5, maxScale: 16 });
|
||
const contours = [
|
||
{ minX: 0.2, minY: 0.3, maxX: 0.5, maxY: 0.6, labelText: null },
|
||
{ minX: 0.4, minY: 0.2, maxX: 0.6, maxY: 0.5, labelText: null },
|
||
];
|
||
const route = { minX: 0.3, minY: 0.35, maxX: 0.65, maxY: 0.45 };
|
||
writeFileSync(process.argv[2], JSON.stringify({
|
||
fill: AREA_FILL,
|
||
union: unionArea([route, null, ...contours]),
|
||
none: unionArea([null]),
|
||
whole: at({ minX: 0, minY: 0, maxX: 1, maxY: 1 }),
|
||
part: at(unionArea([route, ...contours])),
|
||
dot: at({ minX: 0.5, minY: 0.5, maxX: 0.5, maxY: 0.5 }),
|
||
}));
|
||
"""
|
||
|
||
|
||
def _run(tmp_path: Path) -> dict:
|
||
out = tmp_path / "js"
|
||
subprocess.run( # noqa: S603 — 고정 실행 파일
|
||
[
|
||
"node",
|
||
str(TSC),
|
||
str(TS_FILE),
|
||
"--outDir",
|
||
str(out),
|
||
"--rootDir",
|
||
str(ROOT),
|
||
"--module",
|
||
"esnext",
|
||
"--target",
|
||
"es2022",
|
||
"--moduleResolution",
|
||
"bundler",
|
||
"--ignoreConfig",
|
||
],
|
||
cwd=str(ROOT),
|
||
check=True,
|
||
capture_output=True,
|
||
)
|
||
(out / "runner.mjs").write_text(_RUNNER, encoding="utf-8")
|
||
result = tmp_path / "out.json"
|
||
subprocess.run( # noqa: S603
|
||
["node", str(out / "runner.mjs"), str(result)],
|
||
cwd=str(ROOT),
|
||
check=True,
|
||
capture_output=True,
|
||
)
|
||
return json.loads(result.read_text(encoding="utf-8"))
|
||
|
||
|
||
def _screen(fit: dict, nx: float, ny: float) -> tuple[float, float]:
|
||
"""정규화 자리의 화면 자리 — B04 `affineOf` 와 같은 식."""
|
||
s = fit["scale"]
|
||
x = 37.5 * s + 600 * (1 - s) + fit["offsetX"] + nx * 1125 * s
|
||
y = 0 * s + 450 * (1 - s) + fit["offsetY"] + ny * 900 * s
|
||
return x, y
|
||
|
||
|
||
@pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치")
|
||
def test_처음_그림은_등고_영역_맞춤(tmp_path: Path) -> None:
|
||
out = _run(tmp_path)
|
||
assert 0.7 <= out["fill"] < 1 # 꽉 차지 않게 · 둘레 여유
|
||
# 등고선 범위 ∪ 노선 · 빈 목록은 없음
|
||
assert out["union"] == {"minX": 0.2, "minY": 0.2, "maxX": 0.65, "maxY": 0.6}
|
||
assert out["none"] is None
|
||
# 도엽 전체 = 칸 가운데 · 짧은 쪽(세로)이 fill 몫
|
||
whole = out["whole"]
|
||
assert whole["scale"] == pytest.approx(out["fill"])
|
||
assert _screen(whole, 0.5, 0.5) == pytest.approx((600, 450))
|
||
# 일부 영역 = 그 가운데가 칸 가운데 · 네 모서리 모두 칸 안(둘레 여유)
|
||
part = out["part"]
|
||
assert _screen(part, 0.425, 0.4) == pytest.approx((600, 450))
|
||
x0, y0 = _screen(part, 0.2, 0.2)
|
||
x1, y1 = _screen(part, 0.65, 0.6)
|
||
assert x0 > 0 and y0 > 0 and x1 < 1200 and y1 < 900
|
||
assert max((x1 - x0) / 1200, (y1 - y0) / 900) == pytest.approx(out["fill"])
|
||
# 한 점 = 배율 상한에서 멈춤
|
||
assert out["dot"]["scale"] == 16
|
||
|
||
|
||
def test_판은_덮이기만_따라_안_옮김() -> None:
|
||
"""뜬 종단 판이 올라와도 평면 자리 · 글판 · 잰 값 창 · 곡선 바 그대로(349-0)."""
|
||
assert not (B05 / "B05_Profile_UI_RouteEdit_Cover.ts").exists()
|
||
assert not (B05 / "logic" / "B05_Profile_RouteEdit_Visible.ts").exists()
|
||
view = (B05 / "B05_Profile_UI_RouteEdit_View.ts").read_text(encoding="utf-8")
|
||
assert "coveredPx" not in view and "watchCover" not in view
|
||
css = (B05 / "B05_Profile_UI_Style_RouteEdit.css").read_text(encoding="utf-8")
|
||
assert "--b05-routeedit-cover" not in css
|
||
bar = (B05 / "B05_Profile_UI_RouteEdit_CurveBar.ts").read_text(encoding="utf-8")
|
||
assert "bottom: rect.bottom - 8," in bar
|
||
assert "coveredPx" not in bar and "onCoverChange" not in bar
|
||
# 칸 크기가 바뀌어도 보던 가운데 · 배율 유지(다시 맞추지 않음)
|
||
editor = (B05 / "B05_Profile_UI_RouteEdit.ts").read_text(encoding="utf-8")
|
||
assert "if (meta && kept) view = { ...view, ...restoreSavedView(meta, view, kept) };" in editor
|
||
# 접힌 판은 높이 0
|
||
style = (B05 / "B05_Profile_UI_Style.css").read_text(encoding="utf-8")
|
||
assert ".b05-route-profile.is-collapsed {\n height: 0;" in style
|
||
|
||
|
||
def test_처음_열기와_맞춤_단추_같은_셈() -> None:
|
||
view = (B05 / "B05_Profile_UI_RouteEdit_View.ts").read_text(encoding="utf-8")
|
||
assert "return { view: fitView(meta, view, nodes, contours), rotationRad: 0 };" in view
|
||
assert "unionArea([route, ...(contours?.features ?? [])])" in view
|
||
chrome = (B05 / "B05_Profile_UI_RouteEdit_Chrome.ts").read_text(encoding="utf-8")
|
||
assert 'data-act="fit"' in chrome and ">맞춤</button>" in chrome
|
||
editor = (B05 / "B05_Profile_UI_RouteEdit.ts").read_text(encoding="utf-8")
|
||
assert "openingView(projectId, meta, view, planned, contours?.layer ?? null)" in editor
|
||
at = editor.index("""overlay.querySelector('[data-act="fit"]')!""")
|
||
handler = editor[at : at + 260]
|
||
assert "view = fitView(meta, view, planned, contours?.layer ?? null);" in handler
|
||
assert "rotation.set(0);" in handler
|
||
assert len(editor.splitlines()) <= 700
|