Files
Aislo/resources/tester/test_171_2_group_cards.py
T
eomsangdonandClaude Opus 5.5 40ed04b15f feat(ui): 묶음 카드 공용 양식 · B09 묶음 탭 셋 카드 기본(171-2 · 171-3)
- 171-2 새 공용 ui_template_group_cards(.ts · .css) — 머리(호표 · 명칭 · 규격 · 단위 · 합계) · 몸 줄(이름 · 규격 · 수량 · 단위 · 합계 · 비고) · 합계 줄(노무 · 재료 · 경비 · 계)
- 글자 14px 한 벌 · 줄 간격 좁게(공용 카드 16px 끼움 없음) · 숫자 오른쪽 · 줄 금액 = 합계만 + 비목 셋 풍선
- 가로 열 수 1~4(기본 2) · 묶음 12 넘으면 요약 보기(머리 · 합계만 · 머리 누름 = 펼침 · 모두 펼침 · 접기)
- 157-2 산근 카드 파일 갈음(지움)
- 171-3 일위대가표 · 중기사용료 · 단가산출근거 = 카드 기본 · [표로 보기] = 표 그대로 · 보기 · 열 수 기억(b_page_state 두 줄 · 탭마다)
- 값 · 풍선 · 칸 누름 이동 = 표 줄 그대로 · 집계표 · 설계내역서 · B08 대상 밖
- 시험 test_171_2 · test_171_3 · 공용 도우미(가짜 DOM) · test_157_2 갈음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
2026-10-10 08:46:56 +09:00

155 lines
7.4 KiB
Python

"""171-2 묶음 카드 공용 양식(`ui_template_group_cards`) — Node(타입 지움) + 작은 가짜 DOM.
- 머리 = 호표 · 명칭 · 규격 · 단위 · 합계 / 몸 = 이름 · 규격 · 수량 · 단위 · 합계 · 비고 / 합계 줄 = 노무 · 재료 · 경비 · 계
- 줄 금액 = 합계만 · 비목 셋 = 풍선 · 숫자 오른쪽 · 글자 14px 한 벌 · 줄 간격 좁게(공용 카드 16px 끼움 없음)
- 가로 열 수 1~4(기본 2 · 고르면 알림) · 묶음 12 넘으면 요약 보기(머리 · 합계만 · 머리 누름 = 펼침 · 모두 펼침 · 접기)
- 157-2 산근 카드 파일 = 이 양식으로 갈음(지움)
"""
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, run # noqa: E402
GROUPS_JS = """
const mk = (n) => ({
key: `G${n}`, at: n * 10, no: `제 ${n} 호표`, name: `묶음${n}`, spec: "규격", specTip: "원문번호 1",
unit: "m3", sum: 1234.5,
lines: [
{ at: n * 10 + 1, name: "석공", spec: "", qty: 0.072, unit: "인", sum: 18643.3,
parts: [18643.3, null, null], note: "노무3", link: true },
{ at: n * 10 + 2, name: "인용", tag: "품셈" },
],
foot: { at: n * 10 + 3, parts: [100, 20, 3], sum: 123, note: "빈 줄 1", tip: "빈 줄 — 모래" },
});
const picks = [];
const cols = [];
const make = (count, extra = {}) => createGroupCards({
groups: Array.from({ length: count }, (_, i) => mk(i + 1)),
onPick: (at) => picks.push(at), onColumns: (n) => cols.push(n), ...extra,
});
"""
def _run(body):
return run([COMMON], GROUPS_JS + body)
def test_도우미_열수_요약_풍선():
out = _run(
"\nconsole.log(JSON.stringify({"
" cols: [0, 1, 2, 3, 4, 5, '3', null, undefined].map(clampColumns),"
" summary: [12, 13].map(isSummary),"
" tip: [partsTip([1000, 0, null]), partsTip([null, null, null]), partsTip(null)] }));"
)
assert out["cols"] == [2, 1, 2, 3, 4, 2, 2, 2, 2]
assert out["summary"] == [False, True]
assert out["tip"] == ["노무 1,000 · 재료 0 · 경비 -", None, None]
def test_카드_머리_몸_합계_줄():
out = _run(
"\nconst root = make(1);"
"\nconst box = root.querySelector('.ui-group-card');"
"\nconst head = box.querySelector('.ui-group-card__head');"
"\nconst tds = box.querySelector('tbody').children[0].children;"
"\nconst ths = box.querySelector('thead').children[0].children;"
"\nconsole.log(JSON.stringify({"
" head: head.children.map((c) => [c.className, c.textContent, c.title]),"
" ths: ths.map((c) => [c.textContent, c.className]),"
" tds: tds.map((c) => [c.textContent, c.className, c.title]),"
" tag: box.querySelector('.ui-group-card__tag').textContent,"
" foot: box.querySelector('.ui-group-card__foot').children.map((c) => c.textContent),"
" link: box.querySelector('tbody').children.map((r) => r.classList.contains('is-link')),"
" cols: root.querySelector('.ui-group-cards__grid').style.props['--group-cols'],"
" summary: root.all('.ui-btn').map((b) => b.textContent) }));"
)
assert out["head"] == [
["ui-group-card__no", "제 1 호표", ""],
["ui-group-card__title", "묶음1", ""],
["ui-group-card__spec", "규격", "원문번호 1"],
["ui-group-card__unit", "m3", ""],
["ui-group-card__total", "1,234.5", ""],
]
assert out["ths"] == [
["이름", ""], ["규격", ""], ["수량", "is-num"], ["단위", ""], ["합계", "is-num"], ["비고", ""],
] # fmt: skip
# 줄 금액 = 합계만 · 비목 셋 = 풍선 · 숫자 칸 = 오른쪽(is-num)
assert out["tds"] == [
["석공", "ui-group-card__name", ""],
["", "", ""],
["0.1", "is-num", ""],
["인", "", ""],
["18,643.3", "is-num", "노무 18,643.3 · 재료 - · 경비 -"],
["노무3", "ui-group-card__note", ""],
]
assert out["tag"] == "품셈"
assert out["foot"] == ["노무 100", "재료 20", "경비 3", "계 123", "빈 줄 1"]
assert out["link"] == [True, False]
assert out["cols"] == "2" # 기본 2
assert out["summary"] == ["1", "2", "3", "4"] # 12 이하 = 요약 단추 없음
def test_가로_열수_고름_알림_누름_이동():
out = _run(
"\nconst root = make(2, { columns: 3 });"
"\nconst grid = root.querySelector('.ui-group-cards__grid');"
"\nconst before = grid.style.props['--group-cols'];"
"\nroot.querySelector('[data-group-cols=\"4\"]').click();"
"\nroot.querySelector('[data-at=\"21\"]').click();"
"\nroot.querySelector('[data-at=\"20\"]').click();"
"\nconsole.log(JSON.stringify({ before, after: grid.style.props['--group-cols'], cols,"
" pressed: root.all('[data-group-cols]').map((b) => b.attrs['aria-pressed']), picks }));"
)
assert out["before"] == "3" and out["after"] == "4"
assert out["cols"] == [4]
assert out["pressed"] == ["false", "false", "false", "true"]
assert out["picks"] == [21, 20] # 줄 · 머리 누름 = 표 줄 번호 그대로(갈 곳은 부르는 쪽 link)
def test_묶음_12_넘으면_요약_보기():
out = _run(
"\nconst root = make(13, { focus: 52 });"
"\nconst cards = root.all('.ui-group-card');"
"\nconst closed = () => cards.map((c) => c.classList.contains('is-closed'));"
"\nconst first = closed();"
"\nconst focused = root.querySelector('[data-at=\"52\"]');"
"\ncards[0].querySelector('.ui-group-card__head').click();"
"\nconst opened = closed()[0];"
"\nconst btn = (label) => root.all('.ui-btn').find((b) => b.textContent === label);"
"\nbtn('모두 펼침').click(); const all = closed();"
"\nbtn('모두 접기').click(); const none = closed();"
"\nconsole.log(JSON.stringify({ first, opened, all, none, picks,"
" foot: cards.every((c) => c.querySelector('.ui-group-card__foot')),"
" focus: [focused.classList.contains('is-focus'), !!focused.scrolled] }));"
)
# 이동해 온 줄(5번째 묶음 줄) = 그 묶음만 펼침 · 강조 · 스크롤
assert out["first"] == [True] * 4 + [False] + [True] * 8
assert out["focus"] == [True, True]
assert out["opened"] is False # 머리 누름 = 펼침
assert out["picks"] == [] # 요약 보기 머리 누름은 이동 아님
assert out["all"] == [False] * 13 and out["none"] == [True] * 13
assert out["foot"] is True # 접어도 합계 줄은 보임(몸만 숨김)
def test_양식_글자_간격_숫자():
css = (ROOT / "ui_template" / "ui_template_group_cards.css").read_text("utf-8")
theme = (ROOT / "ui_template" / "ui_template_theme.css").read_text("utf-8")
assert "--text-body-sm: 14px;" in theme
assert "font-size: var(--text-body-sm);" in css # 14px 한 벌
assert [ln.strip() for ln in css.splitlines() if "font-size" in ln] == [
"font-size: var(--text-body-sm);",
"font-size: inherit;",
]
assert "padding: 1px var(--spacing-4);" in css # 줄 간격 좁게
assert "spacing-16" not in css and "card-padding" not in css # 공용 카드 16px 끼움 없음
assert "grid-template-columns: repeat(var(--group-cols, 2), minmax(0, 1fr));" in css
assert "text-align: right;" in css
src = (ROOT / COMMON).read_text("utf-8")
assert "createCard" not in src
assert not (ROOT / "B09_Estimation" / "B09_Estimation_UI_Tabs_Sangeun_Cards.ts").exists()