# -*- coding: utf-8 -*- """354-1 → 392-2 유토곡선 판 = 10-14 판(9edaec82e) 되살림(요구 242-10). - 유토곡선 = 종단 칸(bodyWrap) 바닥 덮는 판(absolute · z5) · 자기 판(종단 열 아래 칸) 지움. - 메인 판 끌기 = 종단 · 유토 · 표 세 영역 같은 비율 · 놓을 때 유토 · 표 높이 확정(commitHeight). - 유토 손잡이 = 판 위 경계 밖(bottom = 판 높이) · 표 손잡이와 ±44 px 나란히 · 접기 = 숨김. - 표 판 = 열린 유토 위에 쌓임(setBottomOffset). - 남김 = 유토 [창 분할](판 안 좌상단 = 펼친 때만 보임) · 367-1 늘 전체 맞춤 · 325-2 뿌리 안 판정. """ import json import os import re import subprocess from pathlib import Path HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = Path(HERE).parents[1] def read(rel: str) -> str: return (ROOT / rel).read_text(encoding="utf-8") def css_block(css: str, selector: str) -> str: m = re.search(r"(?:^|\n)" + re.escape(selector) + r" \{([^}]*)\}", css) assert m, selector return m.group(1) # 진짜 높이 나눔(createHeightCascade) — 메인 끌기 중 세 영역 비례 NODE = r""" const path = require("path"), fs = require("fs"); const root = process.argv[1]; const ts = require(path.join(root, "config", "node_modules", "typescript")); const src = fs.readFileSync(path.join(root, "B05_Profile", "B05_Profile_UI_Profile_Heights.ts"), "utf8"); const js = ts.transpileModule(src, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } }).outputText; const exp = {}; new Function("exports", "require", "window", js)(exp, (id) => id.includes("MassHaul") ? { MASSHAUL_MIN_HEIGHT: 160 } : id.includes("TableOverlay") ? { TABLE_OVERLAY_MIN_HEIGHT: 120 } : id.includes("Structures_Marks") ? { STRUCTURE_LANE_HEIGHT_PX: 40 } : { frameOf: () => 0 }, { innerHeight: 900 }); const el = () => { const n = { hidden: false, style: { height: "" }, parent: null }; n.append = (k) => ((k.parent = n), k); n.contains = (o) => { for (let a = o; a; a = a.parent) if (a === n) return true; return false; }; return n; }; function run(bodyH) { const rootEl = el(); rootEl.offsetHeight = bodyH + 100; const body = rootEl.append(el()); body.clientHeight = bodyH; const mass = body.append(el()), table = body.append(el()); const seen = { handle: 0, offset: null }; const c = exp.createHeightCascade({ root: rootEl, body, massHaul: { overlay: mass, desiredHeight: () => 200, syncHandle: () => seen.handle++ }, tableOverlay: { overlay: table, isOpen: () => true, desiredHeight: () => 200, setBottomOffset: (v) => (seen.offset = v) }, isMainDragging: () => true, isSubDragging: () => false, // 끌기 시작 = 종단 400 · 유토 200 · 표 200 mainDragRef: () => ({ chart: 400, mass: 200, table: 200 }), isMainDragCooldown: () => true, hasDetail: () => true, }); const { chartHeight } = c.apply(false); return { chart: chartHeight, mass: mass.style.height, table: table.style.height, ...seen }; } // 본문 − 알약 40 − 손잡이 띠 13 = 나눌 자리 · 800 → 1.0 배 · 1200 → 1.5 배 process.stdout.write(JSON.stringify({ same: run(853), grow: run(1253) })); """ def test_메인_끌기_세_영역_같은_비율(): res = subprocess.run( ["node", "-e", NODE, str(ROOT)], capture_output=True, text=True, encoding="utf-8", check=True, ) out = json.loads(res.stdout) assert out["same"] == { "chart": 400, "mass": "200px", "table": "200px", "handle": 1, "offset": 200, } # 1.5 배 — 셋 다 같은 비율 · 표는 유토 위에 쌓임(바닥 간격 = 유토 높이) assert out["grow"] == { "chart": 600, "mass": "300px", "table": "300px", "handle": 1, "offset": 300, } def test_놓을_때_높이_확정_뿌리_안_판정(): drag = read("B05_Profile/B05_Profile_UI_Profile_Drag.ts") assert "if (massDocked()) massHaul.commitHeight(massHaul.overlay.offsetHeight);" in drag assert "if (tableOverlay.isOpen()) tableOverlay.commitHeight(" in drag # 325-2 「열림 && 뿌리 안」 한 곳 · 하한에 유토 최소 assert drag.count("massHaul.overlay.hidden") == 1 assert "root.contains(massHaul.overlay)" in drag assert "const massMin = massDocked() ? MASSHAUL_MIN_HEIGHT : 0;" in drag heights = read("B05_Profile/B05_Profile_UI_Profile_Heights.ts") assert '{ key: "mass", ref: Math.max(1, dragRef.mass), min: MASSHAUL_MIN_HEIGHT }' in heights def test_종단_칸_바닥_덮는_판(): overlays = read("A00_Common/design/design_profile_overlays.ts") assert "bodyWrap.append(massHaul.overlay, massHaul.handle);" in overlays assert "b05-route-profile__column" not in overlays assert "massHaul.attachScrollSync" not in overlays # 367-1 측점 연계 없앰 profile = read("A00_Common/design/design_profile.ts") assert "content.append(bodyWrap);" in profile assert sum(1 for _ in profile.splitlines()) <= 700 css = read("B05_Profile/B05_Profile_UI_Style_MassHaul.css") panel = css_block(css, ".b05-profile__masshaul-overlay") assert "position: absolute;" in panel and "bottom: 0;" in panel and "z-index: 5;" in panel assert "masshaul-head" not in css and "is-collapsed:not(.is-popout)" not in css def test_손잡이_자리_접기_숨김(): css = read("B05_Profile/B05_Profile_UI_Style_MassHaul.css") assert "left: calc(50% + 44px);" in css_block(css, ".b05-profile__masshaul-handle") assert "left: calc(50% - 44px);" in css_block(css, ".b05-profile__table-handle") mass = read("B05_Profile/B05_Profile_UI_Profile_MassHaul.ts") # 손잡이 = 판 위 경계 밖 · 접으면 바닥 · 접기 = 판 숨김 assert 'handle.style.bottom = open ? `${overlay.offsetHeight}px` : "0px";' in mass assert "overlay.hidden = !open;" in mass and "overlay.hidden = !next;" in mass table = read("B05_Profile/B05_Profile_UI_Profile_TableOverlay.ts") assert "overlay.style.bottom = `${bottomOffset}px`;" in table def test_창_분할_판_좌상단(): popout = read("A00_Common/design/design_profile_popout.ts") assert "massOverlay.append(button);" in popout assert "home: () => massHandle.before(massOverlay)," in popout page = read("A00_Common/design/design_page.css") button = css_block(page, ".b05-profile__masshaul-overlay > .b05-subpanel__popout") # 고정 Y축(z5) 위 — 아래면 단추가 축 띠에 가려짐 assert "position: absolute;" in button and "z-index: 6;" in button