"""급경사 범위 수평 바(PLAN 631 · 요구 537) — `A00_Common/map/logic/map_slope_bands.ts`. 경계 ↔ 범위(기본 · 빈 곳 = 칠 안 함 칸 · 되돌리면 같음) · 칸 나눔(가운데 · 새 색 · 좁음 · 8 단계) · 합침(왼쪽 · 첫 칸은 오른쪽 · 하나면 막음) · 경계 검사(빈 값 · 0 · 250 · 같은 값 · 거꾸로) · 창 = 부품 `ui_template_range_bar`(날 input · button 없음) · 부품 모음 줄. TS 를 tsc 로 옮겨 node 로 돌림(서버 · 화면 없이). ./venv/Scripts/python.exe -m pytest resources/tester/test_631_slope_bar.py -q """ from __future__ import annotations import json import re import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] TS_FILE = ROOT / "A00_Common" / "map" / "logic" / "map_slope_bands.ts" UI_FILE = ROOT / "A00_Common" / "map" / "map_slope_bands.ts" TSC = ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc" _RUNNER = """ import { readFileSync, writeFileSync } from "node:fs"; import * as bands from "./A00_Common/map/logic/map_slope_bands.js"; const cases = JSON.parse(readFileSync(process.argv[2], "utf8")); const D = bands.DEFAULT_SLOPE_BANDS; const out = { DEFAULT: D, COLORS: bands.SLOPE_COLORS.map((c) => c.color) }; // 인자 "D" = 기본 범위 · "DBAR" = 기본 바 const arg = (a) => (a === "D" ? D : a === "DBAR" ? bands.bandsToBar(D) : a); for (const [key, [name, ...args]] of Object.entries(cases)) out[key] = bands[name](...args.map(arg)); out.roundtrip = bands.barToBands(bands.bandsToBar(D)); writeFileSync(process.argv[3], JSON.stringify(out)); """ C3, C2, C1 = "--draw-map-landslide-3", "--draw-map-landslide-2", "--draw-map-landslide-1" def _bar(edges: list, colors: list) -> dict: return {"edges": edges, "colors": colors} @pytest.fixture(scope="module") def r(tmp_path_factory: pytest.TempPathFactory) -> dict: tmp = tmp_path_factory.mktemp("slope_bar") out = tmp / "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") eight = _bar(list(range(0, 80, 10)) + [250], [C3] * 8) cases = { "default_bar": ["bandsToBar", "D"], "gap_bar": [ "bandsToBar", [ {"low": 30, "high": 250, "color": C1}, {"low": 10, "high": 20, "color": C3}, ], ], "full_bar": ["bandsToBar", [{"low": 0, "high": 250, "color": C2}]], "gap_bands": ["barToBands", _bar([0, 10, 20, 30, 250], ["", C3, "", C1])], "split_top": ["splitBar", "DBAR", 3], "split_gap": ["splitBar", "DBAR", 0], "split_narrow": ["splitBar", _bar([0, 0.1, 250], [C3, C2]), 0], "split_eight": ["splitBar", eight, 7], "split_bad": ["splitBar", _bar([0, 30, 20, 250], [C3, C2, C1]), 0], "merge_top": ["mergeBar", "DBAR", 3], "merge_first": ["mergeBar", "DBAR", 0], "merge_mid": ["mergeBar", "DBAR", 2], "merge_one": ["mergeBar", _bar([0, 250], [C3]), 0], "edges_ok": ["checkEdges", [0, 26.8, 70, 83.9, 250]], "edges_blank": ["checkEdges", [0, None, 250]], "edges_zero": ["checkEdges", [0, 0, 250]], "edges_top": ["checkEdges", [0, 20, 250, 250]], "edges_same": ["checkEdges", [0, 20, 20, 250]], "edges_reverse": ["checkEdges", [0, 30, 20, 250]], } job, result = tmp / "job.json", tmp / "out.json" job.write_text(json.dumps(cases), encoding="utf-8") subprocess.run( # noqa: S603 ["node", str(out / "runner.mjs"), str(job), str(result)], cwd=str(ROOT), check=True, capture_output=True, ) return json.loads(result.read_text(encoding="utf-8")) def test_기본_범위는_바로_가도_같음(r): assert r["default_bar"] == _bar([0, 26.8, 70.0, 83.9, 250], ["", C3, C2, C1]) assert r["roundtrip"] == r["DEFAULT"] # 범위 같음 = 그림 · 범례 같음 def test_빈_곳은_칠_안_함_칸(r): assert r["gap_bar"] == _bar([0, 10, 20, 30, 250], ["", C3, "", C1]) assert r["gap_bands"] == [ {"low": 10, "high": 20, "color": C3}, {"low": 30, "high": 250, "color": C1}, ] assert r["full_bar"] == _bar([0, 250], [C2]) def test_칸_나눔(r): # 83.9 ~ 250 → 가운데 167.0 · 위 반 = 안 쓴 첫 색 assert r["split_top"]["edges"] == [0, 26.8, 70.0, 83.9, 167.0, 250] new = next(c for c in r["COLORS"] if c not in (C3, C2, C1)) assert r["split_top"]["colors"] == ["", C3, C2, C1, new] assert r["split_gap"]["edges"] == [0, 13.4, 26.8, 70.0, 83.9, 250] assert r["split_gap"]["colors"][:2] == ["", new] assert r["split_narrow"] is None assert r["split_eight"] is None # 8 단계까지 assert r["split_bad"] is None def test_칸_합침(r): assert r["merge_top"] == _bar([0, 26.8, 70.0, 250], ["", C3, C2]) assert r["merge_first"] == _bar([0, 70.0, 83.9, 250], [C3, C2, C1]) assert r["merge_mid"] == _bar([0, 26.8, 83.9, 250], ["", C3, C1]) assert r["merge_one"] is None def test_경계_검사(r): assert r["edges_ok"] is None assert "1번째 경계" in r["edges_blank"] and "비었습니다" in r["edges_blank"] assert "0 보다 크고" in r["edges_zero"] assert "2번째 경계" in r["edges_top"] and "250" in r["edges_top"] assert "같은 값" in r["edges_same"] assert "왼쪽 경계보다" in r["edges_reverse"] def test_창은_부품만(): src = UI_FILE.read_text(encoding="utf-8") assert "createRangeBar(" in src assert not re.search(r'createElement\("(input|button|select)"\)', src) kit = (ROOT / "ui_template" / "ui_template_ui_kit_parts.ts").read_text(encoding="utf-8") assert '{ key: "range-bar", name: "범위 바", group: "compound",' in kit assert 'file: "ui_template_range_bar.ts"' in kit css = (ROOT / "ui_template" / "ui_template_range_bar.css").read_text(encoding="utf-8") assert not re.search(r"#[0-9a-fA-F]{3,8}\b|rgba?\(|--draw-", css) # 색 = 공통 값만