Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1
This commit is contained in:
@@ -1,91 +0,0 @@
|
||||
/* 283 · 493-2 추천 [적용] = 초기화 — 진짜 TS(`logic/B05_Profile_Route_Reset` · `B05_Profile_UI_RouteReset`
|
||||
* applyResetText)를 Node 로 돌림 · 서버 저장분 받기는 가짜(입력 JSON `server`).
|
||||
* 입력 argv[2] JSON · 표준출력 JSON 한 벌.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
|
||||
function loadTs(file, stubs = {}) {
|
||||
return makeLoader({
|
||||
root: ROOT,
|
||||
cache: false,
|
||||
requireFor: (full) => (request) => {
|
||||
if (stubs[request]) return stubs[request];
|
||||
throw new Error(`stub 없음: ${request} (${file})`);
|
||||
},
|
||||
}).loadTs(path.join(ROOT, file));
|
||||
}
|
||||
|
||||
const reset = loadTs("B05_Profile/logic/B05_Profile_Route_Reset.ts");
|
||||
|
||||
/** 서버 저장분 가짜 — 값 = 돌려줄 것 · "fail" = 받기 실패. */
|
||||
const answer = (value) =>
|
||||
value === "fail" ? Promise.reject(new Error("받기 실패")) : Promise.resolve(value);
|
||||
|
||||
async function applyText(server) {
|
||||
const ui = loadTs("B05_Profile/B05_Profile_UI_RouteReset.ts", {
|
||||
"../A00_Common/design/design_page_data": {
|
||||
pageStructures: () => answer(server.structures),
|
||||
pagePipes: () => answer(server.pipes),
|
||||
},
|
||||
"./B05_Profile_Api_PlusStations": { fetchSavedPlusStations: () => answer(server.plus) },
|
||||
"./logic/B05_Profile_Route_Reset": reset,
|
||||
});
|
||||
return ui.applyResetText("p");
|
||||
}
|
||||
|
||||
/** 493-5 — 추가 측점 저장분 기억: 저장 [127 · 163] → 서버가 [적용]으로 지움 → 다시 세운 페이지가 읽는 값. */
|
||||
async function plusAfterApply(reload) {
|
||||
let server = [127, 163];
|
||||
const drafts = new Map();
|
||||
global.window = { setTimeout: () => 0, clearTimeout: () => undefined };
|
||||
global.fetch = async () => ({
|
||||
ok: true,
|
||||
json: async () => ({ chainages: server, stations: server.map((c) => ({ chainage_m: c })) }),
|
||||
});
|
||||
const plus = loadTs("B05_Profile/B05_Profile_Api_PlusStations.ts", {
|
||||
"../config/config_frontend": { API_BASE_URL: "", API_TIMEOUT_MS: 1000 },
|
||||
"../A00_Common/design/design_store": {
|
||||
draftProjectId: () => "p",
|
||||
useDesignStore: () => ({
|
||||
get: (k) => drafts.get(k) ?? null,
|
||||
set: (k, v) => drafts.set(k, v),
|
||||
}),
|
||||
},
|
||||
"./B05_Profile_UI_Profile_History": { onPageHistory: () => undefined },
|
||||
});
|
||||
await plus.loadSavedPlusStations("p");
|
||||
plus.followPlusDraft("p", { plusStations: [127, 163] }); // 노선 끌기 따라감 기억
|
||||
const before = plus.plusStationsOf("p");
|
||||
server = []; // 추천 [적용] — 서버가 지움 · 세션 초안 비움(clearDrafts)
|
||||
drafts.clear();
|
||||
if (reload) await plus.reloadSavedPlusStations("p");
|
||||
await plus.loadSavedPlusStations("p"); // 다시 세운 페이지
|
||||
return { before, after: plus.plusStationsOf("p"), body: plus.plusStationsPart("p").body };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const plusApply = { fixed: await plusAfterApply(true), stale: await plusAfterApply(false) };
|
||||
const texts = {};
|
||||
for (const [name, server] of Object.entries(input.servers)) texts[name] = await applyText(server);
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
texts,
|
||||
plusApply,
|
||||
notice: reset.routeResetNotice(input.reset, 2),
|
||||
noticeNone: [
|
||||
reset.routeResetNotice(undefined),
|
||||
reset.routeResetNotice({ reset: false, dropped: { pipes: 3 } }),
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
process.stderr.write(String(error?.stack ?? error));
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -149,38 +149,5 @@ const apply = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_Ap
|
||||
out.kindresetAfter = readState("kindreset", "p1");
|
||||
out.carryCalls = [...calls];
|
||||
|
||||
// 추천 [적용] — as_initial · 잠금 막 · 초안 비움 · 다시 세움
|
||||
useDesignStore("p1", null).set("routeDraft", draft);
|
||||
writeState("pipes", [{ chainage_m: 1 }], "p1");
|
||||
calls.length = 0;
|
||||
const snapshot = {
|
||||
planned: [
|
||||
[1, 1],
|
||||
[9, 9],
|
||||
],
|
||||
curveOn: [true, true],
|
||||
curveRadius: [null, null],
|
||||
};
|
||||
out.recommendOk = await apply.saveRecommendedRoute(
|
||||
"p1",
|
||||
snapshot,
|
||||
{ mode: "earthwork" },
|
||||
true,
|
||||
"sheet", // 509-5 — 창에서 고른 지형
|
||||
);
|
||||
out.recommendBody = bodies[bodies.length - 1];
|
||||
out.recommendCalls = calls.filter((c) => !c.startsWith("toast:"));
|
||||
out.recommendDraftsLeft = {
|
||||
route: useDesignStore("p1", null).get("routeDraft"),
|
||||
pipes: readState("pipes", "p1"),
|
||||
};
|
||||
out.rebuilt = rebuilt;
|
||||
|
||||
// 추천 저장 실패 — false · 오류 한 줄 · 다시 안 세움
|
||||
failReplan = true;
|
||||
calls.length = 0;
|
||||
out.recommendFail = await apply.saveRecommendedRoute("p1", snapshot, null, false);
|
||||
out.recommendFailCalls = [...calls];
|
||||
out.rebuiltAfterFail = rebuilt;
|
||||
console.log(JSON.stringify(out));
|
||||
})();
|
||||
|
||||
@@ -164,9 +164,9 @@ def test_화면_고르기_배선():
|
||||
main = (page / "B05_Profile_UI_RouteEdit.ts").read_text(encoding="utf-8")
|
||||
api = (page / "B05_Profile_Api_Replan.ts").read_text(encoding="utf-8")
|
||||
initial_api = (page / "B05_Profile_Api_RouteInitial.ts").read_text(encoding="utf-8")
|
||||
# 509-5 — 평면은 설계 지형 이름만(둘 다 있을 때) · 고르기 · 지형 실음 = 추천 창(test_509_5)
|
||||
assert 'data-slot="terrain-name" hidden' in chrome and 'data-act="terrain"' not in chrome
|
||||
assert 'sources?.sources.includes("las") && sources.sources.includes("sheet")' in main
|
||||
# 626-1 — 평면 도구줄 지형 칩(공용 · 고르기 = 확인창 → 설계 지형 바꿈) · 옛 이름 칩 · 추천 창 고르기는 걷음
|
||||
assert 'data-slot="terrain-name"' not in chrome and "terrain: terrain.root" in chrome
|
||||
assert "chrome.terrain.load(sources)" in main
|
||||
assert "analysisTerrain" not in api and "analysisTerrain" not in initial_api
|
||||
assert "body: JSON.stringify(request)," in initial_api
|
||||
replan_fn = api.split("export async function replanRoute")[1].split("export ")[0]
|
||||
|
||||
@@ -291,8 +291,10 @@ 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")
|
||||
# 509-5 — 지형 바꿈 = 추천 [적용]만(창 지형) · 손 [저장] 초안 = 설계 지형(null)
|
||||
# 626-1 — 지형 바꿈 = 도구줄 지형 칩(`/route/design-terrain`) · 손 [저장] 초안 = 설계 지형(null)
|
||||
assert "terrain: settings.terrain," in apply
|
||||
assert "const settings = () => ({ ...params.snap(), terrain: null });" in apply
|
||||
assert "stationSnapStep: null,\n terrain,\n" in apply # 창 지형(509-5)
|
||||
assert "saveRecommendedRoute" not in apply # 옛 추천 [적용] 화면 부름 걷음
|
||||
chip = (page / "B05_Profile_UI_RouteEdit_Terrain.ts").read_text(encoding="utf-8")
|
||||
assert "/route/design-terrain" in chip
|
||||
assert "pickedTerrain" not in main and "terrainSelect" not in main
|
||||
|
||||
@@ -2,57 +2,15 @@
|
||||
"""283 · 493-2 · 493-5 B05 [저장] = 보이는 화면 그대로 · 초기화 기준 = 노선 추천 [적용](요구 289 · 290).
|
||||
|
||||
- 손 고침 [저장] — 띠 · 확인 창 · 저장 뒤 초기화 알림 · 세션 초안 통째 비움 없음(492-2 걷음 · 소스 확인).
|
||||
- 추천 [적용] 확인 창 — 지워질 수 = 서버 저장분(구조물 · 관 · 추가 측점) = 적용 뒤 알림 수
|
||||
(서버 `route_reset.dropped` = 저장 파일 길이) · 세션 초안은 안 셈 · 관 저장 파일 없음 = 0.
|
||||
- [적용] 뒤 추가 측점 저장분 다시 받음 — 지운 측점이 목록 · [저장]에 남지 않음(493-5).
|
||||
- 추천 [적용] 확인 창 · 적용 뒤 알림 · 추가 측점 다시 받음 시험은 지움 — 화면 [적용] 걷음(626-1 · 요구 530 · D8).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not (ROOT / "config" / "node_modules" / "typescript").is_dir(),
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
SERVERS = {
|
||||
"saved": {
|
||||
"structures": {"structures": [{"id": "s1"}]},
|
||||
"pipes": {"saved": True, "pipe_points": [{}, {}, {}]},
|
||||
"plus": [120],
|
||||
},
|
||||
"unsaved_pipes": { # 관 저장 파일 없음 — 응답 관은 자동 제안 · 서버가 지울 것 0
|
||||
"structures": {"structures": []},
|
||||
"pipes": {"saved": False, "pipe_points": [{}, {}]},
|
||||
"plus": [],
|
||||
},
|
||||
"fail": {"structures": "fail", "pipes": "fail", "plus": "fail"},
|
||||
}
|
||||
RESET = {"reset": True, "dropped": {"structures": 1, "pipes": 3, "plus_stations": 1}}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ts(tmp_path_factory):
|
||||
source = tmp_path_factory.mktemp("s283") / "in.json"
|
||||
source.write_text(json.dumps({"servers": SERVERS, "reset": RESET}), encoding="utf-8")
|
||||
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", str(HERE / "helper_283_route_reset_confirm.cjs"), str(source)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=120,
|
||||
check=False,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def _read(*parts: str) -> str:
|
||||
@@ -79,63 +37,3 @@ def test_띠_걷음():
|
||||
logic = _read("B05_Profile", "logic", "B05_Profile_Route_Reset.ts")
|
||||
for gone in ("ROUTE_RESET_BAND", "draftShapeChanged", "askRouteReset"):
|
||||
assert gone not in logic
|
||||
|
||||
|
||||
# ── 추천 [적용] 확인 창 = 서버가 지울 수 ─────────────────────────────────────
|
||||
|
||||
|
||||
def test_적용_확인창_서버_저장분_수(ts):
|
||||
text = ts["texts"]["saved"]
|
||||
assert text.startswith("추천 노선을 [적용]하면 초기화")
|
||||
for part in ("구조물 1", "관 3", "추가 측점 1", "종단 · 횡단 손 입력"):
|
||||
assert part in text
|
||||
assert text.endswith("적용할까요?")
|
||||
|
||||
|
||||
def test_적용_확인창_수_적용뒤_알림_수_같음(ts):
|
||||
dropped = RESET["dropped"]
|
||||
text, notice = ts["texts"]["saved"], ts["notice"]
|
||||
for name, key in (("구조물", "structures"), ("관", "pipes"), ("추가 측점", "plus_stations")):
|
||||
assert f"{name} {dropped[key]} " in text and f"{name} {dropped[key]}" in notice
|
||||
|
||||
|
||||
def test_관_저장파일_없음_0_받기실패_0(ts):
|
||||
assert "구조물 0 · 관 0 · 추가 측점 0" in ts["texts"]["unsaved_pipes"]
|
||||
assert "구조물 0 · 관 0 · 추가 측점 0" in ts["texts"]["fail"]
|
||||
|
||||
|
||||
def test_적용_확인창_배선():
|
||||
initial = _read("B05_Profile", "B05_Profile_UI_RouteEdit_Initial.ts")
|
||||
assert "confirmText: () => applyResetText(projectId)" in initial
|
||||
ui = _read("B05_Profile", "B05_Profile_UI_RouteReset.ts")
|
||||
assert "useDesignStore" not in ui # 세션 초안 안 셈
|
||||
|
||||
|
||||
# ── 적용 뒤 알림 ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_적용뒤_알림_dropped(ts):
|
||||
assert ts["notice"] == (
|
||||
"노선이 바뀌어 초기화했습니다 — 지움: 구조물 1 · 관 3 · 추가 측점 1 · 새 교차 관 2"
|
||||
)
|
||||
assert ts["noticeNone"] == [None, None]
|
||||
|
||||
|
||||
# ── 493-5 [적용] 뒤 화면 초안 = 새 초기값 ────────────────────────────────────
|
||||
|
||||
|
||||
def test_적용뒤_추가측점_저장분_다시받음(ts):
|
||||
fixed, stale = ts["plusApply"]["fixed"], ts["plusApply"]["stale"]
|
||||
assert fixed["before"] == [127, 163]
|
||||
assert fixed["after"] == [] and fixed["body"] is None # [저장]이 지운 측점을 다시 안 보냄
|
||||
assert stale["after"] == [127, 163] # 대조 — 다시 안 받으면 옛 저장분이 남음(고치기 전)
|
||||
|
||||
|
||||
def test_적용_배선_추가측점_다시받기():
|
||||
src = _read("B05_Profile", "B05_Profile_UI_RouteEdit_Apply.ts")
|
||||
body = src[src.index("export async function saveRecommendedRoute(") :]
|
||||
assert (
|
||||
body.index("clearDrafts(projectId)")
|
||||
< body.index("await reloadSavedPlusStations(projectId)")
|
||||
< body.index("await rebuildAfterRouteSave(projectId)")
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""346-3 화면 — [노선 반영] 없앰 · [저장]이 노선부터 · 추천 [적용] = as_initial 저장 · 저장 뒤 초안 넘김.
|
||||
"""346-3 화면 — [노선 반영] 없앰 · [저장]이 노선부터 · 저장 뒤 초안 넘김(추천 [적용] 화면 부름 = 626-1 걷음).
|
||||
|
||||
노선 이음(`B05_Profile_UI_RouteEdit_Apply`)은 Node 헬퍼 `helper_346_3_route_save.cjs` 가 서버 · 화면
|
||||
창구만 가짜로 두고 돌림 · 서버 길 시험은 `test_346_3_route_save.py` · 저장 차례(노선 먼저 · 안 바뀌면
|
||||
@@ -86,31 +86,6 @@ def test_초안_넘김_차례(out):
|
||||
assert any("상단측 손 고침 1곳" in c for c in out["carryCalls"]) # 요구 274 — 새 지형이 정함
|
||||
|
||||
|
||||
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 body["draft"]["terrain"] == "sheet" # 창 지형 = 설계 지형(509-5)
|
||||
# 잠금 막 하나(472-6 6-B — 저장 직전 저장 관 떠 두기는 서버 한 본문 몫 · 추천은 초안을 비우니 안 받음)
|
||||
assert out["recommendCalls"] == [
|
||||
"overlay:on",
|
||||
"replan",
|
||||
"commit",
|
||||
"grid:forget", # 새 설계 지형 격자로(509-2)
|
||||
"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 "노선 반영</button>" not in chrome
|
||||
@@ -123,9 +98,8 @@ def test_노선_반영_단추_없앰_배선():
|
||||
assert "carryAfterRouteSave" not in save and "saveRouteDraft" not in save
|
||||
server = _read("B06_Section/B06_Section_Router_DesignSave.py")
|
||||
assert server.index('step("route"') < server.index('step("settings"')
|
||||
initial = _read("B05_Profile/B05_Profile_UI_RouteEdit_Initial.ts")
|
||||
at = initial.index("onApply: (picked) => {")
|
||||
assert "saveRecommendedRoute(" in initial[at : at + 900]
|
||||
# 626-1 — 추천 [적용] 화면 부름 걷음(서버 `as_initial` 길 · 시험은 남김)
|
||||
assert "saveRecommendedRoute" not in apply
|
||||
replan = _read("B05_Profile/B05_Profile_Api_Replan.ts")
|
||||
assert "body: JSON.stringify(body)" in replan and "as_initial?: boolean;" in replan
|
||||
|
||||
@@ -143,8 +117,6 @@ def test_넘김_알림은_결과_한_줄에_합침():
|
||||
|
||||
|
||||
def test_노선_반영_글자_없음():
|
||||
recommend = _read("B05_Profile/B05_Profile_UI_Panel_Recommend.ts")
|
||||
assert "노선 반영" not in recommend # 칸 [초기화](「다시 계산은 [저장]에서」 글) 지움(418)
|
||||
for rel in (
|
||||
"B05_Profile/B05_Profile_UI_RouteEdit.ts",
|
||||
"B05_Profile/B05_Profile_UI_RouteEdit_History.ts",
|
||||
|
||||
@@ -24,15 +24,6 @@ def test_초기화_확인_글():
|
||||
assert "recomputation takes" not in text and "without recomputation" in text
|
||||
|
||||
|
||||
def test_노선_추천_창_글():
|
||||
recommend = _read("B05_Profile/B05_Profile_UI_RouteEdit_Recommend.ts")
|
||||
assert "편집 창에 놓입니다" not in recommend
|
||||
assert "[적용]하면 바로 저장 · 새 초기값이 됩니다." in recommend
|
||||
help_ = _read("B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Help.ts")
|
||||
assert "[확인]" not in help_ and "아래 띠" not in help_ and "편집 창" not in help_
|
||||
assert "[적용]하면 이 종단이 그대로 첫 계획선" in help_
|
||||
|
||||
|
||||
def test_노선_편집_다른_옛_글():
|
||||
edits = _read("B05_Profile/logic/B05_Profile_UI_RouteEdit_Edits.ts")
|
||||
assert "[확인] 때 반영" not in edits and "[저장] 때 반영" in edits
|
||||
|
||||
@@ -74,10 +74,6 @@ def test_b05_열기_예약_모달_진행_배선() -> None:
|
||||
assert "await run_precompute(project_id)" in source
|
||||
api = (ROOT / "B05_Profile/B05_Profile_Api_RouteInitial.ts").read_text(encoding="utf-8")
|
||||
assert "/route/initial/progress" in api
|
||||
initial = (ROOT / "B05_Profile/B05_Profile_UI_RouteEdit_Initial.ts").read_text(encoding="utf-8")
|
||||
assert "progress: () => fetchPrecomputeProgress(projectId)" in initial
|
||||
modal = (ROOT / "B05_Profile/B05_Profile_UI_RouteEdit_Recommend.ts").read_text(encoding="utf-8")
|
||||
assert "미리 셈하는 중 ${p.done}/${p.total}" in modal
|
||||
|
||||
|
||||
def test_모달이_여는_갈래_먼저(monkeypatch) -> None:
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
from shapely.geometry import LineString, Point
|
||||
@@ -107,12 +106,3 @@ def test_old_copy_flag_and_line_unchanged(tmp_path: Path) -> None:
|
||||
assert _ensure_planned_initial(root, RADIUS) is None
|
||||
assert {p: p.read_bytes() for p in before} == before
|
||||
assert load_station_snap(root) is False
|
||||
|
||||
|
||||
def test_apply_saves_station_snap_on() -> None:
|
||||
text = (ROOT / "B05_Profile/B05_Profile_UI_RouteEdit_Apply.ts").read_text(encoding="utf-8")
|
||||
assert re.search(
|
||||
r"routeDraftOf\(nodes,\s*\{\s*\.\.\.settings,\s*stationSnap: true,"
|
||||
r"\s*stationSnapStep: null,\s*terrain,\s*\}\)",
|
||||
text,
|
||||
)
|
||||
|
||||
@@ -9,7 +9,6 @@ TS 셈은 `helper_390_5_snap_step_ops.cjs`(Node) · 파이썬 짝 거울 · 저
|
||||
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -225,5 +224,3 @@ def test_서버_측점_셈이_저장_간격을_씀() -> None:
|
||||
assert "draft ? draft.stationSnapStep : saved?.station_snap_step" in stage
|
||||
assert "stationSnapStep: step === 10 ? 10 : null" in stage # 옛 초안 0.1 · 1 m = null(505-2)
|
||||
assert "saved: () => plan" in page and "...snapOptions(inputs.route, plan)" in page
|
||||
apply = (B05 / "B05_Profile_UI_RouteEdit_Apply.ts").read_text(encoding="utf-8")
|
||||
assert re.search(r"stationSnap: true,\s*stationSnapStep: null", apply) # 추천 = 측점 No.
|
||||
|
||||
@@ -28,9 +28,8 @@ def _read(rel: str) -> str:
|
||||
|
||||
|
||||
def test_노선_추천_칸에_초기화_단추_없음():
|
||||
part = _read("B05_Profile/B05_Profile_UI_Panel_Recommend.ts")
|
||||
assert 'data-act="reset"' not in part and "resetButton" not in part
|
||||
assert part.count('data-act="recommend" disabled') == 1
|
||||
# 626-1 — 「노선 추천」 칸 통째 걷음
|
||||
assert not (ROOT / "B05_Profile/B05_Profile_UI_Panel_Recommend.ts").exists()
|
||||
initial = _read("B05_Profile/B05_Profile_UI_RouteEdit_Initial.ts")
|
||||
assert "resetButton" not in initial and "initialRouteSnapshot" not in initial
|
||||
assert "initial_route" not in _read("B05_Profile/B05_Profile_Api_Replan.ts")
|
||||
|
||||
@@ -46,16 +46,3 @@ def test_옛_답은_서버_수_그대로_줄_없음() -> None:
|
||||
assert (
|
||||
old["over"] == 3 and old["violations"] == "" and old["ground"] == "" and old["ends"] == ""
|
||||
)
|
||||
|
||||
|
||||
def test_창에_줄_두_개와_도움말() -> None:
|
||||
rec = (B05 / "B05_Profile_UI_RouteEdit_Recommend.ts").read_text(encoding="utf-8")
|
||||
assert '<div data-line="ground" hidden></div>' in rec
|
||||
assert '<div class="is-warn" data-line="ends" hidden></div>' in rec
|
||||
assert 'line("violations").title = (brokenItems(view?.metrics ?? null) ?? [])' in rec
|
||||
assert "const ends = endsLine(view);" in rec
|
||||
help_ts = (B05 / "B05_Profile_UI_RouteEdit_Recommend_Help.ts").read_text(encoding="utf-8")
|
||||
for name in ('name: "원지반 기울기(참고)",', 'name: "시종점 높이 차",'):
|
||||
assert name in help_ts
|
||||
assert "이보다 가파른 구간을 만들지 않음" not in help_ts
|
||||
assert "「기준 초과」 수와 같음" in help_ts
|
||||
|
||||
@@ -32,36 +32,10 @@ def test_종단_반영_값_정하기() -> None:
|
||||
assert out["readBack"] is False # 캐시에 남아 다시 열 때 되살림
|
||||
|
||||
|
||||
def test_창_단추와_재계산() -> None:
|
||||
rec = _read("B05_Profile_UI_RouteEdit_Recommend.ts")
|
||||
assert "측점 맞춤" not in rec and "snapOn" not in rec and "unsnapped" not in rec
|
||||
assert 'toggleButton("종단 반영", profileOn,' in rec
|
||||
assert "let profileOn = params.withProfile;" in rec
|
||||
toggle = rec[rec.index('toggleButton("종단 반영"') :][:200]
|
||||
assert "profileOn = active;" in toggle and "void run();" in toggle # 누르면 바로
|
||||
assert "rerun.before(profileToggle);" in rec
|
||||
assert "rerun.disabled = busy || invalid.size > 0;" in rec # 늘 켜짐
|
||||
assert 'rerun.addEventListener("click", () => void run(true));' in rec
|
||||
assert "const withProfile = profileOn;" in rec
|
||||
assert "params.onApply(picked);" in rec # 476 — 확인 창 뒤 미리 본 그 후보
|
||||
|
||||
|
||||
def test_요청과_적용에_종단_반영() -> None:
|
||||
init = _read("B05_Profile_UI_RouteEdit_Initial.ts")
|
||||
assert "with_profile: withProfile," in init
|
||||
assert "...(recompute ? { recompute: true } : {})," in init
|
||||
assert "withProfile: choice?.withProfile ?? base.withProfile ?? true," in init
|
||||
on_apply = init[init.index("onApply: (picked) => {") :][:900]
|
||||
assert "with_profile: choiceWithProfile(picked)," in on_apply
|
||||
assert on_apply.count("choiceWithProfile(picked)") == 2 # 저장 칸 · 균형 종단
|
||||
# 626-1 — 추천 창 요청 · [적용] 걷음 · [저장]은 지금 추천의 종단 반영 그대로
|
||||
assert "withProfileOf(" not in init
|
||||
assert "with_profile: choiceWithProfile(now)" in init # [저장]에도 실음
|
||||
api = _read("B05_Profile_Api_RouteInitial.ts")
|
||||
assert "recompute?: boolean;" in api
|
||||
|
||||
|
||||
def test_도움말_글() -> None:
|
||||
help_ts = _read("B05_Profile_UI_RouteEdit_Recommend_Help.ts")
|
||||
assert "「측점 맞춤」을 끄면" not in help_ts and "끔 = 옮기기 전 선" not in help_ts
|
||||
assert 'name: "재계산",' in help_ts and 'name: "측점 맞춤(늘)",' in help_ts
|
||||
assert "단추로 켬 · 끔(기본 켬 · 다섯 갈래 모두)" in help_ts
|
||||
|
||||
@@ -49,21 +49,3 @@ def test_그림_선() -> None:
|
||||
assert 'class="is-plan" d="M6.0 90.0L300.0 62.0L594.0 62.0"' in out["svgOn"]
|
||||
assert "is-initial" not in out["svgOld"] # 옛 답 = 지반 · 균형만
|
||||
assert "is-plan" not in out["legendOff"] and "적용될 초기 종단" in out["legendOff"]
|
||||
|
||||
|
||||
def test_모달_배선과_확인_글() -> None:
|
||||
rec = (B05 / "B05_Profile_UI_RouteEdit_Recommend.ts").read_text(encoding="utf-8")
|
||||
assert '<div data-line="initial" hidden></div>' in rec
|
||||
assert "profileSvg(balance, initialShape)" in rec
|
||||
assert 'line("profile").hidden = !balance && !initialShape;' in rec
|
||||
apply = rec[rec.index('applyButton.addEventListener("click"') :][:400]
|
||||
assert (
|
||||
'showConfirmDialog(await params.confirmText(), "적용")' in apply
|
||||
) # 492-2 — [저장]과 같은 글 · 수
|
||||
assert apply.index("showConfirmDialog") < apply.index("params.onApply(picked);")
|
||||
reset = (B05 / "logic" / "B05_Profile_Route_Reset.ts").read_text(encoding="utf-8")
|
||||
assert (
|
||||
"횡단 손 입력이 지워지고 " in reset and "새 노선으로 다시 셉니다" in reset
|
||||
) # 요구 277 · 284
|
||||
css = (B05 / "B05_Profile_UI_Style_RouteRecommend.css").read_text(encoding="utf-8")
|
||||
assert ".b05-routerec__profile .is-initial" in css and "var(--map-route" in css
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""508-2 ② B05 추천 노선 창 [재계산] — 저장본 없음(409) 안내 줄 옆 · 칸(제약 · 비중) 바꾼 뒤만 보임.
|
||||
|
||||
505-1 · 509-5 규칙 그대로 — 접근 = 저장본만 · 사용자가 누른 셈만 그 자리.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_508_2_recommend_rerun.py -q
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
REC = ROOT / "B05_Profile" / "B05_Profile_UI_RouteEdit_Recommend.ts"
|
||||
|
||||
|
||||
def test_보임_규칙() -> None:
|
||||
ts = REC.read_text(encoding="utf-8")
|
||||
assert "rerun.hidden = !(missing || dirty);" in ts
|
||||
assert "if (missing) stateEl.after(rerun);" in ts # 안내 줄(상태 줄) 옆
|
||||
assert "else profileToggle.after(rerun);" in ts # 칸 바꿈 = 종단 반영 오른쪽(지금처럼)
|
||||
run = ts.split("async function run(recompute = false)")[1].split("pills.forEach")[0]
|
||||
assert "missing = false;" in run.split("try {")[0] # 새 셈마다 지움
|
||||
assert "missing = message.includes(NOT_PREPARED_HEAD);" in run
|
||||
assert 'rerun.addEventListener("click", () => void run(true));' in ts
|
||||
|
||||
|
||||
def test_안내_글_머리_서버와_같음() -> None:
|
||||
ts = REC.read_text(encoding="utf-8")
|
||||
head = re.search(r'const NOT_PREPARED_HEAD = "([^"]+)";', ts).group(1)
|
||||
py = (ROOT / "B05_Profile" / "B05_Profile_Router_RouteInitial.py").read_text(encoding="utf-8")
|
||||
message = re.search(r'^NOT_PREPARED = "([^"]+)"', py, re.M).group(1)
|
||||
assert message.startswith(head)
|
||||
@@ -49,8 +49,8 @@ def test_적용으로_지형이_바뀌면_격자_버림() -> None:
|
||||
grid = _ts("B05_Profile/B05_Profile_Api_TerrainGrid.ts")
|
||||
forget = grid.split("export function forgetDesignTerrainGrid", 1)[1][:200]
|
||||
assert "loaded.delete(projectId)" in forget and "ready.delete(projectId)" in forget
|
||||
apply = _ts("B05_Profile/B05_Profile_UI_RouteEdit_Apply.ts")
|
||||
recommend = apply.split("export async function saveRecommendedRoute", 1)[1]
|
||||
assert recommend.index("forgetDesignTerrainGrid(projectId)") < recommend.index(
|
||||
# 626-1 — 지형 바꿈 = 도구줄 지형 칩(옛 추천 [적용] 대신)
|
||||
chip = _ts("B05_Profile/B05_Profile_UI_RouteEdit_Terrain.ts")
|
||||
assert chip.index("forgetDesignTerrainGrid(projectId)") < chip.index(
|
||||
"await rebuildAfterRouteSave(projectId)"
|
||||
)
|
||||
|
||||
@@ -126,26 +126,12 @@ def _ts(name: str) -> str:
|
||||
return (ROOT / "B05_Profile" / name).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_창_지형_고르기_배선() -> None:
|
||||
modal = _ts("B05_Profile_UI_RouteEdit_Recommend.ts")
|
||||
assert 'data-act="terrain"' in modal and "data-terrain-row hidden" in modal
|
||||
assert 'params.terrains.includes("las") && params.terrains.includes("sheet")' in modal
|
||||
change = modal.split('terrainSelect.addEventListener("change"', 1)[1][:300]
|
||||
assert "void run();" in change # 바꾸면 그 지형 저장본(없으면 안내 · [재계산])
|
||||
assert "recompute,\n terrain,\n" in modal # 서버에 창 지형
|
||||
initial = _ts("B05_Profile_UI_RouteEdit_Initial.ts")
|
||||
assert "terrain: params.terrains()?.design" in initial # 처음 = 설계 지형
|
||||
assert "return { ...normalizeRouteInitial(" in initial and "terrain };" in initial
|
||||
assert "picked.terrain ?? null," in initial # [적용] = 창 지형
|
||||
api = _ts("B05_Profile_Api_RouteInitial.ts")
|
||||
assert "terrain?: AnalysisTerrain;" in api and "body: JSON.stringify(request)," in api
|
||||
|
||||
|
||||
def test_평면_지형_고르기_없음_이름만() -> None:
|
||||
chrome = _ts("B05_Profile_UI_RouteEdit_Chrome.ts")
|
||||
main = _ts("B05_Profile_UI_RouteEdit.ts")
|
||||
assert 'data-act="terrain"' not in chrome and 'data-slot="terrain-name"' in chrome
|
||||
assert "terrainName.textContent = `지형 ${" in main
|
||||
# 626-1 — 평면 도구줄 지형 칩(공용) · 바꾸기 = 확인창 → 설계 지형(`_Terrain`)
|
||||
assert "terrain: terrain.root" in chrome and 'data-slot="terrain-name"' not in chrome
|
||||
assert "chrome.terrain.load(sources);" in main
|
||||
assert "terrainSelect" not in main and "setAnalysisTerrain" not in main
|
||||
assert "routeedit-terrain" not in (ROOT / "A00_Common/b_page_state.ts").read_text(
|
||||
encoding="utf-8"
|
||||
|
||||
@@ -15,7 +15,6 @@ THEME = ROOT / "ui_template" / "ui_template_theme.css"
|
||||
DRAW_CSS = [
|
||||
"B05_Profile/B05_Profile_UI_Style.css",
|
||||
"B05_Profile/B05_Profile_UI_Style_RouteEdit.css",
|
||||
"B05_Profile/B05_Profile_UI_Style_RouteRecommend.css",
|
||||
"B05_Profile/B05_Profile_UI_Style_Table.css",
|
||||
"B06_Section/B06_Section_UI_Style.css",
|
||||
"B06_Section/B06_Section_UI_Style_Cross_Areas.css",
|
||||
@@ -30,8 +29,7 @@ DRAW_READS = [
|
||||
("B05_Profile/B05_Profile_UI_RouteEdit_Render.ts", '"--draw-surface"'),
|
||||
("B05_Profile/B05_Profile_UI_RouteEdit_Render.ts", '"--draw-text-secondary"'),
|
||||
("B05_Profile/B05_Profile_UI_RouteEdit_Render.ts", '"--draw-danger"'),
|
||||
("B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Map.ts", '"--draw-primary"'),
|
||||
("B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Map.ts", '"--draw-text-secondary"'),
|
||||
("B05_Profile/B05_Profile_UI_RouteEdit_RecPreview.ts", '"--draw-map-sketch"'), # 626-1 추천 보기
|
||||
("B05_Profile/B05_Profile_UI_Viewer.ts", '"--draw-surface-raised"'),
|
||||
("B04_PreProcess/B04_PreProcess_UI_Camera.ts", '"--draw-surface-raised"'),
|
||||
("D03_PlanAnalysis/D03_PlanAnalysis_UI_Map.ts", '"--draw-map-sketch"'),
|
||||
@@ -45,7 +43,7 @@ DRAW_READS = [
|
||||
]
|
||||
NO_COLOR_READS = [
|
||||
"B05_Profile/B05_Profile_UI_RouteEdit_Render.ts",
|
||||
"B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Map.ts",
|
||||
"B05_Profile/B05_Profile_UI_RouteEdit_RecPreview.ts",
|
||||
"B05_Profile/B05_Profile_UI_Viewer.ts",
|
||||
"B07_DesignDetail/openwebcad/src/drawControllers/screenCanvas.drawController.ts",
|
||||
]
|
||||
|
||||
@@ -332,7 +332,9 @@ def test_screen_defs() -> None:
|
||||
) # 586-2 캔버스 그리기 따로
|
||||
page = (folder / "D03_PlanAnalysis_UI_Layers.ts").read_text("utf-8") + paint
|
||||
legend = (folder / "D03_PlanAnalysis_UI_LayerLegend.ts").read_text("utf-8")
|
||||
legend += (ROOT / "A00_Common" / "map" / "map_legend.ts").read_text("utf-8") # 624-3 범례 줄 공용
|
||||
legend += (ROOT / "A00_Common" / "map" / "map_legend.ts").read_text(
|
||||
"utf-8"
|
||||
) # 624-3 범례 줄 공용
|
||||
for key, color in (
|
||||
("forestRoadFile", "forest-road-file"),
|
||||
("landslideZones", "landslide-zone"),
|
||||
|
||||
@@ -18,10 +18,7 @@ def test_하한_기본은_0_5():
|
||||
|
||||
|
||||
def test_B05_는_하한_인자를_안_줌():
|
||||
for rel in (
|
||||
"B05_Profile/B05_Profile_UI_RouteEdit.ts",
|
||||
"B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Map.ts",
|
||||
):
|
||||
for rel in ("B05_Profile/B05_Profile_UI_RouteEdit.ts",):
|
||||
assert "minScale" not in read(rel), rel
|
||||
|
||||
|
||||
|
||||
@@ -38,9 +38,16 @@ def test_toolbar_table_d03_same_as_before_plus_terrain() -> None:
|
||||
["terrain"],
|
||||
], d03
|
||||
b05 = {k for g in _toolbar_rows() for k, p in g if "b05" in p}
|
||||
assert {"contour-minor", "footprint", "station-snap", "snap-step", "terrain"} <= b05
|
||||
assert {
|
||||
"contour-minor",
|
||||
"footprint",
|
||||
"station-snap",
|
||||
"snap-step",
|
||||
"recommend",
|
||||
"terrain",
|
||||
} <= b05
|
||||
tools = _read(ROOT / "ui_template" / "ui_template_map_tools.ts")
|
||||
for key in {k for g in _toolbar_rows() for k, _ in g} - {"snap-step", "terrain"}:
|
||||
for key in {k for g in _toolbar_rows() for k, _ in g} - {"snap-step", "recommend", "terrain"}:
|
||||
assert f'"{key}": {{' in tools or f"{key}: {{" in tools, key # 아이콘 · 이름 = MAP_TOOLS
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
"""PLAN 624-3 — B05 평면도에 공용 지도 부품(도구줄 표 · 겹 줄 · 범례 · 지형 칩)(요구 530 · 결정 A② · A③ · A⑦ · A⑨).
|
||||
|
||||
- 도구줄 = 공용 표 「b05」 · 아래 줄 = 공용 겹 줄 · 범례 = 공용 `legendRows`.
|
||||
- 노선 띠 300 m 자르기 = B05 자기 칸(등고선 · 하천)만 · 그 밖 겹 = 안 자르는 둘째 GL 칸.
|
||||
- B05 화면 파일은 D03 파일을 안 부름 · 큰 파일은 안 늘어남.
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
B05 = ROOT / "B05_Profile"
|
||||
read = lambda path: path.read_text(encoding="utf-8") # noqa: E731
|
||||
|
||||
|
||||
def test_도구줄_표_겹_줄_범례() -> None:
|
||||
chrome = read(B05 / "B05_Profile_UI_RouteEdit_Chrome.ts")
|
||||
assert 'mapToolGroups("b05", {' in chrome
|
||||
assert "toolRows.append(mapTools.root, layers.bar)" in chrome
|
||||
assert "info.prepend(layers.legend)" in chrome
|
||||
layers = read(B05 / "B05_Profile_UI_RouteEdit_Layers.ts")
|
||||
assert 'page: "b05"' in layers and "legendRows(" in layers and "mapLayerSpecs(" in layers
|
||||
|
||||
|
||||
def test_띠_자르기는_등고선_하천만() -> None:
|
||||
gl = read(B05 / "B05_Profile_UI_RouteEdit_Gl.ts")
|
||||
assert "container.style.clipPath" in gl # B05 자기 칸(등고선 · 하천 · 1 m)
|
||||
assert 'gl.setVisible("contour", own.contour)' in gl
|
||||
assert 'gl.setVisible("sheets", own.stream)' in gl
|
||||
assert "layers.follow(meta, normalizer, toScreen" in gl
|
||||
layers = read(B05 / "B05_Profile_UI_RouteEdit_Layers.ts")
|
||||
assert "clipPath" not in layers # 공용 겹 칸은 안 자름
|
||||
assert 'const OWN: readonly MapLayerKey[] = ["contour", "stream"]' in layers
|
||||
|
||||
|
||||
def test_처음_겹_숨긴_겹() -> None:
|
||||
src = read(ROOT / "A00_Common" / "map" / "map_layer_list.ts")
|
||||
b05 = re.search(r"b05: \{(.*?)\},", src, re.S).group(1)
|
||||
assert 'hide: ["bands", "forestRoadFile"]' in b05 and 'on: ["contour", "stream"]' in b05
|
||||
|
||||
|
||||
def test_D03_안_부름_파일_한도() -> None:
|
||||
for name in (
|
||||
"B05_Profile_UI_RouteEdit_Layers.ts",
|
||||
"B05_Profile_UI_RouteEdit_Gl.ts",
|
||||
"B05_Profile_UI_RouteEdit_Chrome.ts",
|
||||
"B05_Profile_UI_RouteEdit_RecPreview.ts",
|
||||
"B05_Profile_UI_RouteEdit_Terrain.ts",
|
||||
):
|
||||
src = read(B05 / name)
|
||||
assert "D03_PlanAnalysis" not in src, name
|
||||
assert len(src.splitlines()) <= 700, name
|
||||
assert len(read(B05 / "B05_Profile_UI_RouteEdit.ts").splitlines()) <= 694
|
||||
d03 = read(ROOT / "D03_PlanAnalysis" / "D03_PlanAnalysis_Api.ts")
|
||||
assert 'export * from "../A00_Common/map/map_layer_api";' in d03
|
||||
@@ -0,0 +1,122 @@
|
||||
"""PLAN 626-1 — 노선 추천 미리보기 · [적용] 걷음 · 지형 바꿈 = 지형 칩(요구 530 · 결정 D1 · D2 · D7 · D8).
|
||||
|
||||
- 옛 추천 모달 · 좌측 「노선 추천」 칸 · [적용](`as_initial`) 화면 부름 없음(서버 길 · 시험은 남김).
|
||||
- 미리보기 = 저장본 넷(영선 · 토공 · 기울기 · 비중) 하나만 · 끔 · 다시 셈(`recompute`) 없음.
|
||||
- 지형 칩 → `POST /route/design-terrain` — 노선 그대로(옛 = 새 선) · 초기값 폴더 안 씀 · 같은 지형 = 그대로.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import re
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from uuid import uuid4
|
||||
|
||||
from B05_Profile import B05_Profile_Router_Replan as replan
|
||||
from B05_Profile import B05_Profile_Router_TerrainSwitch as switch
|
||||
from B06_Section import B06_Section_Router_DesignSave as design_save
|
||||
from common_util import common_util_save_guard as guard
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
B05 = ROOT / "B05_Profile"
|
||||
read = lambda path: path.read_text(encoding="utf-8") # noqa: E731
|
||||
|
||||
|
||||
def test_옛_추천_모달_칸_적용_걷음() -> None:
|
||||
for name in (
|
||||
"B05_Profile_UI_RouteEdit_Recommend.ts",
|
||||
"B05_Profile_UI_RouteEdit_Recommend_Help.ts",
|
||||
"B05_Profile_UI_RouteEdit_Recommend_Map.ts",
|
||||
"B05_Profile_UI_Panel_Recommend.ts",
|
||||
"B05_Profile_UI_Style_RouteRecommend.css",
|
||||
):
|
||||
assert not (B05 / name).exists(), name
|
||||
assert "route-recommend" not in read(B05 / "B05_Profile_UI_Panel_Sections.ts")
|
||||
assert "recommend" not in read(ROOT / "A00_Common" / "design" / "design_view_profile.ts")
|
||||
# 화면 어디서도 as_initial 을 안 부름(서버 길은 남김)
|
||||
for path in B05.glob("B05_Profile_UI_*.ts"):
|
||||
assert "as_initial" not in read(path).replace("`as_initial`", ""), path.name
|
||||
assert "as_initial" in read(B05 / "B05_Profile_Router_Replan.py")
|
||||
|
||||
|
||||
def test_미리보기_넷_하나만_저장본만() -> None:
|
||||
src = read(B05 / "B05_Profile_UI_RouteEdit_RecPreview.ts")
|
||||
modes = re.search(r"const MODES = \[(.*?)\]", src, re.S).group(1)
|
||||
assert re.findall(r'"(\w+)"', modes) == ["gradeline", "earthwork", "gentle", "weighted"]
|
||||
assert "recompute" not in src and "createPickMenu({" in src and 'offLabel: "끔"' in src
|
||||
assert "terrain: opts.terrain()" in src # 지금 설계 지형 저장본(D7)
|
||||
chrome = read(B05 / "B05_Profile_UI_RouteEdit_Chrome.ts")
|
||||
assert "recommend: preview.root" in chrome and "terrain: terrain.root" in chrome
|
||||
menu = read(ROOT / "ui_template" / "ui_template_context_menu.ts")
|
||||
assert "export function createPickMenu(" in menu and "aria-checked" in menu
|
||||
|
||||
|
||||
def test_지형_칩_확인창_새_길() -> None:
|
||||
src = read(B05 / "B05_Profile_UI_RouteEdit_Terrain.ts")
|
||||
assert "askConfirm({" in src and "/route/design-terrain" in src
|
||||
assert "chip.set(toChip(design))" in src # 취소 · 실패 = 되돌림
|
||||
assert "rebuildAfterRouteSave(projectId)" in src
|
||||
|
||||
|
||||
def _run(monkeypatch, now: str, want: str):
|
||||
calls: dict = {}
|
||||
line = [[0.0, 0.0], [10.0, 0.0]]
|
||||
|
||||
async def paths(_pid):
|
||||
return Path("root"), "stored"
|
||||
|
||||
async def recompute(*args):
|
||||
calls["args"] = args
|
||||
return {"route_id": 7}
|
||||
|
||||
async def rows(*_a, **_k):
|
||||
return {}
|
||||
|
||||
async def restore(*_a):
|
||||
calls["restored"] = True
|
||||
|
||||
class Pool:
|
||||
def acquire(self):
|
||||
class Ctx:
|
||||
async def __aenter__(self):
|
||||
return object()
|
||||
|
||||
async def __aexit__(self, *_):
|
||||
return False
|
||||
|
||||
return Ctx()
|
||||
|
||||
async def params(*_a):
|
||||
return {}
|
||||
|
||||
def storage(root, scope):
|
||||
calls["scope"] = scope
|
||||
return SimpleNamespace(discard=lambda: None)
|
||||
|
||||
monkeypatch.setattr(replan, "_project_paths", paths)
|
||||
monkeypatch.setattr(replan, "_planned_line", lambda _root: line)
|
||||
monkeypatch.setattr(replan, "_recompute", recompute)
|
||||
monkeypatch.setattr(design_save, "_restore", restore)
|
||||
monkeypatch.setattr(guard, "snapshot_rows", rows)
|
||||
monkeypatch.setattr(guard, "snapshot_storage", storage)
|
||||
monkeypatch.setattr(switch, "get_db_pool", lambda: Pool())
|
||||
monkeypatch.setattr(switch, "get_surface_confirmation_params", params)
|
||||
monkeypatch.setattr(switch, "design_terrain", lambda _p: now)
|
||||
body = switch.DesignTerrainRequest(terrain=want)
|
||||
result = asyncio.run(switch.switch_route_design_terrain(uuid4(), body))
|
||||
return result, calls, line
|
||||
|
||||
|
||||
def test_지형_바꿈_노선_그대로_초기값_안씀(monkeypatch) -> None:
|
||||
result, calls, line = _run(monkeypatch, "las", "sheet")
|
||||
assert result["changed"] is True and result["design"] == "sheet"
|
||||
_pid, _root, _stored, balanced, terrain, lines, initial = calls["args"]
|
||||
assert balanced is None and terrain == "sheet" and initial is False
|
||||
assert lines == (line, line) # 계획고 옮김 없음 · 노선 안 씀
|
||||
assert guard.INITIAL_SCOPE[0] not in calls["scope"]
|
||||
assert "restored" not in calls
|
||||
|
||||
|
||||
def test_같은_지형이면_그대로(monkeypatch) -> None:
|
||||
result, calls, _ = _run(monkeypatch, "sheet", "sheet")
|
||||
assert result == {"status": "success", "design": "sheet", "changed": False}
|
||||
assert "args" not in calls and "scope" not in calls
|
||||
@@ -12,6 +12,7 @@ Node 헬퍼(`helper_b05_routeedit_initial.cjs`)가 TS 를 트랜스파일해 돌
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
@@ -169,26 +170,19 @@ def test_미리_보기_지도_선(results):
|
||||
|
||||
|
||||
def test_도구_막대는_노선_추천_하나만():
|
||||
"""[노선 추천] 하나 · 추천 묶음(로직 단추 · 슬라이더 · 제약 칸) 없음 · 「연 상태로」 없음. 그 묶음은
|
||||
모달(`_Recommend`)로 옮겼다. 345-4 — [노선 추천] · [초기화] · 현재 추천은 편집 머리에서 좌측
|
||||
「노선 추천」 칸(`_Panel_Recommend`)으로 옮김."""
|
||||
"""626-1 — 추천 = 평면 도구줄 아이콘 하나(미리보기 · `_RecPreview`) · 옛 모달 · 좌측 칸 걷음 ·
|
||||
「연 상태로」 · [초기화] 없음."""
|
||||
page = os.path.join(HERE, "..", "..", "B05_Profile")
|
||||
chrome = open(os.path.join(page, "B05_Profile_UI_RouteEdit_Chrome.ts"), encoding="utf-8").read()
|
||||
for gone in ('data-act="recommend"', 'data-act="reset"', "data-rec-now"):
|
||||
for gone in ('data-act="reset"', "data-rec-now"):
|
||||
assert gone not in chrome
|
||||
assert "연 상태로" not in chrome and "history-reset" not in chrome
|
||||
part = open(os.path.join(page, "B05_Profile_UI_Panel_Recommend.ts"), encoding="utf-8").read()
|
||||
assert part.count('data-act="recommend" disabled') == 1 # 단추 하나
|
||||
assert 'data-act="reset"' not in part # 칸 [초기화] 지움(418)
|
||||
assert chrome.count("recommend: preview.root") == 1 # 도구줄 아이콘 하나
|
||||
assert not os.path.exists(os.path.join(page, "B05_Profile_UI_Panel_Recommend.ts"))
|
||||
initial = open(
|
||||
os.path.join(page, "B05_Profile_UI_RouteEdit_Initial.ts"), encoding="utf-8"
|
||||
).read()
|
||||
assert "data-criteria" not in initial and "data-weight" not in initial
|
||||
recommend = open(
|
||||
os.path.join(page, "B05_Profile_UI_RouteEdit_Recommend.ts"), encoding="utf-8"
|
||||
).read()
|
||||
for name in ("follow", "earthwork", "gentle", "weighted"):
|
||||
assert f'"{name}"' in recommend
|
||||
|
||||
|
||||
def test_되돌리기_다시하기가_현재_추천을_함께_되살림(results):
|
||||
@@ -202,55 +196,44 @@ def test_되돌리기_다시하기가_현재_추천을_함께_되살림(results)
|
||||
|
||||
|
||||
def test_노선_추천_다듬기_23_3():
|
||||
"""구분선은 [노선 추천] 오른쪽 · 모달 지도는 편집 창 함수를 가져다 씀 · 모달이 라벨 패널 위."""
|
||||
"""도구줄 차례 = 공용 표 「b05」(624-3) · 스냅 간격 = [측점 스냅] 오른쪽 · 지형 칩 맨 오른쪽."""
|
||||
page = os.path.join(HERE, "..", "..", "B05_Profile")
|
||||
|
||||
def read(name):
|
||||
return open(os.path.join(page, name), encoding="utf-8").read()
|
||||
|
||||
chrome = read("B05_Profile_UI_RouteEdit_Chrome.ts")
|
||||
# 366-2 · 568-2 — [?] | ↶ ↷ | 3D | ⟲ ⟳ · 1m 등고선 · 측점 · 기울기 · 절/성토선 · 거리 재기 | 측점 스냅 · 간격 |
|
||||
# 설계 지형 이름(맨 오른쪽) — 공용 지도 도구줄 묶음 차례([?] 는 hint 가 맨 앞에 붙임)
|
||||
bar = chrome[
|
||||
chrome.index("createMapTools({") : chrome.index('classList.add("b05-routeedit__tools")')
|
||||
]
|
||||
bar = chrome[chrome.index("createMapTools({") : chrome.index("mapTools.hint!")]
|
||||
order = [
|
||||
"hint: { lines: HINT_LINES",
|
||||
"groups: [",
|
||||
'groups: mapToolGroups("b05", {',
|
||||
'mapToolButton("undo"',
|
||||
'mapToolButton("redo"',
|
||||
"],",
|
||||
"tools, // 부른 쪽 단추([3D])",
|
||||
'"rotate-ccw",',
|
||||
'"rotate-cw",',
|
||||
"view3d: tools[0]", # 부른 쪽 단추([3D])
|
||||
'mapToolButton("contour-minor"',
|
||||
'mapToolButton("stations"', # 381-2 [측점] [기울기] = 절/성토선 왼쪽
|
||||
'mapToolButton("stations"',
|
||||
'mapToolButton("slopes"',
|
||||
'mapToolButton("footprint"',
|
||||
'mapToolButton("measure"',
|
||||
"],",
|
||||
'mapToolButton("station-snap"',
|
||||
"snapStep,", # 390-2 스냅 간격 = [측점 스냅] 오른쪽
|
||||
"],", # 390-1 [측점 스냅] | 설계 지형 이름(509-5)
|
||||
"[terrainName],",
|
||||
'"snap-step": snapStep,',
|
||||
"recommend: preview.root,",
|
||||
"terrain: terrain.root,",
|
||||
]
|
||||
at = 0
|
||||
for mark in order:
|
||||
at = bar.index(mark, at) + 1
|
||||
assert 'class="b05-routeedit__terrain" data-slot="terrain-name" hidden' in chrome
|
||||
table = open(
|
||||
os.path.join(HERE, "..", "..", "A00_Common", "map", "map_toolbar.ts"), encoding="utf-8"
|
||||
).read()
|
||||
keys = re.findall(r'key: "([\w-]+)"', table[table.index("MAP_TOOLBAR") :])
|
||||
assert keys.index("station-snap") + 1 == keys.index("snap-step")
|
||||
assert keys[-2:] == ["recommend", "terrain"]
|
||||
assert "b05-route__view-separator" not in chrome # 나눔선 = 공용 도구줄
|
||||
# 분석 지형이 숨으면 앞 구분선도 숨김 · 스냅 끔이면 간격 고르기 흐림
|
||||
style = read("B05_Profile_UI_Style_RouteEdit.css")
|
||||
assert ".ui-toolbar__divider:has(+ .b05-routeedit__terrain[hidden])" in style
|
||||
assert '.ui-toolbar__divider:has(+ [data-act="terrain"][hidden])' in style
|
||||
assert '[data-act="station-snap"][aria-pressed="false"] + .b05-routeedit__snap-step' in style
|
||||
assert "b05-routeedit__spin" not in chrome # 회전 = 도구줄(추천 모달 CSS 는 남음)
|
||||
shared = read("B05_Profile_UI_RouteEdit_Recommend_Map.ts")
|
||||
for name in ("bindRouteEditNavigation", "createMapRotation", "fitCanvas", "metricToScreen"):
|
||||
assert f"{name}(" in shared
|
||||
css = read("B05_Profile_UI_Style_RouteRecommend.css")
|
||||
assert "calc(var(--z-modal, 1000) + 2)" in css # 라벨 패널(+1) 위
|
||||
assert "writing-mode: vertical-lr" in css
|
||||
assert 'backdropClass: "b05-routerec-backdrop"' in read("B05_Profile_UI_RouteEdit_Recommend.ts")
|
||||
assert "b05-routeedit__spin" not in chrome # 회전 = 도구줄
|
||||
|
||||
|
||||
def test_비중_고정_합_100_규칙(results):
|
||||
@@ -300,28 +283,6 @@ def test_추천_갈래_이름_알약_다섯_순서(results):
|
||||
}
|
||||
# 짧은 노선 · 흙 균형은 뺐다
|
||||
assert six["removedLogics"] == [False, False]
|
||||
page = os.path.join(HERE, "..", "..", "B05_Profile", "B05_Profile_UI_RouteEdit_Recommend.ts")
|
||||
with open(page, encoding="utf-8") as handle:
|
||||
text = handle.read()
|
||||
start = text.index("const PILLS")
|
||||
pills = text[start : text.index("] as const", start)]
|
||||
order = [
|
||||
pills.index(f'"{name}"')
|
||||
for name in ("follow", "gradeline", "earthwork", "gentle", "weighted")
|
||||
]
|
||||
assert order == sorted(order) # 알약 순서 · 다섯(PLAN 77-1)
|
||||
for gone in ("safe", "crossings", "cost"):
|
||||
assert f'"{gone}"' not in pills
|
||||
assert '"short"' not in pills and '"balance"' not in pills
|
||||
assert (
|
||||
"grid-template-columns: repeat(5"
|
||||
in open(
|
||||
os.path.join(
|
||||
HERE, "..", "..", "B05_Profile", "B05_Profile_UI_Style_RouteRecommend.css"
|
||||
),
|
||||
encoding="utf-8",
|
||||
).read()
|
||||
) # 네 칸 두 줄
|
||||
|
||||
|
||||
def test_옛_저장값_short_balance는_추적으로_읽음(results):
|
||||
|
||||
@@ -59,8 +59,8 @@ def results():
|
||||
def test_보기를_오가도_좌측_패널_단추_구조물_배치는_같은_노드(results):
|
||||
assert results["sameNodes"] == {"cross": True, "back": True}
|
||||
# 보기마다 새로 만들지 않는다 — 칸은 처음 보일 때 한 번만 서고 뒤로는 숨김 · 보임만.
|
||||
# 아래 고정 영역 + 종단 칸 셋 + 횡단 칸 둘(345-2 선택 포인트 · 345-3 계획노선 지움 · 345-4 노선 추천)
|
||||
assert results["childCount"] == 1 + 3 + 2
|
||||
# 아래 고정 영역 + 종단 칸 둘 + 횡단 칸 둘(345-2 선택 포인트 · 345-3 계획노선 지움 · 626-1 노선 추천 걷음)
|
||||
assert results["childCount"] == 1 + 2 + 2
|
||||
|
||||
|
||||
def test_구조물_배치는_두_보기_모두_맨_위_같은_DOM(results):
|
||||
@@ -80,7 +80,6 @@ def test_보기마다_제_칸만_보인다(results):
|
||||
assert views["profile"]["shownTitles"] == [
|
||||
"구조물 배치",
|
||||
"페이지 설정",
|
||||
"노선 추천",
|
||||
]
|
||||
assert views["cross"]["shownTitles"] == ["구조물 배치", "횡단 보기 설정", "표준 횡단면 설정"]
|
||||
assert views["back"]["shownTitles"] == views["profile"]["shownTitles"]
|
||||
|
||||
@@ -33,9 +33,6 @@ def test_공용_모달_위_고정():
|
||||
# 고르기 창 = 공용 큰 창 곁값(147-2)
|
||||
assert "--modal-max: 94vh;" in _block(css, ".ui-modal.ui-modal--wide")
|
||||
assert "wide: true," in _text("ui_template/ui_template_item_picker.ts")
|
||||
assert "--modal-max: 92vh;" in _block(
|
||||
_text("B05_Profile/B05_Profile_UI_Style_RouteRecommend.css"), ".ui-modal.b05-routerec"
|
||||
)
|
||||
assert "margin-top: 0;" in _block(
|
||||
_text("M02_MasterTemplete/M02_MasterTemplete_Drawing.css"), ".m02-pop > .ui-modal"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user