Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tjoit7rxvpLMM7cafeVTo1
115 lines
3.8 KiB
Python
115 lines
3.8 KiB
Python
"""표 쪽 나눔 — `ui_template_sheet_ops.ts` `pagePlan` 을 Node 로 바로 돌려 봄.
|
|
|
|
격자(`_render`)가 쪽을 이 계획대로 그림.
|
|
|
|
줄 120 문서 → project 쪽 3(50 · 50 · 20) · 쪽마다 머리(격자가 쪽마다 thead) · 합계는 마지막 쪽만 ·
|
|
「전구간」 줄은 맨 위 · master 는 한 쪽.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
OPS = ROOT / "ui_template" / "sheet" / "ui_template_sheet_ops.ts"
|
|
|
|
pytestmark = pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
|
|
|
SCRIPT = """
|
|
const { pagePlan } = await import(process.argv[1]);
|
|
const doc = JSON.parse(process.argv[2]);
|
|
const out = {};
|
|
for (const mode of ["project", "master"]) {
|
|
out[mode] = pagePlan(doc, mode).map((p) => ({
|
|
ids: p.rows.map((r) => r.id), first: p.first, totals: p.totals,
|
|
}));
|
|
}
|
|
console.log(JSON.stringify(out));
|
|
"""
|
|
|
|
|
|
def _plan(doc: dict) -> dict:
|
|
result = subprocess.run(
|
|
[
|
|
"node",
|
|
"--experimental-strip-types",
|
|
"--no-warnings",
|
|
"--input-type=module",
|
|
"-e",
|
|
SCRIPT,
|
|
OPS.as_uri(),
|
|
json.dumps(doc, ensure_ascii=False),
|
|
],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=60,
|
|
)
|
|
assert result.returncode == 0, result.stderr
|
|
return json.loads(result.stdout)
|
|
|
|
|
|
def _doc(n: int, **head) -> dict:
|
|
rows = [{"id": f"p{i}", "값": {}} for i in range(1, n)]
|
|
rows.insert(7, {"id": "all", "값": {}, "고정": "전구간"}) # 문서 가운데 있어도 맨 위로
|
|
return {"양식": "시험", "종류": "표", "판": 1, "열": [], "줄": rows, **head}
|
|
|
|
|
|
def test_120_rows_make_three_pages_totals_last():
|
|
plan = _plan(_doc(120))["project"]
|
|
assert [len(p["ids"]) for p in plan] == [50, 50, 20]
|
|
assert [p["first"] for p in plan] == [1, 51, 101]
|
|
assert [p["totals"] for p in plan] == [False, False, True]
|
|
assert plan[0]["ids"][0] == "all"
|
|
assert sum((p["ids"] for p in plan), []).count("all") == 1
|
|
|
|
|
|
def test_page_rows_from_doc_and_master_single_page():
|
|
got = _plan(_doc(55, 쪽줄=20))
|
|
assert [len(p["ids"]) for p in got["project"]] == [20, 20, 15]
|
|
assert [len(p["ids"]) for p in got["master"]] == [55]
|
|
assert got["master"][0]["totals"] is True
|
|
|
|
|
|
def test_empty_doc_still_one_page_with_totals():
|
|
plan = _plan({"양식": "시험", "종류": "표", "판": 1, "열": [], "줄": []})["project"]
|
|
assert plan == [{"ids": [], "first": 1, "totals": True}]
|
|
|
|
|
|
HEAD_SCRIPT = """
|
|
const { setHeadLabel } = await import(process.argv[1]);
|
|
const out = JSON.parse(process.argv[2]).map(([head, level, text, depth]) => {
|
|
setHeadLabel(head, level, text, depth);
|
|
return head;
|
|
});
|
|
console.log(JSON.stringify(out));
|
|
"""
|
|
|
|
|
|
def test_head_label_slash_splits_layers():
|
|
cases = [
|
|
[["새 열", None, None], 0, "관공/Φ800/관매설", 3],
|
|
[["새 열", None, None], 0, "관공/Φ800", 3], # 적게 → 남은 층 합침
|
|
[["새 열", None, None], 0, "a/b/c/d", 3], # 많으면 끝 층에 이어 붙임
|
|
[["종류", "공법", "규격"], 1, "이름", 3], # / 없음 → 그 층만
|
|
[["새 열"], 0, "가/나", 3], # 모자란 층은 채움
|
|
]
|
|
result = subprocess.run(
|
|
["node", "--experimental-strip-types", "--no-warnings", "--input-type=module", "-e",
|
|
HEAD_SCRIPT, OPS.as_uri(), json.dumps(cases, ensure_ascii=False)],
|
|
capture_output=True, text=True, encoding="utf-8", timeout=60,
|
|
) # fmt: skip
|
|
assert result.returncode == 0, result.stderr
|
|
assert json.loads(result.stdout) == [
|
|
["관공", "Φ800", "관매설"],
|
|
["관공", "Φ800", None],
|
|
["a", "b", "c/d"],
|
|
["종류", "이름", "규격"],
|
|
["가", "나", None],
|
|
]
|