- 1 가림막 저장(확정)도 도중 고침 검사 · 「저장 중 고친 값」 알림(키는 안 막음 · B07~B09 안 번짐) - 2b snapOptions 간격 = 10 만 · 옛 초안 0.1 · 1 m = null(load_station_snap_step 거울) - 3 추가 측점 = 측점 묶음 data-plus · B05 누름 · 가까이 표시가 그 속성으로 - 4d 숨은 동안 고른 카드 = 다음 다시 그림 끝에 밂 · 휠 · 누름이면 버림 - 7 pipe_calc set 다섯 = 풀개 · 횡단 · 종단 보기 onDispose - 8 저장 중 [초기화] = [저장]과 같은 알림 + 막음 · /route/reset = 프로젝트 잠금 안 - 9 isUserPick 거름 = 묶음 띄운 그 누름만 · 계산 중 새 누름 = 폄 - 시험 test_505_2_save_pick 새 · 398_1 busyPick 1 · 390_5 글 맞춤 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
142 lines
6.3 KiB
Python
142 lines
6.3 KiB
Python
"""PLAN 505-2 B05 · B06 저장 · 고름 흠(요구 309-3 · C1 1 · 2b · 3 · 4d · 7 · 8 · 9).
|
|
|
|
못박는 것
|
|
- 1 확정 · 노선 아닌 가림막 저장 도중 고침도 조각 정리 건너뜀 + 「저장 중 고친 값」 알림
|
|
(키는 안 막음 · B07~B09 안 번짐).
|
|
- 2b 옛 세션 초안 간격 0.1 · 1 m = null — 서버 `load_station_snap_step` 과 거울.
|
|
- 3 추가 측점 = 측점 묶음 `data-plus` — 고른 뒤(선 클래스 --selected)에도 B05 누름 판정이 읽음.
|
|
- 4d 숨은 동안 고른 카드 = 다음 다시 그림 끝에 밂 · 사용자 휠 · 누름이면 버림.
|
|
- 7 횡단 · 종단 보기가 건 set 넷 + 여는 이 = 풀개를 onDispose 에 — 자기 값일 때만 비움.
|
|
- 8 저장 중 [초기화] = [저장]과 같은 알림 + 막음 · 서버 /route/reset = 프로젝트 잠금 안.
|
|
- 9 = `test_398_1_reveal_user_pick.py` busyPick 1.
|
|
|
|
./venv/Scripts/python.exe -m pytest resources/tester/test_505_2_save_pick.py -q
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(ROOT))
|
|
HELPER = Path(__file__).resolve().parent / "helper_505_2_save_pick.cjs"
|
|
DESIGN = "A00_Common/design/"
|
|
|
|
|
|
def _read(rel: str) -> str:
|
|
return (ROOT / rel).read_text(encoding="utf-8")
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def out() -> dict:
|
|
run = subprocess.run( # noqa: S603, S607 — 고정 실행 파일
|
|
["node", str(HELPER)], capture_output=True, text=True, encoding="utf-8", timeout=60
|
|
)
|
|
assert run.returncode == 0, run.stderr
|
|
return json.loads(run.stdout)
|
|
|
|
|
|
def _server_step(tmp_path: Path, step: float | None) -> float | None:
|
|
from B05_Profile.B05_Profile_Engine_Sections import load_station_snap_step
|
|
from common_util.common_util_route_geometry import planned_route_working_path
|
|
|
|
path = planned_route_working_path(tmp_path)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text("x,y\n0,0\n10,0\n", encoding="utf-8")
|
|
curves = {"curves": [], "station_snap": True, "station_snap_step": step}
|
|
path.with_name(f"{path.stem}_curves.json").write_text(json.dumps(curves), encoding="utf-8")
|
|
return load_station_snap_step(tmp_path)
|
|
|
|
|
|
def test_2b_초안_간격_서버와_거울(out, tmp_path) -> None:
|
|
steps = [10, 0.1, 1, 20, None]
|
|
assert (
|
|
out["draft"] == [_server_step(tmp_path, s) for s in steps] == [10, None, None, None, None]
|
|
)
|
|
assert out["saved"] == [10, None, None, None]
|
|
assert out["snapKept"] is True # 스냅 켬은 그대로 · 간격만 측점 No.
|
|
|
|
|
|
def test_7_풀개는_자기_값만(out) -> None:
|
|
assert out["afterRelease"] == 0
|
|
assert out["newKept"] == {"a": 0, "b": 1}
|
|
assert out["sinkOpened"] == 1 and out["sinkReleasedOpened"] == 2
|
|
cross = _read(DESIGN + "design_view_cross.ts")
|
|
for call in ("setPipeExtraCountSource(", "setPipeInletSource(", "setPipeRevetChoiceBridge("):
|
|
assert f"host.onDispose(\n {call}" in cross or f"host.onDispose({call}" in cross, call
|
|
assert "const releaseSink = setPipeExtraCountSink(" in cross
|
|
assert "host.onDispose(releaseSink);" in cross
|
|
profile = _read(DESIGN + "design_view_profile.ts")
|
|
assert 'host.onDispose(setPipeCrossOpener(() => shared.ensureView("cross")));' in profile
|
|
|
|
|
|
def test_1_가림막_저장도_도중_고침_검사() -> None:
|
|
save = _read(DESIGN + "design_save.ts")
|
|
assert "const touched = !route && designSignature(ctx.projectId) !== sent;" in save
|
|
assert "if (touched && locked)" in save and "저장 중 고친 값은 아직 저장 안 됨" in save
|
|
assert "!locked && designSignature" not in save
|
|
elements = _read("ui_template/ui_template_elements.ts")
|
|
assert "keydown" not in elements[elements.index("function ensureOverlay") :][:1200]
|
|
|
|
|
|
def test_8_저장_중_초기화_막음() -> None:
|
|
reset = _read(DESIGN + "design_reset.ts")
|
|
body = reset[reset.index("export async function resetDesign(") :]
|
|
assert body.index("saveBusy()") < body.index("showConfirmDialog(")
|
|
assert "if (saveBusy()) return;" in body # 확인 창 동안 뜬 뒤 저장도
|
|
assert "designSaveBusy(projectId)" in body and "SAVE_BUSY_TEXT" in body
|
|
save = _read(DESIGN + "design_save.ts")
|
|
assert save.count("showToast(SAVE_BUSY_TEXT") == 1 # [저장]과 같은 글
|
|
|
|
|
|
def test_8_서버_초기화는_프로젝트_잠금_안(monkeypatch) -> None:
|
|
from B05_Profile import B05_Profile_Router_Lifecycle as lifecycle
|
|
from common_util import common_util_save_guard as guard
|
|
|
|
pid = uuid4()
|
|
seen: list[bool] = []
|
|
|
|
async def body(project_id, session):
|
|
seen.append(guard.project_lock(str(project_id)).locked())
|
|
return "ok"
|
|
|
|
monkeypatch.setattr(lifecycle, "_reset_route_design", body)
|
|
assert asyncio.run(lifecycle.reset_route_design(pid, None)) == "ok"
|
|
assert seen == [True]
|
|
assert not guard.project_lock(str(pid)).locked()
|
|
src = _read("B05_Profile/B05_Profile_Router_Lifecycle.py")
|
|
inner = src[src.index("async def _reset_route_design(") :]
|
|
assert "project_lock" not in inner # 안에서 같은 잠금 다시 안 잡음(교착)
|
|
|
|
|
|
def test_3_추가_측점은_data_plus() -> None:
|
|
longi = _read("B06_Section/B06_Section_UI_Longitudinal.ts")
|
|
assert 'if (station.plus) marker.setAttribute("data-plus", "1");' in longi
|
|
modal = _read("B05_Profile/B05_Profile_UI_Profile_CrossModal.ts")
|
|
render = _read("B05_Profile/B05_Profile_UI_Profile_Render.ts")
|
|
assert 'marker?.hasAttribute("data-plus")' in modal
|
|
assert '!marker.hasAttribute("data-plus")' in render
|
|
assert "station-line--plus" not in modal + render
|
|
|
|
|
|
def test_4d_숨은_동안_고름은_다음_그림에_밂() -> None:
|
|
view = _read("B06_Section/B06_Section_UI_Section_View.ts")
|
|
assert len(view.splitlines()) <= 700
|
|
reveal = view[view.index("const revealCard = ") :][:300]
|
|
assert "pendingReveal = stationId;" in reveal
|
|
draw = view[view.index("cards.scrollTop = keepScrollTop;") :][:200]
|
|
assert 'if (pendingReveal) revealCard(pendingReveal, "auto");' in draw
|
|
assert "pendingReveal = null;" in draw
|
|
assert "cards.onwheel = cards.onpointerdown = () => (pendingReveal = null);" in view
|
|
|
|
|
|
def test_파이프라인_페이지_안_늘림() -> None:
|
|
assert len(_read(DESIGN + "design_pipeline_page.ts").splitlines()) <= 699
|