Files
Aislo/resources/tester/helper_b09_group_cards.py
T
eomsangdonandClaude Opus 5.5 40ed04b15f feat(ui): 묶음 카드 공용 양식 · B09 묶음 탭 셋 카드 기본(171-2 · 171-3)
- 171-2 새 공용 ui_template_group_cards(.ts · .css) — 머리(호표 · 명칭 · 규격 · 단위 · 합계) · 몸 줄(이름 · 규격 · 수량 · 단위 · 합계 · 비고) · 합계 줄(노무 · 재료 · 경비 · 계)
- 글자 14px 한 벌 · 줄 간격 좁게(공용 카드 16px 끼움 없음) · 숫자 오른쪽 · 줄 금액 = 합계만 + 비목 셋 풍선
- 가로 열 수 1~4(기본 2) · 묶음 12 넘으면 요약 보기(머리 · 합계만 · 머리 누름 = 펼침 · 모두 펼침 · 접기)
- 157-2 산근 카드 파일 갈음(지움)
- 171-3 일위대가표 · 중기사용료 · 단가산출근거 = 카드 기본 · [표로 보기] = 표 그대로 · 보기 · 열 수 기억(b_page_state 두 줄 · 탭마다)
- 값 · 풍선 · 칸 누름 이동 = 표 줄 그대로 · 집계표 · 설계내역서 · B08 대상 밖
- 시험 test_171_2 · test_171_3 · 공용 도우미(가짜 DOM) · test_157_2 갈음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
2026-10-10 08:46:56 +09:00

95 lines
4.0 KiB
Python

"""171 묶음 카드 시험 도우미 — TS 소스를 이어 붙여 Node(타입 지움)로 돌림 · 작은 가짜 DOM.
시험 파일 아님(test_ 머리 없음) — test_171_2 · test_171_3 · test_157_2 가 씀.
"""
import json
import re
import subprocess
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
COMMON = "ui_template/ui_template_group_cards.ts"
TABS = "B09_Estimation/B09_Estimation_UI_Tabs.ts"
GROUPS = "B09_Estimation/B09_Estimation_UI_Tabs_Groups.ts"
# 기억(b_page_state) 대신 Map · 단추(createButton) 대신 가짜 button · 157-3 원문번호 풍선(raw) 짝
STUBS = r"""
const kept = new Map();
const readState = (name) => (kept.has(name) ? JSON.parse(kept.get(name)) : null);
const writeState = (name, value) => kept.set(name, JSON.stringify(value));
const createButton = (o) => {
const b = document.createElement("button");
b.className = `ui-btn ui-btn--${o.variant}`;
b.textContent = o.label;
if (o.onClick) b.addEventListener("click", o.onClick);
return b;
};
"""
RAW = r"""
const raw = (shown, codes) => (codes && codes !== shown ? `원문번호 ${String(codes)}` : null);
const SOURCES = [];
const openQuotes = () => {};
"""
FAKE_DOM = r"""
class E {
constructor(tag) {
this.tagName = tag.toUpperCase();
this.children = []; this.parent = null; this.dataset = {}; this.attrs = {};
this.cls = new Set(); this.textContent = ""; this.title = ""; this.on = {};
const props = {};
this.style = { props, setProperty: (k, v) => { props[k] = v; } };
const self = this;
this.classList = {
add: (...c) => c.forEach((x) => self.cls.add(x)),
remove: (...c) => c.forEach((x) => self.cls.delete(x)),
contains: (c) => self.cls.has(c),
toggle: (c, force) => {
const on = force === undefined ? !self.cls.has(c) : force;
on ? self.cls.add(c) : self.cls.delete(c);
return on;
},
};
}
get className() { return [...this.cls].join(" "); }
set className(v) { this.cls = new Set(String(v).split(/\s+/).filter(Boolean)); }
append(...xs) { for (const x of xs) { x.parent = this; this.children.push(x); } }
prepend(...xs) { for (const x of xs.reverse()) { x.parent = this; this.children.unshift(x); } }
addEventListener(t, f) { (this.on[t] ??= []).push(f); }
setAttribute(k, v) { this.attrs[k] = String(v); }
scrollIntoView() { this.scrolled = true; }
matches(sel) {
if (sel.startsWith(".")) return this.cls.has(sel.slice(1));
const m = sel.match(/^\[data-([\w-]+)(?:="(.*)")?\]$/);
const k = m ? m[1].replace(/-(\w)/g, (_, c) => c.toUpperCase()) : "";
if (m) return m[2] === undefined ? k in this.dataset : this.dataset[k] === m[2];
return this.tagName === sel.toUpperCase();
}
closest(sel) { for (let e = this; e; e = e.parent) if (e.matches(sel)) return e; return null; }
all(sel, out = []) { for (const c of this.children) { if (c.matches(sel)) out.push(c); c.all(sel, out); } return out; }
querySelector(sel) { return this.all(sel)[0] ?? null; }
contains(x) { for (let e = x; e; e = e.parent) if (e === this) return true; return false; }
get text() { return this.textContent + this.children.map((c) => c.text).join(""); }
click() { const event = { target: this }; for (let e = this; e; e = e.parent) (e.on.click ?? []).forEach((f) => f(event)); }
}
globalThis.document = { createElement: (t) => new E(t) };
globalThis.requestAnimationFrame = (f) => f();
"""
def run(files, js_body, prelude=""):
"""`files` 차례로 이어 붙인 소스(import 지움) + 가짜 DOM + `js_body` → stdout JSON."""
src = "".join(IMPORTS.sub("", (ROOT / f).read_text("utf-8")) for f in files)
result = subprocess.run(
["node", "--experimental-strip-types", "--no-warnings",
"--input-type=module-typescript"],
input=FAKE_DOM + STUBS + prelude + src + js_body,
capture_output=True, text=True, encoding="utf-8", timeout=60,
) # fmt: skip
assert result.returncode == 0, result.stderr
return json.loads(result.stdout)