"""244-8 B08 거리이정표 다이어그램 영역 스크롤 없앰(사용자 135번). 틀 = overflow hidden · 그림 = 틀 안 맞춤(높이 48vh 넘으면 좁힘) · 휠 = 줌만. 휠은 공용 `A00_Common/b_svg_zoom_pan.ts` 와 다이어그램 틀 듣개를 node 로 돌려 진짜 wheel 사건(cancelable)을 보내 기본동작(스크롤)이 막히는지 · 배율이 바뀌는지 잼. """ import json import re import subprocess from pathlib import Path ROOT = Path(__file__).resolve().parents[2] IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE) DIAGRAM = "B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts" def _read(rel): return (ROOT / rel).read_text(encoding="utf-8") def _node(script): src = IMPORTS.sub("", _read("A00_Common/b_svg_zoom_pan.ts")) result = subprocess.run( ["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript", "-e", 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) def test_틀_스크롤_없음_그림_맞춤(): css = _read("B08_Quantity/B08_Quantity_UI_Milestone.css") block = css[css.index("\n.b08-milestone__diagram {") :] block = block[: block.index("}")] assert "overflow: hidden;" in block and "overflow: auto" not in block assert "max-height" not in block # 높이는 그림이 맞춤 — 틀이 자르지 않음 ui = _read(DIAGRAM) # 149 틀 = 폭 전체 · 높이 고정(CSS) · 그림 범위를 틀 비율로 넓힘(화면 px ↔ 그림 좌표 가로세로 같음) assert "const scale = Math.max(lw / fw, lh / fh);" in ui assert "height: clamp(420px, 58vh, 760px);" in _read( "B08_Quantity/B08_Quantity_UI_Milestone.css" ) def test_휠_진짜_사건_줌_스크롤_막음(): ui = _read(DIAGRAM) line = next(ln.strip() for ln in ui.splitlines() if 'host.addEventListener("wheel"' in ln) got = _node( """ const W = 600, H = 400; class Svg extends EventTarget { classList = { add() {}, remove() {} }; getBoundingClientRect() { return { left: 0, top: 0, width: W, height: H }; } } function wheel(target: EventTarget, deltaY: number) { const ev = new Event("wheel", { cancelable: true, bubbles: true }); for (const [k, v] of Object.entries({ deltaY, clientX: 300, clientY: 200 })) Object.defineProperty(ev, k, { value: v }); target.dispatchEvent(ev); return ev.defaultPrevented; } 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 }); const svgIn = wheel(svg, 100); const zoomIn = layer.t; const svgOut = wheel(svg, -100); const zoomOut = layer.t; // 틀 듣개(그림 밖 여백 · 단추 줄) — 소스 줄 그대로 돌림 · passive 면 preventDefault 무시됨 const host = new EventTarget(); let opts: unknown = null; const add = host.addEventListener.bind(host); host.addEventListener = (t: string, f: any, o: any) => { opts = o; add(t, f, o); }; LINE const hostBlocked = wheel(host, 100); console.log(JSON.stringify({ svgIn, zoomIn, svgOut, zoomOut, hostBlocked, opts })); """.replace("LINE", line) ) # 그림 위 휠 = 줌(커서 자리 붙잡음) · 기본동작(스크롤) 막힘 = 스크롤 위치 안 바뀜 assert got["svgIn"] is True and got["zoomIn"].endswith(" scale(1.15)") assert got["zoomIn"].startswith("translate(-45 -29.99") # 커서(300, 200) 제자리 assert got["svgOut"] is True and got["zoomOut"] != got["zoomIn"] # 밀면 축소 # 그림 밖 틀 위 휠도 페이지 스크롤로 안 샘 assert got["hostBlocked"] is True and got["opts"] == {"passive": False}