"""242-1 B09 총괄 → 설계내역서 빨강 = 그 줄 + 하위 전부 · 고름 표시와 한때 하나. - 이동해 온 줄(Shell show focus) = jumpTo — 범위 = 그 줄 + 뒤 줄 중 레벨이 더 깊은 연속 줄 · 줄 class = is-pick(첫 줄 is-pick-top · 끝 줄 is-pick-end) · 바깥 테두리 한 벌(181-2 CSS) - 오면 고른 줄(is-focus) 풂 · 공용 표 focusRow(at, false) = 스크롤만(공용 강조 안 붙임) - 설계내역서에서 줄 누름(범위 안 · 밖) · 방향키 · Esc · 끌어 놓음 = 빨강 풂 · 누른 줄만 고름 상자 - 표 · 문서 = 가짜 DOM(200-2 시험 틀) · 줄 class = Shell 과 같은 곁값(pickedClass ?? pickClass) """ import json import re import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) from test_184_3_b09_bill_menu import ROWS, _node, _ts # noqa: E402 from test_200_2_b09_bill_level_bar import BAR_JS, SRC # noqa: E402 ROOT = Path(__file__).resolve().parents[2] B09 = ROOT / "B09_Estimation" GROUPS = (B09 / "B09_Estimation_UI_Tabs_Groups.ts").read_text("utf-8") PICK = re.search(r"export function pickClass\(.*?\n}\n", GROUPS, re.DOTALL).group(0) SETUP = ( "let focusAt = null;\n" + BAR_JS.split("const out = {};")[0] # 가짜 DOM 보강 — class 여럿 지움 · tr. 고르기 .replace( "remove: (c) => s.delete(c) }", "remove: (...c) => c.forEach((x) => s.delete(x)) }", ) .replace( 'querySelectorAll() { return this.all().filter((n) => n.tagName === "TR" && n.cls.has("is-focus")); }', "querySelectorAll(sel) { const c = sel.match(/tr\\.([\\w-]+)/)[1];" ' return this.all().filter((n) => n.tagName === "TR" && n.cls.has(c)); }', ) # Shell 차례 — jumpTo → 줄 그림(줄 class 곁값) → levelBar .replace( ' root = new FakeEl("div");', " jumpTo(table.rows, focusAt ? table.rows.findIndex((r) => r.key === focusAt) : -1);\n" ' root = new FakeEl("div");', ) .replace( "td.append(input); tr.append(td); root.append(tr);", "td.append(input); tr.append(td); root.append(tr);" " const c = pickedClass(r) ?? pickClass(jumpRange(), i); if (c) tr.className = c;", ) ) for patch in ["remove: (...c)", "querySelectorAll(sel)", "jumpTo(table.rows", "jumpRange(), i)"]: assert patch in SETUP, patch # 200-2 틀이 바뀌면 여기서 멈춤 RUN_JS = """ const marks = () => Object.fromEntries(root.kids.flatMap((tr, i) => { const c = [...tr.cls].filter((x) => x.startsWith("is-")).sort().join(" "); return c ? [[table.rows[i].key, c]] : []; })); const out = {}; const go = (k) => { focusAt = k; user = {}; draw(); focusAt = null; }; go("sec:1.2"); out.range = [jumpRange(), marks()]; out.ends = ["sec:1", "earth:4", "hand:g", "fee"].map((k) => subtreeEnd(table.rows, at(k))); pick("earth:5"); out.inside = [jumpRange(), marks()]; go(null); pick("earth:4"); out.before = marks(); go("sec:1"); out.came = [jumpRange(), marks(), table.rows.map((r) => pickedClass(r)).filter(Boolean)]; pick("mat:M1"); out.outside = [jumpRange(), marks()]; go(null); out.redraw = marks(); go("sec:1.2"); root.focus(); out.arrow = [key("ArrowDown"), jumpRange(), marks(), order()]; go("sec:4"); root.focus(); out.esc = [key("Escape"), jumpRange(), marks()]; go("sec:4"); root.focus(); out.other = [key("a"), jumpRange()]; console.log(JSON.stringify(out)); """ PICKED = {"sec:1.2": "is-pick is-pick-top", "earth:4": "is-pick", "earth:5": "is-pick", "hand:g": "is-pick", "hand:a": "is-pick", "hand:b": "is-pick is-pick-end"} # fmt: skip def _run(): body = (SETUP + RUN_JS).replace("__ROWS__", json.dumps(ROWS, ensure_ascii=False)) return json.loads(_node(_ts(SRC) + PICK + body)) def test_범위_그_줄_자손_전부_바깥_테두리_한_벌(): out = _run() assert out["range"] == [[1, 6], PICKED] # sec:1 = 레벨 1 아래 전부 · 항목 = 자기만 · 묶음 = 손 줄까지 · 끝 줄 = 자기 assert out["ends"] == [6, 2, 5, 11] def test_표시_하나_총괄에서_오면_고름_풂(): out = _run() assert out["before"] == {"earth:4": "is-focus"} rng, marks, picked = out["came"] assert rng == [0, 6] and picked == [] assert all("is-focus" not in c for c in marks.values()) assert marks["sec:1"] == "is-pick is-pick-top" and marks["hand:b"] == "is-pick is-pick-end" def test_다른_줄_누름_빨강_풂_그_줄만_고름(): out = _run() assert out["inside"] == [None, {"earth:5": "is-focus"}] # 범위 안 줄 assert out["outside"] == [None, {"mat:M1": "is-focus"}] # 범위 밖 줄 assert out["redraw"] == {"mat:M1": "is-focus"} # 다시 그려도 고름만 def test_방향키_Esc_빨강만_풂_다른_키_그대로(): out = _run() assert out["arrow"] == [False, None, {}, []] # 줄 안 옮김(고른 줄 없음) assert out["esc"] == [False, None, {}] assert out["other"] == [False, [7, 8]] def test_Shell_배선_공용_표_스크롤만(): shell = (B09 / "B09_Estimation_UI_Shell.ts").read_text("utf-8") assert "if (bill) jumpTo(bill.rows, at);" in shell assert "pickedClass(bill.rows[i]) ?? pickClass(jumpRange(), i)" in shell assert "table.focusRow(at, false)" in shell # 257-2 모든 탭 빨강 = 스크롤만 common = (ROOT / "ui_template" / "ui_template_data_table.ts").read_text("utf-8") assert "focusRow: (index: number, mark?: boolean) => void;" in common assert "state.focus = mark ? index : null;" in common level = (B09 / "B09_Estimation_UI_Tabs_Bill_Level.ts").read_text("utf-8") assert 'tr.classList.remove("is-pick", "is-pick-top", "is-pick-end")' in level