"""PLAN 588 — 단축키 등록부 · 겹침 판정 · CAD 리본 묶음 · 찾기 (요구 445 C2 · C3). · 등록부 = `A00_Common/key_registry.ts` — ☰ [설정] 표가 여기서 그려짐 · 바꿀 수 있는 줄은 코드가 부름 · 키 글 = 한글 자판에서도 자판 자리로(Ctrl+ㅋ = Ctrl+Z) · 바꾼 키만 먹음 · 못 바꾸는 줄은 바꾼 값 무시 · 겹침 = 같은 화면 자리끼리 · 「모든 화면」과 · 브라우저 몫 · CAD 글자 단독 — 다른 화면끼리는 같은 키 됨 · CAD 묶음 = 리본 탭 › 패널 · 154 명령이 꼭 한 묶음 · 기타 여섯(리본에 없음) 짝: `helper_588_key_registry.cjs`(실제 TS 를 Node 로 돌림). """ from __future__ import annotations import json import os import subprocess import pytest HERE = os.path.dirname(__file__) ROOT = os.path.join(HERE, "..", "..") pytestmark = pytest.mark.skipif( not os.path.isdir(os.path.join(ROOT, "config", "node_modules")), reason="프론트엔드 의존성 미설치", ) def _read(rel: str) -> str: with open(os.path.join(ROOT, rel), encoding="utf-8") as f: return f.read() @pytest.fixture(scope="module") def r() -> dict: proc = subprocess.run( # noqa: S603 — 고정 실행 파일 ["node", os.path.join(HERE, "helper_588_key_registry.cjs")], capture_output=True, text=True, encoding="utf-8", timeout=60, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) def test_CAD_154_명령_꼭_한_묶음_기타_여섯(r): ids = r["groupIds"] assert len(ids) == 154 and len(set(ids)) == 154 assert sorted(r["other"]) == ["LAYER", "PLACETABLE", "RECOVER", "REDO", "TOAREA", "UNDO"] assert r["groups"][0]["label"] == "홈 › 그리기" assert r["groups"][-1]["label"] == "" # 기타 이름은 화면 글(locale) def test_찾기_낱말_모두_대소문자_무시(r): f = r["find"] assert {"LINE", "PLINE"} <= set(f["sun"]) and {"LINE", "PLINE"} <= set(f["L"]) assert "PLINE" in f["sunPl"] and "LINE" not in f["sunPl"] # 낱말 모두 assert f["blank"] == 154 and f["none"] == [] def test_키_글_한글_자판_안전(r): c = r["chord"] assert c["hangulCtrlZ"] == "Ctrl+Z" assert c["ctrlShiftZ"] == "Ctrl+Shift+Z" assert c["meta"] == "Ctrl+Z" assert c["space"] == "Space" assert c["shiftOnly"] == "" and c["process"] == "" assert c["f3"] == "F3" assert c["shiftDigit"] == "Ctrl+Shift+1" assert c["arrow"] == "Shift+ArrowUp" and c["label"] == "Shift+↑" def test_바꾼_키만_먹음_못_바꾸는_줄은_무시(r): k = r["keyIs"] assert k["delDefault"] is True and k["hangulUndo"] is True assert k["delAfter"] is False and k["ctrlDAfter"] is True assert k["fixedIgnored"] == ["Escape"] assert k["cadAction"] == "cad.grid" def test_겹침_판정(r): c = r["check"] assert c["none"] == {} assert c["sameScope"] == {"plan.delete": "dup"} assert c["withAll"] == {"plan.delete": "dup"} assert c["withLoupe"] == {"plan.delete": "dup"} assert c["otherPage"] == {} # D03 과 CAD 는 다른 화면 assert c["reserved"] == {"plan.delete": "reserved"} assert c["cadLetter"] == {"cad.snap": "letter"} assert c["cadShiftLetter"] == {"cad.snap": "letter"} assert c["cadCtrl"] == {} assert c["samePage"] == {"b05.undo": "dup"} # B05 · B06 한 페이지 — 횡단 Enter 와 겹침 assert c["swap"] == {} # 서로 바꿈은 됨 assert c["both"] == {"plan.drawFinish": "dup", "plan.drawBack": "dup"} def test_등록부_꼴(r): ids = [a["id"] for a in r["actions"]] assert len(ids) == len(set(ids)) for a in r["actions"]: assert a["scope"] in r["scopes"] and a["keys"] assert a["id"].startswith(a["scope"] + ".") # 바꿀 수 있는 줄 = 코드가 실제로 부름(등록부에만 있고 안 먹는 키 없음) WIRED = { "b05": "B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts", "profile": "B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts", "plan": "D03_PlanAnalysis", "cad": "B07_DesignDetail/openwebcad/src/inputController", } def _sources(rel: str) -> str: full = os.path.join(ROOT, rel) if os.path.isfile(full): return _read(rel) names = sorted(n for n in os.listdir(full) if n.endswith(".ts")) return "\n".join(_read(os.path.join(rel, n)) for n in names) def test_바꿀_수_있는_줄은_배선됨(r): for a in r["actions"]: if a["fixed"]: continue src = _sources(WIRED[a["scope"]]) assert f'"{a["id"]}"' in src or f"'{a['id']}'" in src, a["id"] def test_손글_안내_지움_표는_등록부(): assert "App_Settings_Keys_" not in _read("ui_template/ui_template_locale_common.ts") assert "App_Settings_Keys_" not in _read("A00_Common/app_settings.ts") # 588-4 — 화면 옮기기 자리는 모두 설정 버튼을 따름(가운데 고정 판정이 남지 않음) PAN_SITES = [ "B05_Profile/B05_Profile_UI_RouteEdit_Input.ts", # B05 · B06 겹침 평면 · D03 지도 "B07_DesignDetail/openwebcad/src/inputController/input-controller.ts", "A00_Common/b_svg_zoom_pan.ts", "B04_PreProcess/B04_PreProcess_UI_MapViewer.ts", "B04_PreProcess/B04_PreProcess_UI_Camera.ts", "B05_Profile/B05_Profile_UI_Drainage_Interact.ts", "B05_Profile/B05_Profile_UI_Profile_Wheel.ts", "B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts", "M02_MasterTemplete/DataFlow/M02_DF_Diagram.ts", "M02_MasterTemplete/M02_MasterTemplete_Labor_Diagram.ts", ] @pytest.mark.parametrize("rel", PAN_SITES) def test_옮기기_자리는_설정_버튼(rel): src = _read(rel) assert "isPanButton(" in src or "isAuxPanButton(" in src, rel