"""546 대시보드 셸 · 세부 페이지 등록부 시험 — 화면 조작은 ORCA. · 역할 넷(일반 · 회사 관리자 · 산림청 · 시스템 관리자)별 메뉴 묶음 · 항목 — 등록부를 Node 로 바로 돌림 · 없는 열쇠 · 권한 없는 열쇠 = 홈 · 주소 열쇠 하나에 한 페이지 · 셸 = 라우터 MountedPage(같은 주소 안 이동 = 페이지만 갈아 끼움) · 옛 ☰ 서랍 · 구역 접기 없음 """ from __future__ import annotations import json import re import shutil import subprocess from pathlib import Path import pytest ROOT = Path(__file__).resolve().parents[2] B01 = ROOT / "B01_Dashboard" def _text(path: Path) -> str: return path.read_text(encoding="utf-8") def _run_views(body: str) -> object: text = _text(B01 / "B01_Dashboard_UI_Views.ts") # import 한 값(type 아닌 이름) = 빈 함수 — 페이지 줄이 새 mount 를 끌어와도 그대로 돎 stubs = [ name.split(" as ")[-1].strip() for group in re.findall(r"^import \{([^}]*)\}", text, flags=re.M) for name in group.split(",") if name.strip() and not name.strip().startswith("type ") ] src = re.sub(r"^import[\s\S]*?;\n", "", text, flags=re.M) script = "".join(f"const {n} = () => {{}};\n" for n in stubs) + src + "\n" + body result = subprocess.run( ["node", "--experimental-strip-types", "--no-warnings", "--input-type=module-typescript", "-e", script], capture_output=True, text=True, encoding="utf-8", timeout=60, ) # fmt: skip assert result.returncode == 0, result.stderr return json.loads(result.stdout) MENU = ( "const menu = (r: ViewRole) => VIEWS.filter((v) => v.roles.includes(r) && v.group !== 'hidden')" ".map((v) => v.group + ':' + v.key);\n" ) @pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") def test_역할별_메뉴(): out = _run_views( MENU + "console.log(JSON.stringify(['USER','ADMIN','FOREST','SYSTEM_ADMIN'].map(menu)));" ) work = ["work:home", "work:projects", "work:assigned", "work:temp"] help_ = ["help:guide", "help:chat"] user, admin, forest, system = out assert user == [*work, "company:company", "company:company-defaults", *help_] assert admin == [*work, "company:people", "company:company", "company:company-defaults", *help_] # 산림청 = 받은 예정노선 없음(보내는 쪽) assert forest == [ "work:home", "work:projects", "work:temp", "company:company", "company:company-defaults", *help_, ] # fmt: skip assert system == [ *work, "company:people", "company:company", "system:sys-overview", "system:sys-companies", "system:sys-users", "system:sys-defaults", "system:sys-logs", "system:ui-kit", *help_, ] # fmt: skip @pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음") def test_주소_열쇠_권한_없으면_홈(): out = _run_views( "console.log(JSON.stringify([" "viewFor(null, 'USER').key, viewFor('없음', 'USER').key," "viewFor('people', 'USER').key, viewFor('people', 'ADMIN').key," "viewFor('sys-logs', 'ADMIN').key, viewFor('sys-logs', 'SYSTEM_ADMIN').key," "viewFor('project', 'USER').key, viewFor('me', 'FOREST').key," "roleOf({ role: 'USER' }, true), roleOf({ role: 'ADMIN' }, true)," "roleOf({ role: 'SYSTEM_ADMIN' }, true), roleOf({ role: 'USER' }, false)," "new Set(VIEWS.map((v) => v.key)).size === VIEWS.length," "]));" ) assert out == [ "home", "home", "home", "people", "home", "sys-logs", "project", "me", "FOREST", "ADMIN", "SYSTEM_ADMIN", "USER", True, ] # fmt: skip def test_셸_한_벌(): shell = _text(B01 / "B01_Dashboard_UI_Shell.ts") assert "covers: (route) => route === ROUTES.B01_ACCOUNT" in shell # 같은 주소 안 = show assert 'currentQuery().get("view")' in shell assert "buildKnowledgeChat()" in shell # 챗봇 둥근 단추 그대로 page = _text(B01 / "B01_Dashboard_UI_Page.ts") assert "mountDashboardShell(root, user, Boolean(session?.is_forest_service))" in page for name in ("Page", "Shell", "Nav", "Views", "Legacy"): src = _text(B01 / f"B01_Dashboard_UI_{name}.ts") assert "setDrawerSteps" not in src and "attachCollapsible" not in src, name assert len(src.splitlines()) <= 700, name css = _text(B01 / "B01_Dashboard_UI_Style_Shell.css") # 색 값은 공통 값 한 벌(theme.css 밝음 · 어두움) — 셸은 별칭만 · :root · 어두움 블록 안 둠 theme = _text(ROOT / "ui_template" / "ui_template_theme.css") assert "--color-accent: #1f7a4d;" in theme and "--color-accent: #3fae75;" in theme assert "--b01-accent: var(--color-accent);" in css and not re.search(r"^:root", css, re.M) assert not re.search(r"--b01-[a-z-]+:\s*#", css) icons = _text(ROOT / "ui_template" / "ui_template_icons.ts") assert "MIT License" in icons and "Paweł Kuna" in icons def test_메뉴_배지_알림(): shell = _text(B01 / "B01_Dashboard_UI_Shell.ts") badges = shell.split("async function refreshBadges")[1].split("\n}\n")[0] assert 'nav.setBadge("people", joins.length, true);' in badges # 가입 요청 = 빨강 assert "!p.linked_project_id || p.update_available" in badges # 받은 예정노선 새로 · 업데이트 assert 'nav.setBadge("temp", temp.length);' in badges assert "const todo = joins.length + newPlans + expiring;" in badges # 알림 = 처리할 일 합 assert "void refreshBadges(ctx, nav, bell);" in shell # 페이지 바뀔 때마다 다시 셈 css = _text(B01 / "B01_Dashboard_UI_Style_Shell.css") assert "@media (max-width: 1024px)" in css # 작은 화면 = 아이콘 레일 assert "html:has(.b01-shell) .app-header" in css # 옛 ☰ 걷음