"""202-1 B08 · B09 표 칸 — 안내 · 경고 줄 = 맨 아래 · 박스보기 · 가로 열 수 = 보조 탭 줄 오른쪽. - B09 안내 줄 = 표 · 카드 판 아래(마지막 자식) · 묶음 탭 도구(보기 단추 · 열 수) = 아래 탭 줄 오른쪽 · 단독 탭 빈 줄에서도 - B08 안내 · 경고 줄 = 표 · 아래 줄 뒤 맨 아래(토공 · 운반 시트 · 자재 · 구조물 집계) - 창 끝까지 셈(199-1) = 아래로 간 안내 줄 몫 포함 """ import json import re import subprocess 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_" BAR = f"{B09}TabBar.ts" DG = {"id": "DG1", "no": 1, "name": "찰쌓기", "unit": "m2", "lines": [], "sum": 0} def _block(css: str, selector: str) -> str: m = re.search(re.escape(selector) + r"\s*\{([^}]*)\}", css) assert m, selector return m.group(1) # ── B09 보조 탭 줄 오른쪽 도구 ────────────────────────────────────────────── def test_도구_자리_보조_줄_오른쪽_빈_줄에서도_보임(): out = run( [TABS, BAR], """ const bars = createTabBars(() => {}); const mk = (n) => { const b = document.createElement('button'); b.textContent = n; return b; }; const snap = () => ({ kids: bars.sub.children.map((c) => (c.classList.contains('b09-estimation__tools') ? 'TOOLS' : c.textContent)), last: bars.sub.children[bars.sub.children.length - 1].children.map((x) => x.textContent), }); bars.draw('dg'); bars.setTools([mk('가'), mk('나')]); const a = snap(); bars.draw('compare'); // 다른 묶음으로 옮겨도 도구 자리는 그대로 마지막 const b = snap(); bars.draw('cost'); // 단독 탭 = 빈 보조 줄 const c = snap(); const ghost = bars.sub.children[0]; bars.setTools([]); const d = snap(); console.log(JSON.stringify({ a: a.kids, aLast: a.last, b: b.kids.slice(-1), bLast: b.last, c: c.kids, cLast: c.last, blank: bars.sub.classList.contains('b09-estimation__tabs--blank'), subHidden: bars.sub.attrs['aria-hidden'] ?? null, ghostHidden: ghost.attrs['aria-hidden'], dLast: d.last.length, })); """, ) tools = "TOOLS" assert out["a"] == ["일위대가목록표", "일위대가표", tools] # 아래 탭 뒤 오른쪽 자리 assert out["aLast"] == ["가", "나"] assert out["b"] == [tools] and out["bLast"] == ["가", "나"] # 탭을 옮겨도 도구는 그대로 assert out["blank"] and len(out["c"]) == 2 and out["c"][1] == tools # 숨은 단추 + 도구 assert out["cLast"] == ["가", "나"] assert out["subHidden"] is None and out["ghostHidden"] == "true" # 줄 전체가 아니라 숨은 단추만 assert out["dLast"] == 0 # 비움 def test_묶음_탭_단추가_보조_줄에_안내는_아래에(): """쉘이 하는 대로 — 줄 그림 → 묶음 판 그림(`setTools` = 보조 줄 도구) → 단추는 줄에, 판에는 없음.""" out = run( [COMMON, TABS, f"{B09}Tabs_Labor.ts", GROUPS, BAR], f"\nconst spec = buildDaegaBlocks({{ sheets: {{ daega_blocks: [{json.dumps(DG, ensure_ascii=False)}] }} }});" "\nconst bars = createTabBars(() => {});" "\nconst hint = document.createElement('p');" "\nconst res = {};" "\nfor (const view of ['cards', 'table']) {" "\n chosen.set('dg', view);" "\n bars.draw('dg'); bars.setTools([]);" "\n const panel = renderGroupPanel({ tab: 'dg', spec, hint, goto: () => {}, redraw: () => {}," " makeTable: () => ({ root: document.createElement('table'), focusRow: () => {} })," " setTools: bars.setTools });" "\n const tools = bars.sub.children[bars.sub.children.length - 1];" "\n res[view] = { inRow: tools.all('[data-b09-group-view]').map((b) => b.textContent)," " cols: tools.all('[data-group-cols]').length," " inPanel: panel.flatMap((e) => [e, ...e.all('[data-b09-group-view]'), ...e.all('[data-group-cols]')])" ".filter((e) => e.dataset.b09GroupView || e.dataset.groupCols).length," " hintLast: panel[panel.length - 1] === hint, n: panel.length };" "\n}" "\nconsole.log(JSON.stringify(res));", RAW, ) assert out["cards"]["inRow"] == ["표로 보기"] and out["cards"]["cols"] == 4 assert out["table"]["inRow"] == ["박스보기"] and out["table"]["cols"] == 0 for view in ("cards", "table"): assert out[view]["inPanel"] == 0 # 판 안에는 단추 없음 assert out[view]["hintLast"] and out[view]["n"] == 2 # 판 + 안내 줄(맨 아래) def test_잇기_쉘_안내_줄_아래_도구_비움(): shell = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Shell.ts").read_text("utf-8") assert ( "panel.replaceChildren(...(level ? [level] : []), table.root, ...(hint ? [hint] : []))" in shell ) # 200-2 레벨 줄 · 203-1 연결 점검 탭 없앰과 합침 assert "...(hint ? [hint] : []), table.root" not in shell # 위 자리 없음 assert re.search(r"bars\.draw\(tab\);\s*bars\.setTools\(\[\]\);", shell) assert "setTools: bars.setTools" in shell def test_CSS_도구_오른쪽_맞춤_빈_줄_숨김_규칙에_안_걸림(): css = (ROOT / "B09_Estimation" / "B09_Estimation_UI_Page.css").read_text("utf-8") tools = _block(css, ".b09-estimation__tools") assert "margin-left: auto" in tools and "flex-wrap: nowrap" in tools hidden = _block(css, ".b09-estimation__tabs--blank > .ui-btn") assert "visibility: hidden" in hidden # 직계 단추만 — 도구 묶음은 안 숨김 assert ".b09-estimation__bar" not in css # 옛 머리 줄(안내 + 단추) 풂 # ── B08 안내 · 경고 줄 맨 아래 ────────────────────────────────────────────── def test_B08_표_카드_차례_표_아래줄_안내(): src = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Earthwork.ts").read_text("utf-8") fn = re.search(r"export function tableCard\(.*?\n}\n", src, re.S) assert fn code = fn.group(0).replace("export ", "") js = ( "const createCard = ({ body }) => body;\n" + code + "\nconsole.log(JSON.stringify(tableCard(['i1', 'i2'], 'T', ['a1', 'a2'])));" ) result = subprocess.run( ["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript"], input=js, capture_output=True, text=True, encoding="utf-8", timeout=60, ) assert result.returncode == 0, result.stderr assert json.loads(result.stdout) == ["T", "a1", "a2", "i1", "i2"] # 표 · 아래 줄 · 안내 줄 def test_B08_자재_구조물_안내_줄_표_아래(): material = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Material.ts").read_text("utf-8") assert "createCard({ body: [toolbar, table.root, excel, notes] })" in material assert "toolbar.append(save);" in material # 저장 단추만 위 줄(안내는 아래) summary = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Summary.ts").read_text("utf-8") assert "createCard({ body: [table, ...parts] })" in summary css = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Page.css").read_text("utf-8") assert "justify-content: flex-end" in _block(css, ".b08-material__toolbar") # ── 창 끝까지 셈(199-1)에 아래 줄 몫 ──────────────────────────────────────── def test_창_끝까지_셈에_아래_안내_줄_포함(): proc = subprocess.run( ["node", str(ROOT / "resources" / "tester" / "helper_202_1_fill_below.cjs")], capture_output=True, text=True, encoding="utf-8", timeout=60, ) assert proc.returncode == 0, proc.stderr r = json.loads(proc.stdout) assert r["below"] == r["belowExpected"] == 134 # 아래 줄 · 안내 줄 둘 + 여백 assert r["bottom"] == r["viewport"] # 문서 맨 아래 = 창 끝 → 페이지 스크롤 0 assert r["afterHintGrow"] == r["firstMinusGrow"] # 안내 줄이 늘면 표 칸이 그만큼 줄어듦