Merge remote-tracking branch 'origin/dev' into sub_laptop_8

This commit is contained in:
2026-10-10 13:00:30 +09:00
11 changed files with 251 additions and 26 deletions
@@ -146,13 +146,14 @@ def test_화면_보기_바꿈_누름_이동():
"\nconst o = { tab: 'dg', spec, hint: null, focus: 'DG1|1', goto: (l) => gone.push(l),"
" redraw: () => redraws++, makeTable: () => ({ root: document.createElement('table'),"
" focusRow: (i) => focused.push(i) }) };"
"\nconst [bar, view] = renderGroupPanel(o);"
"\nconst label = bar.children[0].textContent;"
"\nconst [view] = renderGroupPanel(o);"
"\nconst btn = view.querySelector('[data-b09-group-view]');"
"\nconst label = btn.textContent;"
"\nview.querySelector('[data-at=\"2\"]').click();"
"\nview.querySelector('[data-at=\"0\"]').click();"
"\nconst lit = view.querySelector('[data-at=\"3\"]').classList.contains('is-focus');"
"\nconst box = view.querySelector('.ui-group-card').classList.contains('is-pick');"
"\nbar.children[0].click();"
"\nbtn.click();"
"\nconst [bar2, view2] = renderGroupPanel(o);"
"\nconsole.log(JSON.stringify({ label, table: view.dataset.b09Table, gone, lit, box, redraws,"
" label2: bar2.children[0].textContent, tag2: view2.tagName, focused,"
@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
"""185-1 B09 총괄설계내역서 · 공사원가계산서 줄 레벨 — 줄 `level` · 줄 class(182 is-lvN) · 칸 class · CSS.
레벨 = 화면이 정함(줄 짜임이 `B09_Estimation_UI_Tabs_Total.ts` 에 있음 · 셈 짝은 값만 냄 → 거울 없음).
총괄 = 가.~바. 소계 1(굵게) · 그 밖 2 · 원가계산서 = 구분만 1 · 비목 소계 2 · 세목 3.
들여쓰기 = 명칭 칸(is-indent) · 앞 칸(is-flat)은 그대로.
"""
import json
import re
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
B09 = ROOT / "B09_Estimation"
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
SUMS = {"material": 194234417, "labor": 346800554, "expense": 169324165, "supply_net": 69474220}
BODY = f"""
const user = {{ estimate: {{}} }};
const ctx = {{ sheets: {{ total: computeTotal({json.dumps(SUMS)}, user.estimate) }}, payload: {{}}, user, setUser: () => {{}} }};
const pack = (t) => t.rows.map((r, i) => [r.key, r.level, t.rowClass(i), t.cellClass(i, "name"), t.cellClass(i, t.columns[0].key), t.cellClass(i, "amount")]);
console.log(JSON.stringify({{ total: pack(buildTotal(ctx)), cost: pack(buildCost(ctx)) }}));
"""
def _run() -> dict:
names = ("Calc_Base", "Calc_Total", "UI_Tabs", "UI_Tabs_Total")
script = "".join(
IMPORTS.sub("", (B09 / f"B09_Estimation_{n}.ts").read_text(encoding="utf-8")) for n in names
)
result = subprocess.run(
["node", "--experimental-strip-types", "--no-warnings",
"--input-type=module-typescript"],
input=script + BODY, capture_output=True, text=True, encoding="utf-8", timeout=60,
) # fmt: skip
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)
def test_총괄_레벨():
rows = {k: (lv, cls) for k, lv, cls, *_ in _run()["total"]}
top = ["direct", "net_cost", "da", "supply_price", "contract", "grand"]
assert all(rows[k] == (1, "is-lv1 is-title") for k in top)
for k in ["in_price", "in_env", "indirect_labor", "machine", "retirement", "overhead", "vat",
"supply"]: # fmt: skip
assert rows[k] == (2, "is-lv2"), k
assert {lv for lv, _ in rows.values()} == {1, 2}
def test_원가계산서_레벨():
rows = {k: (lv, cls) for k, lv, cls, *_ in _run()["cost"]}
for k in ["overhead", "profit", "supply_price", "vat", "contract", "supply", "grand"]:
assert rows[k] == (1, "is-lv1 is-title"), k
for k in ["material", "labor", "expense_sum"]:
assert rows[k] == (2, "is-lv2"), k
for k in ["direct_material", "scrap", "direct_labor", "indirect_labor", "expense", "safety"]:
assert rows[k] == (3, "is-lv3"), k
def test_칸_class_명칭만_들여씀():
out = _run()
for tab in ("total", "cost"):
for _k, _lv, _cls, name, first, amount in out[tab]:
assert (name, first, amount) == ("is-indent", "is-flat", None)
def test_CSS_레벨_규칙():
css = (B09 / "B09_Estimation_UI_Page.css").read_text(encoding="utf-8")
assert re.search(
r'tr\[class\*="is-lv"\] td\.is-flat:first-child \{\s*padding-left: var\(--spacing-8\);', css
)
for lv, em in ((2, 1), (3, 2)):
assert re.search(
rf"tr\.is-lv{lv} td\.is-indent \{{\s*padding-left: calc\(var\(--spacing-8\) \+ {em}em\);",
css,
)
# 글자 크기 = 182 규칙 재사용(줄 높이 28px → 16px 까지)
assert re.search(r"tr\.is-lv1 td \{\s*font-size: var\(--text-body\);", css)
@@ -0,0 +1,76 @@
"""185-2 B09 묶음 탭 도구 줄 — 보기 바꿈 단추 글 한 줄 · 가로 열 수 단추와 같은 줄.
- 카드 보기 = 보기 바꿈 단추가 가로 열 수 줄(`ui-group-cards__bar`) 안 · 표 보기 = 머리 줄(`b09-estimation__bar`)
- 단추 · 라벨 = `white-space: nowrap` · 도구 줄 = 줄바꿈 없음(`flex-wrap: nowrap`)
"""
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}
HINT = "(() => { const h = document.createElement('p'); h.className = 'hint'; 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"\nchosen.set('dg', '{view}');"
f"\nconst o = {{ tab: 'dg', spec, hint: {hint}, goto: () => {{}}, redraw: () => {{}},"
" makeTable: () => ({ root: document.createElement('table'), focusRow: () => {} }) };"
"\nconst out = renderGroupPanel(o);"
"\nconst top = out.find((e) => e.all('[data-b09-group-view]').length || e.dataset.b09GroupView);"
"\nconst btn = top.dataset.b09GroupView ? top : top.querySelector('[data-b09-group-view]');"
"\nconst bar = btn.closest('.ui-group-cards__bar') ?? btn.closest('.b09-estimation__bar') ?? top;"
"\nconsole.log(JSON.stringify({ n: out.length, first: out[0].className, label: btn.textContent,"
" bar: bar.className, cols: bar.all('[data-group-cols]').map((b) => b.dataset.groupCols) }));",
RAW,
)
def test_카드_보기_단추가_가로_열수_줄에():
out = _panel("cards", "null")
assert out["bar"] == "ui-group-cards__bar" and out["label"] == "표로 보기"
assert out["cols"] == ["1", "2", "3", "4"] # 같은 줄에 열 수 단추 넷
assert out["n"] == 1 # 안내 글 없으면 카드 판 하나뿐
def test_카드_보기_안내_글은_카드_위에_따로():
out = _panel("cards", HINT)
assert out["n"] == 2 and out["first"] == "hint"
assert out["bar"] == "ui-group-cards__bar"
def test_표_보기_단추는_머리_줄():
out = _panel("table", "null")
assert out["bar"] == "b09-estimation__bar" and out["label"] == "박스보기"
assert out["cols"] == [] # 표 보기 = 열 수 단추 없음
def test_CSS_한_줄_nowrap():
cards = (ROOT / "ui_template" / "ui_template_group_cards.css").read_text("utf-8")
assert "flex-wrap: nowrap" in _block(cards, ".ui-group-cards__bar")
item = _block(
cards,
".ui-group-cards__bar .ui-btn,\n.ui-group-cards__label,\n.ui-group-cards__count",
)
assert "white-space: nowrap" in item and "flex: none" in item
page = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Page.css").read_text("utf-8")
assert "white-space: nowrap" in _block(page, ".b09-estimation__bar .ui-btn")
assert "actions: [toggle]" in (ROOT / GROUPS).read_text("utf-8")