"""도면 → 면적산출 한 번 옮기기(PLAN 83-3 · `M02_MasterTemplete_AreaFromDrawing.ts`) — Node 로 TS 를 돌려 판정. 못박는 것 - 표 자료(CAD `table-from-drawing.ts` 꼴) → 새 시트 「도면에서 n」(안 쓴 가장 작은 n · 안 쓴 id) · 기존 시트 안 덮음. - 숫자 칸 = 수 · 빈 칸 안 적음 · 병합 = A1 범위 · 칸 크기 · 글 크기는 기본(도면 mm 안 옮김 · 105-1). - [새 시트] 고름 → 공개 손잡이 addSheet(시트, {activate}) 한 번 · 손잡이 없으면 안 옮기고 알림. - 패널 메시지 이름 = CAD `aislo-table-export.ts` 와 같음 · 읽기 면적은 보내기 단추 없음 · 시트 2장 이상이면 탭 줄. """ import json import re import shutil import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] M02 = ROOT / "M02_MasterTemplete" SCRIPT = r""" const fs = require("fs"); const ts = require(process.argv[1] + "/config/node_modules/typescript"); const src = fs.readFileSync(process.argv[1] + "/M02_MasterTemplete/M02_MasterTemplete_AreaFromDrawing.ts", "utf8"); const js = ts.transpileModule(src, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const toasts = []; const stubs = { "@ui/ui_template_elements": { showToast: (m, k) => toasts.push([k, m]) } }; const box = { exports: {} }; new Function("exports", "module", "require", js)(box.exports, box, (n) => stubs[n] ?? require(n)); const { tableToSheet, nextSheetName, importDrawingTable } = box.exports; (async () => { const sheetPick = async () => "sheet"; const book = { 시트: [{ id: "s1", 이름: "면적" }, { id: "s2", 이름: "도면에서 1" }] }; const grid = { kind: "grid", rows: [["구분", "수량", ""], ["흙깎기", 1500, "m3"], ["", 2.5, ""]], merges: [{ row: 0, col: 1, rowSpan: 1, colSpan: 2 }, { row: 1, col: 0, rowSpan: 2, colSpan: 1 }], colWidths: [40, 30, 300], rowHeights: [10, 7, 1], }; const text = { kind: "text", rows: [["합계", 3000], ["소계", ""]], merges: [], colWidths: [], rowHeights: [] }; const out = {}; out.gridSheet = tableToSheet(grid, book); out.textSheet = tableToSheet(text, { 시트: [] }); out.names = [nextSheetName({ 시트: [] }), nextSheetName(book), nextSheetName({ 시트: [{ 이름: "도면에서 2" }] })]; const added = []; const handle = { getDoc: () => book, addSheet: (s, o) => added.push([s, o]) }; out.imported = await importDrawingTable(handle, text, sheetPick); out.added = added; out.noHandle = await importDrawingTable({ getDoc: () => book }, grid, sheetPick); out.noSheet = await importDrawingTable(null, grid, sheetPick); out.toasts = toasts; console.log(JSON.stringify(out)); })(); """ @pytest.fixture(scope="module") def run(): node = shutil.which("node") if not node: pytest.skip("node 없음") done = subprocess.run( [node, "-e", SCRIPT, str(ROOT)], capture_output=True, text=True, encoding="utf-8", check=True, ) return json.loads(done.stdout) def test_grid_table_becomes_new_sheet(run): sheet = run["gridSheet"] assert sheet["이름"] == "도면에서 2" # 「도면에서 1」 이 이미 있음 assert sheet["id"] == "s3" assert sheet["칸"] == { "A1": {"값": "구분"}, "B1": {"값": "수량"}, "A2": {"값": "흙깎기"}, "B2": {"값": 1500}, "C2": {"값": "m3"}, "B3": {"값": 2.5}, } assert sheet["병합"] == ["B1:C1", "A2:A3"] # 칸 크기 · 글 크기 = 기본(105-1) — 도면 mm(폭 · 높이)을 안 옮김 assert "열" not in sheet and "행" not in sheet def test_text_fallback_has_no_sizes(run): sheet = run["textSheet"] assert sheet["이름"] == "도면에서 1" and sheet["id"] == "s1" assert sheet["칸"] == {"A1": {"값": "합계"}, "B1": {"값": 3000}, "A2": {"값": "소계"}} assert "병합" not in sheet and "열" not in sheet and "행" not in sheet def test_sheet_names(run): assert run["names"] == ["도면에서 1", "도면에서 2", "도면에서 1"] def test_import_uses_add_sheet_once(run): assert run["imported"] == "도면에서 2" assert len(run["added"]) == 1 sheet, opts = run["added"][0] assert sheet["이름"] == "도면에서 2" and opts == {"activate": True} assert run["noHandle"] is None and run["noSheet"] is None kinds = [kind for kind, _ in run["toasts"]] assert kinds == [ "success", "info", "error", "error", ] # 옮김 · 글 폴백 확인 · 손잡이 없음 · 시트 없음 assert "「도면에서 2」" in run["toasts"][0][1] def test_panel_wiring_matches_cad(): cad = (ROOT / "B07_DesignDetail/openwebcad/src/integration/aislo-table-export.ts").read_text( encoding="utf-8" ) panel = (M02 / "M02_MasterTemplete_AreaDrawing.ts").read_text(encoding="utf-8") def const(text, name): return re.search(rf"{name} = ['\"]([^'\"]+)['\"]", text).group(1) assert const(cad, "AISLO_EXPORT_TABLE_REQUEST_MESSAGE") == const(panel, "EXPORT_TABLE_REQUEST") assert const(cad, "AISLO_EXPORT_TABLE_MESSAGE") == const(panel, "EXPORT_TABLE") assert "readOnly: true" in panel and "preview: true" not in panel # 보기 전용 + 고르기 apanel = (M02 / "M02_MasterTemplete_AreaPanel.ts").read_text(encoding="utf-8") assert "onTable: opts.readOnly ? undefined" in apanel # 읽기 면적 = 단추 없음 main = (M02 / "M02_MasterTemplete_UI_Main.ts").read_text(encoding="utf-8") assert "tabs: ((doc as { 시트?: unknown[] }).시트?.length ?? 0) > 1" in main