- 설계내역서 밖 탭도 이동해 온 줄 = 같은 빨강 상자(줄 누름 · 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
131 lines
5.6 KiB
Python
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
|