- ui_template_theme.css 끝 블록 = --draw-* 17 개 · 밝음 / 어두움 / 시스템 어두움 글자 hex - B05 · B06 · B08 그림 CSS stroke · fill 125 줄 · --b06-tag = --draw-* - 캔버스 · 3D 읽기 = B05 노선 편집 지도 · 노선 추천 지도 · 3D 바탕 · B04 3D 바탕 · 지도 MC 글 · D03 고른 측점 · CAD 바탕 · 격자 - 지킴 시험 test_551_2_draw_tokens(화면 변수를 모두 바꿔도 그림 풀린 색 같음) - 기존 시험 7 개 = 그림 줄이 옛 --color-* 이름을 잡던 단정을 --draw-* 로 고침(값 같음) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
156 lines
6.3 KiB
Python
156 lines
6.3 KiB
Python
"""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_RouteRecommend.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"'),
|
|
("B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Map.ts", '"--draw-primary"'),
|
|
("B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Map.ts", '"--draw-text-secondary"'),
|
|
("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-primary"'),
|
|
("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_Recommend_Map.ts",
|
|
"B05_Profile/B05_Profile_UI_Viewer.ts",
|
|
"B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts",
|
|
]
|
|
|
|
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}")
|
|
|
|
|
|
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():
|
|
assert HEX.fullmatch(value), (mode, name, value)
|
|
assert len([k for k in blocks["light"] if k.startswith("--draw-")]) == 17
|
|
|
|
|
|
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)
|