Files
Aislo/resources/tester/test_242_1_b09_bill_jump_range.py
T
eomsangdonandClaude Sonnet 5.5 f468a3a3fa feat(b09): 이동해 온 줄 빨강 모든 탭 · 비고 빈 줄 지움 · 총공사비 아래 고정 줄 (257-2)
- 설계내역서 밖 탭도 이동해 온 줄 = 같은 빨강 상자(줄 누름 · Esc · 방향키로 풂)
- 설계내역서 비고 「빈 줄 N」 지움(Py · TS 짝 · 「(제외금액)」 둠)
- 총괄설계내역서 · 공사원가계산서 총공사비 = 템플릿 아래 고정 줄(줄 이동 누름 빠짐)
- 시험 test_257_2 · 기대값 고침(145_3 · 145_4 · 182 · 185_1 · 242_1)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SE9MxeXTu2uaDZ55BJowAY
2026-10-11 14:05:02 +09:00

131 lines
5.6 KiB
Python

"""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.<class> 고르기
.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