Files
Aislo/resources/tester/test_200_2_b09_bill_level_bar.py
T

203 lines
9.4 KiB
Python

"""200-2 B09 설계내역서 레벨 글 · 레벨 도구 줄 — 글 칸 왼쪽 맞춤 · 레벨 = 공종 · 명칭 칸 padding 들여쓰기 ·
글꼴 = 공용 표 레벨 글꼴(228-1 · 레벨 · 묶음 따라) · 단추 넷(고른 줄 · 못 하면 disabled) ·
키 셋(Tab · Shift+Tab · Alt+↑↓) · 편집 중엔 키 안 먹음.
- 단추 · 키 = 우클릭 메뉴(billMenu) 같은 항목을 부름 — 줄 꼴 · 저장 꼴은 184-3 시험 그대로
- 표 · 문서 = 가짜 DOM(줄 tr · 글 칸 input) · 다시 그림 = buildBill + levelBar 다시
"""
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
ROOT = Path(__file__).resolve().parents[2]
B09 = ROOT / "B09_Estimation"
BAR_JS = """
class FakeEl {
constructor(tag) {
this.tagName = tag.toUpperCase(); this.kids = []; this.dataset = {}; this.cls = new Set();
this.on = {}; this.parent = null; this.isConnected = true; this.disabled = false; this.textContent = "";
}
set className(v) { this.cls = new Set(v.split(" ").filter(Boolean)); }
get classList() { const s = this.cls; return { add: (c) => s.add(c), remove: (c) => s.delete(c) }; }
append(...kids) { for (const k of kids) { k.parent = this; this.kids.push(k); } }
addEventListener(type, f) { (this.on[type] ??= []).push(f); }
fire(type, ev) { for (const f of this.on[type] ?? []) f(ev); }
contains(x) { for (let n = x; n; n = n.parent) if (n === this) return true; return false; }
all() { return this.kids.flatMap((k) => [k, ...k.all()]); }
closest() { for (let n = this; n; n = n.parent) if (n.tagName === "TR") return n; return null; }
querySelectorAll() { return this.all().filter((n) => n.tagName === "TR" && n.cls.has("is-focus")); }
querySelector(sel) { const i = sel.match(/data-i="(-?\\d+)"/)[1]; return this.all().find((n) => n.dataset.i === i) ?? null; }
focus() { doc.activeElement = this; }
blur() { doc.activeElement = doc.body; }
}
const doc = new FakeEl("#document");
doc.body = new FakeEl("body");
doc.activeElement = doc.body;
doc.createElement = (t) => new FakeEl(t);
globalThis.document = doc;
const hovers = [];
function createButton(o) {
const b = new FakeEl("button");
b.label = o.label; b.variant = o.variant;
b.addEventListener("click", o.onClick);
return b;
}
function attachHoverInfo(el, text) { hovers.push([el.textContent, text]); }
let user = {};
let table, root, bar;
const draw = () => {
table = buildBill({ sheets: { bill: { rows: __ROWS__, totals: null } }, payload: {},
get user() { return user; }, setUser: (u) => (user = u) });
if (root) root.isConnected = false;
if (root?.contains(doc.activeElement)) doc.activeElement = doc.body; // 지운 요소 초점 = body
root = new FakeEl("div");
table.rows.forEach((r, i) => {
const tr = new FakeEl("tr"); tr.dataset.i = String(i);
const td = new FakeEl("td"); const input = new FakeEl("input");
td.append(input); tr.append(td); root.append(tr);
});
bar = levelBar(root, table);
};
const at = (key) => table.rows.findIndex((r) => r.key === key);
const tr = (key) => root.kids[at(key)];
const pick = (key) => root.fire("click", { target: tr(key).kids[0] });
const buttons = () => bar.tools;
const off = () => buttons().map((b) => b.disabled);
const order = () => (user.bill?.rows ?? []).map((e) => [e.key, e.parent ?? null]);
const key = (k, more = {}) => {
let stopped = false;
doc.fire("keydown", { key: k, shiftKey: false, altKey: false, ...more, preventDefault: () => (stopped = true) });
return stopped;
};
const out = {};
draw();
out.labels = buttons().map((b) => [b.label, b.dataset.b09Level, b.variant]);
out.guide = bar.guide.textContent;
out.hover = hovers.length;
out.none = off();
pick("earth:4");
out.earth4 = off();
out.focus = [tr("earth:4").cls.has("is-focus"), pickedClass(table.rows[at("earth:4")]),
pickedClass(table.rows[at("earth:5")])];
buttons()[3].fire("click");
out.down = order();
user = {}; draw();
out.kept = [off(), tr("earth:4").cls.has("is-focus"), pickedClass(table.rows[at("earth:4")])];
root.focus();
out.tabStop = key("Tab");
out.tabNoop = order();
out.shiftTab = [key("Tab", { shiftKey: true }), order()];
user = {}; draw();
pick("earth:5");
root.focus();
out.altUp = [key("ArrowUp", { altKey: true }), order()];
user = {}; draw(); pick("earth:4");
out.altDown = [key("ArrowDown", { altKey: true }), order()];
user = {}; draw(); pick("earth:4");
doc.activeElement = tr("earth:4").kids[0].kids[0];
out.editing = [key("Tab", { shiftKey: true }), key("ArrowDown", { altKey: true }), order()];
out.esc = [key("Escape"), doc.activeElement === root];
out.afterEsc = [key("Tab", { shiftKey: true }), order().length > 0];
user = {}; draw(); pick("earth:4");
doc.activeElement = new FakeEl("input");
out.outside = [key("Tab", { shiftKey: true }), order()];
root.focus();
key("Escape");
out.unpick = [off(), tr("earth:4").cls.has("is-focus"), key("Tab")];
out.keyAct = [
keyAct({ key: "Tab", shiftKey: false, altKey: false }, false),
keyAct({ key: "Tab", shiftKey: true, altKey: false }, false),
keyAct({ key: "ArrowUp", shiftKey: false, altKey: true }, false),
keyAct({ key: "ArrowDown", shiftKey: false, altKey: true }, false),
keyAct({ key: "Tab", shiftKey: false, altKey: false }, true),
keyAct({ key: "ArrowUp", shiftKey: false, altKey: false }, false),
keyAct({ key: "Tab", shiftKey: false, altKey: false, ctrlKey: true }, false),
];
console.log(JSON.stringify(out));
"""
SRC = ["UI_Tabs", "UI_Tabs_Bill_Link", "UI_Tabs_Bill_Edit", "UI_Tabs_Bill", "UI_Tabs_Bill_Drag",
"UI_Tabs_Bill_Level"] # fmt: skip
IN, OUT, UP, DOWN = "들여쓰기 →", "내어쓰기 ←", "위로 ↑", "아래로 ↓"
def _bar():
body = BAR_JS.replace("__ROWS__", json.dumps(ROWS, ensure_ascii=False))
return json.loads(_node(_ts(SRC) + body))
def _keys(order):
return [k for k, _ in order]
def test_단추_넷_고른_줄_못_하면_흐림():
out = _bar()
assert out["labels"] == [["→ 들여쓰기", IN, "ghost"], ["← 내어쓰기", OUT, "ghost"],
["↑ 위로", UP, "ghost"], ["↓ 아래로", DOWN, "ghost"]] # fmt: skip
assert "Tab" in out["guide"] and "Shift+Tab" in out["guide"] and "Alt+↑↓" in out["guide"]
assert out["hover"] == 1 # 안내 한 줄 = 공용 호버로 자세히
assert out["none"] == [True] * 4 # 고른 줄 없음 = 모두 흐림
# earth:4 = 1.1 첫 줄 — 들여(위 형제 없음) · 위로 = 못 함 · 내어 · 아래로 = 됨(184-3 메뉴와 같음)
assert out["earth4"] == [True, False, True, False]
assert out["focus"] == [True, "is-focus", None]
# [↓ 아래로] = 메뉴 「아래로 ↓」 와 같은 결과
keys = _keys(out["down"])
assert keys.index("earth:5") + 1 == keys.index("earth:4")
# 다시 그려도 고른 줄 · 단추 상태 남음
assert out["kept"] == [[True, False, True, False], True, "is-focus"]
def test_키_셋_편집_중엔_안_먹음():
out = _bar()
assert out["tabStop"] is True and out["tabNoop"] == [] # 못 하는 들여쓰기 = 바뀜 없음
stopped, order = out["shiftTab"]
assert stopped and ("earth:4", "sec:1") in [tuple(x) for x in order] # 내어쓰기
for name in ("altUp", "altDown"):
stopped, order = out[name]
keys = _keys(order)
assert stopped and keys.index("earth:5") + 1 == keys.index("earth:4"), name
# 글 칸 편집 중 = 키 안 먹음(기본 동작 그대로) · Esc = 편집 끝(표에 초점) → 키 먹음
assert out["editing"] == [False, False, []]
assert out["esc"] == [False, True]
assert out["afterEsc"] == [True, True]
# 초점이 표 · 도구 줄 밖 = 안 먹음 · Esc(편집 아님) = 고름 풂 → 흐림 · Tab 은 기본 동작
assert out["outside"] == [False, []]
assert out["unpick"] == [[True] * 4, False, False]
# 그냥 ↑ = 옮기기(204 방향키 · test_204_1)
assert out["keyAct"] == [IN, OUT, UP, DOWN, None, UP, None]
def test_왼쪽_맞춤_레벨_들여쓰기_글꼴_공용_표():
css = (B09 / "B09_Estimation_UI_Page.css").read_text("utf-8")
bill = '.b09-estimation [data-b09-table="bill"]'
# 레벨 들여쓰기 · 글꼴 = 공용 표 level 옵션(test_201_1 · test_228_1) · 페이지 CSS 에 따로 두지 않음
assert "is-lv" not in css and "font-size: 13px" not in css
common = (ROOT / "ui_template" / "ui_template_data_table.css").read_text("utf-8")
assert "td.ui-data-table__lv.is-lv1.is-node" in common
src = (B09 / "B09_Estimation_UI_Tabs_Bill.ts").read_text("utf-8")
assert "of: (i) => rows[i]?.level as number | undefined," in src
assert 'group: (i) => rows[i]?.kind !== "item",' in src
# 글 칸 왼쪽 맞춤(수량만 오른쪽)
assert re.search(
re.escape(f'{bill} .ui-data-table__input:not([data-k="qty"]) {{') + r"\s*text-align: left;",
css,
)
assert ".b09-estimation__level" not in css # 229-1 표 위 도구 줄 없앰
def test_셸_배선():
src = (B09 / "B09_Estimation_UI_Shell.ts").read_text("utf-8")
assert "makeTable(bill ? (i) => pickedClass(bill.rows[i]) : undefined)" in src
assert "if (level) bars.setTools(level.tools);" in src # 229-1 단추 = 보조 탭 줄
assert "panel.replaceChildren(table.root, ...(level ? [level.guide] : [])" in src
lvl = (B09 / "B09_Estimation_UI_Tabs_Bill_Level.ts").read_text("utf-8")
assert "table.menu(index)" in lvl # 단추 · 키 = 우클릭 메뉴 같은 함수
assert "b09-estimation__level" not in lvl