- 공용 표 level 옵션: 레벨 칸 class is-lv1~5 · 묶음 줄 is-node(group 지정 · 안 주면 바로 아래 줄이 더 깊은 줄) - 크기 Lv1 16 · Lv2 15 · Lv3 14 · Lv4 13 · Lv5 12(토큰) · 굵게 = 묶음 · 항목 줄은 14 이하 - 들여쓰기 한 단 = 표 기본 1em 토큰(레벨 글자 크기와 무관 · 끌기 한 단과 같음) - 설계내역서 group = 항목 아닌 줄(빈 공종도 묶음) · 들여 · 내어쓰기는 셈 다시 → 레벨 · 글꼴 바로 - 새 시험 test_228_1 · test_200_2 · 201_1 · 185_1 기대값 고침 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SE9MxeXTu2uaDZ55BJowAY
138 lines
7.0 KiB
Python
138 lines
7.0 KiB
Python
"""201-1 공용 표 level(보기 전용) · mergeSame(같은 값 세로 병합) 옵션 + B08 · B09 적용 표.
|
||
|
||
- level { key, of } = 그 열 칸 class ui-data-table__lv · 줄 style --dt-lv(레벨 − 1) · 글 상자 칸도 · 다루기 단추 없음
|
||
· 레벨 글꼴 class is-lv1~5 · 묶음 is-node(228-1 · test_228_1)
|
||
- mergeSame [열] = 위아래 같은 글이 이어지면 rowspan · 앞 병합 열이 갈리면 갈림 · 빈 값 · 빈칸 글 · 고칠 칸 안 합침 ·
|
||
합계 줄 안 합침 · 그린 구간(win) 안에서만 · 병합 표 칸 = data-c(열 번호)
|
||
- 옵션 없으면 무변(rowspan · data-c · --dt-lv 없음)
|
||
"""
|
||
|
||
import json
|
||
import re
|
||
import subprocess
|
||
from pathlib import Path
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
|
||
JS = r"""
|
||
const fs = require("fs");
|
||
const path = require("path");
|
||
const ts = require(path.join(process.argv[1], "config", "node_modules", "typescript"));
|
||
const file = path.join(process.argv[1], "ui_template", "ui_template_data_table.ts");
|
||
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||
}).outputText;
|
||
const box = { exports: {} };
|
||
new Function("exports", "module", "require", js)(box.exports, box, (p) => (p.endsWith(".css") ? {} : require(p)));
|
||
const m = box.exports;
|
||
const body = (html) => html.split("<tbody>")[1].split("</tbody>")[0].split("</tr>").filter(Boolean);
|
||
const columns = [
|
||
{ key: "g", label: "구분" }, { key: "s", label: "규격" }, { key: "n", label: "명칭" },
|
||
{ key: "v", label: "값", kind: "number" },
|
||
];
|
||
const rows = [
|
||
{ g: "흙깍기", s: "가", n: "a", v: 1 },
|
||
{ g: "흙깍기", s: "가", n: "b", v: 1 },
|
||
{ g: "흙깍기", s: "나", n: "c", v: 1 },
|
||
{ g: "운반", s: "나", n: "d", v: 2 },
|
||
{ g: "운반", s: " ", n: "e", v: 2 },
|
||
{ g: "", s: " ", n: "f", v: 2 },
|
||
{ g: "", s: "다", n: "g", v: 3 },
|
||
];
|
||
const total = { g: "계", v: 12 };
|
||
const out = {};
|
||
out.plain = m.renderDataTableHtml({ columns, rows, total });
|
||
const merged = { columns, rows, total, mergeSame: ["g", "s"] };
|
||
out.merged = body(m.renderDataTableHtml(merged));
|
||
out.mergedFoot = m.renderDataTableHtml(merged).split("<tfoot>")[1];
|
||
out.spans = m.mergeSpans(merged, 0, rows.length);
|
||
out.win = m.mergeSpans(merged, 1, 4);
|
||
out.edit = m.mergeSpans({ ...merged, editable: (i, k) => i === 1 && k === "g" }, 0, 3);
|
||
out.level = body(m.renderDataTableHtml({
|
||
columns, rows: rows.slice(0, 3), level: { key: "n", of: (i) => [1, 2, null][i] },
|
||
editable: (i, k) => i === 1 && k === "n",
|
||
}));
|
||
out.cell = [m.cellColumn({ dataset: { c: "3" }, cellIndex: 1 }), m.cellColumn({ dataset: {}, cellIndex: 1 })];
|
||
console.log(JSON.stringify(out));
|
||
"""
|
||
|
||
|
||
def _run() -> dict:
|
||
result = subprocess.run(
|
||
["node", "-e", JS, str(ROOT)], capture_output=True, text=True, encoding="utf-8", timeout=60
|
||
)
|
||
assert result.returncode == 0, result.stderr
|
||
return json.loads(result.stdout)
|
||
|
||
|
||
def test_옵션_없으면_무변():
|
||
html = _run()["plain"]
|
||
assert "rowspan" not in html.split("<tbody>")[1]
|
||
assert "data-c=" not in html and "--dt-lv" not in html and "ui-data-table__lv" not in html
|
||
|
||
|
||
def test_mergeSame_같은_값만_rowspan():
|
||
out = _run()
|
||
# 구분: 흙깍기 3 · 운반 2 · 빈 값 2(안 합침) / 규격: 가 2(흙깍기 안) · 나 = 구분이 갈려 따로 · 빈칸 글 안 합침
|
||
assert [s[0] for s in out["spans"]] == [3, 0, 0, 2, 0, 1, 1]
|
||
assert [s[1] for s in out["spans"]] == [2, 0, 1, 1, 1, 1, 1]
|
||
assert all(s[2] == 1 and s[3] == 1 for s in out["spans"]) # 지정 안 한 열 = 그대로
|
||
rows = out["merged"]
|
||
assert '<td rowspan="3" data-c="0">흙깍기</td>' in rows[0].replace(' class="is-merged"', "")
|
||
assert 'class="is-merged"' in rows[0]
|
||
assert (
|
||
rows[1].count("<td") == 2 and '<td data-c="2">b</td>' in rows[1]
|
||
) # 합쳐진 칸 빠짐 · 열 번호
|
||
assert '<td data-num="" data-c="3">2</td>' in rows[4] # 숫자 값 그대로
|
||
assert "rowspan" not in out["mergedFoot"] # 합계 줄 안 합침
|
||
# 그린 구간(1~3줄) 안에서만 · 고칠 칸 안 합침
|
||
assert out["win"] == [[2, 1, 1, 1], [0, 1, 1, 1], [1, 1, 1, 1]]
|
||
assert [s[0] for s in out["edit"]] == [1, 1, 1]
|
||
assert out["cell"] == [3, 1]
|
||
|
||
|
||
def test_level_보기_전용_들여쓰기():
|
||
rows = _run()["level"]
|
||
assert [re.search(r'style="--dt-lv:(\d)"', r).group(1) for r in rows] == ["0", "1", "0"]
|
||
# 아래 줄이 더 깊음 = 묶음
|
||
assert '<td class="ui-data-table__lv is-lv1 is-node">a</td>' in rows[0]
|
||
assert rows[0].count("ui-data-table__lv") == 1 # 지정 열만
|
||
assert '<td class="is-edit ui-data-table__lv is-lv2" data-num="">' in rows[1] # 글 상자 칸도
|
||
assert "button" not in "".join(rows) # 다루기 단추 없음(설계내역서만)
|
||
css = (ROOT / "ui_template" / "ui_template_data_table.css").read_text("utf-8")
|
||
pad = r"padding-left: calc\(var\(--spacing-8\) \+ var\(--dt-lv, 0\) \* var\(--dt-indent\)\);"
|
||
assert re.search(r"td\.ui-data-table__lv \{\s*" + pad + r"\s*text-align: left;", css)
|
||
assert re.search(r"td\.ui-data-table__lv \.ui-data-table__input \{\s*" + pad, css)
|
||
assert re.search(r"td\.is-merged \{\s*vertical-align: top;", css)
|
||
|
||
|
||
#: 적용 표 — [파일, 들어 있어야 할 글]
|
||
APPLIED = [
|
||
("B08_Quantity/B08_Quantity_UI_Earthwork_Sheets.ts", 'mergeSame: ["group", "spec"]'),
|
||
("B08_Quantity/B08_Quantity_UI_Material.ts", 'mergeSame: ["name"]'),
|
||
("B08_Quantity/B08_Quantity_UI_Material.ts", 'mergeSame: ["group"]'),
|
||
("B08_Quantity/B08_Quantity_UI_Material.ts", "mergeSame: built.mergeSame"),
|
||
("B09_Estimation/B09_Estimation_UI_Tabs_Base.ts", 'mergeSame: ["part"]'),
|
||
("B09_Estimation/B09_Estimation_UI_Tabs_Base.ts", 'level: blockLevel("name"'),
|
||
("B09_Estimation/B09_Estimation_UI_Tabs_Labor.ts", 'level: blockLevel("name"'),
|
||
("B09_Estimation/B09_Estimation_UI_Tabs_Sangeun.ts", 'level: blockLevel("text"'),
|
||
("B09_Estimation/B09_Estimation_UI_Tabs_Sangeun.ts", 'mergeSame: ["kind"]'),
|
||
("B09_Estimation/B09_Estimation_UI_Tabs_Total.ts", 'mergeSame: ["group", "part"]'),
|
||
("B09_Estimation/B09_Estimation_UI_Tabs_Total.ts", 'mergeSame: ["part"]'),
|
||
("B09_Estimation/B09_Estimation_UI_Tabs_Bill.ts", 'group: (i) => rows[i]?.kind !== "item"'),
|
||
("B09_Estimation/B09_Estimation_UI_Shell.ts", "mergeSame: spec.mergeSame"),
|
||
]
|
||
|
||
|
||
def test_B08_B09_적용():
|
||
for path, text in APPLIED:
|
||
assert text in (ROOT / path).read_text("utf-8"), (path, text)
|
||
# 옛 글 빈칸 지우기 · 앞 빈칸 들여쓰기는 없앰(병합 · level 로 갈음)
|
||
mat = (ROOT / "B08_Quantity/B08_Quantity_UI_Material.ts").read_text("utf-8")
|
||
assert '=== row.name ? ""' not in mat and '=== row.group ? ""' not in mat
|
||
labor = (ROOT / "B09_Estimation/B09_Estimation_UI_Tabs_Labor.ts").read_text("utf-8")
|
||
assert 'const CHILD = "ㄴ "' in labor and '" 합 계"' not in labor
|
||
# 셸 칸 → 열 = cellColumn(병합 표에서 cellIndex 가 밀림)
|
||
shell = (ROOT / "B09_Estimation/B09_Estimation_UI_Shell.ts").read_text("utf-8")
|
||
assert ".cellIndex" not in shell and shell.count("cellColumn(") == 2
|