"""171-2 묶음 카드 공용 양식(`ui_template_group_cards`) — Node(타입 지움) + 작은 가짜 DOM. - 머리 = 호표 · 명칭 · 규격 · 단위 · 합계 / 몸 = 이름 · 규격 · 수량 · 단위 · 합계 · 비고 / 합계 줄 = 노무 · 재료 · 경비 · 계 - 줄 금액 = 합계만 · 비목 셋 = 풍선 · 숫자 오른쪽 · 글자 14px 한 벌 · 줄 간격 좁게(공용 카드 16px 끼움 없음) - 가로 열 수 1~4(기본 2 · 고르면 알림) · 접기 없음(181-2) · 이동해 온 박스 = `is-pick`(붉은 외곽선) · 머리 누름 = 이동 - 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)," " 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["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"] # 가로 열 수 단추뿐(요약 · 접기 단추 없음) 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_묶음_많아도_접힘_없음_고른_박스_붉은_외곽선(): out = _run( "\nconst root = make(13, { focus: 52 });" "\nconst cards = root.all('.ui-group-card');" "\nconst focused = root.querySelector('[data-at=\"52\"]');" "\nconst first = cards.map((c) => c.classList.contains('is-pick'));" "\ncards[0].querySelector('.ui-group-card__head').click();" "\nconsole.log(JSON.stringify({ first, picks," " closed: cards.map((c) => c.classList.contains('is-closed'))," " buttons: root.all('.ui-btn').map((b) => b.textContent)," " foot: cards.every((c) => c.querySelector('.ui-group-card__foot'))," " focus: [focused.classList.contains('is-focus'), cards[4].scrolled === true] }));" ) # 이동해 온 줄(5번째 묶음 줄) = 그 박스만 붉은 외곽선 · 박스 스크롤(248-1 줄 강조 안 붙임) assert out["first"] == [False] * 4 + [True] + [False] * 8 assert out["focus"] == [False, True] assert out["closed"] == [False] * 13 # 13 묶음도 접힘 없음 assert out["picks"] == [10] # 머리 누름 = 이동(표 줄 번호 그대로) assert out["buttons"] == ["1", "2", "3", "4"] # 모두 펼침 · 접기 단추 없음 assert out["foot"] is True def test_고른_박스_없으면_외곽선_없음(): out = _run( "\nconst root = make(3);" "\nconsole.log(JSON.stringify(root.all('.ui-group-card').map(" "(c) => c.classList.contains('is-pick'))));" ) assert out == [False, False, False] 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 "is-closed" not in css and "is-closed" not in (ROOT / COMMON).read_text("utf-8") assert "border-color: var(--color-danger);" in css # 고른 박스 = B06 고름 꼴(181-2) 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()