- 그림칸 = 양식 drawing_area 그대로(여백 2 % 없앰) · 도각 편집이 영역 싣고 [완료] 때 저장 - 도면 응답 guide(작도 영역 도면 좌표) · M02 구조물 도각 응답 area - CAD 그리기 겹판 점선 사각형(저장 · DXF · 인쇄 제외) · 모서리 · 변 끌기(도각 편집 · M02 양식만) - 숫자 칸 공용 부품(왼쪽 · 위 · 폭 · 높이 mm) — M02 양식 · B07 도각 편집 · 끌기와 한 벌 - B08 구조물도 · M02 도면 카드 = 얹은 도각 영역 가이드(보기만) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNMXVCZXMpHpVvqmFkeChL
130 lines
4.6 KiB
Python
130 lines
4.6 KiB
Python
"""169-1 · 169-3 작도 영역 숫자 칸(`cad_host_area.ts`) · B07 도각 편집(`cad_host_frame_edit.ts`)
|
|
— Node 로 TS 를 돌려 판정(가짜 DOM).
|
|
|
|
못박는 것
|
|
- 칸 = 왼쪽 · 위 · 폭 · 높이 ↔ 저장 꼴 [x0, y0, x1, y1] · 폭 · 높이 0 이하 거절(그 칸만 짚음).
|
|
- 도각 편집 = 서버 영역을 칸 · 가이드(끌기 켬)로 싣고 · 끌기 → 칸 · 칸 → CAD 갈기 · [완료] 때 영역 실어 저장.
|
|
"""
|
|
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
SCRIPT = r"""
|
|
const fs = require("fs");
|
|
const root = process.argv[1];
|
|
const ts = require(root + "/config/node_modules/typescript");
|
|
const load = (file, stubs) => {
|
|
const src = fs.readFileSync(root + "/" + file, "utf8");
|
|
const js = ts.transpileModule(src, {
|
|
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
|
}).outputText;
|
|
const box = { exports: {} };
|
|
new Function("exports", "module", "require", js)(box.exports, box, (n) => stubs[n] ?? require(n));
|
|
return box.exports;
|
|
};
|
|
const node = () => ({
|
|
kids: [], hidden: false, textContent: "", className: "", disabled: false, on: {},
|
|
classList: { add() {} }, append(...x) { this.kids.push(...x); },
|
|
addEventListener(k, f) { this.on[k] = f; },
|
|
});
|
|
globalThis.document = { createElement: node };
|
|
const inputs = [];
|
|
const errors = [];
|
|
const elements = {
|
|
showToast: () => {},
|
|
createButton: (o) => Object.assign(node(), { click: o.onClick }),
|
|
createInputField: (o) => {
|
|
const input = Object.assign(node(), { value: o.value });
|
|
const index = inputs.push(input) - 1;
|
|
return { root: node(), input, setError: (e) => (errors[index] = e) };
|
|
},
|
|
};
|
|
const area = load("ui_template/cad_host/cad_host_area.ts", { "@ui/ui_template_elements": elements });
|
|
const edit = load("ui_template/cad_host/cad_host_frame_edit.ts", {
|
|
"@ui/ui_template_elements": elements,
|
|
"./cad_host_area": area,
|
|
});
|
|
|
|
(async () => {
|
|
const out = {};
|
|
out.toFields = area.areaToFields([42, 47, 812, 567]);
|
|
out.toArea = area.fieldsToArea([42, 567, 770, 520]);
|
|
out.zero = area.fieldsToArea([0, 10, 0, 5]);
|
|
|
|
const loads = [];
|
|
const guides = [];
|
|
const saved = [];
|
|
const editor = edit.createFrameTemplateEditor({
|
|
api: {
|
|
fetch: async () => ({ drawing: { entities: [], drawing_area: [10, 20, 410, 320] }, customized: false }),
|
|
save: async (drawing) => saved.push(drawing),
|
|
importFile: async () => ({ drawing: { entities: [] }, entity_count: 0 }),
|
|
reset: async () => {},
|
|
},
|
|
sendLoad: (drawing, meta, frameEdit, fields, extra) => loads.push(extra),
|
|
setGuideArea: (a) => guides.push(a),
|
|
requestCadDrawing: async () => ({ entities: [{ id: "x" }] }),
|
|
restoreDrawing: () => {},
|
|
onSaved: () => {},
|
|
currentDrawingInfo: () => null,
|
|
});
|
|
const fields = inputs.slice(-4);
|
|
await editor.button.click();
|
|
out.loaded = loads[0];
|
|
out.shown = fields.map((f) => f.value);
|
|
editor.guideChanged([10, 20, 300.04, 320]);
|
|
out.dragged = fields.map((f) => f.value);
|
|
fields[3].value = "100";
|
|
fields[3].on.input();
|
|
out.typed = guides.slice();
|
|
fields[2].value = "0";
|
|
fields[2].on.input();
|
|
out.zeroError = errors.slice(-4);
|
|
fields[2].value = "290";
|
|
fields[2].on.input();
|
|
await editor.banner.kids[3].kids[0].click(); // [완료]
|
|
out.saved = saved;
|
|
console.log(JSON.stringify(out));
|
|
})();
|
|
"""
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def result() -> dict:
|
|
node = shutil.which("node")
|
|
if not node:
|
|
pytest.skip("node 없음")
|
|
proc = subprocess.run(
|
|
[node, "-e", SCRIPT, str(ROOT)], capture_output=True, text=True, encoding="utf-8"
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return json.loads(proc.stdout.strip().splitlines()[-1])
|
|
|
|
|
|
def test_fields_round_trip(result):
|
|
assert result["toFields"] == [42, 567, 770, 520]
|
|
assert result["toArea"] == [42, 47, 812, 567]
|
|
assert result["zero"] is None
|
|
|
|
|
|
def test_frame_edit_loads_area_and_guide(result):
|
|
assert result["loaded"] == {"guide": {"area": [10, 20, 410, 320], "editable": True}}
|
|
assert result["shown"] == ["10", "320", "400", "300"]
|
|
# 끌기 → 칸(0.1 mm)
|
|
assert result["dragged"] == ["10", "320", "290", "300"]
|
|
|
|
|
|
def test_typing_updates_guide_and_flags_zero(result):
|
|
# 높이 100 → [10, 220, 300, 320] (끌기 값 300.04 는 칸에서 300 으로)
|
|
assert result["typed"] == [[10, 220, 300, 320]]
|
|
assert result["zeroError"][2] == "0 보다 커야 함" and result["zeroError"][3] is None
|
|
|
|
|
|
def test_finish_saves_area_with_drawing(result):
|
|
assert result["saved"] == [{"entities": [{"id": "x"}], "drawing_area": [10, 220, 300, 320]}]
|