- 배수유역 머리 [자동 제안] → 고름 창(⚙ 창 틀) ① 전체 자동(all) ② 지정 포함 추가(add) · 낡은 동안 끔 - [배관 배치 초기화] — 저장분 즉시 지움 없앰 · 관 목록 없이 셈(세류선 교차만) · 초안 - 셋 다 답이 오면 세션 초안 + 사진첩 한 걸음(400 ms 넘은 답도 제 걸음) - 제안 목록 [적용] — 관 자리 후보는 안 더함(점선 원 · 목록 그대로) · 관경 · 시설 제안 없으면 꺼짐 - computeDetailBasins(points | null, auto) · 글 = 툴팁 두 값 고침 · 새 글 끝에 새 키 - 시험 — 349 흐름에 자동 제안 · 347_2 · 298 · 312 고쳐 씀 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VmC34fsxxbweEG3RUjs3Mn
148 lines
6.0 KiB
Python
148 lines
6.0 KiB
Python
"""349 배수유역 자동/수동 흐름 — 화면 파일(`B05_Profile_UI_Drainage_Live` · `_Run` · `_Suggest`)을
|
|
가짜 DOM · 시계 · fetch 로 돌림(`helper_349_drainage_live_flow.cjs`).
|
|
|
|
- 수동: 꼭짓점 옮김 = 흐림 + [업데이트] · [업데이트] 뒤 묶음이 같은 노선 · 관으로 다시 돌아도 답을 받음
|
|
(입력 지문) · 답이 오면 흐림 · 「계산 중」 사라짐 · 제안 「제안 N건」 · [적용].
|
|
- 요청 중 또 옮기면 앞 요청 끊고 다시 흐림.
|
|
- 노선 [저장] 뒤 서버 답이 「낡음」 = 수동은 흐림 + [업데이트] · 자동은 3 s 뒤 조용히.
|
|
- ⚙ 자동: 흐림 없음 · 3 s 조용한 뒤 셈 · 그 사이 또 옮기면 처음부터 · [업데이트] 막 없음(349-0e).
|
|
- 낡은 동안 제안 [적용] 끔(두 모드) · 새 답이 오면 다시 켬(349-0e).
|
|
- 되돌리기로 초안이 없어지면 저장분 다시 받기 · 그 답의 낡음대로 · 초안용 답 끊음 · 자동도 3 s 셈 대신(349-0i).
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def r():
|
|
proc = subprocess.run(
|
|
["node", os.path.join(HERE, "helper_349_drainage_live_flow.cjs")],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=60,
|
|
encoding="utf-8",
|
|
)
|
|
assert proc.returncode == 0, proc.stderr
|
|
return json.loads(proc.stdout)
|
|
|
|
|
|
STALE_TITLE = "노선이 바뀌어 제안이 낡음 — 새 계산 뒤 적용"
|
|
|
|
|
|
def state(stale, veil, busy, fetches, auto=False):
|
|
return {
|
|
"stale": stale,
|
|
"blur": stale and not auto, # 자동 = 흐림 없음
|
|
"veil": veil,
|
|
"busy": busy,
|
|
"fetches": fetches,
|
|
"applyOff": stale,
|
|
}
|
|
|
|
|
|
def look(row):
|
|
return {k: v for k, v in row.items() if k != "applyTitle"}
|
|
|
|
|
|
def test_manual_update_answer_lands_after_same_rerun(r):
|
|
assert look(r["moved"]) == state(True, True, False, 0)
|
|
assert look(r["sameAgain"]) == state(True, True, False, 0) # 판 번호만 바뀜 — 다시 안 흐림
|
|
assert r["sent"]["fetches"] == 1 and r["sent"]["busy"] and not r["sent"]["veil"]
|
|
rerun = r["rerunWhileBusy"]
|
|
assert not rerun["aborted"] and rerun["busy"] # 같은 입력 다시 돎 — 요청 그대로
|
|
assert look(r["answered"]) == {**state(False, False, False, 1), "applied": 1}
|
|
|
|
|
|
def test_suggestions_and_apply(r):
|
|
assert r["suggest"]["shown"] and r["suggest"]["title"] == "제안 2건"
|
|
assert len(r["suggest"]["rows"]) == 2
|
|
points = {p["chainage_m"]: p for p in r["applied"]["points"]}
|
|
assert points[100]["options"]["pipe_diameter_mm"] == 1200
|
|
assert 200 not in points # 관 자리 후보는 [적용]이 안 더함 — [자동 제안] 몫(369)
|
|
assert r["applied"]["remembered"] == 1
|
|
|
|
|
|
def test_자동_제안_두_방식_초기화_세류선만_한_걸음(r):
|
|
assert r["proposeStale"] == {
|
|
"off": True,
|
|
"title": "B05_Drainage_Btn_Propose_Stale",
|
|
"calls": 0, # 낡은 동안 끔 — 눌러도 요청 없음
|
|
}
|
|
assert r["proposeFresh"] == {"off": False, "title": "B05_Drainage_Btn_Propose_Tip"}
|
|
assert r["propose"] == {
|
|
"autos": ["all", "add", None],
|
|
"points": [1, 1, None], # ① ② = 지금 관 목록 함께 · 초기화 = 목록 없이(세류선만)
|
|
"remembered": 3, # 셋 다 초안
|
|
"records": 3, # 답마다 사진첩 한 걸음
|
|
}
|
|
|
|
|
|
def test_move_while_busy_cancels(r):
|
|
moved = r["movedWhileBusy"]
|
|
assert moved["aborted"] and moved["veil"] and not moved["busy"]
|
|
assert look(r["fresh"]) == state(False, False, False, 3)
|
|
|
|
|
|
def test_stale_blocks_apply(r):
|
|
assert r["movedWhileBusy"]["applyOff"]
|
|
assert r["movedWhileBusy"]["applyTitle"] == STALE_TITLE
|
|
assert r["staleApply"] == 1 # 눌러도 옛 제안이 안 들어감(앞 [적용] 한 번뿐)
|
|
assert not r["fresh"]["applyOff"] and r["fresh"]["applyTitle"] != STALE_TITLE
|
|
|
|
|
|
def test_saved_stale_shows_update(r):
|
|
assert {k: v for k, v in look(r["savedStale"]).items() if k != "status"} == state(
|
|
True, True, False, 3
|
|
)
|
|
assert r["savedStale"]["status"] == "배수유역 낡음"
|
|
|
|
|
|
def test_auto_mode(r):
|
|
assert look(r["autoSwitch"]) == state(
|
|
True, False, False, 3, True
|
|
) # 자동 — 흐림 없음 · [업데이트] 없음
|
|
assert r["auto2999"]["fetches"] == 3
|
|
assert r["auto3000"]["fetches"] == 4 and r["auto3000"]["route"] is None
|
|
assert r["auto3000"]["busy"] and not r["auto3000"]["blur"] # 계산 중 = 구석 표시만
|
|
assert look(r["autoDone"]) == state(False, False, False, 4)
|
|
assert look(r["autoRestart"]) == state(
|
|
True, False, False, 4, True
|
|
) # 2 s + 2 s — 처음부터 다시 3 s
|
|
assert look(r["autoMoved"]) == {**state(False, False, False, 5), "mode": "auto"}
|
|
assert look(r["autoSavedStale"]) == state(True, False, False, 5, True)
|
|
assert look(r["autoSavedDone"]) == state(False, False, False, 6)
|
|
|
|
|
|
def test_undo_back_to_saved(r):
|
|
# 자동 — 끌기 뒤 1 s 에 되돌리기: 3 s 셈 없음 · 저장분 하나 · 답 오면 낡음 풂
|
|
assert look(r["autoUndoWaiting"]) == {**state(True, False, False, 6, True), "savedLoads": 2}
|
|
assert r["autoUndoNoFetch"]["fetches"] == 6
|
|
assert look(r["autoUndo"]) == {**state(False, False, False, 6), "savedLoads": 2}
|
|
# 수동 — [업데이트] 요청 중 되돌리기: 초안용 요청 끊음 · 저장분 답 올 때까지 흐림 그대로
|
|
waiting = r["undoWaiting"]
|
|
assert waiting["aborted"] and waiting["stale"] and waiting["blur"] and not waiting["busy"]
|
|
assert look(r["undo"]) == {**state(False, False, False, 7), "savedLoads": 3}
|
|
# 저장분 자체가 낡음 = 흐림 · [업데이트] · [적용] 꺼짐 그대로
|
|
assert look(r["undoSavedStale"]) == {
|
|
**state(True, True, False, 7),
|
|
"savedLoads": 4,
|
|
"mode": "manual",
|
|
}
|
|
|
|
|
|
def test_input_key(r):
|
|
assert look(r["keys"]) == {
|
|
"rev": True,
|
|
"order": True,
|
|
"node": False,
|
|
"pipe": False,
|
|
"id": False,
|
|
"saved": True,
|
|
}
|