Merge remote-tracking branch 'origin/sub_laptop_3' into main_laptop_1

This commit is contained in:
2026-10-31 21:34:12 +09:00
51 changed files with 1205 additions and 2497 deletions
@@ -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",
-9
View File
@@ -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.
+2 -3
View File
@@ -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
+1 -27
View File
@@ -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)
+3 -3
View File
@@ -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"
+2 -4
View File
@@ -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"),
+1 -4
View File
@@ -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
+9 -2
View File
@@ -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
+55
View File
@@ -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
+22 -61
View File
@@ -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):
+2 -3
View File
@@ -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"
)