"""247-1 B08 구조물 집계표 — 시설이 모두 조합에 이은 측점 줄 = 초록 램프. - 칸 class: 화면 소스의 `missing` · `linked` · `cellClass` 를 떼어 공용 표로 그림(Node) 모두 연결 = 초록 · 하나라도 빈 칸 = 노랑 · 시설 없음 · 합계 줄 = 램프 없음 - 호버 글: 초록 = 「모두 연결됨」 - CSS: 초록 = 성공 토큰(밝음 · 어두움) """ import json import re from test_198_1_b08_summary_lamp import PRELUDE, ROOT, UI, _lamp_class, _node DATA = (ROOT / "B08_Quantity" / "B08_Quantity_Summary_Data.ts").read_text(encoding="utf-8") def _fac(fid: str, at: float, *linked: bool) -> dict: slots = [ {"copy": f"{fid}{i}", "group": "칸", "item": None, "linked": v} for i, v in enumerate(linked) ] return { "id": fid, "type_id": "box", "name": "BOX", "facility": None, "chainage_m": at, "slots": slots, } def test_모두_연결_초록_빈_칸_노랑_없음_없음(): grab = lambda pat: re.search(pat, UI, re.S).group(1) # noqa: E731 missing = grab(r"(const missing = stations\.map\(.*?\);\n)") linked = grab(r"(const linked = stations\.map\(.*?\);\n)") cell = grab(r"cellClass: (\(i, key\) =>.*?),\n\s*frozen") line = re.search(r"(const LINKED_CLASS = .*?;)", UI).group(1) facilities = [_fac("a", 0, True, True), _fac("b", 20, True, False), _fac("c", 20, True)] unlinked = [f for f in facilities if not all(s["linked"] for s in f["slots"])] table = json.dumps(str(ROOT / "ui_template" / "ui_template_data_table.ts")) # 두 함수만 떼어 씀(모듈 import 없이) data = json.dumps( "".join( re.search(rf"export (const {name} = .*?;\n)\n", DATA, re.S).group(1) for name in ("unlinkedAt", "unassignedText") ) ) got = _node( PRELUDE + f""" const load = (src) => {{ const box = {{ exports: {{}} }}; new Function("exports", "module", "require", js(src))( box.exports, box, (p) => (p.endsWith(".css") ? {{}} : require(p))); return box.exports; }}; const t = load(fs.readFileSync({table}, "utf8")); const {{ unlinkedAt, unassignedText }} = new Function( js({data}) + "; return {{ unlinkedAt, unassignedText }};")(); const L = (k) => k; const LAMP_CLASS = {json.dumps(_lamp_class())}; {line} const stations = [0, 20, 40]; const unlinked = {json.dumps(unlinked, ensure_ascii=False)}; const pipe = {{ facilities: {json.dumps(facilities, ensure_ascii=False)} }}; {missing}{linked}const cellClass = {cell}; const html = t.renderDataTableHtml({{ columns: [{{ key: "station", label: "측점" }}, {{ key: "c1", label: "조합" }}], rows: stations.map((s) => ({{ station: String(s) }})), total: {{ station: "계", c1: 1 }}, frozen: 1, empty: "", cellClass }}); const rows = html.split("")[1].split(""); console.log(JSON.stringify({{ rows: rows.slice(0, 3), tail: html.split("")[1] ?? "", lamps: html.split(LAMP_CLASS).length - 1 }})); """ ) lamp = _lamp_class() assert f'class="is-frozen is-edge {lamp} is-linked"' in got["rows"][0] # 0 = 모두 연결 assert f'class="is-frozen is-edge {lamp}"' in got["rows"][1] # 20 = 빈 칸 하나 = 노랑 assert lamp not in got["rows"][2] # 40 = 시설 없음 assert got["lamps"] == 2 # 합계 줄 없음 def test_초록_호버_모두_연결됨(): hover = re.search( r"attachHoverInfo\(\n\s*table\.root,\n\s*(\(cell\) => \{.*?\n\s*\}),\n", UI, re.S ) got = _node( PRELUDE + f""" const L = (k) => ({{ B08_Quantity_Summary_LampTip: "구조물 조합 미연결", B08_Quantity_Summary_LampOk: "모두 연결됨" }})[k]; const missing = ["", "BOX: 칸"]; const text = eval(js("(" + {json.dumps(hover.group(1))} + ")")); const cell = (i) => ({{ closest: () => ({{ dataset: {{ i: String(i) }} }}) }}); console.log(JSON.stringify([text(cell(0)), text(cell(1))])); """ ) assert got == ["모두 연결됨", "구조물 조합 미연결 — BOX: 칸"] locale = (ROOT / "ui_template" / "ui_template_locale_b4.ts").read_text(encoding="utf-8") assert 'B08_Quantity_Summary_LampOk: ["모두 연결됨",' in locale def test_초록_성공_토큰(): css = (ROOT / "B08_Quantity" / "B08_Quantity_UI_Page.css").read_text(encoding="utf-8") m = re.search(r"td\.b08-summary__lamp\.is-linked::before\s*\{([^}]*)\}", css) assert m and re.search(r"background:\s*var\(--color-success\)", m.group(1)) theme = (ROOT / "ui_template" / "ui_template_theme.css").read_text(encoding="utf-8") assert len(re.findall(r"--color-success:\s*#", theme)) >= 2 # 밝음 · 어두움