"""PLAN 472-5 5-A (요구 273) — 관 · 구조물 값 주인 하나(설계 가게) · 사본 갈림 막음. 못박는 것 - 되돌리기 · 다시하기(세션 직접 되씀)가 가게 듣는 쪽을 되돌린 값으로 한 번씩 부름(5-C 훅 · 묶음과 안 겹침) — 옛 코드는 안 불러 종단 목록 · 횡단 판 · 배수 편집기가 옛 값을 쥐고 다음 조작이 되돌림을 덮었음(옛 코드 실패). - 배수 편집기 받기 — 제 쓰기 메아리는 안 받음 · 남이 고친 옵션 · 차례도 받음 · 고른 관(id) 유지. - 배선 — 종단 보기 = 관(숨어도) · 구조물(보일 때) 구독 · 횡단 보기 = 남 고침(옵션만도) 다시 실음 · 횡단 판은 쓰기 전에 제 값을 적음(메아리 가름). ./venv/Scripts/python.exe -m pytest resources/tester/test_472_5_shop_owner.py -q """ from __future__ import annotations import json import shutil import subprocess from pathlib import Path import pytest HERE = Path(__file__).resolve().parent ROOT = HERE.parents[1] pytestmark = pytest.mark.skipif( not (ROOT / "config" / "node_modules" / "vite").is_dir(), reason="프론트엔드 의존성 미설치", ) def _read(*parts: str) -> str: return (ROOT.joinpath(*parts)).read_text(encoding="utf-8") @pytest.fixture(scope="module") def out(tmp_path_factory) -> dict: work = tmp_path_factory.mktemp("s4725") subprocess.run( # noqa: S603 — 고정 실행 파일 [ "node", str(ROOT / "config" / "node_modules" / "vite" / "bin" / "vite.js"), "build", "--configLoader", "runner", "--ssr", "../resources/tester/helper_472_5_shop_entry.ts", "--outDir", str(work), "--logLevel", "error", ], cwd=ROOT, check=True, capture_output=True, timeout=300, ) bundle = work / "bundle.mjs" shutil.copy(work / "helper_472_5_shop_entry.js", bundle) proc = subprocess.run( # noqa: S603 ["node", str(HERE / "helper_472_5_shop.mjs"), str(bundle)], cwd=ROOT, capture_output=True, text=True, encoding="utf-8", timeout=120, ) assert proc.returncode == 0, proc.stderr return json.loads(proc.stdout) def test_되돌리기가_가게_듣는_쪽을_부름(out) -> None: assert [s["structure_id"] for s in out["afterUndo"]["structures"]] == ["a"] assert out["afterUndo"]["pipes"][0]["options"] == {"diameter_mm": 800} assert [s["structure_id"] for s in out["afterRedo"]["structures"]] == ["a", "b"] assert out["afterRedo"]["pipes"][0]["options"] == {"diameter_mm": 1000} def test_되돌리기_알림은_한_번씩(out) -> None: """겹침 정리 — 관 · 구조물 = 가게 알림 한 번 · 추가 측점 = 자기 훅 · 묶음 = 출처마다 한 번.""" assert out["counts"] == { "structures": 1, "pipes": 1, "plusStations": 1, "fed": ["pipe", "plus", "structure"], } def test_배수_편집기_남_고침만_받음(out) -> None: assert out["echo"] is False assert out["adopted"] is True assert out["options"] == {"diameter_mm": 1200} assert out["picked"] == "k1" def test_배선() -> None: profile = _read("A00_Common", "design", "design_view_profile.ts") assert ( 'designStore.subscribe("pipes", (next) => next && profilePanel.drainage.adoptPipes(next))' in profile ) assert ( 'designStore.subscribe("structures", (next) => host.visible() && ' "bridge.takeStructures(next))" in profile ) cross = _read("A00_Common", "design", "design_view_cross.ts") assert "structuresPanel.foreign() && reloadPanel()" in cross assert ( 'shop.subscribe("pipes", onShop)' in cross and 'shop.subscribe("structures", onShop)' in cross ) panel = _read("B06_Section", "B06_Section_UI_Page_Structures_Panel.ts") assert panel.index("seen.pipes = JSON.stringify(next);") < panel.index( "writePendingPipes(deps.projectId, next);" ) assert panel.index("seen.structures = JSON.stringify(structures);") < panel.index( "writePendingStructures(deps.projectId, structures);" ) # 열린 관 폼 — 밖에서 바뀐 값(되돌리기 · 다른 보기)으로 다시 엶(옛 칸 값 되쓰던 흠) form = _read("B05_Profile", "B05_Profile_UI_Structures_Panel.ts") assert "if (at === null || applyingLive || tempPipeChainage !== null) return;" in form assert "if (now && JSON.stringify(now) !== JSON.stringify(was)) loadPipeForm(now);" in form # 고른 관이 그 자리를 떠남(폼 옮김 ↶ ↷) = 새로 선 관 하나로 따라감 — 목록 받기(고름 풂) 앞(472 덧) assert form.index("if (moved) selectedPipeChainage = moved.chainage_m;") < form.index( "followPipeList(panelContext, pipeFacilities);" ) history = _read("B05_Profile", "B05_Profile_UI_Profile_History.ts") assert "announceDesignStore(projectId);" in history