"""244-6 B08 거리이정표 추가 고침(사용자 132 · 133번) — 표 폭 = 다이어그램 폭 · 거래처 상자 항목 글 · 좌측 패널 카드 없음(공사지 · 기준점 = 다이어그램 모달 · [기준점 +]) · 팬(가운데 · 빈 곳 왼쪽 끌기 · 원배율에서도) · 화면 글 「업체」 → 「거래처」. 팬은 공용 `A00_Common/b_svg_zoom_pan.ts` + `…_Milestone_Layout.ts`(panStart · panContent)를 node 로 돌려 pointerdown · pointermove · pointerup 사건을 실제로 보내 그림 묶음 transform 이 바뀌는지 잼. """ import json import re import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[2] IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE) UI = ("UI_Milestone.ts", "UI_Milestone_Diagram.ts", "UI_Milestone_Modal.ts", "UI_Milestone_Route.ts", "UI_Milestone_State.ts", "Milestone_Layout.ts") # fmt: skip def _read(rel): return (ROOT / rel).read_text(encoding="utf-8") def _node(script): src = "".join( IMPORTS.sub("", _read(rel)) for rel in ("A00_Common/b_svg_zoom_pan.ts", "B08_Quantity/B08_Quantity_Milestone_Layout.ts") ) # 254 — 글이 길어 명령 줄(Windows 32767자)을 넘지 않게 stdin 으로 넘김 result = subprocess.run( ["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript", "-"], input=src + "\n" + script, capture_output=True, text=True, encoding="utf-8", timeout=60, ) # fmt: skip assert result.returncode == 0, result.stderr return json.loads(result.stdout) PAN = """ const W = 600, H = 400; class Svg extends EventTarget { classList = { add() {}, remove() {} }; setPointerCapture() {} releasePointerCapture() {} getBoundingClientRect() { return { left: 0, top: 0, width: W, height: H }; } } const shape = { closest: (s: string) => (s.includes(".b08-milestone__box") ? {} : null) }; const empty = { closest: () => null }; function fire(svg: EventTarget, type: string, button: number, x: number, y: number, target: unknown) { const ev = new Event(type, { cancelable: true }); for (const [k, v] of Object.entries({ button, clientX: x, clientY: y, pointerId: 1, target })) Object.defineProperty(ev, k, { value: v }); svg.dispatchEvent(ev); } function drag(button: number, target: unknown) { const svg = new Svg(); const layer = { t: "", setAttribute(_k: string, v: string) { this.t = v; } }; attachSvgZoomPan({ svg: svg as any, layer: layer as any, widthPx: W, heightPx: H, pad: { left: 0, right: 0, top: 0, bottom: 0 }, wheelZoom: true, content: panContent(W, H), panStart }); fire(svg, "pointerdown", button, 100, 100, target); fire(svg, "pointermove", button, 160, 130, target); fire(svg, "pointerup", button, 160, 130, target); return layer.t; } console.log(JSON.stringify({ leftEmpty: drag(0, empty), leftShape: drag(0, shape), middleShape: drag(1, shape), right: drag(2, empty), })); """ def test_실제_포인터_사건으로_팬(): got = _node(PAN) # 원배율(1)에서도 빈 곳 왼쪽 끌기 · 가운데 버튼 끌기 = 그림 묶음이 끈 만큼 옮김 assert got["leftEmpty"] == "translate(60 30) scale(1)" assert got["middleShape"] == "translate(60 30) scale(1)" assert got["leftShape"] == "" and got["right"] == "" # 상자 위 왼쪽 = 그 화면 고르기 몫 def test_거래처_상자_항목_글(): data = { "site": {"mid_station_m": None, "mid_road": "비포장"}, "bases": [], "routes": [ {"id": "g", "name": "거창군청", "address": "", "to": "site", "dir": None, "legs": [], "rows": [{"item": "장비", "dest": "start", "note": ""}, {"item": " 장비 ", "dest": "mid", "note": ""}, {"item": "골재", "dest": "mid", "note": ""}]}, {"id": "k", "name": "", "address": "", "to": "site", "dir": None, "legs": [], "rows": [{"item": "", "dest": "start", "note": ""}]}, ], } # fmt: skip lay = _node(f"console.log(JSON.stringify(layoutMilestone({json.dumps(data)}, null, 0)));") boxes = {b["id"]: b for b in lay["boxes"]} assert boxes["g"]["items"] == "장비 · 골재" and boxes["g"]["label"] == "거창군청" assert boxes["k"]["items"] == "" and boxes["k"]["label"] == "거래처" ui = _read("B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts") # 244-12 — 글 줄 = 항목(윗줄) · 이름 · 작은 글씨 assert '[box.items, "b08-milestone__items"]' in ui and '[box.label, ""]' in ui def test_표_폭_다이어그램_폭_좌측_카드_없음(): css = _read("B08_Quantity/B08_Quantity_UI_Milestone.css") block = css[css.index(".b08-milestone {") :] assert "width: 100%;" in block[: block.index("}")] # 149 틀 = 페이지 폭 전체 assert ".b08-milestone > .ui-data-table {\n width: 100%;" in css # 149 표도 폭 전체 ui = _read("B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts") assert "const fw = Math.max(host.clientWidth, 320);" in ui # 149 다이어그램 = 틀 폭 · 틀 비율 page = _read("B08_Quantity/B08_Quantity_UI_Milestone.ts") assert "children: [diagram.root, none, table.root]" in page # 좌측 패널 카드 없음 · 공사지 · 기준점은 모달 options = _read("B08_Quantity/B08_Quantity_UI_Options.ts") assert "milestoneCardBody" not in options and "await saveMilestone(projectId);" in options assert "milestoneCardBody" not in _read("B08_Quantity/B08_Quantity_UI_Milestone_State.ts") modal = _read("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts") site = modal[modal.index("export function openSiteInfo") :] assert 'label: "중점 측점 (km)"' in site and 'label: "시점 → 중점 도로"' in site assert 'setLeg(fresh, "start", 0, {' in site and "putMilestone(" in site # 244-13 — 기준점 = 등록 모달 분류 「기준위치」 · [기준점 +] 없앰 · 254 여럿 · 삭제 = 그 기준점만 base = modal[ modal.index("export function openPlaceModal") : modal.index("interface SiteAddress") ] assert "fresh.bases.push({" in base and 'label: "삭제"' in base assert "fresh.bases = fresh.bases.filter((b) => b.id !== base.id);" in base assert 'label: "기준점 +"' not in ui and "openBaseInfo" not in ui and "addBase" not in ui assert "content: panContent(vw, vh)" in ui and "panStart," in ui def test_화면_글_거래처(): for name in UI: text = _read(f"B08_Quantity/B08_Quantity_{name}") assert "업체" not in text, name modal = _read("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts") assert 'label: "거래처 이름"' in modal page = _read("B08_Quantity/B08_Quantity_UI_Milestone.ts") table = _read("B08_Quantity/B08_Quantity_Milestone_Table.ts") assert '{ key: "company", label: "거래처 이름"' in table and "`거래처 ${" in page for name in UI: assert len(_read(f"B08_Quantity/B08_Quantity_{name}").splitlines()) <= 700