Files
Aislo/resources/tester/test_164_detail_zoom.py
T

150 lines
6.8 KiB
Python

"""PLAN 164 — B06 [상세] 그림 줌 · 팬(164-1) · 표 강조 붉은 한 벌(164-2).
짝: `B06_Section/B06_Section_UI_Cross_Detail.ts`(`attachDetailZoom` · 칸 누름) ·
`_UI_Style_Cross_Detail.css`(칸 비율 · 붉은 강조). 실제 TS 를 `helper_164_detail_zoom.cjs` 가 Node 로 돌린다.
"""
from __future__ import annotations
import json
import os
import subprocess
import pytest
HERE = os.path.dirname(__file__)
ROOT = os.path.join(HERE, "..", "..")
B06 = os.path.join(ROOT, "B06_Section")
pytestmark = pytest.mark.skipif(
not os.path.isdir(os.path.join(ROOT, "config", "node_modules")),
reason="프론트엔드 의존성 미설치",
)
def _read(name: str) -> str:
with open(os.path.join(B06, name), encoding="utf-8") as f:
return f.read().replace(chr(13), "")
@pytest.fixture(scope="module")
def out() -> dict:
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
["node", os.path.join(HERE, "helper_164_detail_zoom.cjs")],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def test_감싸지_않음_비율(out):
# 179-3 — 복제를 g 하나로 감싸지 않음(축 · 눈금 · 도형 한 장 → 도형 층만)
assert out["wrapped"] is False and out["children"] == 4
assert out.get("ratio") is None # 173-4 — 칸 = viewBox(칸 크기로 새로 그림) · 비율 변수 없음
assert out["before"] is None
assert out["ticksBefore"] > 0
def test_축_고정_도형_층만_움직임_눈금_다시(out):
# 179-3 — 팬 뒤 도형 층만 transform · 축 층 · clip 그룹 제자리 · 눈금 글은 보이는 범위로
assert out["plotTransform"] == "translate(100 20) scale(1)"
assert out["axesTransform"] is None and out["clipTransform"] is None
assert out["ticksChanged"] is True and out["ticksAfter"] > 0
assert out["restoredTicksShifted"] is True # 다시 떠도 이어 간 범위로
# 확대 — 십자는 중심(300,150)에서 배율 되돌림
assert out["centerTransform"].startswith("translate(300 150) scale(0.869")
assert out["noAxesHandle"] is True # 축 옵션 없음 = 줌만
assert out["noLayer"] is None # 도형 층 없음 = 줌 없음
def test_휠_당기면_확대_커서_고정(out):
assert out["wheelIn"]["scale"] == pytest.approx(1.15)
assert out["cursorFixed"] == pytest.approx(300) # 커서 아래 svg x 300 이 제자리
assert out["wheelOut"] < out["wheelIn"]["scale"]
assert out["max"] == 16
def test_가운데_단추_팬_좌클릭은_팬_아님(out):
assert out["leftDrag"] == {"scale": 1, "tx": 0, "ty": 0}
# 1배에서도 팬 · 화면 200·40 px = svg 100·20(칸 2 배)
assert out["middlePan"] == {"scale": 1, "tx": 100, "ty": 20}
assert out["panClickStopped"] is True
def test_다시_떠도_배율_유지_더블클릭_원복(out):
assert out["restored"] == "translate(100 20) scale(1)"
assert out["reset"] == "translate(0 0) scale(1)"
def test_모달_배선_단추_칸_비율():
src = _read("B06_Section_UI_Cross_Detail.ts")
css = _read("B06_Section_UI_Style_Cross_Detail.css")
assert "wheelZoom: true" in src and "attachSvgZoomPan({" in src
assert "cad.replaceChildren(clone, ...(handle ? [buildZoomControls(handle)] : []))" in src
assert "attachDetailZoom(clone, axes, zoom, (next) => (zoom = next))" in src
assert "layer.append(...clone.childNodes)" not in src # 감싸기 지움(179-3)
assert 'clone.querySelector<SVGGElement>("g.b06-chart__plot-layer")' in src
assert "createCrossAxes(axisLayer, axes)" in src
view = _read("B06_Section_UI_Section_View.ts")
assert "const plot = fitted && crossPlotFromBase(fitted, heightPx);" in view
assert "return { svg, axes };" in view
assert "position: relative;" in css
# 173-4 — 그림 = 칸 크기로 새로 그림 · svg 칸 가득
assert ".b06-detail__cad > svg {\n display: block;\n width: 100%;\n height: 100%;" in css
assert "--ratio" not in css and "--ratio" not in src
def test_강조_붉은_한_벌_고름_hover_다름_빈칸():
css = _read("B06_Section_UI_Style_Cross_Detail.css")
for selector in (
".b06-detail__cad .b06-chart__area.is-active > polygon {\n fill: color-mix(in srgb, var(--color-danger)",
".b06-detail__cad .b06-chart__design-hit.is-active {\n stroke: color-mix(in srgb, var(--color-danger)",
".b06-detail__cad .b06-chart__center.is-active line {\n stroke: var(--color-danger);",
".b06-detail__cad .b06-chart__spoil-fill.is-active {\n stroke: var(--color-danger);",
".b06-detail__qtable .is-picked {\n background: color-mix(in srgb, var(--color-danger) 18%",
".b06-detail__qtable tr:has(.is-picked) {",
".b06-detail__qtable [data-key][data-empty] {\n cursor: default;",
):
assert selector in css, selector
assert "royal-amethyst" not in css and "--color-warning);" not in css.split("/* 그림 강조")[1]
assert (
".b06-detail__qtable [data-key]:hover {\n background: color-mix(in srgb, var(--color-danger) 8%"
in css
)
def test_빈칸_누름_없음_이름_칸_첫_키():
src = _read("B06_Section_UI_Cross_Detail.ts")
assert 'el.toggleAttribute("data-empty", !present.has(el.dataset.key ?? ""))' in src
assert 'if (!key || el.hasAttribute("data-empty")) return;' in src
# 179-4 — 묶음 이름 칸 = 하위 줄만큼(양식 2줄 · 더한 줄만큼 늘어남) · 그 줄 첫 값 칸 키
assert "nameCell(groupL, { row: groupSpan(layout.left, index) }, keyL, id, 0)" in src
assert "nameCell(groupM, { row: groupSpan(layout.middle, index) }, keyM, id, 1)" in src
def test_표_글자_14px_이름_칸_머리색_빈칸_값칸만():
# 179-3 — 표 글자 14px · 이름 칸(th) 바탕 = 데이터 표 머리색 · 빈 칸 바탕 지움은 값 칸(td)만
css = _read("B06_Section_UI_Style_Cross_Detail.css")
table = css.split(".b06-detail__qtable {", 1)[1].split("}", 1)[0]
assert "font-size: var(--text-body-sm);" in table
assert (
"--b06-detail-head: color-mix(in srgb, var(--color-accent) 6%, var(--color-surface));"
in table
)
head = css.split(".b06-detail__qtable th {", 1)[1].split("}", 1)[0]
assert "background: var(--b06-detail-head);" in head and "color: var(--color-text);" in head
empty = css.split(".b06-detail__qtable [data-key][data-empty] {", 1)[1].split("}", 1)[0]
assert "background" not in empty
assert ".b06-detail__qtable td[data-key][data-empty] {\n background: none;" in css
assert (
".b06-detail__qtable th[data-key][data-empty] {\n background: var(--b06-detail-head);"
in css
)
theme = open(
os.path.join(ROOT, "ui_template", "ui_template_theme.css"), encoding="utf-8"
).read()
assert "--text-body-sm: 14px;" in theme