Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy
443 lines
20 KiB
Python
443 lines
20 KiB
Python
"""PLAN 302-1 — 인쇄 미리보기 틀(초안 길 등록 · 서버 표 PDF · 엑셀 · 표 → 서버 이음매)."""
|
|
|
|
import io
|
|
import json
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from fastapi import APIRouter, FastAPI
|
|
from fastapi.testclient import TestClient
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from B09_Estimation import B09_Estimation_Router as b09 # noqa: E402
|
|
from B09_Estimation.draft import B09_Draft_Print_Router as pr # noqa: E402
|
|
|
|
DRAFT = ROOT / "B09_Estimation/draft"
|
|
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
|
|
PID = "00000000-0000-0000-0000-000000000001"
|
|
|
|
|
|
def _text(path: Path) -> str:
|
|
return path.read_text("utf-8")
|
|
|
|
|
|
def _client() -> TestClient:
|
|
app = FastAPI()
|
|
app.include_router(pr.router, prefix="/api/projects")
|
|
return TestClient(app)
|
|
|
|
|
|
def _ts(expr: str) -> object:
|
|
"""표 짜기(공용 표 + 인쇄 표) TS 를 노드로 — expr 결과 JSON."""
|
|
js = "".join(
|
|
IMPORTS.sub("", _text(p))
|
|
for p in (
|
|
ROOT / "ui_template/ui_template_data_table.ts",
|
|
DRAFT / "B09_Draft_Print_Table.ts",
|
|
)
|
|
)
|
|
run = subprocess.run(
|
|
["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript"],
|
|
input=js + f"\nconsole.log(JSON.stringify({expr}));",
|
|
capture_output=True, text=True, encoding="utf-8", timeout=60,
|
|
) # fmt: skip
|
|
assert run.returncode == 0, run.stderr
|
|
return json.loads(run.stdout)
|
|
|
|
|
|
#: 머리 두 줄(묶음) · 숫자 열 · 레벨 · 세로 병합 · 합계 · 제목 줄
|
|
TABLE = """{
|
|
columns: [
|
|
{ key: "kind", label: "공종" },
|
|
{ key: "name", label: "이름", width: 120 },
|
|
{ key: "q", label: "수량", group: ["물량"], kind: "number", decimals: 2 },
|
|
{ key: "a", label: "금액", group: ["물량"], kind: "number" },
|
|
],
|
|
rows: [
|
|
{ kind: "토공", name: "토공", q: null, a: null },
|
|
{ kind: "토공", name: "터파기", q: 12.5, a: 1500 },
|
|
{ kind: "토공", name: "되메우기 <A&B>", q: 3, a: 300 },
|
|
{ kind: "포장", name: "포장", q: 1234.567, a: 2000000 },
|
|
],
|
|
total: { name: "합계", a: 2001800 },
|
|
frozen: 1,
|
|
mergeSame: ["kind"],
|
|
level: { key: "name", of: (i) => (i === 0 ? 1 : 2) },
|
|
rowClass: (i) => (i === 0 ? "is-title" : null),
|
|
onRowClick: () => {},
|
|
}"""
|
|
|
|
|
|
def _sheets():
|
|
return _ts(
|
|
f"(() => {{ const t = {TABLE}; return ["
|
|
"{name: '갑 / 수량[1]', orientation: 'portrait', html: sheetHtml({id:'a', name:'갑', table:t})," # noqa: E501
|
|
" grid: sheetGrid(t)},"
|
|
"{name: '을: 원가표가 아주 아주 길어 서른한 자를 넘는 이름입니다만', orientation: 'landscape'," # noqa: E501
|
|
" html: sheetHtml({id:'b', name:'을', table:t, images:[{src:'data:image/png;base64,iVBORw0KGgo='," # noqa: E501
|
|
" caption:'그림 <1>'}]}), grid: sheetGrid(t)}]; })()"
|
|
)
|
|
|
|
|
|
# ── 길 등록 ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
def test_초안_길은_선언으로_등록_정적_표엔_이름_없음():
|
|
router = _text(ROOT / "A00_Common/router.ts")
|
|
assert (
|
|
'import.meta.glob<{ ROUTE: DraftRoute }>("../*/draft/*_Route.ts", { eager: true })'
|
|
in router
|
|
)
|
|
assert "routeTable[route] ?? draft?.load" in router
|
|
assert "draft?.workflow" in router and "draft?.project" in router
|
|
assert "draftRoute(currentRoute())?.footerless" in _text(ROOT / "A00_Common/main.ts")
|
|
route = _text(DRAFT / "B09_Draft_Print_Route.ts")
|
|
assert 'path: "b09-print"' in route and "workflow: true" in route and "project: true" in route
|
|
# 가짜 대조 — 정적 표 · 셸 · 문구에 초안 이름이 없음(폴더째 지우면 길이 함께 사라짐)
|
|
for rel in (
|
|
"config/config_frontend.ts", "A00_Common/router.ts", "A00_Common/main.ts",
|
|
"ui_template/ui_template_loupe.ts", "B09_Estimation/B09_Estimation_UI_Shell.ts",
|
|
"B09_Estimation/B09_Estimation_UI_Tabs.ts",
|
|
): # fmt: skip
|
|
body = _text(ROOT / rel)
|
|
assert "b09-print" not in body and "Draft_Print" not in body, rel
|
|
# 돋보기 · 기본 메뉴 막음 = 주소 꼴(b0x-)로 함께 걸림
|
|
assert re.search(r"/\^\(b0\[1-9\]", _text(ROOT / "ui_template/ui_template_loupe.ts"))
|
|
|
|
|
|
def test_서버_초안_길_끼우기_폴더_없으면_길_없음(tmp_path, monkeypatch):
|
|
paths = {r.path for r in b09.router.routes}
|
|
assert "/api/projects/{project_id}/print/table-pdf" in paths
|
|
assert "/api/projects/{project_id}/print/table-xlsx" in paths
|
|
# 가짜 폴더 — 비면 아무것도 안 끼움 · *_Router.py 가 있으면 그 길만 · 깨진 초안은 건너뜀
|
|
pkg = tmp_path / "fakepkg"
|
|
(pkg / "draft").mkdir(parents=True)
|
|
(pkg / "__init__.py").write_text("")
|
|
(pkg / "draft/__init__.py").write_text("")
|
|
monkeypatch.syspath_prepend(str(tmp_path))
|
|
empty = APIRouter()
|
|
assert b09.include_drafts(empty, pkg / "draft", "fakepkg.draft") == []
|
|
assert empty.routes == []
|
|
(pkg / "draft/X_Router.py").write_text(
|
|
"from fastapi import APIRouter\nrouter = APIRouter()\n"
|
|
"@router.get('/x')\ndef x():\n return 1\n"
|
|
)
|
|
(pkg / "draft/Y_Router.py").write_text("import 없는_모듈\n")
|
|
got = APIRouter()
|
|
assert b09.include_drafts(got, pkg / "draft", "fakepkg.draft") == ["X_Router"]
|
|
assert [r.path for r in got.routes] == ["/x"]
|
|
# 초안이 같은 머리를 스스로 달았으면 한 번만(도면 302-2 꼴)
|
|
(pkg / "draft/Z_Router.py").write_text(
|
|
"from fastapi import APIRouter\nrouter = APIRouter(prefix='/api/projects')\n"
|
|
"@router.get('/{project_id}/z')\ndef z():\n return 1\n"
|
|
)
|
|
head = APIRouter(prefix="/api/projects")
|
|
assert b09.include_drafts(head, pkg / "draft", "fakepkg.draft") == ["X_Router", "Z_Router"]
|
|
assert {r.path for r in head.routes} == {"/api/projects/x", "/api/projects/{project_id}/z"}
|
|
assert [r.path for r in sys.modules["fakepkg.draft.Z_Router"].router.routes] == [
|
|
"/api/projects/{project_id}/z"
|
|
] # 원본 그대로(앱에 바로 붙이는 시험)
|
|
assert (
|
|
"/api/projects/{project_id}/print/drawings-pdf" in paths
|
|
or not (DRAFT / "B09_Draft_Print_Drawings_Router.py").exists()
|
|
)
|
|
|
|
|
|
def test_로그인_회사_권한은_B09_길과_같음():
|
|
main = _text(ROOT / "main.py")
|
|
assert "app.include_router(b09_estimation_router, dependencies=protected_with_company)" in main
|
|
assert "Depends(require_project_access)" in main
|
|
# 초안 router 는 머리 · 의존성 없이 B09 router 안으로만 들어감
|
|
assert "APIRouter()" in _text(DRAFT / "B09_Draft_Print_Router.py")
|
|
|
|
|
|
# ── 서버 표 출력 ──────────────────────────────────────────────────────────
|
|
|
|
|
|
def test_table_pdf_쪽_방향_시트_이름_쪽_번호():
|
|
pymupdf = pytest.importorskip("pymupdf")
|
|
sheets = _sheets()
|
|
res = _client().post(
|
|
f"/api/projects/{PID}/print/table-pdf",
|
|
json={"title": "수량산출", "sheets": [{k: s[k] for k in ("name", "orientation", "html")}
|
|
for s in sheets]},
|
|
) # fmt: skip
|
|
if res.status_code == 503:
|
|
pytest.skip(res.json()["detail"]) # 서버에 Edge · Chrome 없음
|
|
assert res.status_code == 200, res.text
|
|
assert res.content[:5] == b"%PDF-" and res.headers["content-type"] == "application/pdf"
|
|
doc = pymupdf.open(stream=res.content)
|
|
assert doc.page_count == 2
|
|
p1, p2 = doc[0], doc[1]
|
|
assert p1.rect.width < p1.rect.height and p2.rect.width > p2.rect.height # 시트마다 방향
|
|
t1, t2 = p1.get_text(), p2.get_text()
|
|
assert "갑 / 수량[1]" in t1 and "을: 원가표" in t2 # 쪽 머리 = 시트 이름
|
|
assert "1 / 2" in t1 and "2 / 2" in t2 # 쪽 번호
|
|
assert "되메우기 <A&B>" in t1 and "1,234.57" in t1 and "2,001,800" in t1 # 화면과 같은 글
|
|
assert "그림 <1>" in t2
|
|
|
|
|
|
def test_table_pdf_넓은_표는_쪽_폭에_맞춤_긴_표는_머리_되풀이():
|
|
pymupdf = pytest.importorskip("pymupdf")
|
|
rows = "".join(f"<tr><td>줄{i}</td><td data-num>{i}</td></tr>" for i in range(120))
|
|
html = (
|
|
'<table class="ui-data-table__table" style="--dt-w:3000px"><colgroup>'
|
|
'<col style="width:1500px"><col style="width:1500px"></colgroup>'
|
|
f"<thead><tr><th>이름</th><th>값</th></tr></thead><tbody>{rows}</tbody></table>"
|
|
)
|
|
doc = pr.document_html("t", [{"name": "넓은 표", "orientation": "portrait", "html": html}])
|
|
try:
|
|
data = pr.spawn_pdf(doc)
|
|
except pr.PrintError as error:
|
|
pytest.skip(str(error))
|
|
pdf = pymupdf.open(stream=data)
|
|
words = [w for p in pdf for w in p.get_text("words")]
|
|
assert max(w[2] for w in words) <= pdf[0].rect.width # 오른쪽이 쪽 밖으로 안 나감
|
|
texts = [p.get_text() for p in pdf]
|
|
assert "줄119" in texts[-1]
|
|
assert all("넓은 표" in t and "이름" in t for t in texts) # 쪽마다 머리
|
|
|
|
|
|
def test_table_pdf_바깥_요청_막음():
|
|
html = '<table><thead></thead></table><img src="http://127.0.0.1:9/x.png">'
|
|
doc = pr.document_html("t", [{"name": "s", "orientation": "portrait", "html": html}])
|
|
assert "<script" not in pr.PRINT_CSS
|
|
try:
|
|
assert pr.spawn_pdf(doc)[:5] == b"%PDF-" # 막힌 그림 = 빈 칸 · PDF 는 나옴
|
|
except pr.PrintError as error:
|
|
pytest.skip(str(error))
|
|
|
|
|
|
def _assert_print(wb, orientations: list[str], heads: list[int]) -> None:
|
|
"""엑셀 인쇄 설정(302-5) — 시트 방향 = PDF · A4 · 폭 1쪽 · 높이 자유 · 머리 줄 반복."""
|
|
assert len(wb.worksheets) == len(orientations)
|
|
for ws, orient, head in zip(wb.worksheets, orientations, heads):
|
|
ps = ws.page_setup
|
|
assert ps.orientation == orient, ws.title
|
|
assert int(ps.paperSize) == 9 # A4
|
|
assert (int(ps.fitToWidth), int(ps.fitToHeight)) == (1, 0), ws.title
|
|
assert ws.sheet_properties.pageSetUpPr.fitToPage is True
|
|
assert ws.print_title_rows.replace("$", "") == f"1:{head}", ws.title
|
|
assert ws.page_margins.left < 0.5 and ws.page_margins.bottom < 0.6
|
|
|
|
|
|
def test_table_xlsx_시트_차례_이름_머리_병합_숫자_칸():
|
|
from openpyxl import load_workbook
|
|
|
|
sheets = _sheets()
|
|
keys = ("name", "orientation", "grid")
|
|
body = {"title": "수량", "sheets": [{k: s[k] for k in keys} for s in sheets]}
|
|
body["sheets"].append({"name": "갑 _ 수량_1_", "grid": sheets[0]["grid"]}) # 같은 이름
|
|
res = _client().post(f"/api/projects/{PID}/print/table-xlsx", json=body)
|
|
assert res.status_code == 200, res.text
|
|
wb = load_workbook(io.BytesIO(res.content))
|
|
_assert_print(wb, [*[s["orientation"] for s in sheets], "portrait"], [2, 2, 2])
|
|
names = wb.sheetnames
|
|
assert names[0] == "갑 _ 수량_1_" # 금지 글자 = _
|
|
assert (
|
|
names[1] == "을_ 원가표가 아주 아주 길어 서른한 자를 넘는 이름입니다만"[:31]
|
|
and len(names[1]) == 31
|
|
)
|
|
assert names[2] == "갑 _ 수량_1_ (2)"
|
|
ws = wb[names[0]]
|
|
merged = {str(m) for m in ws.merged_cells.ranges}
|
|
# 머리 두 줄 — 「물량」 = 옆 둘 · 공종 · 이름 = 아래 둘 / 본문 공종 「토공」 세 줄 세로 병합
|
|
assert {"C1:D1", "A1:A2", "B1:B2", "A3:A5"} <= merged
|
|
assert (ws["C1"].value, ws["C2"].value, ws["A1"].value) == ("물량", "수량", "공종")
|
|
assert ws["A1"].font.b and ws["C2"].font.b # 머리 굵게
|
|
assert (
|
|
ws["C4"].value == 12.5
|
|
and ws["C4"].data_type == "n"
|
|
and ws["C4"].number_format == "#,##0.00"
|
|
)
|
|
assert ws["D6"].value == 2000000 and ws["D6"].number_format == "#,##0"
|
|
assert ws["B5"].value == "되메우기 <A&B>" and ws["C3"].value is None
|
|
assert ws["B4"].alignment.indent == 1 and ws["B3"].font.b # 레벨 2 들여씀 · 제목 줄 굵게
|
|
assert (
|
|
ws["B7"].value == "합계" and ws["D7"].value == 2001800 and ws["D7"].font.b
|
|
) # 합계 = 끝 줄
|
|
assert ws["A3"].border.left.style == "thin"
|
|
assert ws.freeze_panes == "B3"
|
|
|
|
|
|
def test_table_xlsx_꼴_틀리면_400():
|
|
res = _client().post(
|
|
f"/api/projects/{PID}/print/table-xlsx", json={"sheets": [{"name": "a", "grid": {}}]}
|
|
)
|
|
assert res.status_code == 400
|
|
res = _client().post(f"/api/projects/{PID}/print/table-pdf", json={"sheets": []})
|
|
assert res.status_code == 422
|
|
|
|
|
|
def test_시트_이름_다듬기():
|
|
used: set[str] = set()
|
|
assert pr.sheet_title("a/b\\c?d*e[f]g:h", used) == "a_b_c_d_e_f_g_h"
|
|
assert pr.sheet_title("", used) == "시트"
|
|
assert pr.sheet_title("A_B_C_D_E_F_G_H", used) == "A_B_C_D_E_F_G_H (2)" # 대소문자 같은 이름
|
|
|
|
|
|
# ── 영역 이음매 — 원가계산(302-4) 쪽 → 페이지와 같은 서버 몸 → 표 PDF · 엑셀 ────────────
|
|
|
|
ENTRY = ROOT / "resources/tester/print_frame/print_frame_entry.ts"
|
|
VITE = ROOT / "config/node_modules/vite/bin/vite.js"
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def cost_bodies(tmp_path_factory) -> dict:
|
|
sys.path.insert(0, str(ROOT / "resources/tester"))
|
|
import test_302_4_print_cost as t302_4
|
|
|
|
if shutil.which("node") is None:
|
|
pytest.skip("node 가 없음")
|
|
work = tmp_path_factory.mktemp("p302_1")
|
|
root = t302_4.t145._project(work / "proj", [(100, "SS000901"), (400, "SS000902")])
|
|
data = work / "payload.json"
|
|
data.write_text(json.dumps(t302_4._payload(root), ensure_ascii=False, default=str), "utf-8")
|
|
out = work / "build"
|
|
entry_rel = "../" + str(ENTRY.relative_to(ROOT)).replace("\\", "/")
|
|
build = subprocess.run(
|
|
["node", str(VITE), "build", "--configLoader", "runner", "--ssr", entry_rel,
|
|
"--outDir", str(out)],
|
|
cwd=str(ROOT), capture_output=True, text=True, encoding="utf-8", errors="replace",
|
|
timeout=180,
|
|
) # fmt: skip
|
|
assert build.returncode == 0, f"vite ssr 번들 실패:\n{build.stdout}\n{build.stderr}"
|
|
run = subprocess.run(
|
|
["node", str(out / "print_frame_entry.js"), str(data)],
|
|
capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=60,
|
|
) # fmt: skip
|
|
assert run.stdout.strip(), run.stderr
|
|
return json.loads(run.stdout)
|
|
|
|
|
|
def test_원가_영역_엑셀_18장_차례_그대로(cost_bodies):
|
|
from openpyxl import load_workbook
|
|
|
|
body = cost_bodies["xlsx"]
|
|
assert len(body["sheets"]) == 18
|
|
res = _client().post(f"/api/projects/{PID}/print/table-xlsx", json=body)
|
|
assert res.status_code == 200, res.text
|
|
wb = load_workbook(io.BytesIO(res.content))
|
|
assert wb.sheetnames == [s["name"] for s in body["sheets"]] # 이름 31자 안 · 차례 그대로
|
|
first = wb[wb.sheetnames[0]]
|
|
assert first.max_row > body["sheets"][0]["grid"]["head_rows"] # 값 줄이 들어감
|
|
assert any(c.data_type == "n" for row in first.iter_rows() for c in row if c.value is not None)
|
|
_assert_print( # 엑셀 인쇄 방향 = 그 시트 PDF 방향(302-5)
|
|
wb,
|
|
[s["orientation"] for s in cost_bodies["pdf"]["sheets"]],
|
|
[s["grid"]["head_rows"] for s in body["sheets"]],
|
|
)
|
|
|
|
|
|
def test_원가_영역_PDF_시트_이름_가로(cost_bodies):
|
|
pymupdf = pytest.importorskip("pymupdf")
|
|
body = cost_bodies["pdf"]
|
|
assert all(s["orientation"] == "landscape" and "<table" in s["html"] for s in body["sheets"])
|
|
res = _client().post(f"/api/projects/{PID}/print/table-pdf", json=body)
|
|
if res.status_code == 503:
|
|
pytest.skip(res.json()["detail"])
|
|
assert res.status_code == 200, res.text
|
|
doc = pymupdf.open(stream=res.content)
|
|
assert doc.page_count >= 18
|
|
text = "".join(p.get_text() for p in doc)
|
|
assert all(s["name"] in text for s in body["sheets"])
|
|
assert all(p.rect.width > p.rect.height for p in doc)
|
|
|
|
|
|
def test_앱_전체_길_목록에_겹친_앞머리_없음():
|
|
import main
|
|
|
|
paths = [getattr(r, "path", "") for r in main.app.routes]
|
|
assert not [p for p in paths if "/api/projects/api/projects" in p]
|
|
for tail in ("drawings", "drawings-pdf", "table-pdf", "table-xlsx"):
|
|
assert paths.count(f"/api/projects/{{project_id}}/print/{tail}") == 1, tail
|
|
|
|
|
|
# ── 영역 이음매 — 수량산출(302-3) 쪽 → 페이지와 같은 서버 몸 → 표 PDF · 엑셀 ────────────
|
|
|
|
#: 1×1 PNG — 거리이정표 지도 그림 자리
|
|
TINY_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==" # noqa: E501
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def quantity_bodies() -> dict:
|
|
sys.path.insert(0, str(ROOT / "resources/tester"))
|
|
import test_302_3_print_quantity as t302_3
|
|
|
|
if shutil.which("node") is None:
|
|
pytest.skip("node 가 없음")
|
|
job = {"project": "p302", "responses": t302_3._responses(), "image_b64": TINY_PNG}
|
|
out = subprocess.run(
|
|
["node", str(ROOT / "resources/tester/print_frame/print_quantity_seam.mjs"), str(ROOT)],
|
|
input=json.dumps(job, ensure_ascii=False), cwd=ROOT, capture_output=True, text=True,
|
|
encoding="utf-8", timeout=300,
|
|
) # fmt: skip
|
|
assert out.returncode == 0, out.stderr
|
|
return json.loads(out.stdout.strip().splitlines()[-1])
|
|
|
|
|
|
def test_수량_영역_엑셀_차례_그대로(quantity_bodies):
|
|
from openpyxl import load_workbook
|
|
|
|
body = quantity_bodies["xlsx"]
|
|
assert len(body["sheets"]) >= 20
|
|
res = _client().post(f"/api/projects/{PID}/print/table-xlsx", json=body)
|
|
assert res.status_code == 200, res.text
|
|
wb = load_workbook(io.BytesIO(res.content))
|
|
used: set[str] = set()
|
|
assert wb.sheetnames == [pr.sheet_title(s["name"], used) for s in body["sheets"]]
|
|
assert all(
|
|
wb[n].max_row >= s["grid"]["head_rows"] for n, s in zip(wb.sheetnames, body["sheets"])
|
|
)
|
|
_assert_print(
|
|
wb,
|
|
[s["orientation"] for s in quantity_bodies["pdf"]["sheets"]],
|
|
[s["grid"]["head_rows"] for s in body["sheets"]],
|
|
)
|
|
|
|
|
|
def test_수량_영역_PDF_시트_이름_지도_그림(quantity_bodies):
|
|
pymupdf = pytest.importorskip("pymupdf")
|
|
body = quantity_bodies["pdf"]
|
|
assert any('src="data:image/png' in s["html"] for s in body["sheets"]) # 그림 = data: 주소
|
|
res = _client().post(f"/api/projects/{PID}/print/table-pdf", json=body)
|
|
if res.status_code == 503:
|
|
pytest.skip(res.json()["detail"])
|
|
assert res.status_code == 200, res.text
|
|
doc = pymupdf.open(stream=res.content)
|
|
assert doc.page_count >= len(body["sheets"])
|
|
text = "".join(p.get_text() for p in doc).replace("\n", " ")
|
|
missing = [s["name"] for s in body["sheets"] if s["name"] not in text]
|
|
assert not missing
|
|
assert sum(len(p.get_images()) for p in doc) >= 1 # 거리이정표 지도 그림이 들어감
|
|
|
|
|
|
def test_B09_좌측_하단_단추는_초안_입구에서만():
|
|
ui = _text(DRAFT / "B09_Draft_Print_UI.ts")
|
|
assert "DRAFT.actions.push(" in ui and "navigateTo(ROUTE.path" in ui
|
|
assert 'label: "인쇄 미리보기"' in ui
|
|
shell = _text(ROOT / "B09_Estimation/B09_Estimation_UI_Shell.ts")
|
|
assert 'import.meta.glob("./draft/*_UI.ts", { eager: true })' in shell
|
|
dock = _text(ROOT / "B09_Estimation/B09_Estimation_UI_Dock.ts")
|
|
assert "DRAFT.actions.map((make) => make())" in dock and "Print" not in dock
|
|
|
|
|
|
def test_좌측_하단_두_줄_가로_넘침_없음():
|
|
"""302-5 — 위 = [PDF][엑셀] · 아래 = [?](도움말 줄) [B09 로 돌아가기] · 줄 폭을 나눠 가짐."""
|
|
page = _text(DRAFT / "B09_Draft_Print_Page.ts")
|
|
assert "files.append(pdfBtn, xlsxBtn)" in page
|
|
assert "nav.dataset.helpRow" in page and "nav.append(back)" in page
|
|
assert "actions.append(files, nav)" in page
|
|
css = re.sub(r"\s+", " ", _text(DRAFT / "B09_Draft_Print_Page.css"))
|
|
assert ".b09-print__actions.ui-sidebar-actions { flex-direction: column; }" in css
|
|
btn = css[css.index(".ui-sidebar-actions.b09-print__actions .b09-print__row > .ui-btn") :]
|
|
btn = btn[: btn.index("}")]
|
|
# 공용 단추 줄(min-width: auto = 글보다 안 좁아짐)보다 앞서 이기는 규칙 · 글 줄임표
|
|
assert "flex: 1 1 0" in btn and "min-width: 0" in btn and "text-overflow: ellipsis" in btn
|