fix(b09): 묶음 탭 이동 빨강 = 설계내역서 규칙 — 빨강 하나 · 줄 · 카드 누름 · 방향키 · Esc = 빨강 풂 · 누른 줄만 고름 (248-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SE9MxeXTu2uaDZ55BJowAY
This commit is contained in:
@@ -66,6 +66,9 @@ class E {
|
||||
setAttribute(k, v) { this.attrs[k] = String(v); }
|
||||
scrollIntoView() { this.scrolled = true; }
|
||||
matches(sel) {
|
||||
if (sel.includes(",")) return sel.split(",").some((s) => this.matches(s.trim()));
|
||||
const tag = sel.match(/^([a-z]+)([.[].*)$/); // tr.is-pick · tr[data-i="3"]
|
||||
if (tag) return this.matches(tag[1]) && this.matches(tag[2]);
|
||||
if (sel.startsWith(".")) return this.cls.has(sel.slice(1));
|
||||
const m = sel.match(/^\[data-([\w-]+)(?:="(.*)")?\]$/);
|
||||
const k = m ? m[1].replace(/-(\w)/g, (_, c) => c.toUpperCase()) : "";
|
||||
@@ -75,11 +78,15 @@ class E {
|
||||
closest(sel) { for (let e = this; e; e = e.parent) if (e.matches(sel)) return e; return null; }
|
||||
all(sel, out = []) { for (const c of this.children) { if (c.matches(sel)) out.push(c); c.all(sel, out); } return out; }
|
||||
querySelector(sel) { return this.all(sel)[0] ?? null; }
|
||||
querySelectorAll(sel) { return this.all(sel); }
|
||||
get isConnected() { return true; }
|
||||
contains(x) { for (let e = x; e; e = e.parent) if (e === this) return true; return false; }
|
||||
get text() { return this.textContent + this.children.map((c) => c.text).join(""); }
|
||||
click() { const event = { target: this }; for (let e = this; e; e = e.parent) (e.on.click ?? []).forEach((f) => f(event)); }
|
||||
}
|
||||
globalThis.document = { createElement: (t) => new E(t) };
|
||||
const docKeys = []; // 248-1 문서 keydown — key(이름, 대상) 로 부름
|
||||
globalThis.document = { createElement: (t) => new E(t), addEventListener: (t, f) => t === "keydown" && docKeys.push(f) };
|
||||
const key = (k, target = null) => docKeys.forEach((f) => f({ key: k, target }));
|
||||
globalThis.requestAnimationFrame = (f) => f();
|
||||
"""
|
||||
|
||||
|
||||
@@ -122,9 +122,9 @@ def test_묶음_많아도_접힘_없음_고른_박스_붉은_외곽선():
|
||||
" foot: cards.every((c) => c.querySelector('.ui-group-card__foot')),"
|
||||
" focus: [focused.classList.contains('is-focus'), cards[4].scrolled === true] }));"
|
||||
)
|
||||
# 이동해 온 줄(5번째 묶음 줄) = 그 박스만 붉은 외곽선 · 그 줄 강조 · 박스 스크롤
|
||||
# 이동해 온 줄(5번째 묶음 줄) = 그 박스만 붉은 외곽선 · 박스 스크롤(248-1 줄 강조 안 붙임)
|
||||
assert out["first"] == [False] * 4 + [True] + [False] * 8
|
||||
assert out["focus"] == [True, True]
|
||||
assert out["focus"] == [False, True]
|
||||
assert out["closed"] == [False] * 13 # 13 묶음도 접힘 없음
|
||||
assert out["picks"] == [10] # 머리 누름 = 이동(표 줄 번호 그대로)
|
||||
assert out["buttons"] == ["1", "2", "3", "4"] # 모두 펼침 · 접기 단추 없음
|
||||
|
||||
@@ -149,10 +149,10 @@ def test_화면_보기_바꿈_누름_이동():
|
||||
"\nconst [view] = renderGroupPanel(o);"
|
||||
"\nconst btn = calls[0].find((e) => e.dataset.b09GroupView);"
|
||||
"\nconst label = btn.textContent;"
|
||||
"\nview.querySelector('[data-at=\"2\"]').click();"
|
||||
"\nview.querySelector('[data-at=\"0\"]').click();"
|
||||
"\nconst lit = view.querySelector('[data-at=\"3\"]').classList.contains('is-focus');"
|
||||
"\nconst box = view.querySelector('.ui-group-card').classList.contains('is-pick');"
|
||||
"\nview.querySelector('[data-at=\"2\"]').click();"
|
||||
"\nview.querySelector('[data-at=\"0\"]').click();"
|
||||
"\nbtn.click();"
|
||||
"\nconst [view2] = renderGroupPanel(o);"
|
||||
"\nconsole.log(JSON.stringify({ label, table: view.dataset.b09Table, gone, lit, box, redraws,"
|
||||
@@ -162,7 +162,7 @@ def test_화면_보기_바꿈_누름_이동():
|
||||
)
|
||||
assert out["label"] == "표로 보기" and out["table"] == "dg" # 기본 = 카드
|
||||
assert out["gone"] == [{"tab": "labor", "key": "L00005"}, None] # 줄 누름 = 표와 같은 이동
|
||||
assert out["lit"] is True # 이동해 온 줄(키 DG1|1 = 표 3번째 줄) 강조
|
||||
assert out["lit"] is False # 248-1 이동해 온 줄 = 빨강 하나(줄 강조 안 붙임)
|
||||
assert out["box"] is True # 그 호표 박스 = 붉은 외곽선(181-2)
|
||||
assert out["redraws"] == 1
|
||||
assert out["label2"] == "박스보기" and out["tag2"] == "TABLE" and out["focused"] == [3]
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
"""248-1 B09 묶음 탭(일위대가표 · 중기사용료 · 단가산출근거) 다른 탭 이동 빨강 = 설계내역서(242-1) 규칙.
|
||||
|
||||
- 이동해 온 호표 = 빨강 하나 — 카드 = 박스 is-pick 만(줄 is-focus 안 붙음) · 표 = 구간 is-pick 만 ·
|
||||
공용 focusRow(at, false) = 스크롤만
|
||||
- 줄 · 카드 누름 · 방향키 · Esc = 빨강 풂 · 누른 줄만 고름(is-focus) · Esc = 고름도 풂 · 다시 그려도 그대로
|
||||
- 글 칸 안 키 · 다른 키 = 그대로
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from resources.tester.helper_b09_group_cards import COMMON, GROUPS, RAW, TABS, run # noqa: E402
|
||||
from resources.tester.test_171_3_b09_group_tabs import DG # noqa: E402
|
||||
from resources.tester.test_181_2_b09_box_pick import ROWS # noqa: E402
|
||||
|
||||
LABOR = "B09_Estimation/B09_Estimation_UI_Tabs_Labor.ts"
|
||||
INPUT = "{ closest: (s) => (s.includes('input') ? {} : null) }" # 글 칸 안 키
|
||||
|
||||
|
||||
def test_카드_빨강_하나_누름_키_풂():
|
||||
out = run(
|
||||
[COMMON, TABS, LABOR, GROUPS],
|
||||
f"\nconst spec = buildDaegaBlocks({{ sheets: {{ daega_blocks: [{json.dumps(DG, ensure_ascii=False)}] }} }});"
|
||||
"\nconst o = { tab: 'dg', spec, hint: null, focus: 'DG1|1', goto: () => {}, redraw: () => {},"
|
||||
" setTools: () => {}, makeTable: () => null };"
|
||||
"\nconst marks = (v) => [v.all('.is-pick').length, v.all('.is-focus').map((e) => e.dataset.at ?? 'box')];"
|
||||
"\nconst out = {};"
|
||||
"\nlet [v] = renderGroupPanel(o); out.came = marks(v);"
|
||||
"\nv.querySelector('[data-at=\"2\"]').click(); out.line = marks(v);"
|
||||
"\n[v] = renderGroupPanel(o); v.querySelector('.ui-group-card').click(); out.box = marks(v);"
|
||||
"\n[v] = renderGroupPanel(o); key('a'); key('ArrowDown', "
|
||||
+ INPUT
|
||||
+ "); out.kept = marks(v);"
|
||||
"\nkey('ArrowDown'); out.arrow = marks(v);"
|
||||
"\n[v] = renderGroupPanel(o); v.querySelector('[data-at=\"2\"]').click(); key('Escape'); out.esc = marks(v);"
|
||||
"\nconsole.log(JSON.stringify(out));",
|
||||
RAW,
|
||||
)
|
||||
assert out["came"] == [1, []] # 박스 빨강만 · 줄 강조 없음
|
||||
assert out["line"] == [0, ["2"]] # 누른 줄만 고름
|
||||
assert out["box"] == [0, []] # 카드 빈 곳 누름 = 빨강만 풂
|
||||
assert out["kept"] == [1, []] # 다른 키 · 글 칸 안 = 그대로
|
||||
assert out["arrow"] == [0, []]
|
||||
assert out["esc"] == [0, []] # Esc = 고름도 풂
|
||||
|
||||
|
||||
def test_표_빨강_하나_누름_키_풂():
|
||||
out = run(
|
||||
[COMMON, TABS, GROUPS],
|
||||
f"\nconst spec = {{ columns: [{{ key: 'key' }}], rows: {json.dumps(ROWS)} }};"
|
||||
"\nlet extra = null; let root = null; const scrolled = [];"
|
||||
"\nconst makeTable = (e) => { extra = e; root = document.createElement('table');"
|
||||
" spec.rows.forEach((_, i) => { const tr = document.createElement('tr'); tr.dataset.i = String(i);"
|
||||
" tr.className = e(i) ?? ''; tr.append(document.createElement('td')); root.append(tr); });"
|
||||
" return { root, focusRow: (i, mark) => scrolled.push([i, mark]) }; };"
|
||||
"\nconst o = { tab: 'dg', spec, hint: null, focus: 'B|1', goto: () => {}, redraw: () => {},"
|
||||
" setTools: () => {}, makeTable };"
|
||||
"\nchosen.set('dg', 'table');"
|
||||
"\nconst marks = () => [root.children.map((tr) => tr.className || null),"
|
||||
" spec.rows.map((_, i) => extra(i))];"
|
||||
"\nconst out = {};"
|
||||
"\nrenderGroupPanel(o); out.came = marks(); out.scrolled = [...scrolled];"
|
||||
"\nroot.children[1].children[0].click(); out.click = marks();"
|
||||
"\nrenderGroupPanel(o); key('Enter'); key('ArrowUp', " + INPUT + "); out.kept = marks()[0];"
|
||||
"\nkey('ArrowUp'); out.arrow = marks();"
|
||||
"\nrenderGroupPanel(o); root.children[5].click(); key('Escape'); out.esc = marks();"
|
||||
"\nconsole.log(JSON.stringify(out));",
|
||||
)
|
||||
red = [None] * 3 + ["is-pick is-pick-top", "is-pick", "is-pick", "is-pick is-pick-end", None]
|
||||
none = [None] * 8
|
||||
assert out["came"] == [red, red] # 구간 빨강만
|
||||
assert out["scrolled"] == [[4, False]] # 스크롤만(공용 강조 안 붙임)
|
||||
focus1 = [None, "is-focus"] + [None] * 6
|
||||
assert out["click"] == [focus1, focus1] # 누른 줄만 · 다시 그려도
|
||||
assert out["kept"] == red
|
||||
assert out["arrow"] == [none, none]
|
||||
assert out["esc"] == [none, none]
|
||||
|
||||
|
||||
def test_배선():
|
||||
groups = (ROOT / GROUPS).read_text("utf-8")
|
||||
assert "table.focusRow(at, false)" in groups
|
||||
common = (ROOT / COMMON).read_text("utf-8")
|
||||
assert 'el?.classList.add("is-focus")' not in common
|
||||
assert "unpickCards(grid, true);" in common
|
||||
Reference in New Issue
Block a user