"""346-3 화면 — [노선 반영] 없앰 · [저장]이 노선부터 · 추천 [적용] = as_initial 저장 · 저장 뒤 초안 넘김. 노선 이음(`B05_Profile_UI_RouteEdit_Apply`)은 Node 헬퍼 `helper_346_3_route_save.cjs` 가 서버 · 화면 창구만 가짜로 두고 돌림 · 서버 길 시험은 `test_346_3_route_save.py` · 저장 차례(노선 먼저 · 안 바뀌면 안 보냄 · 실패 때 초안 남음)는 `test_design_store_save.py`. """ from __future__ import annotations import json import subprocess from pathlib import Path import pytest HERE = Path(__file__).resolve().parent ROOT = HERE.parents[1] def _read(rel: str) -> str: return (ROOT / rel).read_text(encoding="utf-8") @pytest.fixture(scope="module") def out() -> dict: run = subprocess.run( ["node", str(HERE / "helper_346_3_route_save.cjs")], capture_output=True, text=True, encoding="utf-8", check=True, ) return json.loads(run.stdout) def test_노선_안_바뀜_안_보냄(out): assert out["noneAtAll"] is None assert out["unchanged"] is None # 편집기가 정함 — 가게에 초안이 남아 있어도 assert out["storeOnly"]["draft"]["rev"] == 3 # 편집기 없으면 가게 routeDraft def test_초안은_계약_RouteDraft_꼴(out): draft = out["body"]["draft"] assert set(draft) == { "rev", "nodes", "curveOn", "curveRadius", "curveLock", "curveArc", "stationSnap", "terrain", } n = len(draft["nodes"]) assert all( len(draft[name]) == n for name in ("curveOn", "curveRadius", "curveLock", "curveArc") ) assert draft["curveOn"] == [True, False, True] and draft["curveRadius"] == [None, 25, None] assert draft["stationSnap"] is True and draft["terrain"] == "sheet" assert out["body"]["initial"] == {"mode": "follow"} assert "balanced_profile" not in out["body"] # 손으로 고친 노선 = 계획고 옮김만(349-0v) assert "as_initial" not in out["body"] # 손으로 고친 노선 = 초기값 그대로 def test_저장_뒤_가게_초안_비움_결과_한_줄(out): assert out["savedRouteId"] == 9 assert out["storeAfterSave"] is None assert out["keptAfterSave"] == 1 assert out["summary"] == ( "노선 저장 — 관 지킴 3 · 새 교차 관 1 · 오목 보충 관 2 · 구조물 옮김 1 · 멀어짐 0 · " "노선 밖 고침 3 건 · 배수유역 낡음" ) # 서버 cross_dropped 1 + 화면 넘김 밖 2 = 한 줄 def test_초안_넘김_차례(out): assert out["carryCalls"] == [ "invalidate:9", "load:9", "keys:9:9", "carry:7->9:quiet", "crossSave:9", ] assert out["outside"] == 2 def test_추천_적용은_as_initial_저장_뒤_다시_세움(out): assert out["recommendOk"] is True body = out["recommendBody"] assert body["as_initial"] is True and body["initial"] == {"mode": "earthwork"} assert body["draft"]["nodes"] == [[1, 1], [9, 9]] assert out["recommendCalls"] == ["overlay:on", "replan", "overlay:off"] # 잠금 막 하나 assert out["recommendDraftsLeft"] == {"route": None, "pipes": None} # 새 초기값에서 다시 assert out["rebuilt"] == 1 def test_추천_저장_실패는_한_줄_다시_안_세움(out): assert out["recommendFail"] is False errors = [c for c in out["recommendFailCalls"] if c.startswith("toast:error:")] assert len(errors) == 1 and "체인 실패" in errors[0] assert out["rebuiltAfterFail"] == 1 def test_노선_반영_단추_없앰_배선(): chrome = _read("B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts") assert 'data-act="apply"' not in chrome and "노선 반영" not in chrome apply = _read("B05_Profile/B05_Profile_UI_RouteEdit_Apply.ts") assert "addEventListener" not in apply # 단추 듣기 없음 — [저장]이 부름 save = _read("A00_Common/design/design_save.ts") assert save.index('"route",') < save.index('"pipes",') assert "await pendingRouteSave(ctx.projectId)" in save assert "carryAfterRouteSave(ctx.projectId, fromRouteId, routeId)" in save initial = _read("B05_Profile/B05_Profile_UI_RouteEdit_Initial.ts") at = initial.index("onApply: (picked) => {") assert "saveRecommendedRoute(" in initial[at : at + 900] replan = _read("B05_Profile/B05_Profile_Api_Replan.ts") assert "body: JSON.stringify(body)" in replan and "as_initial?: boolean;" in replan def test_넘김_알림은_결과_한_줄에_합침(): """346-3 덧 — [저장] 뒤 「노선 밖 고침」 두 줄 → 한 줄 · 넘김은 조용히 수만 · 기본은 알림 그대로.""" keys = _read("B06_Section/B06_Section_Station_Keys.ts") assert "{ quiet = false }: { quiet?: boolean } = {}," in keys assert "if (outside.size && !quiet)" in keys apply = _read("B05_Profile/B05_Profile_UI_RouteEdit_Apply.ts") assert "carryStationDrafts(projectId, fromRouteId, toRouteId, { quiet: true })" in apply def test_노선_반영_글자_없음(): recommend = _read("B05_Profile/B05_Profile_UI_Panel_Recommend.ts") assert "노선 반영" not in recommend and "다시 계산은 [저장]에서" in recommend for rel in ( "B05_Profile/B05_Profile_UI_RouteEdit.ts", "B05_Profile/B05_Profile_UI_RouteEdit_History.ts", "B05_Profile/B05_Profile_UI_RouteEdit_Draft.ts", "A00_Common/design/design_view_profile.ts", ): text = _read(rel) assert "[노선 반영]" not in text and "[반영 전으로]" not in text, rel