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

This commit is contained in:
2026-10-25 19:39:00 +09:00
40 changed files with 566 additions and 132 deletions
+13 -1
View File
@@ -115,7 +115,12 @@ const toasts = [];
let role = "SYSTEM_ADMIN";
let help;
const STUBS = {
"@config/config_frontend": { ROUTES, API_BASE_URL: "" },
"@config/config_frontend": {
ROUTES,
API_BASE_URL: "",
PLAN_STEP_ROUTES: ["d02-plan-base", "d03-plan-analysis", "d04-plan-confirm"],
STAB_STEP_ROUTES: ["e02-stab-ground", "e03-stab-section", "e04-stab-3d", "e05-stab-report"],
},
"./ui_template_locale": { t: (key) => key },
"./ui_template_modal": { openModal: () => undefined },
"./ui_template_elements": {
@@ -190,6 +195,13 @@ const ALL = [
"b09-print",
"m01-master-data",
"m02-master-template",
// 529-1 D · E 묶음 — 묶음 안에서만 [<] [>] · 등록(D01 · E01) = [?] 만
"d01-plan-register",
"d02-plan-base",
"d04-plan-confirm",
"e01-stab-register",
"e02-stab-ground",
"e05-stab-report",
];
const rows = {};
for (const route of ALL) rows[route] = { helpRow: rowOf(route, true), plain: rowOf(route, false) };
@@ -13,7 +13,7 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
LAYOUT = "B08_Quantity/B08_Quantity_Milestone_Layout.ts"
KMAP = "B08_Quantity/B08_Quantity_UI_Milestone_KakaoMap.ts"
KMAP = "ui_template/ui_template_kakao_map.ts"
ORIGIN = "http://100.72.32.27:5173"
@@ -25,7 +25,8 @@ STUBS = f"""
const el = (tag: string, o: any = {{}}) => ({{ tag, hidden: false, textContent: o.text ?? "",
className: o.className, dataset: {{}}, children: o.children ?? [] }});
const requestAnimationFrame = (f: () => void) => setTimeout(f, 0);
const request = async () => ({{ key: "fake-js-key" }});
const API_BASE_URL = "";
(globalThis as any).fetch = async () => ({{ ok: true, json: async () => ({{ key: "fake-js-key" }}) }});
const injected: string[] = [];
(globalThis as any).window = globalThis;
(globalThis as any).location = {{ origin: {json.dumps(ORIGIN)} }};
@@ -50,7 +51,7 @@ def _view(setup):
+ IMPORTS.sub("", _read(KMAP))
+ """
// 156 경로 탭 지도 칸 없앰 — 같은 SDK 싣기 · 안내 길 = 연결선 모달 지도 칸(editMap)
const view = editMap("p1", { onMove: () => {}, hint: "길 선 = 카카오 길찾기 · 경로 그림은 서버가 자동으로 만듦" });
const view = editMap({ onMove: () => {}, hint: "길 선 = 카카오 길찾기 · 경로 그림은 서버가 자동으로 만듦" });
view.set([{ key: "r1", name: "거창군청", lon: 127.9, lat: 35.68, movable: true }], routePaths());
await new Promise((r) => setTimeout(r, 30));
const [hint, box] = view.root.children;
@@ -30,7 +30,7 @@ IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
B08 = "B08_Quantity/B08_Quantity_"
CALC = B08 + "Milestone_Calc.ts"
LAYOUT = B08 + "Milestone_Layout.ts"
KMAP = B08 + "UI_Milestone_KakaoMap.ts"
KMAP = "ui_template/ui_template_kakao_map.ts"
STATE = B08 + "UI_Milestone_State.ts"
BASE = "/api/projects/00000000-0000-0000-0000-000000000001/quantity/milestone"
@@ -158,7 +158,8 @@ const el = (tag: string, o: any = {}) => ({ tag, hidden: false, textContent: o.t
const requestAnimationFrame = (f: () => void) => setTimeout(f, 0);
(globalThis as any).window = globalThis;
(globalThis as any).document = { createElement: () => ({}), head: { append: () => undefined } };
const request = async () => ({ key: "fake-js-key" });
const API_BASE_URL = "";
(globalThis as any).fetch = async () => ({ ok: true, json: async () => ({ key: "fake-js-key" }) });
const LOG: any = { markers: [], handlers: [], lines: 0 };
class LatLng { lat: number; lng: number;
constructor(lat: number, lng: number) { this.lat = lat; this.lng = lng; }
@@ -183,7 +184,7 @@ def test_고르는_지도_누름_끌기():
[KMAP],
"""
const moved: any[] = []; const clicked: any[] = [];
const m = editMap("p1", { hint: "h", onMove: (k, lon, lat) => moved.push([k, lon, lat]),
const m = editMap({ hint: "h", onMove: (k, lon, lat) => moved.push([k, lon, lat]),
onClick: (lon, lat) => clicked.push([lon, lat]), center: () => [127.83, 35.64] });
m.set([], []);
await new Promise((r) => setTimeout(r, 20));
@@ -292,7 +293,7 @@ def test_화면_등록_모달_연결선_모달():
assert "소재지" in modal and "거래처 위치" not in modal
for text in (modal, place):
assert 'label: "직접 입력"' not in text and "buildAddressField" not in text
assert "editMap(projectId" in place and "onClick: (lon, lat) => void moveTo(lon, lat)" in place
assert "editMap({" in place and "onClick: (lon, lat) => void moveTo(lon, lat)" in place
assert "/quantity/milestone/reverse?lon=" in place
# ② 상자 누름 = 같은 등록 모달 · [기준점 +] 없음
assert "base: (id) => openPlaceModal(projectId, { add: false, id })," in diagram
@@ -151,7 +151,7 @@ def test_화면_모달_팬줌_그림탭():
assert 'from "../B01_Dashboard/B01_Dashboard_Api_Fetch"' in place
assert "searchAddress(text)" in place
# ② 시점 모달 — 지도 = 공용 카카오 지도(255-1 · 주소 줄 지움)
assert "/quantity/milestone/site-address" in modal and "editMap(projectId" in modal
assert "/quantity/milestone/site-address" in modal and "editMap({" in modal
# ⑤ 거래처 상자 = 같은 등록 모달(고침 · 삭제 · 244-13) · 구간 km · 도로 구분 = 연결선 모달
for text in ('"수정"', 'label: "삭제"', "rowValues()", "putMilestone(projectId, fresh)"):
assert text in add
@@ -22,11 +22,12 @@ if str(ROOT) not in sys.path:
from B08_Quantity import B08_Quantity_Router_Milestone as ms # noqa: E402
from B08_Quantity.B08_Quantity_Milestone_Calc import migrate_milestone # noqa: E402
from common_util import common_util_map_router as map_router # noqa: E402
IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE)
BASE = "/api/projects/00000000-0000-0000-0000-000000000001/quantity/milestone"
LAYOUT = "B08_Quantity/B08_Quantity_Milestone_Layout.ts"
KMAP = "B08_Quantity/B08_Quantity_UI_Milestone_KakaoMap.ts"
KMAP = "ui_template/ui_template_kakao_map.ts"
GEOCHANG = {"lon": 127.9095, "lat": 35.6866}
BYEONGGOK = {"lon": 127.85, "lat": 35.66}
BODY = {
@@ -108,14 +109,14 @@ def test_좌표_저장_안_함(tmp_path):
def test_JS_키_길(monkeypatch):
app = FastAPI()
app.include_router(ms.router)
app.include_router(map_router.router) # 529-2 공용 길로 옮김
client = TestClient(app)
monkeypatch.delenv(ms.KAKAO_JS, raising=False)
assert client.get(BASE + "/map-key").json() == {"key": None}
monkeypatch.setenv(ms.KAKAO_JS, " ")
assert client.get(BASE + "/map-key").json() == {"key": None}
monkeypatch.setenv(ms.KAKAO_JS, "fake-js-key")
assert client.get(BASE + "/map-key").json() == {"key": "fake-js-key"}
monkeypatch.delenv(map_router.KAKAO_JS, raising=False)
assert client.get("/api/map/kakao-key").json() == {"key": None}
monkeypatch.setenv(map_router.KAKAO_JS, " ")
assert client.get("/api/map/kakao-key").json() == {"key": None}
monkeypatch.setenv(map_router.KAKAO_JS, "fake-js-key")
assert client.get("/api/map/kakao-key").json() == {"key": "fake-js-key"}
def test_표지_셋():
@@ -164,10 +165,11 @@ def _map(key, paths, extra=""):
const KEY = {json.dumps(key)};
const PATHS = {json.dumps(paths)};
const asked: string[] = [];
const request = async (url: string) => (asked.push(url), {{ key: KEY }});
const API_BASE_URL = "";
(globalThis as any).fetch = async (url: string) => (asked.push(url), {{ ok: true, json: async () => ({{ key: KEY }}) }});
{extra}
// 156 경로 탭 지도 칸 없앰 — 같은 SDK 싣기 · 안내 길 = 연결선 모달 지도 칸(editMap)
const view = editMap("p1", {{ onMove: () => {{}}, hint: "길 선 = 카카오 길찾기 · 경로 그림은 서버가 자동으로 만듦" }});
const view = editMap({{ onMove: () => {{}}, hint: "길 선 = 카카오 길찾기 · 경로 그림은 서버가 자동으로 만듦" }});
view.set([{{ key: "r1", name: "거창군청", lon: 127.9, lat: 35.68, movable: true }},
{{ key: "base", name: "병곡리", lon: 127.85, lat: 35.66, movable: true }},
{{ key: "start", name: "시점", lon: 127.83, lat: 35.64, movable: false }}], PATHS);
@@ -181,7 +183,7 @@ console.log(JSON.stringify({{ asked, injected, hint: hint.textContent, boxHidden
def test_키_없음_안내_한_줄():
got = _map(None, [])
assert got["asked"] == ["/projects/p1/quantity/milestone/map-key"]
assert got["asked"] == ["/map/kakao-key"]
assert got["injected"] == [] # SDK 안 부름
assert got["hint"] == (
"카카오 JavaScript 키 · 도메인 등록 필요 — 지도 대신 [그림 변경]으로 넣으세요"
@@ -73,11 +73,12 @@ def test_폭_전체_지도_높이_한_값():
block = css[css.index(".b08-milestone {") :]
assert "width: 100%;" in block[: block.index("}")]
assert ".b08-milestone > .ui-data-table .ui-data-table__table {\n width: 100%;" in css
assert css.count("height: 480px;") == 1 and ".b08-milestone__map {" in css
kcss = _read("ui_template/ui_template_kakao_map.css") # 529-2 공용 지도 칸
assert kcss.count("height: 480px;") == 1 and ".ui-kakao-map__box {" in kcss
def test_153_지도_보기_유지_상자_색_끌어_옮김():
kmap = _read("B08_Quantity/B08_Quantity_UI_Milestone_KakaoMap.ts")
kmap = _read("ui_template/ui_template_kakao_map.ts")
assert (
"let fitted = false;" in kmap and "map.panTo(" in kmap
) # 처음 한 번만 맞춤 · 그 뒤 줌 그대로
@@ -170,9 +171,7 @@ def test_157_도구_왼쪽_아래_경로_탭_지도_그림_변경_초기화():
route = _read("B08_Quantity/B08_Quantity_UI_Milestone_Route.ts")
assert 'label: "그림 변경"' in route and 'label: "초기화"' in route
assert "그림 지우기" not in route and "자동 그림으로 되돌리기" not in route.split("*/", 1)[1]
assert (
"map.root.hidden = user;" in route and "editMap(projectId" in route
) # 그림 없으면 길찾기 지도
assert "map.root.hidden = user;" in route and "editMap({" in route # 그림 없으면 길찾기 지도
assert ".b08-milestone__route-head {" in css and "justify-content: space-between;" in css
state = _read("B08_Quantity/B08_Quantity_UI_Milestone_State.ts")
assert "export const hasUserImage" in state and "canRevertImage" not in state
@@ -46,6 +46,6 @@ def test_시점_모달_km_카카오_지도():
# 설명 줄 · 등록 주소 · 브이월드 주소 줄 · B01 타일 지도 없음
for gone in ("시점 = 노선 첫 점", "프로젝트 등록 주소", "브이월드 주소", "buildAddressMap"):
assert gone not in modal
assert "editMap(projectId" in site
assert "editMap({" in site
assert '["start", "시점", s.start_lonlat]' in site and '["mid", "중점", s.mid_lonlat]' in site
assert 'dialogClass: "b08-milestone__modal"' in site # 다른 모달과 같은 창 · 지도 높이
+13
View File
@@ -32,6 +32,19 @@ def results():
return json.loads(proc.stdout)
def test_529_1_D_E_묶음_안에서만_앞뒤(results):
rows = results["rows"]
for route in ("d01-plan-register", "e01-stab-register"):
assert rows[route]["plain"]["labels"] == ["?", "저장"], route
for route in ("d02-plan-base", "d04-plan-confirm", "e02-stab-ground", "e05-stab-report"):
assert rows[route]["plain"]["labels"] == ["?", "<", ">", "저장"], route
# 묶음 첫 단계 [<] · 끝 단계 [>] 흐림(B 묶음과 따로)
assert rows["d02-plan-base"]["plain"]["disabled"][:3] == [False, True, False]
assert rows["d04-plan-confirm"]["plain"]["disabled"][:3] == [False, False, True]
assert rows["e02-stab-ground"]["plain"]["disabled"][:3] == [False, True, False]
assert rows["e05-stab-report"]["plain"]["disabled"][:3] == [False, False, True]
@pytest.mark.parametrize("shape", ["helpRow", "plain"])
def test_다섯_주소만_물음_앞뒤_차례(results, shape):
for route in FIVE:
+5 -1
View File
@@ -28,11 +28,15 @@ def test_title_order_and_live_edit():
assert "고친 글" in read_help("B05", tmp_path)[1]["text"]
@pytest.mark.parametrize("page", ["B01", "B10", "../B05", "b05"])
@pytest.mark.parametrize("page", ["B01", "B10", "../B05", "b05", "D05", "E06", "C01"])
def test_bad_page_refused(page):
with pytest.raises(HTTPException):
read_help(page)
def test_529_1_D_E_pages_open_empty(tmp_path):
assert read_help("D02", tmp_path) == [] and read_help("E05", tmp_path) == []
def test_root_is_repo_resources():
assert HELP_ROOT.parts[-2:] == ("resources", "help")