feat(b09): 좌측 하단 고정 줄 — [박스보기|호표보기] 한 설정(묶음 탭 셋 · 옛 탭별 값 하나로 · 탭 줄 토글 걷음) · [하이퍼링크|팝업] 스위치(pref global · 기본 팝업) · DRAFT.actions 자리 · [?][저장][내역 초기화] 그대로 (303-1 · 304-1)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
This commit is contained in:
2026-10-13 07:56:53 +09:00
co-authored by Claude Opus 5.5
parent bc92038071
commit 55e565f3bf
17 changed files with 460 additions and 88 deletions
@@ -62,20 +62,19 @@ def test_157_2_두_보기_같은_값():
def test_157_2_단추_바꿈():
out = _node(
"\nconsole.log(JSON.stringify([viewLabel('table'), viewLabel('cards'),"
" flipView('table'), flipView(flipView('table'))]));"
)
assert out == ["박스보기", "표로 보기", "cards", "table"]
out = _node("\nconsole.log(JSON.stringify([viewLabel('table'), viewLabel('cards')]));")
assert out == ["호표보기", "박스보기"] # 304-1 단추 글 = 그 보기 이름
def test_157_2_기억_실패_때_카드():
out = _node(
"\nconst a = [readGroupView('sg', null), readGroupView('sg', { sg: 'x' }),"
" readGroupView('sg', { dg: 'table' }), readGroupView('sg', { sg: 'table' })];"
"\nconst a = [readGroupView(null), readGroupView({ sg: 'x' }), readGroupView({ dg: 'table' }),"
" readGroupView({ sg: 'table' }), readGroupView('table'), readGroupView('x'),"
" readGroupView({ dg: 'cards', sg: 'table' })];"
"\nconsole.log(JSON.stringify(a));"
)
assert out == ["cards", "cards", "cards", "table"]
# 304-1 한 값 · 옛 탭별 값 = dg 먼저 · 없으면 다른 탭 · 모르는 값 = 카드
assert out == ["cards", "cards", "table", "table", "table", "cards", "cards"]
def test_157_2_화면_잇기():
+14 -15
View File
@@ -118,27 +118,27 @@ def test_단가산출근거_카드():
assert out["links"] == [None, {"tab": "labor", "key": "LB2"}]
def test_보기_기억_탭마다():
def test_보기_한_값_열_수_탭마다():
out = run(
[COMMON, TABS, GROUPS],
"\nconst a = [readGroupView('dg'), readGroupCols('dg')];"
"\nrememberGroup('b09-group-view', 'dg', 'table');"
"\nconst a = [readGroupView(), readGroupCols('dg')];"
"\nkept.set('b09-group-view', JSON.stringify({ machine_blocks: 'table' }));" # 옛 탭별 값
"\nrememberGroup('b09-group-cols', 'sg', 3);"
"\nconst b = [readGroupView('dg'), readGroupView('sg'), readGroupView('machine_blocks'),"
"\nconst b = [groupView(), readGroupView(), readGroupView(),"
" readGroupCols('sg'), readGroupCols('dg'), readGroupCols('dg', { dg: 9 })];"
"\nconsole.log(JSON.stringify({ a, b, kept: Object.fromEntries(kept),"
" tabs: GROUP_TABS, other: ['bill', 'sum_material', 'sum_labor', 'sum_expense',"
" 'sum_machine', 'dg_list'].map(isGroupTab) }));",
)
assert out["a"] == ["cards", 2] # 기본 = 카드 · 가로 2
assert out["b"] == ["table", "cards", "cards", 3, 2, 2]
assert json.loads(out["kept"]["b09-group-view"]) == {"dg": "table"}
assert out["b"] == ["table", "table", "table", 3, 2, 2] # 304-1 셋 탭 한 값
assert json.loads(out["kept"]["b09-group-view"]) == "table" # 첫 읽기에 옛 값 → 한 값
assert json.loads(out["kept"]["b09-group-cols"]) == {"sg": 3}
assert out["tabs"] == ["dg", "machine_blocks", "sg"]
assert out["other"] == [False] * 6 # 집계표 넷 · 설계내역서 · 목록표 = 대상 밖
def test_화면_보기_바꿈_누름_이동():
def test_화면_보기_고름_누름_이동():
out = run(
[COMMON, TABS, f"{B09}Labor.ts", GROUPS],
f"\nconst spec = buildDaegaBlocks({{ sheets: {{ daega_blocks: [{json.dumps(DG, ensure_ascii=False)}] }} }});"
@@ -147,26 +147,25 @@ def test_화면_보기_바꿈_누름_이동():
" redraw: () => redraws++, setTools: (t) => calls.push(t), makeTable: () => ({ root: document.createElement('table'),"
" focusRow: (i) => focused.push(i) }) };"
"\nconst [view] = renderGroupPanel(o);"
"\nconst btn = calls[0].find((e) => e.dataset.b09GroupView);"
"\nconst label = btn.textContent;"
"\nconst label = calls.flat().filter((e) => e.dataset.b09GroupView).length;"
"\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 before = calls.length; chooseGroupView('table');"
"\nconst [view2] = renderGroupPanel(o);"
"\nconsole.log(JSON.stringify({ label, table: view.dataset.b09Table, gone, lit, box, redraws,"
" label2: calls[1].find((e) => e.dataset.b09GroupView).textContent, tag2: view2.tagName, focused,"
" tools2: calls.length - before, tag2: view2.tagName, focused,"
" kept: Object.fromEntries(kept) }));",
RAW,
)
assert out["label"] == "표로 보기" and out["table"] == "dg" # 기본 = 카드
assert out["label"] == 0 and out["table"] == "dg" # 기본 = 카드 · 304-1 탭 줄 보기 단추 없음
assert out["gone"] == [{"tab": "labor", "key": "L00005"}, None] # 줄 누름 = 표와 같은 이동
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]
assert json.loads(out["kept"]["b09-group-view"]) == {"dg": "table"}
assert out["redraws"] == 0
assert out["tools2"] == 0 and out["tag2"] == "TABLE" and out["focused"] == [3]
assert json.loads(out["kept"]["b09-group-view"]) == "table"
def test_잇기():
+3 -3
View File
@@ -1,6 +1,6 @@
"""181-2 박스보기 — 이름 · 접기 없음 · 목록표에서 고른 호표 = 박스 붉은 외곽선 · 표 보기 = 줄 구간 전체.
- 단추 글 「박스보기」 ↔ 「표로 보기」 · 요약(접기) 보기 지움
- 단추 글 「박스보기」 · 「호표보기」(304-1 좌측 스위치) · 요약(접기) 보기 지움
- 표 보기 = 고른 호표 줄 구간(키 `|` 앞머리 같은 줄) 모두 `is-pick` · 첫 줄 `is-pick-top` · 끝 줄 `is-pick-end`
- 붉은 색 = `--color-danger`(B06 횡단 카드 고름 꼴)
"""
@@ -52,7 +52,7 @@ def test_표_보기_줄_class_넘김():
"\nconst o = { tab: 'dg', spec, hint: null, focus: 'B|1', goto: () => {}, redraw: () => {}, setTools: () => {},"
" makeTable: (e) => { extra = e; return { root: document.createElement('table'),"
" focusRow: () => {} }; } };"
"\nchosen.set('dg', 'table');"
"\nchooseGroupView('table');"
"\nrenderGroupPanel(o);"
"\nconsole.log(JSON.stringify(spec.rows.map((_, i) => extra(i))));",
)
@@ -63,7 +63,7 @@ def test_표_보기_줄_class_넘김():
def test_글_접기_없음_CSS_잇기():
groups = (ROOT / GROUPS).read_text("utf-8")
assert '"표로 보기" : "박스보기"' in groups and "호표별 보기" not in groups
assert '"박스보기" : "호표보기"' in groups and "호표별 보기" not in groups # 304-1 글
common = (ROOT / COMMON).read_text("utf-8")
for gone in ("isSummary", "GROUP_SUMMARY_OVER", "is-closed", "모두 펼침", "모두 접기"):
assert gone not in common
+1 -1
View File
@@ -286,4 +286,4 @@ def test_내역_초기화_배선():
assert "showConfirmDialog(" in block and "if (!ok) return;" in block
assert "const { bill: _bill, ...rest } = user;" in block
assert 'run("/estimation", "PUT"' in block and "{ user: rest }" in block
assert "actions.append(save, reset);" in src
assert "createDock([save, reset]," in src # 303-1 하단 고정 줄 맨 아래 줄
+8 -8
View File
@@ -1,6 +1,6 @@
"""185-2 B09 묶음 탭 보기 바꿈 단추 글 한 줄 · 가로 열 수 단추와 같은 줄.
"""185-2 B09 묶음 탭 가로 열 수 단추 한 줄 · 보기 고름 = 좌측 하단(304-1 · 탭 줄에 없음).
- 보기 바꿈 단추 · 가로 열 수 = 도구 묶음(`setTools` · 202-1 로 페이지 보조 탭 줄 오른쪽) · 카드 판 머리 띠 = 묶음 수만
- 가로 열 수 = 도구 묶음(`setTools` · 202-1 로 페이지 보조 탭 줄 오른쪽) · 카드 판 머리 띠 = 묶음 수만
- 단추 · 라벨 = `white-space: nowrap` · 도구 묶음 = 줄바꿈 없음(`flex-wrap: nowrap`)
"""
@@ -31,7 +31,7 @@ def _panel(view: str, hint: str):
return run(
[COMMON, TABS, f"{B09}Labor.ts", GROUPS],
f"\nconst spec = buildDaegaBlocks({{ sheets: {{ daega_blocks: [{json.dumps(DG, ensure_ascii=False)}] }} }});"
f"\nchosen.set('dg', '{view}');"
f"\nchooseGroupView('{view}');"
"\nconst tools = [];"
f"\nconst o = {{ tab: 'dg', spec, hint: {hint}, goto: () => {{}}, redraw: () => {{}},"
" setTools: (t) => tools.push(...t),"
@@ -39,7 +39,7 @@ def _panel(view: str, hint: str):
"\nconst out = renderGroupPanel(o);"
"\nconst btn = tools.find((e) => e.dataset.b09GroupView);"
"\nconst bar = out[0].all('.ui-group-cards__bar')[0] ?? null;"
"\nconsole.log(JSON.stringify({ n: out.length, first: out[0].className, label: btn.textContent,"
"\nconsole.log(JSON.stringify({ n: out.length, first: out[0].className, label: btn ? btn.textContent : null, nTools: tools.length,"
" cols: tools.flatMap((e) => e.all('[data-group-cols]')).map((b) => b.dataset.groupCols),"
" barBtn: bar ? bar.all('[data-b09-group-view]').length : null,"
" barCols: bar ? bar.all('[data-group-cols]').length : null }));",
@@ -49,7 +49,7 @@ def _panel(view: str, hint: str):
def test_카드_보기_단추가_열수_단추와_같은_도구_묶음에():
out = _panel("cards", "null")
assert out["label"] == "표로 보기"
assert out["label"] is None # 304-1 보기 단추 = 좌측 하단(탭 줄에 없음)
assert out["cols"] == ["1", "2", "3", "4"] # 같은 묶음에 열 수 단추 넷
assert out["barBtn"] == 0 and out["barCols"] == 0 # 카드 판 머리 띠에는 없음
assert out["n"] == 1 # 안내 글 없으면 카드 판 하나뿐
@@ -63,8 +63,8 @@ def test_카드_보기_안내_글은_카드_판_아래에():
def test_표_보기_단추는_도구_묶음에():
out = _panel("table", "null")
assert out["label"] == "박스보기" and out["n"] == 1
assert out["cols"] == [] # 표 보기 = 열 수 단추 없음
assert out["label"] is None and out["n"] == 1
assert out["cols"] == [] and out["nTools"] == 0 # 표 보기 = 도구 없음
def test_CSS_한_줄_nowrap():
@@ -81,4 +81,4 @@ def test_CSS_한_줄_nowrap():
page, ".b09-estimation__tools .ui-btn,\n.b09-estimation__tools .ui-group-cards__label"
)
assert "white-space: nowrap" in tools
assert "actions: [toggle]" in (ROOT / GROUPS).read_text("utf-8")
assert "toggle" not in (ROOT / GROUPS).read_text("utf-8") # 304-1 탭 줄 토글 걷음
@@ -36,7 +36,7 @@ def _panel(view: str, hint: str):
return run(
[COMMON, TABS, f"{B09}Labor.ts", GROUPS],
f"\nconst spec = buildDaegaBlocks({{ sheets: {{ daega_blocks: [{json.dumps(DG, ensure_ascii=False)}] }} }});"
f"\nchosen.set('dg', '{view}');"
f"\nchooseGroupView('{view}');"
f"\nconst hint = {hint};"
"\nconst o = { tab: 'dg', spec, hint, goto: () => {}, redraw: () => {}, setTools: () => {},"
" makeTable: () => ({ root: document.createElement('table'), focusRow: () => {} }) };"
@@ -80,7 +80,7 @@ def test_묶음_탭_단추가_보조_줄에_안내는_아래에():
"\nconst hint = document.createElement('p');"
"\nconst res = {};"
"\nfor (const view of ['cards', 'table']) {"
"\n chosen.set('dg', view);"
"\n chooseGroupView(view);"
"\n bars.draw('dg'); bars.setTools([]);"
"\n const panel = renderGroupPanel({ tab: 'dg', spec, hint, goto: () => {}, redraw: () => {},"
" makeTable: () => ({ root: document.createElement('table'), focusRow: () => {} }),"
@@ -95,8 +95,9 @@ def test_묶음_탭_단추가_보조_줄에_안내는_아래에():
"\nconsole.log(JSON.stringify(res));",
RAW,
)
assert out["cards"]["inRow"] == ["표로 보기"] and out["cards"]["cols"] == 4
assert out["table"]["inRow"] == ["박스보기"] and out["table"]["cols"] == 0
# 304-1 보기 고름 = 좌측 하단 스위치(보조 줄에 없음) · 가로 열 수 = 보조 줄 그대로
assert out["cards"]["inRow"] == [] and out["cards"]["cols"] == 4
assert out["table"]["inRow"] == [] and out["table"]["cols"] == 0
for view in ("cards", "table"):
assert out[view]["inPanel"] == 0 # 판 안에는 단추 없음
assert out[view]["hintLast"] and out[view]["n"] == 2 # 판 + 안내 줄(맨 아래)
@@ -60,7 +60,7 @@ def test_표_빨강_하나_누름_키_풂():
" 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');"
"\nchooseGroupView('table');"
"\nconst marks = () => [root.children.map((tr) => tr.className || null),"
" spec.rows.map((_, i) => extra(i))];"
"\nconst out = {};"
@@ -205,6 +205,6 @@ def test_패널_훅_draft_없으면_빈칸():
tabs = (ROOT / "B09_Estimation/B09_Estimation_UI_Tabs.ts").read_text("utf-8")
shell = (ROOT / "B09_Estimation/B09_Estimation_UI_Shell.ts").read_text("utf-8")
ui = (ROOT / "B09_Estimation/draft/B09_Draft_Schedule_UI.ts").read_text("utf-8")
assert "{ builders: {}, extend: [], panels: [] }" in tabs # draft 없으면 카드 없음
assert "{ builders: {}, extend: [], panels: [], actions: [] }" in tabs # draft 없으면 카드 없음
assert "...DRAFT.panels.map((make) => make(() => user, setUser))" in shell
assert "DRAFT.panels.push(" in ui
+139
View File
@@ -0,0 +1,139 @@
"""303 B09 행 고름 = 팝업 · 하이퍼링크 스위치 · 좌측 하단 줄.
303-1 좌측 하단 고정 줄 — [박스보기|호표보기] · [하이퍼링크|팝업] · `DRAFT.actions`(비면 줄 없음) ·
[?][저장][내역 초기화](도움말 줄) · 스위치 = pref global · 기본 = 팝업
303-2 표 짜기 = `…_UI_Builders`(화면 · 팝업 · 인쇄 같은 옵션)
"""
import json
import re
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, TABS, run # noqa: E402
B09 = ROOT / "B09_Estimation"
DOCK = "B09_Estimation/B09_Estimation_UI_Dock.ts"
BUILDERS = "B09_Estimation/B09_Estimation_UI_Builders.ts"
DOCK_JS = (
"\nconst save = document.createElement('button'); save.textContent = '저장';"
"\nconst reset = document.createElement('button'); reset.textContent = '내역 초기화';"
"\nconst shape = (dock) => dock.children.map((r) => ({ sw: r.dataset.b09Switch ?? null,"
" draft: 'b09DraftActions' in r.dataset, help: 'helpRow' in r.dataset,"
" text: r.children.map((b) => b.textContent) }));"
)
def test_하단_줄_차례_초안_단추_없으면_줄_없음():
out = run(
[COMMON, TABS, GROUPS, DOCK],
DOCK_JS + "\nconst a = createDock([save, reset], () => {});"
"\nDRAFT.actions.push(() => { const b = document.createElement('button');"
" b.textContent = '인쇄 미리보기'; return b; });"
"\nconst b = createDock([save, reset], () => {});"
"\nconsole.log(JSON.stringify({ cls: a.className, a: shape(a), b: shape(b) }));",
)
assert out["cls"] == "ui-sidebar-actions b09-dock" # 바닥 고정 = 공용 액션 줄 한 벌
view = {"sw": "view", "draft": False, "help": False, "text": ["박스보기", "호표보기"]}
link = {"sw": "link", "draft": False, "help": False, "text": ["하이퍼링크", "팝업"]}
main = {"sw": None, "draft": False, "help": True, "text": ["저장", "내역 초기화"]}
assert out["a"] == [view, link, main] # 초안 단추 없음 = 줄 없음
draft = {"sw": None, "draft": True, "help": False, "text": ["인쇄 미리보기"]}
assert out["b"] == [view, link, draft, main] # 초안 단추 = [저장] 줄 위
def test_스위치_기본_팝업_누름_pref():
out = run(
[COMMON, TABS, GROUPS, DOCK],
DOCK_JS + "\nconst dock = createDock([save, reset], () => {});"
"\nconst row = dock.children[1];"
"\nconst pressed = () => row.children.map((b) => b.attrs['aria-pressed']);"
"\nconst a = [linkMode(), pressed()];"
"\nrow.children[0].click();"
"\nconst b = [linkMode(), pressed(), kept.get('b09-link-mode')];"
"\nrow.children[1].click();"
"\nconst c = [linkMode(), pressed(), kept.get('b09-link-mode')];"
"\nkept.set('b09-link-mode', '\"x\"');"
"\nconsole.log(JSON.stringify({ a, b, c, odd: linkMode() }));",
)
assert out["a"] == ["popup", ["false", "true"]] # 기본 = 팝업
assert out["b"] == ["link", ["true", "false"], '"link"']
assert out["c"] == ["popup", ["false", "true"], '"popup"']
assert out["odd"] == "popup" # 모르는 값 = 팝업
def test_DRAFT_actions_자리():
tabs = (B09 / "B09_Estimation_UI_Tabs.ts").read_text("utf-8")
assert "actions: Array<() => HTMLElement>;" in tabs
assert "{ builders: {}, extend: [], panels: [], actions: [] }" in tabs
state = (ROOT / "A00_Common/b_page_state.ts").read_text("utf-8")
assert '"b09-link-mode": { bucket: "pref", scope: "global" },' in state
css = (B09 / "B09_Estimation_UI_Page.css").read_text("utf-8")
m = re.search(r"\.b09-dock\.ui-sidebar-actions\s*\{([^}]*)\}", css)
assert m and "flex-direction: column" in m.group(1) # 줄 여럿 세로
# 표 짜기 — 탭 짜기 18 개 대신 가짜(줄 하나) · 옵션 = 표 그대로 넘김
FAKE_BUILDS = "".join(
f"\nconst {name} = () => ({{ columns: [], rows: [{{ key: '{name}' }}] }});"
for name in re.findall(
r":\s*(build\w+),",
(ROOT / BUILDERS).read_text("utf-8"),
)
)
def test_Builders_화면_같은_옵션():
out = run(
[TABS, BUILDERS],
"\nDRAFT.builders.draft_x = () => '안내';"
"\nconst spec = { columns: [{ key: 'a', label: 'A' }], rows: [{ key: 'r' }], frozen: 2,"
" editable: () => true, onEdit: () => {}, rowClass: () => 'x', cellClass: () => 'y',"
" level: { key: 'a', of: () => 1 }, mergeSame: ['a'], link: () => null, menu: () => [] };"
"\nconst o = tableOptionsOf(spec, 'bill');"
"\nconst plain = tableOptionsOf({ columns: [], rows: [], editOnDouble: false });"
"\nconsole.log(JSON.stringify({ keys: Object.keys(o).sort(), caption: o.caption,"
" same: ['columns', 'rows', 'editable', 'onEdit', 'rowClass', 'cellClass', 'level', 'mergeSame']"
".every((k) => o[k] === spec[k]), frozen: o.frozen, total: o.total, empty: o.empty,"
" dbl: [o.editOnDouble, plain.editOnDouble], key: [o.keyEdit, plain.keyEdit],"
" plainCaption: plain.caption ?? null, plainFrozen: plain.frozen,"
" bill: tabTableOf('bill', {}).rows[0].key, draft: tabTableOf('draft_x', {}),"
" none: tabTableOf('nope', {}) }));",
FAKE_BUILDS,
)
assert out["keys"] == sorted(
[
"columns",
"rows",
"total",
"frozen",
"empty",
"caption",
"editable",
"editOnDouble",
"keyEdit",
"onEdit",
"rowClass",
"cellClass",
"level",
"mergeSame",
]
) # 누름 · 메뉴 · 풍선 없음(그리는 쪽 몫)
assert out["caption"] == "설계내역서" and out["same"] is True
assert out["frozen"] == 2 and out["total"] is None and out["empty"] == ""
assert out["dbl"] == [True, False] # 243-1 기본 = 더블클릭
assert out["key"] == [False, True] # 설계내역서(메뉴) Enter · F2 = 레벨 키 몫
assert out["plainCaption"] is None and out["plainFrozen"] == 0
assert out["bill"] == "buildBill" and out["draft"] == "안내" and out["none"] is None
def test_화면이_Builders_옵션을_씀():
shell = (B09 / "B09_Estimation_UI_Shell.ts").read_text("utf-8")
assert "...tableOptionsOf(spec, tab)," in shell
assert "const spec = tabTableOf(tab, ctx);" in shell
assert "BUILDERS" not in shell
@@ -0,0 +1,103 @@
"""304-1 B09 박스보기 · 호표보기 = 좌측 하단 한 설정(묶음 탭 셋 함께).
- 한 값(`b09-group-view` · pref global)이 dg · machine_blocks · sg 셋에 함께 · 기본 = 박스(카드)
- 옛 탭별 값(`{탭: 보기}`) = 첫 읽기에 한 값으로(dg · 없으면 다른 탭 · 없으면 기본) 옮겨 적음
- 보조 탭 줄 보기 토글 걷음 · 가로 열 수 단추(박스 보기)는 그대로
- 좌측 하단 스위치 [박스보기|호표보기] 누름 = 값 바꿈 · 다시 그림 부름
"""
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
LABOR = "B09_Estimation/B09_Estimation_UI_Tabs_Labor.ts"
DOCK = "B09_Estimation/B09_Estimation_UI_Dock.ts"
DG = {"id": "DG1", "no": 1, "name": "찰쌓기", "unit": "m2", "lines": [], "sum": 0}
SPEC = (
"\nconst spec = buildDaegaBlocks({ sheets: { daega_blocks: ["
+ json.dumps(DG, ensure_ascii=False)
+ "] } });"
"\nconst tools = [];"
"\nconst draw = (tab) => renderGroupPanel({ tab, spec, hint: null, goto: () => {},"
" setTools: (t) => tools.push(...t),"
" makeTable: () => ({ root: document.createElement('table'), focusRow: () => {} }) })[0];"
"\nconst kinds = () => ['dg', 'machine_blocks', 'sg'].map((t) => draw(t).tagName);"
)
def test_한_설정_셋_탭_함께():
out = run(
[COMMON, TABS, LABOR, GROUPS],
SPEC + "\nconst a = kinds();"
"\nchooseGroupView('table');"
"\nconst b = kinds();"
"\nconsole.log(JSON.stringify({ a, b, kept: Object.fromEntries(kept),"
" toggles: tools.filter((e) => e.dataset.b09GroupView).length,"
" cols: tools.flatMap((e) => e.all('[data-group-cols]')).length }));",
RAW,
)
assert out["a"] == ["DIV"] * 3 # 기본 = 박스
assert out["b"] == ["TABLE"] * 3 # 한 값이 셋 탭 함께
assert json.loads(out["kept"]["b09-group-view"]) == "table"
assert out["toggles"] == 0 # 탭 줄 보기 토글 없음
assert out["cols"] == 4 * 3 # 박스 보기 가로 열 수 단추 = 탭 줄 그대로
def test_옛_탭별_값_첫_읽기에_한_값으로():
cases = [
({"dg": "table", "sg": "cards"}, "table"),
({"dg": "cards", "sg": "table"}, "cards"), # dg 먼저
({"sg": "table"}, "table"), # dg 없으면 다른 탭
({"machine_blocks": "table", "sg": "cards"}, "table"),
({}, "cards"), # 없으면 기본
]
for old, want in cases:
out = run(
[COMMON, TABS, LABOR, GROUPS],
"\nkept.set('b09-group-view', "
+ json.dumps(json.dumps(old))
+ ");"
+ SPEC
+ "\nconst got = kinds();"
"\nconsole.log(JSON.stringify({ got, kept: Object.fromEntries(kept) }));",
RAW,
)
assert out["got"] == ["TABLE" if want == "table" else "DIV"] * 3, old
assert json.loads(out["kept"]["b09-group-view"]) == want, old # 옮겨 적음
def test_좌측_스위치_누름():
out = run(
[COMMON, TABS, GROUPS, DOCK],
"\nlet redraws = 0;"
"\nconst dock = createDock([document.createElement('button')], () => redraws++);"
"\nconst row = dock.children[0];"
"\nconst pressed = () => row.children.map((b) => b.attrs['aria-pressed']);"
"\nconst labels = row.children.map((b) => b.textContent);"
"\nconst a = pressed();"
"\nrow.children[0].click();" # 이미 박스 = 그대로
"\nconst b = [redraws, pressed()];"
"\nrow.children[1].click();"
"\nconsole.log(JSON.stringify({ name: row.dataset.b09Switch, labels, a, b, c: [redraws, pressed()],"
" view: groupView(), kept: Object.fromEntries(kept) }));",
)
assert out["name"] == "view" and out["labels"] == ["박스보기", "호표보기"]
assert out["a"] == ["true", "false"] # 기본 = 박스보기
assert out["b"] == [0, ["true", "false"]]
assert out["c"] == [1, ["false", "true"]] and out["view"] == "table"
assert json.loads(out["kept"]["b09-group-view"]) == "table" # [저장] 없이 pref
def test_잇기():
groups = (ROOT / GROUPS).read_text("utf-8")
assert "toggle" not in groups and "redraw" not in groups and "createButton" not in groups
shell = (ROOT / "B09_Estimation/B09_Estimation_UI_Shell.ts").read_text("utf-8")
assert "createDock([save, reset], () => isGroupTab(current) && show(current))" in shell
state = (ROOT / "A00_Common/b_page_state.ts").read_text("utf-8")
assert '"b09-group-view": { bucket: "pref", scope: "global" },' in state