Files
Aislo/resources/tester/test_m02_area_from_drawing.py
T
eomsangdonandClaude Opus 5.5 01d819bfb6 feat(m02): 도면 → 면적 칸 크기 기본 · 넣을 자리 고르기(105-1)
- 도면 표 받은 뒤 작은 창 [새 시트] / [고른 칸부터] · 칸 크기 · 글 크기는 기본(도면 mm 안 옮김)
- 스프레드시트 공개 손잡이 pasteTable — 붙여넣기 길(parseClipboard + pasteBlock) · 덮을 값 · 알약 칸 셈(pasteHits)
- 값 · 알약 이은 칸을 덮으면 물음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015KYZ2HWWdUwZWYnn3tQz7X
2026-10-08 16:15:54 +09:00

137 lines
5.5 KiB
Python

"""도면 → 면적산출 한 번 옮기기(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