# -*- coding: utf-8 -*-
"""공용 표 양식(PLAN 70-1) 회귀검증 — 묶음 머리 · 고정 열 · 숫자 꼴 · 빈 값 · 합계 · 1,000줄.
Node 헬퍼(`helper_ui_data_table.cjs`)가 `ui_template/ui_template_data_table.ts` 를 트랜스파일해
견본(`fixture_data_table_sample.js` · 머리 3줄 · 30열 · 66줄)으로 HTML 을 만들고, 여기서 판정함.
"""
import json
import os
import subprocess
import pytest
HERE = os.path.dirname(os.path.abspath(__file__))
@pytest.fixture(scope="module")
def r():
proc = subprocess.run(
["node", os.path.join(HERE, "helper_ui_data_table.cjs")],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def test_견본_크기(r):
assert r["cols"] == 30
assert r["headRows"] == 3
assert r["bodyRows"] == 66
def test_묶음_머리_합침(r):
first = r["firstHeadRow"]
# 측점 · 거리 · 유용토 등 묶음 없는 열은 머리 3줄을 세로로 차지
assert 'rowspan="3" class="is-frozen is-edge" style="left:0px" title="측점">측점' in first
assert first.count('rowspan="3"') == 5 # 측점 거리 유용토 차인토량 누가토량
assert 'colspan="13">절토' in first # 토사 · 연암 · 측구파기 토사 · 암 각 3 + 보정량계
assert 'colspan="4">면고르기' in first
assert 'colspan="2">노면정리' in first
second = r["secondHeadRow"]
assert 'colspan="3">토사' in second and 'colspan="3">연암' in second
assert 'rowspan="2" data-num="" title="보정량계"' in second # 한 단 묶음은 머리 2줄
def test_고정_열과_숫자_칸(r):
row = r["firstBodyRow"]
assert row.startswith('
| 120 | ')
assert r["frozenCells"] == 66
assert '0 | ' in row # 거리 0 은 빈 값 아님
def test_숫자_꼴(r):
f = r["fmt"]
assert f["thousands"] == "1,234,567.89"
assert f["neg"] == "-97.90"
assert f["zero"] == "0.00"
assert f["nul"] == f["undef"] == f["blank"] == f["nan"] == "-"
assert f["text"] == "120+18" and f["textNum"] == "5"
def test_합계_줄(r):
assert r["foot"].startswith('
| 합계 | ')
assert "is-empty" in r["foot"] # 누가토량 합계는 비움
assert r["noTotal"] is False
def test_글_새지_않음(r):
assert "<b>" in r["escaped"] and "a&<" in r["escaped"]
def test_천_줄_빠름(r):
assert r["bigRows"] == 1000
assert r["bigMs"] < 300
def test_보이는_줄만_그림(r):
w = r["win"]
assert w["n"] == 62 # 위 빈 줄 + 60줄 + 아래 빈 줄
assert 'style="height:1525px">' in w["top"] # 61줄 × 25
assert w["second"].startswith(
' |
'
) # 61 번째(홀수) 줄 — 번갈아 색은 전체 줄 번호로
assert 'style="height:21975px"' in w["bottom"] # (1000 - 121) × 25
def test_빈_칸_글_풍선_꾸밈_옵션(r):
o = r["opts"]
assert ' | ' in o # empty="" — 「-」 대신 빈칸
assert '메쌓기 | ' in o
assert 'rowspan="2" class="is-frozen is-edge" style="left:0px" title="측점">측점' in o
def test_줄_누름_옵션일_때만_data_i(r):
# 95-3 — onRowClick 을 주면 본문 줄에만 data-i · is-clickable(합계 줄 제외) · 안 주면 글 그대로
pick = r["pick"]
assert '
' in pick
assert '
' in pick
assert pick.count("data-i=") == 2
assert "data-i" not in r["opts"] and "is-clickable" not in r["foot"]
def test_155_1_글자_14px_열_폭_같은_비율(r):
# 155-1 — 표 글 12 → 14px · 열 폭(12px 글 기준 값)에 14/12 를 곱함 · 줄 높이 28px
assert abs(r["scale"] - 14 / 12) < 1e-9
w = r["widths"]
assert '' in w
assert 'style="width:350px"' in w # 표 폭 = 112 + 98 + 140
assert 'class="is-frozen is-edge" style="left:112px"' in w # 둘째 고정 열 left 도 같이
assert "font-size: var(--text-body-sm);" in r["css"]
assert "--dt-row-h: 28px;" in r["css"]
def test_155_2_줄_꼴_곁값(r):
# 155-2 — rowClass 가 낸 class 를 본문 줄에만 · 빈 값이면 안 붙임 · 앞 빈칸을 접지 않는 서식
rc = r["rowClass"]
assert '
| 제 1 호표 |
' in rc
assert '| ㄴ 석공 |
' in rc
assert rc.count("is-title") == 1 # 합계 줄엔 안 붙음
assert "white-space-collapse: preserve-spaces;" in r["css"]
assert "tr.is-title td" in r["css"]