"""PLAN 346-2 — 노선 편집 평면 발자국 그리기. 가짜 footprint → 그릴 선 수 · 색 갈래(도로 끝 · 깎기 · 쌓기 · 깎기 여부 없음) · 바뀌는 점은 앞뒤가 함께 가짐 · 편집기 그리기 · [발자국] 단추 · 밝음 · 어두움 색 토큰이 이어져 있음. """ from __future__ import annotations import json import re import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] B05 = ROOT / "B05_Profile" TS_FILE = B05 / "logic" / "B05_Profile_Footprint_Strokes.ts" TSC = ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc" _RUNNER = """ import { writeFileSync } from "node:fs"; import { footprintStrokes } from "./B05_Profile/logic/B05_Profile_Footprint_Strokes.js"; const line = (y) => [[0, y], [10, y], [20, y], [30, y], [40, y]]; const base = { roadLeft: line(3), roadRight: line(-3), slopeLeft: line(6), slopeRight: line(-7) }; const out = { none: footprintStrokes(null), plain: footprintStrokes(base), flagged: footprintStrokes({ ...base, slopeLeftCut: [true, true, false, false, true], slopeRightCut: [false, false, false, false, false], }), short: footprintStrokes({ roadLeft: [[0, 0]], roadRight: [], slopeLeft: [], slopeRight: [] }), badFlags: footprintStrokes({ ...base, slopeLeftCut: [true] }), }; writeFileSync(process.argv[2], JSON.stringify(out)); """ 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, ) for emitted in out.rglob("*.js"): emitted.write_text( re.sub(r'(from\s+"\.\.?/[^"]+)"', r'\1.js"', emitted.read_text(encoding="utf-8")), encoding="utf-8", ) (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 _kinds(strokes: list[dict]) -> list[str]: return [stroke["kind"] for stroke in strokes] @pytest.mark.skipif(not TSC.is_file(), reason="프론트엔드 의존성 미설치") def test_가짜_발자국_선_수와_색_갈래(tmp_path: Path) -> None: out = _run(tmp_path) assert out["none"] == [] and out["short"] == [] # 깎기 여부 없음 = 도로 끝 둘 + 비탈 끝 둘(한 색) assert _kinds(out["plain"]) == ["road", "road", "slope", "slope"] assert _kinds(out["badFlags"]) == ["road", "road", "slope", "slope"] # 좌 = 깎기 → 쌓기(끝 한 점 깎기는 2점 미만이라 뺌) · 우 = 쌓기 한 줄 flagged = out["flagged"] assert _kinds(flagged) == ["road", "road", "cut", "fill", "fill"] assert flagged[2]["points"] == [[0, 6], [10, 6], [20, 6]] # 바뀌는 점(20)을 앞 조각도 가짐 assert flagged[3]["points"] == [[20, 6], [30, 6], [40, 6]] assert flagged[4]["points"] == [[0, -7], [10, -7], [20, -7], [30, -7], [40, -7]] def test_편집기가_발자국을_그리고_단추로_켬끔() -> None: render = (B05 / "B05_Profile_UI_RouteEdit_Render.ts").read_text(encoding="utf-8") assert "drawFootprint(context, toScreen, style, () => drawRouteEditScene(context, scene))" in ( render ) # 등고선 뒤 · 노선 앞(노선이 발자국 위에 보임) assert render.index("drawFootprint(context") < render.index("scene.expected,") chrome = (B05 / "B05_Profile_UI_RouteEdit_Chrome.ts").read_text(encoding="utf-8") assert 'data-act="footprint"' in chrome and "bindFootprintToggle(" in chrome footprint = (B05 / "B05_Profile_UI_RouteEdit_Footprint.ts").read_text(encoding="utf-8") assert 'subscribe("footprint"' in footprint and "let shown = true;" in footprint css = (B05 / "B05_Profile_UI_Style_RouteEdit_Footprint.css").read_text(encoding="utf-8") for token in ("--b05-footprint-road", "--b05-footprint-cut", "--b05-footprint-fill"): assert token in footprint assert css.count(f"{token}:") == 3 # 밝음 · 어두움 · 시스템 어두움