Merge remote-tracking branch 'origin/sub_laptop_5' into sub_laptop_6
This commit is contained in:
@@ -243,7 +243,7 @@ async function run() {
|
||||
// ③ 편집기 다리 — 사진첩 감싸기 · 초안 되살림
|
||||
{
|
||||
const told = [];
|
||||
let dirty = true;
|
||||
const dirty = true;
|
||||
const fake = {
|
||||
commit: () => undefined,
|
||||
undo: () => ({
|
||||
@@ -259,15 +259,6 @@ async function run() {
|
||||
picked: -1,
|
||||
}),
|
||||
redo: () => null,
|
||||
reset: () => ({
|
||||
planned: [[0, 0]],
|
||||
curveOn: [true],
|
||||
curveRadius: [null],
|
||||
curveLock: [null],
|
||||
curveArc: [null],
|
||||
apexLock: [false],
|
||||
picked: -1,
|
||||
}),
|
||||
canUndo: () => true,
|
||||
canRedo: () => false,
|
||||
isDirty: () => dirty,
|
||||
@@ -287,8 +278,6 @@ async function run() {
|
||||
});
|
||||
watched.undo();
|
||||
watched.redo();
|
||||
dirty = false;
|
||||
watched.reset();
|
||||
const restored = [];
|
||||
draftLink.restore(bridge, (snap, message, record) =>
|
||||
restored.push([snap.planned.length, record]),
|
||||
|
||||
@@ -151,11 +151,6 @@ const snap = (nodes, lock = false) => ({
|
||||
out.redo3 = state();
|
||||
out.editorAfterRedo = editor.slice(out.editorAfterUndo.length);
|
||||
out.profileReads = profileReads;
|
||||
// [반영 전으로] — 사진첩 한 걸음 · 되돌리면 옮긴 노선
|
||||
history.reset();
|
||||
out.reset = state();
|
||||
pageAlbum.undo();
|
||||
out.resetUndo = state();
|
||||
controls.dispose();
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/* PLAN 352-3 단위검증 헬퍼 — 분석 지형 기본값(고른 적 없으면 데이터대로) · 사용자가 바꿀 때만 기억.
|
||||
* 진짜 `B05_Profile_Api_Replan.ts` 를 트랜스파일해 Node 로 돌림. 화면 상태 보관소만 가짜(쓰기 횟수 셈).
|
||||
* 결과 = 표준출력 JSON 한 벌. 짝 = `test_352_3_analysis_terrain_default.py`. */
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const store = new Map();
|
||||
let writes = 0;
|
||||
const STUBS = {
|
||||
config_frontend: { API_BASE_URL: "" },
|
||||
b_page_state: {
|
||||
readState: (key, scope) => store.get(`${key}|${scope}`) ?? null,
|
||||
writeState: (key, value, scope) => (writes++, store.set(`${key}|${scope}`, value)),
|
||||
},
|
||||
common_util_surface_sampler: { heightAt: () => 0 },
|
||||
B05_Profile_Api_TerrainGrid: { loadDesignTerrainGrid: async () => null },
|
||||
};
|
||||
const cache = new Map();
|
||||
function loadTs(full) {
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
const key = Object.keys(STUBS).find((name) => request.endsWith(name));
|
||||
if (key) return STUBS[key];
|
||||
return require(request);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
return box.exports;
|
||||
}
|
||||
const api = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_Api_Replan.ts"));
|
||||
|
||||
const out = {};
|
||||
// ① 사정을 모를 때 — 라이다
|
||||
out.unknown = api.analysisTerrain("p0");
|
||||
// ② 둘 다 있고 고른 적 없음 — 데이터대로(설계 라이다 → 라이다) · 쓰기 0
|
||||
api.setTerrainContext("p1", true, "las");
|
||||
out.bothNoPick = api.analysisTerrain("p1");
|
||||
out.writesAfterOpen = writes;
|
||||
// ③ 설계가 수치지형도인 프로젝트 · 고른 적 없음 — 설계대로(고르기 글 = 실제 쓰는 지형)
|
||||
api.setTerrainContext("p2", true, "sheet");
|
||||
out.sheetDesignNoPick = api.analysisTerrain("p2");
|
||||
// ④ 사용자가 바꿈 — 그때만 기억 · 다음 접근(사정 다시 알림)에도 남음
|
||||
api.setAnalysisTerrain("p1", "sheet");
|
||||
out.afterPick = api.analysisTerrain("p1");
|
||||
out.writesAfterPick = writes;
|
||||
api.setTerrainContext("p1", true, "las");
|
||||
out.nextAccess = api.analysisTerrain("p1");
|
||||
// ⑤ 하나뿐인 프로젝트 — 옛 값이 남아 있어도 있는 것만 씀
|
||||
store.set("routeedit-terrain|p3", "las");
|
||||
api.setTerrainContext("p3", false, "sheet");
|
||||
out.sheetOnlyStale = api.analysisTerrain("p3");
|
||||
store.set("routeedit-terrain|p4", "sheet");
|
||||
api.setTerrainContext("p4", false, "las");
|
||||
out.lasOnlyStale = api.analysisTerrain("p4");
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -166,7 +166,7 @@ def test_화면_고르기_배선():
|
||||
assert '<option value="las">라이다</option>' in chrome
|
||||
assert '<option value="sheet">수치지형도</option>' in chrome
|
||||
assert 'sources?.sources.includes("las") && !!sources?.sources.includes("sheet")' in main
|
||||
assert 'saved === "sheet" || saved === "las" ? saved : fallback' in api
|
||||
assert 'saved === "sheet" || saved === "las" ? saved : (context?.design ?? fallback)' in api
|
||||
# 모달 분석 셋(종단 띠는 345-5 에서 없앰)이 고른 지형을 실어 보냄 · [확인] 재계산은 안 실음
|
||||
assert api.count("terrain: analysisTerrain(projectId)") == 2
|
||||
assert "terrain: analysisTerrain(projectId)" in initial_api
|
||||
|
||||
@@ -294,4 +294,6 @@ def test_화면_확인이_고른_지형을_실음():
|
||||
# 346-3 — 노선 초안(draft.terrain)에 실림 · [저장]이 보냄
|
||||
assert "terrain: params.terrain() })" in apply and "terrain: settings.terrain," in apply
|
||||
assert "pickedTerrain() !== designTerrain" in main # 지형만 바꿔도 [확인]이 다시 셈
|
||||
assert "analysisTerrain(projectId, designTerrain)" in main # 고른 값 없으면 설계 지형
|
||||
assert (
|
||||
"setTerrainContext(projectId, choosable, designTerrain)" in main
|
||||
) # 고른 값 없으면 설계 지형
|
||||
|
||||
@@ -173,7 +173,7 @@ def test_늦은_응답_버림(r):
|
||||
|
||||
def test_편집기_다리(r):
|
||||
d = r["draftLink"]
|
||||
assert d["told"] == [3, 2, None] # 쌓기 · 되돌리기 · 열 때로(null) — 다시하기 없음은 안 알림
|
||||
assert d["told"] == [3, 2] # 쌓기 · 되돌리기 — 다시하기 없음은 안 알림
|
||||
assert d["restored"] == [
|
||||
[r["n"], True]
|
||||
] # 저장 안 한 초안 = 새 걸음으로 되살림 · 없으면 안 부름
|
||||
|
||||
@@ -54,11 +54,6 @@ def test_다시하기는_앞으로(out) -> None:
|
||||
assert out["profileReads"] == 6 # 종단도도 걸음마다 다시 읽음
|
||||
|
||||
|
||||
def test_반영_전으로도_한_걸음(out) -> None:
|
||||
assert out["reset"]["route"] is None and out["reset"]["cross"] # 노선만 열 때로
|
||||
assert out["resetUndo"]["route"] == 70
|
||||
|
||||
|
||||
def test_단축키는_사진첩_한_곳():
|
||||
tools = (ROOT / "B05_Profile/B05_Profile_UI_Profile_Panel_Tools.ts").read_text(encoding="utf-8")
|
||||
assert "undoOwner" not in tools
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""352-3 분석 지형 기본값 — 고른 적 없으면 데이터대로 · 열 때 안 씀 · 사용자가 바꿀 때만 기억 · 바꾸면 묶음에 알림.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_352_3_analysis_terrain_default.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def out() -> dict:
|
||||
run = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", str(HERE / "helper_352_3_terrain.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
assert run.returncode == 0, run.stderr
|
||||
return json.loads(run.stdout)
|
||||
|
||||
|
||||
def test_고른_적_없으면_데이터대로_열_때_안_씀(out) -> None:
|
||||
assert out["unknown"] == "las"
|
||||
assert out["bothNoPick"] == "las" # 라이다가 있는 프로젝트 = 라이다
|
||||
assert out["writesAfterOpen"] == 0 # 열 때 pref 에 쓰지 않음
|
||||
assert out["sheetDesignNoPick"] == "sheet" # 설계 지형대로 — 고르기 글 = 실제 쓰는 지형
|
||||
|
||||
|
||||
def test_사용자가_바꿀_때만_기억(out) -> None:
|
||||
assert out["afterPick"] == "sheet" and out["writesAfterPick"] == 1
|
||||
assert out["nextAccess"] == "sheet" # 다음 접근에도
|
||||
|
||||
|
||||
def test_하나뿐이면_있는_것만(out) -> None:
|
||||
assert out["sheetOnlyStale"] == "sheet"
|
||||
assert out["lasOnlyStale"] == "las"
|
||||
|
||||
|
||||
def test_편집기_배선() -> None:
|
||||
main = (ROOT / "B05_Profile/B05_Profile_UI_RouteEdit.ts").read_text(encoding="utf-8")
|
||||
assert main.count("setAnalysisTerrain(") == 1 # 바꿀 때 한 곳뿐 — 열 때 쓰기 없음
|
||||
handler = main.split('terrainSelect.addEventListener("change"', 1)[1][:900]
|
||||
assert "setAnalysisTerrain(projectId" in handler
|
||||
assert "options.draft?.edited(" in handler # 바꾸면 계산 묶음에 알림
|
||||
# 설계 지형이 수치지형도면 확정 모델(수치지형도 서피스) 등고선을 「LAS」로 쓰지 않음
|
||||
assert 'analysisTerrain(projectId) === "las" && designTerrain === "las"' in main
|
||||
@@ -1,7 +1,7 @@
|
||||
"""계획노선 편집 겉틀 — 345-3 부터 모달이 아니라 종단 보기 가운데 위 본문(PLAN 127-1 의 공용 모달 조각은 걷음).
|
||||
|
||||
· 덮개(`ui-modal__backdrop`) · 전체 화면 · 닫기 단추 없음 · 부모를 받아 붙음(`document.body` 아님)
|
||||
· [취소] → [반영 전으로](이력 처음으로) · [확인] → [노선 반영] · 머리 끝 `tools` 자리([3D])
|
||||
· [저장 전으로] 없음(352-2) · ↶ ↷ 는 기호 단추 · 머리 끝 `tools` 자리([3D])
|
||||
· 오른쪽 횡단 미리보기 = 접는 칸 · 재계산 대기 막은 화면 전체(fixed) · R 라벨은 칸 안 절대 위치
|
||||
· 바깥 누름 · Esc 로 안 닫힘(모달 아님)
|
||||
"""
|
||||
@@ -33,13 +33,16 @@ def test_겉틀은_본문_칸():
|
||||
assert 'overlay.className = "b05-routeedit";' in chrome
|
||||
assert "b05-routeedit__close" not in chrome and 'data-slot="close"' not in chrome
|
||||
assert '"tools"' in chrome and "replaceWith(...tools)" in chrome
|
||||
# 346-3 — [노선 반영] 없앰(노선은 페이지 [저장]이 맨 앞에서 보냄) · [취소] = [저장 전으로]
|
||||
assert ">저장 전으로</button>" in chrome and 'data-act="apply"' not in chrome
|
||||
assert "노선 반영</button>" not in chrome
|
||||
# 346-3 — [노선 반영] 없앰(노선은 페이지 [저장]이 맨 앞에서 보냄) · 352-2 — [저장 전으로] 없앰
|
||||
assert 'data-act="apply"' not in chrome and 'data-act="cancel"' not in chrome
|
||||
assert ">저장 전으로</button>" not in chrome and "노선 반영</button>" not in chrome
|
||||
assert ">취소</button>" not in chrome and ">확인</button>" not in chrome
|
||||
# 352-2 — 되돌리기 · 다시하기 = 글 없이 기호 + aria-label · title(단축키)
|
||||
assert 'aria-label="되돌리기" disabled>↶</button>' in chrome
|
||||
assert 'aria-label="다시하기" disabled>↷</button>' in chrome
|
||||
assert 'title="되돌리기 (Ctrl+Z)"' in chrome and 'title="다시하기 (Ctrl+Y)"' in chrome
|
||||
# 시험 · 배선이 집는 자리는 남김
|
||||
for hook in ('data-act="cancel"', "b05-routeedit__measure-close"):
|
||||
assert hook in chrome
|
||||
assert "b05-routeedit__measure-close" in chrome
|
||||
assert 'data-act="side-toggle"' in chrome and "is-collapsed" in chrome
|
||||
|
||||
|
||||
@@ -58,6 +61,36 @@ def test_CSS_덮개_걷음_대기막은_전체():
|
||||
assert "@media (width < 1500px)" not in css # 접기는 단추로(처음만 폭으로)
|
||||
|
||||
|
||||
def test_352_1_머리줄_상자_없음_도구는_뜬_단추_횡단은_우측_덮개():
|
||||
chrome = _read("B05_Profile_UI_RouteEdit_Chrome.ts")
|
||||
for gone in ("b05-routeedit__box", "b05-routeedit__head", "b05-routeedit__actions", "<h3>"):
|
||||
assert gone not in chrome
|
||||
# 도구줄 = 3D 와 같은 떠 있는 유리 단추 · 구분선도 3D 것
|
||||
assert 'class="b05-route__view-controls b05-routeedit__tools"' in chrome
|
||||
assert "ui-btn--ghost" not in chrome.split("<aside", 1)[0]
|
||||
# 도구줄은 지도 칸 안 · [?] 는 도구줄 뒤
|
||||
wrap = chrome.index("b05-routeedit__canvas-wrap")
|
||||
assert wrap < chrome.index("b05-routeedit__tools") < chrome.index("b05-routeedit__hint")
|
||||
assert chrome.index("b05-routeedit__side-wrap") > chrome.index("b05-routeedit__busy")
|
||||
css = _read("B05_Profile_UI_Style_RouteEdit.css")
|
||||
for gone in (".b05-routeedit__box", ".b05-routeedit__head", ".b05-routeedit__actions"):
|
||||
assert gone not in css
|
||||
root = _block(css, ".b05-routeedit")
|
||||
assert "padding" not in root and "gap" not in root
|
||||
side = _block(css, ".b05-routeedit__side-wrap")
|
||||
assert "position: absolute;" in side and "right: var(--spacing-12);" in side
|
||||
z = int(re.search(r"z-index: (\d+);", side).group(1))
|
||||
assert z < 3 # 아래 종단 판(z 3)이 위
|
||||
style = _read("B05_Profile_UI_Style.css")
|
||||
assert "z-index: 3;" in _block(style, ".b05-route-profile")
|
||||
# 회전 단추 · 잰 값 창은 펴진 횡단 판을 비킴
|
||||
assert "var(--b05-side-top)" in _block(css, ".b05-routeedit__spin")
|
||||
assert "var(--b05-side-open)" in _block(css, ".b05-routeedit__measure")
|
||||
assert "is-side-open" in chrome
|
||||
glass = _block(css, ".b05-routeedit__terrain")
|
||||
assert "backdrop-filter: blur(6px);" in glass and "background:" in glass
|
||||
|
||||
|
||||
def test_페이지_수명_닫기_없음():
|
||||
main = _read("B05_Profile_UI_RouteEdit.ts")
|
||||
assert "openModal" not in main and "Escape" not in main
|
||||
@@ -67,10 +100,13 @@ def test_페이지_수명_닫기_없음():
|
||||
assert "new ResizeObserver(" in main
|
||||
assert "host.onDispose(dispose);" in main
|
||||
assert "historyControls.listen(host.visible());" in main
|
||||
assert "history?.reset();" in main # [반영 전으로]
|
||||
assert "history?.reset()" not in main # 352-2 — [저장 전으로] 지움
|
||||
hist = _read("B05_Profile_UI_RouteEdit_History.ts")
|
||||
assert "reset" not in hist.split("export interface RouteEditHistory", 1)[1].split("}", 1)[0]
|
||||
assert len(main.splitlines()) <= 700
|
||||
label = _read("B05_Profile_UI_RouteEdit_Label.ts")
|
||||
assert "document.body.append" not in label and "container.append(root);" in label
|
||||
assert '"곡선 삭제" : "곡선 추가"' in label # 352-5 — 버튼 이름은 명사형
|
||||
assert "container: overlay," in main
|
||||
apply = _read("B05_Profile_UI_RouteEdit_Apply.ts")
|
||||
assert "close" not in apply.split("export function bindRouteApply", 1)[1]
|
||||
|
||||
@@ -210,9 +210,9 @@ def test_노선_추천_다듬기_23_3():
|
||||
chrome = read("B05_Profile_UI_RouteEdit_Chrome.ts")
|
||||
# 345-4 — [노선 추천]은 좌측 칸 · 머리 구분선은 [3D](tools) 뒤 · 되돌리기 앞 둘 그대로
|
||||
tools = chrome.index('data-slot="tools"')
|
||||
divider = chrome.index("b05-routeedit__divider", tools)
|
||||
divider = chrome.index("b05-route__view-separator", tools) # 352-1 — 3D 도구줄 구분선 그대로
|
||||
assert tools < divider < chrome.index('data-act="contour-minor"')
|
||||
assert chrome.count("b05-routeedit__divider") == 2
|
||||
assert chrome.count("b05-route__view-separator") == 2
|
||||
shared = read("B05_Profile_UI_RouteEdit_Recommend_Map.ts")
|
||||
for name in ("bindRouteEditNavigation", "createMapRotation", "fitCanvas", "metricToScreen"):
|
||||
assert f"{name}(" in shared
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
③ 휴대폰 · 공개 쪽(A01) = 위 줄 그대로(본문 위 끝 64) · ☰ 는 위 줄 오른쪽
|
||||
④ 데스크탑(1440 · 마우스) · 로그인 뒤 B 쪽 = 위 줄 걷힘 · ☰ 왼쪽 위 36×36 · 진행 판 없음 · 위 줄 항목이 모두 서랍에 · Esc 로 닫힘
|
||||
⑤ 데스크탑 · 공개 쪽(A01) = 위 줄 그대로 · ☰ 없음
|
||||
⑥ (352-4) 좌측 패널이 펼쳐진 동안 ☰ 는 제목 줄 앞 칸에 든 모양(테두리 · 바탕 · 그림자 없음 · 자리 8,8 그대로)
|
||||
· 접으면 떠 있는 단추 · 서랍 첫 줄 로고 우측 · 프로젝트 이름 우측 끝 — 글로 봄(서버 없이)
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -131,3 +133,22 @@ def test_drawer_holds_steps_and_header_folds():
|
||||
assert desk.evaluate(OUTLET_TOP) == 64
|
||||
assert not desk.evaluate("document.querySelector('.app-burger').getClientRects().length")
|
||||
browser.close()
|
||||
|
||||
|
||||
def test_352_4_burger_in_panel_title_row_text():
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
shell = (root / "A00_Common/app_shell.ts").read_text(encoding="utf-8")
|
||||
open_panel = (
|
||||
"html.is-bm-route:has(.ui-workflow-overlay__panel--title:not(.is-collapsed)) .app-burger {"
|
||||
)
|
||||
assert open_panel in shell # 패널 펼침 = 제목 줄에 든 모양
|
||||
body = shell.split(open_panel, 1)[1].split("}", 1)[0]
|
||||
for line in ("border-color: transparent;", "background: transparent;", "box-shadow: none;"):
|
||||
assert line in body
|
||||
# 자리(8,8 · 36)는 그대로 — 접으면 아래 기본 규칙(떠 있는 단추)으로 돌아감
|
||||
base = shell.split("html.is-bm-route .app-burger {", 1)[1].split("}", 1)[0]
|
||||
assert "position: fixed;" in base and "left: var(--spacing-8);" in base
|
||||
assert "html.is-bm-route .app-drawer__brand .app-brand { align-self: flex-end; }" in shell
|
||||
overlay = (root / "ui_template/ui_template_overlay.css").read_text(encoding="utf-8")
|
||||
project = overlay.split(".ui-workflow-overlay__project {", 1)[1].split("}", 1)[0]
|
||||
assert "margin-left: auto;" in project
|
||||
|
||||
Reference in New Issue
Block a user