- 좌측 패널 · 아래 단추 넷 · 종단도 · 구조물 배치 폼을 페이지에 한 번만 만듦(design_shared) — 두 보기가 같은 노드 - 보기는 가운데 위 칸만: 종단 보기 = 3D(design_view_profile) · 횡단 보기 = 횡단 카드(design_view_cross · 처음 보일 때 그림) - 옛 B05_Profile_UI_Page · B06_Section_UI_Page 삭제 · 한 벌이 받은 조작은 지금 보기로, 관 목록 · 횡단 재계산 알림은 두 보기 모두로 - 진행 단계 종단설계 · 횡단설계 두 칸이 보기를 따라 켜짐 · 누르면 주소만 바꿈 · 제목도 따라감(setTitle · setActiveStep) - [저장] · [확정] 한 벌 — 두 보기 몫을 모아 saveDesign 한 번 · 종단 보기 [확정] 켬(횡단 보기를 숨긴 채 세워 횡단 자료를 얻음) - [초기화] · 계획노선 편집은 같은 페이지라도 다시 세움(remountRoute) - 덧붙임: 방향키는 초점 있는 쪽만(조정창 ↔ 종단도 계획고) · 횡단 보기 계획고 편집에도 [3D 업데이트] 대기 표시 · 종단 보기 폼 관 옵션을 횡단 캐시에도 얹음 · 종단 보기에서 횡단 조정 칸 가림 - 나눔: Cross_View 756→658(_Cross_View_Frame) · Page_Structures_Panel 700→547(_Structures_Spec) · 옛 B05 Page 786 → design_view_profile 638(_Page_Latest · _Page_Stations) - 시험: test_design_view_parity 한 벌 기준(한 패널 · 보기 오가도 같은 노드) · page_single · profile_single · save_channels · save_recompute 등 옮긴 자리로 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmELpdXFDRZHhfbGuRhSmL
156 lines
6.8 KiB
Python
156 lines
6.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""B05 · B06 한 페이지 두 보기 — **좌측 패널 한 벌 지킴**(계획서 24-2f · 24-3b).
|
|
|
|
① 좌측 패널을 **하나** 세우고 보기를 오가도 뿌리 · 구조물 배치 · 아래 고정 영역 · 단추 넷이 같은 노드인지,
|
|
보기마다 제 칸만 보이는지, [보기 전환] 글자가 따라가는지 본다(Node 헬퍼 `helper_design_view_parity.cjs`
|
|
가 등록부를 트랜스파일해 가짜 DOM 위에서 돌린다).
|
|
② 보기 파일(`design_view_profile.ts` · `design_view_cross.ts`)이 좌측 칸 · 아래 단추 · 저장 단추를
|
|
직접 만들면 빨강 — 칸 · 단추 · 저장은 한 벌(`design_shared`)만 만든다.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
ROOT = Path(HERE).parents[1]
|
|
PAGE_FILES = (
|
|
ROOT / "A00_Common" / "design" / "design_view_profile.ts",
|
|
ROOT / "A00_Common" / "design" / "design_view_cross.ts",
|
|
)
|
|
# 페이지 파일에 나오면 안 되는 것 — 좌측 칸 · 아래 단추 · 저장 단추를 직접 만든다는 표시.
|
|
FORBIDDEN_IN_PAGES = (
|
|
"createButton(",
|
|
"attachCollapsible(",
|
|
"buildGroup(",
|
|
"createSectionFrame(",
|
|
"registerPanelSection(",
|
|
"createDesignDock(",
|
|
"ui-sidebar-actions",
|
|
"b05-route__dock",
|
|
"b05-route__actions",
|
|
"b06-profile__actions",
|
|
"b06-profile__form",
|
|
"b05-route__panel",
|
|
"b06-profile__group",
|
|
"saveButton",
|
|
"confirmButton",
|
|
"actionDock",
|
|
)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def results():
|
|
proc = subprocess.run(
|
|
["node", os.path.join(HERE, "helper_design_view_parity.cjs")],
|
|
capture_output=True,
|
|
text=True,
|
|
encoding="utf-8",
|
|
timeout=60,
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
def test_보기를_오가도_좌측_패널_단추_구조물_배치는_같은_노드(results):
|
|
assert results["sameNodes"] == {"cross": True, "back": True}
|
|
# 보기마다 새로 만들지 않는다 — 칸은 처음 보일 때 한 번만 서고 뒤로는 숨김 · 보임만.
|
|
assert results["childCount"] == 1 + 5 + 2 # 아래 고정 영역 + 종단 칸 다섯 + 횡단 칸 둘
|
|
|
|
|
|
def test_구조물_배치는_두_보기_모두_맨_위_같은_DOM(results):
|
|
views = results["views"]
|
|
assert views["profile"]["structuresHtml"] == views["cross"]["structuresHtml"]
|
|
assert all(views[name]["structuresFirst"] for name in ("profile", "cross", "back"))
|
|
|
|
|
|
def test_구조물_목록은_두_보기_모두_아래_고정_영역_맨_위(results):
|
|
views = results["views"]
|
|
assert views["profile"]["dockFirstChild"] == views["cross"]["dockFirstChild"]
|
|
assert all(views[name]["dockIsLast"] for name in ("profile", "cross", "back"))
|
|
|
|
|
|
def test_보기마다_제_칸만_보인다(results):
|
|
views = results["views"]
|
|
assert views["profile"]["shownTitles"] == [
|
|
"구조물 배치",
|
|
"페이지 설정",
|
|
"계획노선",
|
|
"경로 계산 설정",
|
|
"선택 포인트 상세 설정",
|
|
]
|
|
assert views["cross"]["shownTitles"] == ["구조물 배치", "횡단 보기 설정", "표준 횡단면 설정"]
|
|
assert views["back"]["shownTitles"] == views["profile"]["shownTitles"]
|
|
assert results["sectionIds"]["cross"] == ["structures", "cross-view", "standard-cross"]
|
|
|
|
|
|
def test_아래_단추는_네_개_보기_전환_글자만_따라감(results):
|
|
views = results["views"]
|
|
for name in ("profile", "cross", "back"):
|
|
assert views[name]["dockActions"] == ["reset", "save", "confirm", "switchView"]
|
|
assert views[name]["dockLabels"][:3] == ["초기화", "저장", "확정"]
|
|
# [확정]은 종단 보기에서도 켜진다(한 벌 저장이 횡단 자료를 같은 페이지에서 얻음).
|
|
assert views[name]["dockDisabled"] == [False, False, False, False]
|
|
assert views["profile"]["dockLabels"][3] == "횡단 보기"
|
|
assert views["cross"]["dockLabels"][3] == "종단 보기"
|
|
assert views["back"]["dockLabels"][3] == "횡단 보기"
|
|
|
|
|
|
def test_단추는_한_벌_동작을_부름(results):
|
|
names = ["reset", "save", "confirm", "switch"]
|
|
assert results["calls"] == [
|
|
*(f"profile:{n}" for n in names),
|
|
*(f"cross:{n}" for n in names),
|
|
*(f"profile:{n}" for n in names),
|
|
]
|
|
|
|
|
|
def test_횡단_보기_칸_정의와_차례():
|
|
"""B06 칸 정의는 소스로도 본다 — 차례가 바뀌면 빨강."""
|
|
source = (ROOT / "B06_Section" / "B06_Section_UI_Page_Panel.ts").read_text(encoding="utf-8")
|
|
found = re.findall(
|
|
r'id: "([\w-]+)",\s*title:[^\n]*\n\s*views: \["cross"\],\s*order: (\d+)', source
|
|
)
|
|
ordered = [name for name, _ in sorted(found, key=lambda item: int(item[1]))]
|
|
assert ordered == ["cross-view", "standard-cross"]
|
|
shared = (ROOT / "A00_Common" / "design" / "design_sections.ts").read_text(encoding="utf-8")
|
|
assert 'views: ["profile", "cross"]' in shared # 구조물 배치는 한 정의로 두 보기
|
|
|
|
|
|
@pytest.mark.parametrize("page", PAGE_FILES, ids=lambda path: path.name)
|
|
def test_보기_파일이_좌측_칸_단추_저장을_직접_만들지_않는다(page):
|
|
text = page.read_text(encoding="utf-8")
|
|
found = [marker for marker in FORBIDDEN_IN_PAGES if marker in text]
|
|
assert not found, f"{page.name} 이(가) 좌측 칸 · 단추를 직접 만든다: {found}"
|
|
assert "createDesignPanel(" not in text and "saveDesign(" not in text
|
|
# 좌측 패널은 한 벌을 쓴다 — 종단 보기는 칸 재료 모듈(`createRoutePanel`)이 대신 부른다.
|
|
assert "sharedPanel(shared)" in text or "createRoutePanel(" in text
|
|
route_panel = (ROOT / "B05_Profile" / "B05_Profile_UI_Panel.ts").read_text(encoding="utf-8")
|
|
assert "sharedPanel(shared)" in route_panel
|
|
|
|
|
|
def test_한_벌이_패널_종단도_폼을_한_번만_만든다():
|
|
shared = (ROOT / "A00_Common" / "design" / "design_shared.ts").read_text(encoding="utf-8")
|
|
for maker in ("createDesignPanel(", "createDesignProfile(", "createStructuresSection("):
|
|
assert shared.count(maker) == 1, maker
|
|
for guard in (
|
|
"if (shared.panel) return shared.panel;",
|
|
"if (shared.profile) return shared.profile;",
|
|
"if (shared.structures) return shared.structures;",
|
|
):
|
|
assert guard in shared
|
|
# 보기 쪽은 만들지 않고 한 벌을 쓴다.
|
|
for relative in (
|
|
"B05_Profile/B05_Profile_UI_Panel.ts",
|
|
"B05_Profile/B05_Profile_UI_Profile_Panel.ts",
|
|
"B06_Section/B06_Section_UI_Section_View_Profile.ts",
|
|
"B06_Section/B06_Section_UI_Page_Structures_Panel.ts",
|
|
):
|
|
text = (ROOT / relative).read_text(encoding="utf-8")
|
|
for maker in ("createDesignPanel(", "createDesignProfile(", "createStructuresSection("):
|
|
assert maker not in text, f"{relative} 가 {maker} 를 따로 부름"
|