feat(종단보기): 346-3 [노선 반영] 없앰 — [저장]이 노선부터(POST /route/replan · draft = 계약 RouteDraft · 안 바뀌면 안 보냄) · 뒤 차례는 새 노선 · 횡단 초안 측점 열쇠로 넘김 · 결과 한 줄

- design_save 차례 = 노선 → 관 → 구조물 → 계획고(옛 노선 칸 → 새 노선) → 암거 옵션 → 상단측 → 횡단(carryStationDrafts 뒤 저장) · 잠금 막 = [저장] 하나 · 실패 = 멈춤 · 초안 그대로 · 오류 한 줄
- [확정] 중 노선이 바뀌면 노선만 저장하고 다시 세움(새 노선으로 다시 [확정])
- _RouteEdit_Apply = 노선 저장 이음(편집기 등록부 · pendingRouteSave · saveRouteDraft · carryAfterRouteSave · routeSaveSummary · rebuildAfterRouteSave) · 단추 듣기 없음
- 결과 한 줄 = 관 지킴 · 새 교차 관 · 오목 보충 · 구조물 옮김 · 멀어짐 · 노선 밖 고침(서버 cross_dropped + 화면) · 배수유역 낡음
- RE_Chrome [노선 반영] 지움 · [반영 전으로] → [저장 전으로] · replanRoute 본문 = RouteSaveBody
- 시험: test_design_store_save 차례 넷 더함 · 꼴 시험 셋 고쳐 씀 · 323_9 대역

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmC34fsxxbweEG3RUjs3Mn
This commit is contained in:
2026-10-16 12:14:22 +09:00
co-authored by Claude Opus 5.5
parent 6757d1dcb8
commit 47366d3740
10 changed files with 388 additions and 117 deletions
@@ -33,6 +33,10 @@ const STUBS = {
showLoadingOverlay: () => calls.push("overlay:on"),
hideLoadingOverlay: () => calls.push("overlay:off"),
},
// 노선 저장(346-3) — 이 시험은 노선이 그대로인 [저장]만 봄
[path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_Apply.ts")]: {
pendingRouteSave: async () => null,
},
[path.join(B06, "B06_Section_Api_Fetch.ts")]: {
fetchSectionDetail: async () => (calls.push(`fetch:${server}`), detail()),
saveSections: async () => calls.push("cross:save"),
@@ -30,7 +30,22 @@ const record = (name, fail) => async () => {
if (fail) throw new Error(`${name} 실패`);
};
let failStructures = false;
// 노선 저장(346-3) — 초안 · 실패를 시험이 정함
let routeBody = null;
let failRoute = false;
const routeStub = {
pendingRouteSave: async () => routeBody,
saveRouteDraft: async () => {
calls.push("route");
if (failRoute) throw new Error("노선 저장 실패");
return { route_id: 9, pipes: { kept: 3, crossing: 1, concave: 0 }, cross_dropped: 1 };
},
carryAfterRouteSave: async (_p, from, to) => (calls.push(`carry:${from}->${to}`), 2),
routeSaveSummary: (saved, outside) => (calls.push(`summary:${saved.route_id}:${outside}`), ""),
rebuildAfterRouteSave: async () => calls.push("rebuild"),
};
const STUBS = {
"../../B05_Profile/B05_Profile_UI_RouteEdit_Apply": routeStub,
"@ui/ui_template_elements": {
showToast: (text, kind) => calls.push(`toast:${kind}`),
showLoadingOverlay: () => calls.push("overlay:on"),
@@ -161,6 +176,57 @@ async function run() {
});
out.noRouteCalls = [...calls];
// ⑥ 노선이 바뀜(346-3) — 노선 먼저 · 뒤 차례는 새 노선(9) · 화면 몫 건너뜀 · 횡단 초안 넘김 · 다시 세움
b05.replace({ station_offsets: { "30.000": 0.2 }, curve_radii: {} }); // 옛 노선(7) 계획고 초안
routeBody = {
draft: {
rev: 1,
nodes: [
[0, 0],
[10, 0],
],
},
};
calls.length = 0;
out.routeOk = await saveDesign({
projectId: "p1",
routeId: () => 7,
prepare: record("prepare"),
steps: { pipes: record("pipes:screen"), alignment: record("alignment:screen") },
cross: async (confirm) => calls.push(`cross:${confirm}`),
done: async (confirm) => calls.push(`done:${confirm}`),
failed: () => calls.push("failed"),
});
out.routeCalls = [...calls];
// ⑦ 노선 저장 실패 — 거기서 멈춤 · 다른 초안 그대로
b05.replace({ station_offsets: { "50.000": 0.3 }, curve_radii: {} });
failRoute = true;
calls.length = 0;
out.routeFailOk = await saveDesign({
projectId: "p1",
routeId: () => 7,
cross: async (confirm) => calls.push(`cross:${confirm}`),
done: async (confirm) => calls.push(`done:${confirm}`),
failed: () => calls.push("failed"),
});
out.routeFailCalls = [...calls];
out.routeFailDraft = edit.pendingAlignment(7)?.station_offsets ?? null;
failRoute = false;
// ⑧ [확정] 중 노선이 바뀜 — 노선 저장 뒤 다시 세움 · 확정 단계는 안 닫음(새 노선으로 다시)
calls.length = 0;
await saveDesign(
{
projectId: "p1",
routeId: () => 7,
cross: async (confirm) => calls.push(`cross:${confirm}`),
done: async (confirm) => calls.push(`done:${confirm}`),
failed: () => calls.push("failed"),
},
{ confirm: true },
);
out.routeConfirmCalls = [...calls];
routeBody = null;
// ⑤ [초기화] — 초안 · 한 벌 모두 버림
b05.replace({ station_offsets: { "80.000": 1 }, curve_radii: {} });
edit.clearAlignmentDrafts();
@@ -291,6 +291,7 @@ def test_화면_확인이_고른_지형을_실음():
page = ROOT / "B05_Profile"
apply = (page / "B05_Profile_UI_RouteEdit_Apply.ts").read_text(encoding="utf-8")
main = (page / "B05_Profile_UI_RouteEdit.ts").read_text(encoding="utf-8")
assert "terrain: params.terrain()," in apply
# 346-3 — 노선 초안(draft.terrain)에 실림 · [저장]이 보냄
assert "terrain: params.terrain() })" in apply and "terrain: settings.terrain," in apply
assert "pickedTerrain() !== designTerrain" in main # 지형만 바꿔도 [확인]이 다시 셈
assert "analysisTerrain(projectId, designTerrain)" in main # 고른 값 없으면 설계 지형
@@ -33,10 +33,12 @@ def test_겉틀은_본문_칸():
assert 'overlay.className = "b05-routeedit";' in chrome
assert "b05-routeedit__close" not in chrome and 'data-slot="close"' not in chrome
assert '"tools"' in chrome and "replaceWith(...tools)" in chrome
assert ">반영 전으로</button>" in chrome and ">노선 반영</button>" in chrome
# 346-3 — [노선 반영] 없앰(노선은 페이지 [저장]이 맨 앞에서 보냄) · [취소] = [저장 전으로]
assert ">저장 전으로</button>" in chrome and 'data-act="apply"' not in chrome
assert "노선 반영</button>" not in chrome
assert ">취소</button>" not in chrome and ">확인</button>" not in chrome
# 시험 · 배선이 집는 자리는 남김
for hook in ('data-act="apply"', 'data-act="cancel"', "b05-routeedit__measure-close"):
for hook in ('data-act="cancel"', "b05-routeedit__measure-close"):
assert hook in chrome
assert 'data-act="side-toggle"' in chrome and "is-collapsed" in chrome
+2 -2
View File
@@ -114,9 +114,9 @@ def test_초기화_계획노선_편집은_다시_세움():
assert step in rebuild
assert rebuild.index("invalidateSectionDetail") < rebuild.index("remountRoute(")
assert "mountRouteEditor(mainContent, host, activeProjectId, rebuildAfterReplan," in view
# 346-3 — [저장]이 노선을 보낸 뒤 결과 캐시를 비우고 편집기가 건 다시 세우기(rebuildAfterReplan)
apply = _read("B05_Profile/B05_Profile_UI_RouteEdit_Apply.ts")
assert apply.index("clearDrafts(projectId);") < apply.index("await params.onApplied();")
assert "clearResults(projectId);" in apply
assert "clearResults(projectId);" in apply and "rebuild: params.onApplied," in apply
def test_종단_보기_확정은_횡단_보기를_숨긴_채_세움():
+49 -1
View File
@@ -48,7 +48,15 @@ def test_계획고_가게_한_벌(out):
def test_저장_차례(out):
assert out["order"] == ["pipes", "structures", "alignment", "culvertOptions", "uphill", "cross"]
assert out["order"] == [
"route",
"pipes",
"structures",
"alignment",
"culvertOptions",
"uphill",
"cross",
] # 346-3 노선 먼저
assert out["saveOk"] is True
assert out["saveCalls"] == [
"overlay:on",
@@ -78,3 +86,43 @@ def test_구조물_실패는_알리고_넘어감_확정_노선없음(out):
def test_초기화는_초안과_한_벌을_버림(out):
assert out["cleared"] == {"draft": None, "pending": None}
def test_노선이_바뀌면_노선_먼저_뒤_차례는_새_노선(out):
"""346-3 — 노선 초안이 있으면 맨 앞 · 관 · 구조물 기본 창구 · 계획고 초안(옛 노선 7 칸)을 새 노선 9 로 ·
화면 몫 일(옛 노선 판)은 건너뜀 · 횡단 초안 넘김(측점 열쇠) · 결과 한 줄 · 다시 세움."""
assert out["routeOk"] is True
assert out["routeCalls"] == [
"overlay:on",
"prepare",
"route",
"pipes",
"structures",
'alignment:9:{"30.000":0.2}',
"culvert:aislo:draft:culvertopt:p1:7",
"uphill",
"carry:7->9",
"summary:9:2",
"toast:info",
"done:false",
"rebuild",
"overlay:off",
]
assert "pipes:screen" not in out["routeCalls"] and "alignment:screen" not in out["routeCalls"]
assert "cross:false" not in out["routeCalls"] # 옛 노선 횡단 본문 대신 넘김
def test_노선이_그대로면_안_보냄(out):
assert "route" not in out["saveCalls"]
def test_노선_저장_실패는_멈추고_초안_남음(out):
assert out["routeFailOk"] is False
assert out["routeFailCalls"] == ["overlay:on", "route", "failed", "overlay:off"]
assert out["routeFailDraft"] == {"50.000": 0.3} # 계획고 초안 그대로
def test_확정_중_노선이_바뀌면_다시_세우고_확정은_다시(out):
calls = out["routeConfirmCalls"]
assert calls[1] == "route" and calls[-2:] == ["rebuild", "overlay:off"]
assert "done:true" not in calls and "cross:true" not in calls