feat(b09): 설계내역서 「연결 ▾」 칸 단추 · 우클릭과 같은 연결 메뉴 (182-2)

- 비고 앞 「연결」 칸 — 손 항목 줄만 「연결 ▾」 · 연결됨 = 호표 번호 ▾ · 누름 = 그 칸 아래 연결 메뉴
- 메뉴 = 일위대가 호표 · 단가산출 호표 · 단가산출 로직 + 로직 입력값 · 연결 풀기(166-2 고르기 창 재사용)
- 단추 칸 = 이동 안 함 · 묶음 줄 · 자동 줄 = 안 엶
- 시험 test_182 연결 단추(칸 · 메뉴 · 고르기 창 → ref) · node 입력 = 표준입력(명령 줄 길이)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
This commit is contained in:
2026-10-10 11:55:45 +09:00
co-authored by Claude Opus 5.5
parent 8b64d794da
commit 4da01d7435
3 changed files with 105 additions and 6 deletions
+74 -2
View File
@@ -74,10 +74,11 @@ def _rows(sheets):
def _node(source: str) -> str:
# 표준입력으로 줌 — 「-e」 는 윈도 명령 줄 길이(32k)를 넘음
result = subprocess.run(
["node", "--experimental-strip-types", "--no-warnings",
"--input-type=module-typescript", "-e", source],
capture_output=True, text=True, encoding="utf-8", timeout=60,
"--input-type=module-typescript"],
input=source, capture_output=True, text=True, encoding="utf-8", timeout=60,
) # fmt: skip
assert result.returncode == 0, result.stderr
return result.stdout
@@ -228,3 +229,74 @@ def test_우클릭_레벨_메뉴_TS():
user = {"bill": {"rows": [r if r["key"] != "hand:x" else out["indent"] for r in HAND]}}
rows2 = _rows(compute_estimation(INPUTS, user, None, BOOKS))
assert (rows2["hand:x"]["level"], rows2["hand:g1"]["sum"]) == (4, 12300 + 2460 + 1230)
LINK_JS = """
const opened = [];
const picked = {};
globalThis.createMapContextMenu = () => ({
element: { isConnected: true, style: {} },
open: (x, y, items) => opened.push({ x, y, items }),
});
globalThis.requestAnimationFrame = () => 0;
globalThis.createFindList = () => ({
search: { value: "", addEventListener() {}, focus() {} },
hint: {},
root: {},
setRows: (rows) => (picked.rows = rows),
});
globalThis.openModal = (o) => {
picked.title = o.title;
o.mount({ append() {} }, () => (picked.closed = true));
};
let user = __USER__;
const ctx = { sheets: { bill: __BILL__, daega: __DAEGA__ }, payload: {},
get user() { return user; }, setUser: (u) => (user = u) };
const listeners = {};
const root = { addEventListener: (type, fn) => (listeners[type] = fn), contains: () => true };
attachRowMenu(root, () => []);
const cell = { getBoundingClientRect: () => ({ left: 10, bottom: 40 }) };
listeners.click({ target: { closest: () => cell } });
const table = buildBill(ctx);
const at = (key) => table.rows.findIndex((r) => r.key === key);
const name = (x) => (x === "-" ? "-" : x[0]);
const out = {
columns: table.columns.map((c) => c.key).slice(-2),
connect: ["hand:a", "hand:c", "hand:g1", "earth:4"].map((k) => table.rows[at(k)].connect),
link: [table.link(at("hand:a"), "connect"), table.link(at("hand:a"), "name")],
cls: ["sec:1", "hand:g1", "hand:a", "earth:4"].map((k) => table.rowClass(at(k))),
edit: [table.editable(at("hand:g1"), "name"), table.editable(at("hand:g1"), "qty")],
};
table.onCell(at("hand:a"), "connect");
table.onCell(at("hand:g1"), "connect");
table.onCell(at("earth:4"), "connect");
table.onCell(at("hand:c"), "connect");
out.menus = opened.map((m) => [m.x, m.y, m.items.map(name)]);
opened[1].items[0][1](); // hand:c → 일위대가 호표 고르기 창
picked.rows[0].onPick();
out.picked = [picked.title, picked.closed, picked.rows.map((r) => r.tag)];
out.ref = user.bill.rows.find((r) => r.key === "hand:c").ref;
console.log(JSON.stringify(out));
"""
def test_연결_단추_칸_메뉴_고르기_창_TS():
sheets = compute_estimation(INPUTS, USER, None, BOOKS)
body = (
LINK_JS.replace("__USER__", json.dumps(USER, ensure_ascii=False))
.replace("__BILL__", json.dumps(sheets["bill"], ensure_ascii=False))
.replace("__DAEGA__", json.dumps(BOOKS["daega"], ensure_ascii=False))
)
src = _ts(["UI_Tabs", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill"])
out = json.loads(_node(src + body))
assert out["columns"] == ["connect", "note"] # 비고 앞 「연결」 칸
assert out["connect"] == ["대가3 ▾", "연결 ▾", "", ""] # 손 항목 줄만 · 연결됨 = 호표 번호
assert out["link"] == [None, {"tab": "dg", "key": "DG:a"}] # 단추 칸 = 이동 안 함
assert out["cls"] == [
"is-lv1 is-title", "is-lv3 is-group is-hand", "is-lv4 is-hand", "is-lv3"] # fmt: skip
assert out["edit"] == [True, False] # 묶음 줄 = 이름만
picks = ["일위대가 호표", "단가산출 호표", "단가산출 로직(M01 · 새 산근)"]
# 누른 칸 아래(left · bottom) · 묶음 줄 · 자동 줄 = 안 엶 · 연결된 줄 = 연결 풀기 더함
assert out["menus"] == [[10, 40, [*picks, "-", "연결 풀기"]], [10, 40, picks]]
assert out["picked"] == ["일위대가 호표 고르기", True, ["대가3"]] # 166-2 고르기 창 재사용
assert out["ref"] == DG