Files
Aislo/resources/tester/test_200_1_b09_box_tool_line.py
T

189 lines
8.1 KiB
Python

"""200-1 B09 박스보기 안내 글 줄임표 + 호버 · 카드 같은 가로 줄 높이 같게.
- 안내 글 = 줄임표 한 줄 · 잘렸을 때만 공용 호버(`attachHoverInfo`)로 전문 · 자리 = 표 · 카드 판 아래(202-1 로 옮김)
- 카드 격자 = 행 늘림(`align-items: stretch`) · 카드 = 세로 flex · 몸통이 늘어 합계 줄이 아래
- 카드 제목 줄 · 합계 줄 바탕색(200-3)
"""
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from resources.tester.helper_b09_group_cards import COMMON, GROUPS, RAW, TABS, run # noqa: E402
B09 = "B09_Estimation/B09_Estimation_UI_Tabs_"
DG = {"id": "DG1", "no": 1, "name": "찰쌓기", "unit": "m2", "lines": [], "sum": 0}
LONG = "손료 = 기준가(천원) × 손료계수 ÷ 10,000 · 운전사 = 인 × 시간 노임 · 연료 = ℓ/hr × 적용 단가"
HINT = (
"(() => { const h = document.createElement('p');"
f" h.className = 'ui-general-block__empty'; h.textContent = '{LONG}'; return h; }})()"
)
def _block(css: str, selector: str) -> str:
m = re.search(re.escape(selector) + r"\s*\{([^}]*)\}", css)
assert m, selector
return m.group(1)
def _panel(view: str, hint: str):
"""고른 보기 · 안내 글로 그린 뒤 안내 글 자리 · 호버 기록."""
return run(
[COMMON, TABS, f"{B09}Labor.ts", GROUPS],
f"\nconst spec = buildDaegaBlocks({{ sheets: {{ daega_blocks: [{json.dumps(DG, ensure_ascii=False)}] }} }});"
f"\nchooseGroupView('{view}');"
f"\nconst hint = {hint};"
"\nconst o = { tab: 'dg', spec, hint, goto: () => {}, redraw: () => {}, setTools: () => {},"
" makeTable: () => ({ root: document.createElement('table'), focusRow: () => {} }) };"
"\nconst out = renderGroupPanel(o);"
"\nconst last = out[out.length - 1];"
"\nconst inBar = hint ? out.some((e) => e.all('.ui-group-cards__bar').some((b) => b.contains(hint))) : false;"
"\nconst call = hoverCalls[0];"
"\nconst wide = call ? call.text({ scrollWidth: 900, clientWidth: 300, textContent: 'T' }) : null;"
"\nconst fit = call ? call.text({ scrollWidth: 300, clientWidth: 300, textContent: 'T' }) : null;"
"\nconsole.log(JSON.stringify({ n: out.length, hintLast: !!hint && last === hint, inBar,"
" hintCls: hint ? hint.className : '', hover: hoverCalls.length,"
" hoverIsHint: call ? call.root === hint : false, wide, fit, first: out[0].tagName }));",
RAW,
)
def test_카드_보기_안내_글은_카드_판_아래():
out = _panel("cards", HINT)
assert out["n"] == 2 and out["hintLast"] and not out["inBar"] # 카드 판 + 안내 글(맨 아래)
assert "b09-estimation__line-hint" in out["hintCls"]
def test_표_보기_안내_글은_표_아래():
out = _panel("table", HINT)
assert out["n"] == 2 and out["first"] == "TABLE" and out["hintLast"] # 표 + 안내 글
def test_안내_글_잘렸을_때만_공용_호버로_전문():
for view in ("cards", "table"):
out = _panel(view, HINT)
assert out["hover"] == 1 and out["hoverIsHint"]
assert out["wide"] == "T" # 잘림(scrollWidth > clientWidth) = 전문
assert out["fit"] is None # 다 보이면 안 띄움
def test_안내_글_없으면_호버도_없음():
for view in ("cards", "table"):
out = _panel(view, "null")
assert out["n"] == 1 and out["hintLast"] is False and out["hover"] == 0
def test_CSS_안내_글_줄임표_한_줄():
page = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Page.css").read_text("utf-8")
hint = _block(page, ".b09-estimation__line-hint")
for need in (
"overflow: hidden",
"text-overflow: ellipsis",
"white-space: nowrap",
"min-width: 0",
):
assert need in hint, need
def test_CSS_카드_같은_줄_높이_같게():
css = (ROOT / "ui_template" / "ui_template_group_cards.css").read_text("utf-8")
grid = _block(css, ".ui-group-cards__grid")
assert "align-items: stretch" in grid and "align-items: start" not in grid
card = _block(css, ".ui-group-card")
assert "display: flex" in card and "flex-direction: column" in card
assert "flex: 1 1 auto" in _block(css, ".ui-group-card__body") # 합계 줄 = 카드 아래
def test_잇기():
groups = (ROOT / GROUPS).read_text("utf-8")
assert "attachHoverInfo(hint," in groups and "placeTools: o.setTools" in groups
cards = (ROOT / COMMON).read_text("utf-8")
assert "opts.lead" not in cards and "if (opts.placeTools) {" in cards
# ── 200-3 카드 제목 줄 · 합계 줄 바탕색(밝음 · 어두움) ─────────────────────────
def _theme_vars(dark: bool) -> dict:
"""테마 CSS 의 `:root`(밝음) · 어두움 덮어쓰기 토큰 → 한 벌."""
css = (ROOT / "ui_template" / "ui_template_theme.css").read_text("utf-8")
light = dict(re.findall(r"(--[\w-]+):\s*([^;]+);", _block(css, ":root")))
if dark:
light.update(re.findall(r"(--[\w-]+):\s*([^;]+);", _block(css, ':root[data-theme="dark"]')))
return light
def _rgb(token: str, theme: dict) -> tuple:
"""토큰 → (r, g, b) · var() 별칭 따라감 · 16진수만."""
val = theme[token].split("/*")[0].strip()
while m := re.fullmatch(r"var\((--[\w-]+)\)", val):
val = theme[m.group(1)].split("/*")[0].strip()
assert re.fullmatch(r"#[0-9a-fA-F]{6}", val), (token, val)
return tuple(int(val[i : i + 2], 16) for i in (1, 3, 5))
def _mix(spec: str, theme: dict) -> tuple:
"""`color-mix(in srgb, var(A) N%, var(B))` → 섞은 (r, g, b)."""
m = re.fullmatch(
r"color-mix\(in srgb,\s*var\((--[\w-]+)\)\s*(\d+)%,\s*var\((--[\w-]+)\)\)", spec
)
assert m, spec
a, n, b = _rgb(m.group(1), theme), int(m.group(2)) / 100, _rgb(m.group(3), theme)
return tuple(a[i] * n + b[i] * (1 - n) for i in range(3))
def _lum(c: tuple) -> float:
ch = [
(v / 255) / 12.92 if v / 255 <= 0.03928 else (((v / 255) + 0.055) / 1.055) ** 2.4 for v in c
]
return 0.2126 * ch[0] + 0.7152 * ch[1] + 0.0722 * ch[2]
def _contrast(a: tuple, b: tuple) -> float:
hi, lo = sorted((_lum(a), _lum(b)), reverse=True)
return (hi + 0.05) / (lo + 0.05)
def _tokens() -> tuple:
css = (ROOT / "ui_template" / "ui_template_group_cards.css").read_text("utf-8")
root = _block(css, ".ui-group-cards")
head = re.search(r"--group-head-bg:\s*([^;]+);", root)
foot = re.search(r"--group-foot-bg:\s*([^;]+);", root)
assert head and foot
return css, head.group(1), foot.group(1)
def test_카드_제목_줄과_합계_줄_바탕색_토큰():
css, head, foot = _tokens()
assert head != foot
assert "background-color: var(--group-head-bg)" in _block(css, ".ui-group-card__head")
assert "background-color: var(--group-foot-bg)" in _block(css, ".ui-group-card__foot")
for spec in (head, foot): # 고른 값 없이 테마 변수만(색 고정값 없음)
assert "#" not in spec and "rgb(" not in spec
def test_밝음_어두움_두_색이_다르고_글_대비_유지():
_, head, foot = _tokens()
for dark in (False, True):
theme = _theme_vars(dark)
h, f = _mix(head, theme), _mix(foot, theme)
assert sum(abs(h[i] - f[i]) for i in range(3)) >= 40, (dark, h, f) # 서로 다른 색
card = _rgb("--color-surface-raised", theme)
assert sum(abs(f[i] - card[i]) for i in range(3)) >= 40 # 카드 바탕과도 구분
for color in ("--color-text", "--color-text-secondary", "--color-accent"):
assert _contrast(_rgb(color, theme), h) >= 4.5, (dark, "head", color)
for color in ("--color-text", "--color-text-body", "--color-text-secondary"):
assert _contrast(_rgb(color, theme), f) >= 4.5, (dark, "foot", color)
def test_어두움_테마_값():
# 어두움 테마에서 쓰는 토큰이 실제로 어두움 값으로 덮임(밝음 값 그대로가 아님)
light, dark = _theme_vars(False), _theme_vars(True)
for token in ("--color-surface", "--color-surface-raised", "--color-accent"):
assert _rgb(token, light) != _rgb(token, dark), token