Files
Aislo/resources/tester/test_m02_area_from_drawing_place.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

174 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""도면 → 면적산출 넣을 자리 고르기(PLAN 105-1) — Node 로 판정 · 화면은 ORCA.
못박는 것
- 받은 뒤 작은 창 [새 시트] / [고른 칸부터] · 취소면 아무것도 안 함.
- [고른 칸부터] = 손잡이 `pasteTable`(붙여넣기 길) 한 번 · 표 → `<table>` 글(병합 = rowspan · colspan · 글 이스케이프).
- 덮을 자리에 값 · 알약 이은 칸이 있으면 물음 · 없으면 안 물음 · 물음 [취소]면 안 붙임.
- 덮을 자리 셈 `pasteHits`(스프레드시트 부품) — 값 · 식 칸 수 · 알약 칸 수(빈 값 · 범위 밖은 뺌).
"""
import json
import shutil
import subprocess
import tempfile
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
ENTRY = ROOT / "resources" / "tester" / "spreadsheet" / "spreadsheet_paste_hits_entry.ts"
VITE = ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"
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 asked = [];
let answer = true;
const stubs = {
"@ui/ui_template_elements": {
showToast: (m, k) => toasts.push([k, m]),
showConfirmDialog: async (m, ok) => (asked.push([m, ok]), answer),
},
};
const box = { exports: {} };
new Function("exports", "module", "require", js)(box.exports, box, (n) => stubs[n] ?? require(n));
const { tableToHtml, importDrawingTable } = box.exports;
const table = {
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 book = { 시트: [{ id: "s1", 이름: "면적" }] };
(async () => {
const out = { html: tableToHtml(table) };
const calls = [];
let hit = { 칸: 0, 연결: 0, 범위: "C3:E5" };
const handle = {
getDoc: () => book,
addSheet: (s) => calls.push(["addSheet", s.이름]),
pasteTable: async (html, check) => {
calls.push(["pasteTable", html === out.html]);
return (await check(hit)) ? hit.범위 : null;
},
};
const sizes = [];
const pick = (got) => async (rows, cols) => (sizes.push([rows, cols]), got);
out.cancel = await importDrawingTable(handle, table, pick(null));
out.clean = await importDrawingTable(handle, table, pick("cell"));
out.askedClean = asked.length;
hit = { 칸: 2, 연결: 1, 범위: "C3:E5" };
out.overwrite = await importDrawingTable(handle, table, pick("cell"));
answer = false;
out.refused = await importDrawingTable(handle, table, pick("cell"));
out.sheet = await importDrawingTable(handle, table, pick("sheet"));
out.noPaste = await importDrawingTable({ getDoc: () => book, addSheet: () => 0 }, table, pick("cell"));
Object.assign(out, { calls, sizes, asked, 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_표는_html_표로(run):
assert run["html"] == (
"<table>"
'<tr><td>구분</td><td colspan="2">수량</td></tr>'
'<tr><td rowspan="2">흙&lt;깎기&gt;</td><td>1500</td><td>m3</td></tr>'
"<tr><td>2.5</td><td></td></tr>"
"</table>"
)
def test_넣을_자리_고르기(run):
assert run["sizes"][0] == [3, 3] # 창 글 — 3행 × 3열
assert run["cancel"] is None # 취소 = 아무것도 안 함
assert run["clean"] == "C3:E5" and run["askedClean"] == 0 # 빈 자리 = 안 물음
assert run["overwrite"] == "C3:E5" # 덮음 [덮어쓰기]
assert run["refused"] is None # 덮음 [취소]
assert run["sheet"] == "도면에서 1"
assert run["noPaste"] is None
assert run["calls"] == [
["pasteTable", True],
["pasteTable", True],
["pasteTable", True],
["addSheet", "도면에서 1"],
]
def test_덮으면_물음(run):
assert len(run["asked"]) == 2
message, ok = run["asked"][0]
assert "C3:E5" in message and "값 있는 칸 2개" in message and "알약 이은 칸 1개" in message
assert ok == "덮어쓰기"
kinds = [kind for kind, _ in run["toasts"]]
assert kinds == ["success", "success", "success", "error"]
assert "C3:E5" in run["toasts"][0][1]
def test_손잡이와_붙여넣기_길():
sheet = (ROOT / "A00_Common/spreadsheet/spreadsheet.ts").read_text(encoding="utf-8")
assert "pasteTable: (html, check) => pasteTableAt(ctx, html, check)," in sheet
clip = (ROOT / "A00_Common/spreadsheet/spreadsheet_clipboard.ts").read_text(encoding="utf-8")
body = clip.split("export async function pasteTableAt")[1].split("\n}\n")[0]
assert "parseClipboard(html, " in body and "pasteBlock(ctx, block, at)" in body
@pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
def test_덮을_자리_셈():
with tempfile.TemporaryDirectory(prefix="spreadsheet_paste_hits_") as outdir:
entry_rel = "../" + str(ENTRY.relative_to(ROOT)).replace("\\", "/")
build = subprocess.run(
[
"node",
str(VITE),
"build",
"--configLoader",
"runner",
"--ssr",
entry_rel,
"--outDir",
outdir,
],
cwd=str(ROOT),
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=120,
)
assert build.returncode == 0, f"vite ssr 번들 실패:\n{build.stdout}\n{build.stderr}"
got = subprocess.run(
["node", str(Path(outdir) / "spreadsheet_paste_hits_entry.js")],
capture_output=True,
text=True,
encoding="utf-8",
errors="replace",
timeout=60,
)
assert got.stdout.strip(), got.stderr
result = json.loads(got.stdout)
assert result["fail"] == 0, result["failures"]
assert result["pass"] == 4