Files
Aislo/resources/tester/test_551_2_draw_tokens.py
T
eomsangdonandClaude Sonnet 5.5 e8f3cb8a9d fix(639-1): 낡은 시험 둘 고침 · vite 지켜보기에서 코드 아닌 폴더 제외
- test_551_2: 선 그리기 색 읽는 자리 = A00_Common/map/map_route_preview.ts (636-2 가 RecPreview 에서 옮김)
- test_624_2: D03 도구줄에 추천(recommend) 묶음 더해짐(636-2)
- vite.config.ts: server.watch.ignored = tmp · storage · docs · venv · logs · resources/tester · *.log

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-01 12:49:12 +09:00

231 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""551-2 그림 전용 값(--draw-*) 지킴 — 화면 테마 변수(--color-*)를 바꿔도 그림 색은 그대로.
- `ui_template_theme.css` 끝 블록의 --draw-* 는 밝음 · 어두움 · 시스템 어두움 모두 글자 hex · 어두움 · 시스템 어두움은 밝음에 있는 것만 덮음.
- 그림 CSS(B05 · B06 · B08 SVG)의 stroke · fill 은 --color-* 를 쓰지 않음.
- 지도 · 3D · CAD 캔버스가 읽는 바탕 · 선 색은 --draw-*.
- 옛 변수 값을 일부러 바꿔도 그림 선언의 풀린 색이 같음(밝음 · 어두움 · 시스템 어두움).
"""
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
THEME = ROOT / "ui_template" / "ui_template_theme.css"
DRAW_CSS = [
"B05_Profile/B05_Profile_UI_Style.css",
"B05_Profile/B05_Profile_UI_Style_RouteEdit.css",
"B05_Profile/B05_Profile_UI_Style_Table.css",
"B06_Section/B06_Section_UI_Style.css",
"B06_Section/B06_Section_UI_Style_Cross_Areas.css",
"B06_Section/B06_Section_UI_Style_Cross_Chart.css",
"B06_Section/B06_Section_UI_Style_Cross_Detail.css",
"B08_Quantity/B08_Quantity_UI_Hydro.css",
"B08_Quantity/B08_Quantity_UI_Milestone.css",
]
# 캔버스 · 3D 가 읽는 그림 색 — (파일, 꼭 있어야 할 글)
DRAW_READS = [
("B05_Profile/B05_Profile_UI_RouteEdit_Render.ts", '"--draw-surface"'),
("B05_Profile/B05_Profile_UI_RouteEdit_Render.ts", '"--draw-text-secondary"'),
("B05_Profile/B05_Profile_UI_RouteEdit_Render.ts", '"--draw-danger"'),
("A00_Common/map/map_route_preview.ts", '"--draw-map-sketch"'), # 636-2 선 그리기 공용
("B05_Profile/B05_Profile_UI_Viewer.ts", '"--draw-surface-raised"'),
("B04_PreProcess/B04_PreProcess_UI_Camera.ts", '"--draw-surface-raised"'),
("D03_PlanAnalysis/D03_PlanAnalysis_UI_Map.ts", '"--draw-map-sketch"'),
("B04_PreProcess/B04_PreProcess_UI_MapOverlays.ts", '"--draw-warning"'),
("B04_PreProcess/B04_PreProcess_UI_MapOverlays.ts", '"--draw-canvas"'),
("B07_DesignDetail/openwebcad/src/App.css", "--cad-canvas: var(--draw-canvas);"),
(
"B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts",
"themeColor('--draw-grid'",
),
]
NO_COLOR_READS = [
"B05_Profile/B05_Profile_UI_RouteEdit_Render.ts",
"B05_Profile/B05_Profile_UI_RouteEdit_RecPreview.ts",
"B05_Profile/B05_Profile_UI_Viewer.ts",
"B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts",
]
DRAW_NAMES_READ = {m for _, n in DRAW_READS for m in re.findall(r"--draw-[a-z-]+", n)}
DECL = re.compile(r"^\s*(stroke|fill|stop-color|--b06-tag)\s*:(.*);", re.M)
VAR = re.compile(r"var\((--[a-z0-9-]+)(?:\s*,\s*([^()]*(?:\([^()]*\))?[^()]*))?\)")
HEX = re.compile(r"#[0-9a-f]{6}")
#: 색이 아닌 그림 값 — 불투명(테마 안 따름 · 밝음 블록에만)
DRAW_ALPHAS = {"--draw-map-dim"}
def _blocks(css: str) -> dict[str, dict[str, str]]:
"""밝음 · 어두움 · 시스템 어두움 블록의 --변수 정의(뒤 블록이 앞을 덮음)."""
css = re.sub(r"/\*.*?\*/", "", css, flags=re.S)
out: dict[str, dict[str, str]] = {"light": {}, "dark": {}, "auto": {}}
def walk(text: str, media: bool) -> None:
i = 0
while True:
open_at = text.find("{", i)
if open_at < 0:
return
head = text[i:open_at].strip().split("}")[-1].strip()
depth, j = 1, open_at + 1
while depth:
depth += {"{": 1, "}": -1}.get(text[j], 0)
j += 1
body = text[open_at + 1 : j - 1]
if head.startswith("@media (prefers-color-scheme: dark)"):
walk(body, True)
else:
mode = None
if head == ":root":
mode = "light"
elif head == ':root[data-theme="dark"]':
mode = "dark"
elif media and head == ':root:not([data-theme="light"])':
mode = "auto"
if mode:
for name, value in re.findall(r"(--[a-z0-9-]+)\s*:([^;]*);", body):
out[mode][name] = " ".join(value.split())
i = j
walk(css, False)
return out
def _modes(css: str) -> dict[str, dict[str, str]]:
b = _blocks(css)
return {
"light": b["light"],
"dark": {**b["light"], **b["dark"]},
"auto": {**b["light"], **b["auto"]},
}
def resolve(value: str, env: dict[str, str]) -> str:
for _ in range(10):
new = VAR.sub(lambda m: env.get(m.group(1), (m.group(2) or "").strip()), value)
if new == value:
break
value = new
return " ".join(value.split()).lower()
def draw_decls() -> list[tuple[str, str]]:
out = []
for rel in DRAW_CSS:
text = re.sub(r"/\*.*?\*/", "", (ROOT / rel).read_text(encoding="utf-8"), flags=re.S)
out += [(rel, m.group(2).strip()) for m in DECL.finditer(text)]
return out
def test_draw_values_are_literal_hex():
blocks = _blocks(THEME.read_text(encoding="utf-8"))
for mode, env in blocks.items():
draws = {k: v for k, v in env.items() if k.startswith("--draw-")}
assert draws, mode
for name, value in draws.items():
if name in DRAW_ALPHAS: # 불투명 값(색 아님 · 635-3)
assert 0 < float(value) < 1, (mode, name, value)
continue
assert HEX.fullmatch(value), (mode, name, value)
# 밝음 블록이 전부 정의 · 어두움 · 시스템 어두움은 그 안의 것만 덮음(고정 개수 아님)
light = {k for k in blocks["light"] if k.startswith("--draw-")}
for mode in ("dark", "auto"):
assert {k for k in blocks[mode] if k.startswith("--draw-")} <= light, mode
# 캔버스 · 3D 가 읽는 바탕 · 선 색은 세 모드 모두에서 풀림
for mode, env in _modes(THEME.read_text(encoding="utf-8")).items():
for name in DRAW_NAMES_READ:
assert name in env, (mode, name)
def test_drawing_css_uses_no_screen_tokens():
decls = draw_decls()
assert len(decls) > 100
bad = [(rel, v) for rel, v in decls if "var(--color-" in v]
assert not bad, bad
def test_canvas_and_3d_read_draw_tokens():
for rel, needle in DRAW_READS:
assert needle in (ROOT / rel).read_text(encoding="utf-8"), (rel, needle)
for rel in NO_COLOR_READS:
text = (ROOT / rel).read_text(encoding="utf-8")
assert not re.search(r"""(getPropertyValue|themeColor)\(\s*["']--color-""", text), rel
def test_changing_screen_tokens_keeps_drawing_colors():
css = THEME.read_text(encoding="utf-8")
# 옛 화면 변수를 모두 초록으로 바꾼 판 — sub1 이 테마를 갈아도 그림이 안 따라가는지
swapped = re.sub(r"(--color-[a-z0-9-]+\s*:)[^;]*;", r"\1 #00ff00;", css)
before, after = _modes(css), _modes(swapped)
decls = draw_decls()
for mode in ("light", "dark", "auto"):
for rel, value in decls:
a = resolve(value, before[mode])
assert "var(" not in a, (mode, rel, value)
assert a == resolve(value, after[mode]), (mode, rel, value)
def _lum(hex_color: str) -> float:
rgb = [int(hex_color[i : i + 2], 16) / 255 for i in (1, 3, 5)]
lin = [c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4 for c in rgb]
return 0.2126 * lin[0] + 0.7152 * lin[1] + 0.0722 * lin[2]
def test_map_drawing_colors_fixed_and_readable_on_dark_map():
"""600-1 지도 위 그림 색 — 테마 안 따름(어두움 블록에 없음) · 어두운 바탕 `#1f2937` · 검은 테 둘 다 ≥ 3 : 1."""
blocks = _blocks(THEME.read_text(encoding="utf-8"))
names = [
k
for k in blocks["light"]
if re.fullmatch(r"--draw-map-(sketch|measure|warn|route-\d|slope-(up|down))", k)
]
assert len(names) == 10, names
for name in DRAW_ALPHAS: # 635-3 어둡게 = 밝음 블록 하나(어두움 · 시스템 어두움 덮지 않음)
assert name in blocks["light"] and all(name not in blocks[m] for m in ("dark", "auto"))
ground = _lum("#1f2937")
for name in names:
for mode in ("dark", "auto"):
assert name not in blocks[mode], (mode, name)
lum = _lum(blocks["light"][name])
assert (lum + 0.05) / (ground + 0.05) >= 3, name
assert (lum + 0.05) / 0.05 >= 3, name
def test_slope_down_node_distinct_from_route_mint():
"""625-5 − 노드 색 = 진한 파랑 · 민트 노선 선(route-0)과 · 어두운 바탕과 · 검은 테와 각각 ≥ 3 : 1."""
light = _blocks(THEME.read_text(encoding="utf-8"))["light"]
blue = light["--draw-map-slope-down"]
assert blue == "#3b6bff"
for other in (light["--draw-map-route-0"], "#1f2937"):
hi, lo = sorted((_lum(blue), _lum(other)), reverse=True)
assert (hi + 0.05) / (lo + 0.05) >= 3, other
assert (_lum(blue) + 0.05) / 0.05 >= 3
def test_paint_network_uses_cased_route_lines():
src = (ROOT / "D03_PlanAnalysis/D03_PlanAnalysis_UI_DrawPaint.ts").read_text(encoding="utf-8")
body = src[src.index("export function paintNetwork") : src.index("export function pointLayer")]
assert "strokeCased(context, color, width)" in body
assert "routeColor(index)" in body
def test_pick_highlight_readable_on_any_map_background():
"""613-2 고름 강조 = 노랑 줄(`--draw-map-sketch`) + 검은 테 — 바탕(위성 · 어두운 지도)이 어떤 밝기여도
줄 · 검은 테 중 큰 쪽이 ≥ 3 : 1 · 테 한쪽 4 px 이상 · 노랑 줄은 검은 테와 함께 그림."""
blocks = _blocks(THEME.read_text(encoding="utf-8"))
yellow = _lum(blocks["light"]["--draw-map-sketch"])
for step in range(201):
ground = step / 200
line = (
(yellow + 0.05) / (ground + 0.05)
if yellow > ground
else (ground + 0.05) / (yellow + 0.05)
)
casing = (ground + 0.05) / 0.05
assert max(line, casing) >= 3, ground
src = (ROOT / "D03_PlanAnalysis/D03_PlanAnalysis_UI_Edit.ts").read_text(encoding="utf-8")
assert int(re.search(r"PICK_HALO_PX = (\d+)", src).group(1)) >= 4
assert "strokeCased(context, colors.active, PICK_LINE_PX)" in src
assert "context.strokeStyle = colors.casing;" in src