Files
Aislo/resources/tester/test_280_5_b07_settings.py
T
eomsangdonandClaude Opus 5.5 871174d214 feat(523-1): B07 설정 모달 공용 고침 — 끌기 닫힘 막음 · 폭 절반 · 그룹 상자 · 단추 줄 하나 · 자동 체크 없앰
- 공용 모달 바깥 누르기 = 누름 · 뗌 둘 다 배경일 때만 닫음(숫자 칸 끌기로 안 닫힘 · 평면도 ⚙ 포함 모든 openModal)
- ⚙ 설정 모달(종단 · 횡단 · 토적 · 구조물 · 전개도) 폭 720 → 360 px · 그룹 상자(legend) · 맨 아래 단추 줄 하나([안내선 숨기기] [기본값] [취소] [확인])
- [확인] = 바뀐 것만 저장(축척 → 배치) 뒤 다시 그림 · 붉은 칸이면 꺼짐 · 횡단 [이 장] [모든 장] [배율 적용] 단추 → 「적용」 고르기 + [확인]
- 축척 「자동」 체크 걷음 — 기본 = 지정값(종단 평면도 1/5,000 · 토적 X 1/2,000 · 전개도 X · Y 1/50) · 옛 null = 기본으로 읽음
- 낡은 시험 고침: 153-1(지운 buildCrossLayoutControls 대신 ⚙ 횡단 모달) · 212-3 · 180-3(안내선 단추 자리) · 280-5(자동 체크 · 단추 판정) · 485 축척 · 토적 · 종단 평면 칸(옛 자동 축척 값 → 지정 기본값)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 14:34:31 +09:00

271 lines
12 KiB
Python

# -*- coding: utf-8 -*-
"""280-5 · 280-6 · 281 B07 좌측 ⚙ 설정 · 확정 단추 지움 · 저장 후 이동.
못박는 것
- ⚙ = 카드 제목(평면도 · 횡단도 · 구조물 · 전개도) · 한 장 단추 안(종단 1장 · 토적) · 표지 없음.
- ⚙ 누름 = 카드 접힘 그대로 · 도면 안 엶(Enter 도) · 제목 누름 · 단추 누름은 그대로.
- 모달 — 횡단 = 배치 칸(지금 장 값) + 표 배율 + 안내선 · 평면도 = 평면도 관리 모달 · 구조물 = 축척 없음 줄 ·
머리 [원본에서 다시 그리기] = 그 묶음 도면을 연 때만 켬(표준 횡단면도 = 횡단 묶음).
- 523-1 — 그룹 상자(legend) · 맨 아래 단추 줄 하나([기본값] [취소] [확인]) · 「자동」 체크 없음 ·
[확인] = 바뀐 것만 저장 · 축척 기본 = 지정값(종단 평면도 1/5,000 · 토적 X 1/2,000 · 전개도 1/50).
- 저장 묶음 — 고친 탭(●) + 지금 탭만 confirm · 고친 것 없으면 요청 0 · 실패 / 도각 편집 = 거짓(머묾) ·
원본 되돌림 = 물음 → invalidate → 다시 실음 · [취소] = 그대로.
- 페이지 — [현재 도면 확정] · [수정] · 자동 B08 이동 · 「모든 횡단 확정」 잠금 없음 · 읽기 전용 안 묶음 ·
☰ · 단계 판 · [<] [>] = 저장 → (B08 · B09) 단계 4 완료 → 이동 · 탭 줄 겹판.
- 서버 `design-drawings-complete` = 단계 4 완료.
Node 헬퍼 `helper_280_5_b07_settings.cjs` 가 화면 그릇 흉내로 돌림.
"""
from __future__ import annotations
import asyncio
import json
import os
import re
import subprocess
from pathlib import Path
from uuid import UUID
import pytest
import B07_DesignDetail.B07_DesignDetail_Router as router
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = Path(HERE).parents[1]
B07 = ROOT / "B07_DesignDetail"
def _read(path: Path) -> str:
return path.read_text(encoding="utf-8").replace("\r\n", "\n")
@pytest.fixture(scope="module")
def out():
proc = subprocess.run(
["node", os.path.join(HERE, "helper_280_5_b07_settings.cjs")],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def test_설정_단추_자리(out):
assert out["cardGears"] == ["plan", "cross", "structure", "development"]
assert out["cardGearInTitle"] is True
assert out["buttonGears"] == ["longitudinal", "mass_haul"]
assert out["buttonGearRole"] == [["SPAN", "button", 0]] * 2 # 단추 안 단추 금지
assert out["coverGear"] == 0
def test_설정_누름은_접힘_도면_열기_안_일으킴(out):
assert out["collapsedSame"] is True
assert out["selectedAfterGear"] == []
assert out["enterStops"] is True
assert out["settingsCalls"] == [
"plan",
"cross",
"structure",
"development",
"longitudinal",
"mass_haul",
]
assert out["titleClickToggles"] is True and out["buttonClickOpens"] is True
def test_횡단_모달은_배치_칸_표_배율_안내선(out):
cross = out["crossModal"]
assert cross["title"] == "횡단도 설정"
assert cross["legends"] == ["장 배치", "표", "축척 — 횡단도 장(표준 횡단면도 뺌)"]
assert cross["labels"] == ["배치 열", "행", "적용", "표 배율"]
assert cross["values"] == ["3", "2", "1.2"] # 지금 장(1장) 값 · 표 배율
assert cross["footers"] == 1 and cross["buttonsInGroups"] == 0 # 단추 줄 하나(523-1)
assert cross["footerButtons"] == ["안내선 숨기기", "기본값", "취소", "확인"]
assert cross["head"] == [["원본에서 다시 그리기", False]]
assert cross["notes"] == ["지금 도면: cross_s1"]
saved = out["crossSave"]
assert saved["closed"] is True and saved["scaleCalls"] == 0 # 축척 그대로 = 안 보냄
assert saved["saved"] == [[{"default": [4, 4], "sheets": {"1": [2, 2]}, "table_scale": 1.2}, 1]]
def test_구조물_종단_평면도_모달(out):
structure = out["structureModal"]
assert structure["title"] == "구조물 도면 설정"
assert structure["legends"] == ["축척"] and "M02 그림 — 축척 없음" in structure["notes"]
assert structure["footerButtons"] == ["취소", "확인"] and structure["closed"] is True
assert structure["head"][0][1] is True # 다른 묶음 도면을 연 중 = 꺼짐
assert out["longModal"] == {"title": "종단도 설정", "legends": ["축척 — 종단도"], "footers": 1}
assert out["planModal"] == {"viaManager": True, "head": [["원본에서 다시 그리기", False]]}
assert out["planRedraw"] == {"redraws": 1, "closed": True}
assert out["standardInCross"] is True
def test_저장은_고친_탭만_실패면_머묾(out):
assert out["noEdits"] == {"ok": True, "calls": []}
saved = out["saveEdited"]
assert saved["ok"] is True
assert saved["confirms"] == [
["confirm", "cross_s1", "live", {"q": 1}], # 지금 탭 = CAD 에서 · 수량표 같이
["confirm", "longitudinal", "stash", None], # 다른 고친 탭 = 받아 둔 편집본
]
assert saved["clean"] == [False, False, False]
assert sorted(saved["confirmed"]) == [
"cross_s1",
"longitudinal",
] # 안 고친 mass_haul 은 안 보냄
assert out["saveFail"] == {"ok": False, "toasts": ["error"]}
assert out["frameEditing"] == {"ok": False, "calls": 0}
def test_원본에서_다시_그리기(out):
assert out["redrawable"] is True
assert out["redraw"] == {
"ok": True,
"calls": ["ask", "invalidate", "forget", "reload"],
"confirmed": False,
}
assert out["pristine"] is False # 원본 그대로 = 꺼짐
assert out["redrawCancel"] == {"ok": False, "invalidates": 0}
def test_페이지_확정_단추_잠금_자동이동_없음():
page = _read(B07 / "B07_DesignDetail_UI_Page.ts")
for gone in (
"현재 도면 확정",
'"수정"',
"all_confirmed",
"allDrawingsConfirmed",
"confirmDesignDrawing",
"invalidateDesignDrawing",
"crossLayoutSlot",
):
assert gone not in page, gone
assert "confirmed: false, // 잠금 없음(요구 281)" in page
assert "frameRow.append(frameEditor.button);" in page # 298-2 — [도각 편집] = 윗줄
assert "onStepClick: (stepIndex) => moveToStage(stepIndex)," in page
move = page[
page.index("const moveToStage = (") : page.index("const layout = createWorkflowLayout")
]
assert move.index("saveEditedDrawings(store)") < move.index("completeDesignStage(projectId)")
assert move.index("completeDesignStage(projectId)") < move.index("moveStage(projectId")
assert "if (stepIndex > 4 && drawings.length > 0)" in move
assert "currentStage: 5," in page
assert len(page.splitlines()) <= 700
def test_탭_줄_겹판_약속():
page = _read(B07 / "B07_DesignDetail_UI_Page.ts")
assert 'cad.element.style.getPropertyValue("--cad-tab-slot-left") !== ""' in page
assert "if (slotted) cad.element.append(tabs.element);" in page
assert "else cadStack.prepend(tabs.element);" in page
css = _read(B07 / "B07_DesignDetail_UI_Style.css")
block = css[css.index(".b07-cad-stack .cad-host > .cad-tabs {") :]
block = block[: block.index("}")]
assert "right: var(--cad-tab-slot-right, 0px);" in block
assert "left: var(--cad-tab-slot-left, 0px);" in block
assert re.search(r"height:\s*25px", block)
class _Cursor:
async def __aenter__(self) -> _Cursor:
return self
async def __aexit__(self, *_: object) -> bool:
return False
class _Connection:
async def begin(self) -> None: ...
async def commit(self) -> None: ...
async def rollback(self) -> None: ...
def cursor(self) -> _Cursor:
return _Cursor()
class _Acquire:
async def __aenter__(self) -> _Connection:
return _Connection()
async def __aexit__(self, *_: object) -> bool:
return False
def test_서버_단계_완료(monkeypatch, tmp_path: Path) -> None:
stages: list[tuple[str, int]] = []
async def source(_project_id: UUID) -> tuple[int, Path, Path, bool]:
return 1, tmp_path, tmp_path / "longitudinal.json", True
async def complete(project_id: str, stage_no: int, action: str, _cursor: object) -> None:
stages.append((project_id, stage_no, action))
monkeypatch.setattr(router, "_confirmed_source", source)
monkeypatch.setattr(router, "mark_stage", complete)
monkeypatch.setattr(
router, "get_db_pool", lambda: type("P", (), {"acquire": lambda self: _Acquire()})()
)
project = UUID("8b98ca75-c572-4002-8468-2adae4323414")
result = asyncio.run(router.complete_design_stage(project))
assert result == {"status": "success", "project_id": str(project)}
assert stages == [(str(project), 4, "next")] # 298-2 [>] = 4 완료 · 5 진행 중
async def blocked(_project_id: UUID) -> tuple[int, Path, Path, bool]:
raise PermissionError("B06 종·횡단 확정 후 상세 설계를 진행할 수 있습니다.")
monkeypatch.setattr(router, "_confirmed_source", blocked)
assert asyncio.run(router.complete_design_stage(project)).status_code == 409
RANGE = "1/100~1/10,000 사이 정수"
def test_485_2_축척_칸_판정(out):
assert out["readAxis"] == [1000, RANGE, RANGE, RANGE, RANGE, RANGE]
assert out["toSend"] == [None, 500] # 기본과 같으면 null
def test_485_2_종단_축척_저장(out):
scale = out["scale"]
assert scale["longLabels"] == ["X(가로) 1/", "Y(세로) 1/", "평면도 1/"]
assert scale["longLegend"] == ["축척 — 종단도"]
assert scale["longFooter"] == ["기본값", "취소", "확인"]
assert scale["longStart"]["values"] == ["1000", "200", "5000"] # 평면도 = 지정값(523-1)
assert scale["longStart"]["disabled"] == [False, False, False]
for bad in ("longLow", "longFrac"): # 범위 밖 · 소수 = 붉음 + [확인] 꺼짐
assert scale[bad]["errors"] == [RANGE, None, None]
assert scale[bad]["save"] is False
assert scale["longOk"]["errors"] == [None, None, None] and scale["longOk"]["save"] is True
# 저장본 2장 — 물음 [취소] = 요청 없음 · 모달 그대로
assert scale["cancel"] == {"calls": ["ask"], "open": True, "closed": 0}
save = scale["save"]
assert "저장본 2장" in save["ask"]
assert save["sent"] == {"longitudinal": {"x": 500, "y": None, "plan": None}} # 기본 → null
assert save["open"] is False
assert save["closed"] == [[True, "longitudinal"]] # 화면 다시 세움 · 같은 도면
assert scale["reset"]["values"] == ["1000", "200", "5000"] # [기본값] = 칸만
assert scale["same"] == {"calls": 0, "open": False} # 바뀐 것 없음 = 닫기만
assert scale["planLow"]["errors"] == [None, None, "1/500~1/50,000 사이 정수"]
assert scale["planLow"]["save"] is False
assert scale["planSent"] == {"longitudinal": {"x": None, "y": None, "plan": 3000}}
assert scale["cancelButton"] == {"calls": 0, "open": False}
def test_523_1_토적_전개도_지정값_자동_없음(out):
scale = out["scale"]
assert scale["massStart"] == {
"values": ["2000", "50000"],
"errors": [None, None],
"disabled": [False, False],
"save": True,
}
assert scale["massSent"] == {"ask": 0, "sent": {"mass_haul": {"x": 3000, "y": None}}}
assert scale["dev"]["values"] == ["50", "50"] and scale["dev"]["disabled"] == [False, False]
assert scale["structure"] == 0 # 구조물 = 축척 칸 없음
assert scale["autoBoxes"] == 0 # 「자동」 체크 = 모든 모달에서 0