From 4737756502accd84845d9d34afaa696341e42a8c Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 11 Oct 2026 15:17:40 +0900 Subject: [PATCH] =?UTF-8?q?fix(b08):=20=EA=B1=B0=EB=A6=AC=EC=9D=B4?= =?UTF-8?q?=EC=A0=95=ED=91=9C=20km=20=EC=B9=B8=20=EB=81=9D=EC=88=98=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC=20=E2=80=94=203=EC=9E=90=EB=A6=AC=20?= =?UTF-8?q?=EB=B0=98=EC=98=AC=EB=A6=BC=20=C2=B7=20=EB=81=9D=200=20?= =?UTF-8?q?=EC=A7=80=EC=9B=80=20=C2=B7=20=EC=95=88=20=EA=B3=A0=EC=B9=98?= =?UTF-8?q?=EB=A9=B4=20=EC=A0=80=EC=9E=A5=20m=20=EA=B7=B8=EB=8C=80?= =?UTF-8?q?=EB=A1=9C=20(261-1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 시점 창 · 구간 창 · 다이어그램 선 위 km 칸 · 자동 값 글 = 같은 도우미(fmtKm) - 안 고친 칸은 반올림 값으로 덮지 않음 - 시험 test_261_1 추가 · test_255_1 기대값만 고침 Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_018jonfdxn1Xit9bdkNJpE4G --- .../B08_Quantity_UI_Milestone_Diagram.ts | 11 +-- .../B08_Quantity_UI_Milestone_LegModal.ts | 29 +++++--- .../B08_Quantity_UI_Milestone_Modal.ts | 13 +++- .../test_255_1_b08_milestone_site_modal.py | 2 +- .../tester/test_261_1_b08_milestone_km_fmt.py | 71 +++++++++++++++++++ 5 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 resources/tester/test_261_1_b08_milestone_km_fmt.py diff --git a/B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts b/B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts index a6178f573..ef0fa6f26 100644 --- a/B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts +++ b/B08_Quantity/B08_Quantity_UI_Milestone_Diagram.ts @@ -39,7 +39,7 @@ import { type DiagramBox, } from "./B08_Quantity_Milestone_Layout"; import { openLegInfo } from "./B08_Quantity_UI_Milestone_LegModal"; -import { openPlaceModal, openSiteInfo, toastKm } from "./B08_Quantity_UI_Milestone_Modal"; +import { fmtKm, openPlaceModal, openSiteInfo, toastKm } from "./B08_Quantity_UI_Milestone_Modal"; import { findSegmentKm, liveMilestone, @@ -87,11 +87,12 @@ function editLegAt( className: "ui-input", attrs: { type: "number", min: "0", step: "any" }, }); - km.value = leg.km == null ? "" : String(leg.km); + km.value = leg.km == null ? "" : fmtKm(leg.km); + const shown = km.value; // 안 고치면 저장 값 그대로 · 반올림 값으로 안 덮음 km.setAttribute("aria-label", "km"); if (leg.auto_km != null) { - km.placeholder = String(leg.auto_km); // 빈칸 = 자동 값 - km.title = `자동 값 ${leg.auto_km} km`; + km.placeholder = fmtKm(leg.auto_km); // 빈칸 = 자동 값 + km.title = `자동 값 ${fmtKm(leg.auto_km)} km`; } const box = el("div", { className: "b08-milestone__leg-edit", children: [road.root, km] }); box.dataset.b08MilestoneLegEdit = `${from}:${i}`; @@ -108,7 +109,7 @@ function editLegAt( const value = km.value.trim() && Number.isFinite(n) && n >= 0 ? n : null; const patch = { road: (road.select.value || null) as Road | null, - ...(km.value.trim() && value === null ? {} : { km: value }), + ...(km.value === shown || (km.value.trim() && value === null) ? {} : { km: value }), }; putMilestone(projectId, setLeg(liveMilestone(projectId), from, i, patch)); }; diff --git a/B08_Quantity/B08_Quantity_UI_Milestone_LegModal.ts b/B08_Quantity/B08_Quantity_UI_Milestone_LegModal.ts index 089b7c485..881dd86ca 100644 --- a/B08_Quantity/B08_Quantity_UI_Milestone_LegModal.ts +++ b/B08_Quantity/B08_Quantity_UI_Milestone_LegModal.ts @@ -24,7 +24,7 @@ import { } from "./B08_Quantity_Milestone_Calc"; import { ownerOf, segmentEnds, setLeg } from "./B08_Quantity_Milestone_Layout"; import { editMap, type MapPin } from "./B08_Quantity_UI_Milestone_KakaoMap"; -import { toastKm } from "./B08_Quantity_UI_Milestone_Modal"; +import { fmtKm, toastKm } from "./B08_Quantity_UI_Milestone_Modal"; import { reverseAddress } from "./B08_Quantity_UI_Milestone_Place"; import { findSegmentKm, @@ -47,7 +47,13 @@ export function legsEditor( kind: SegKind, ): { root: HTMLElement; value: () => Leg[] } { const list = el("div"); - const rows: Array<{ road: HTMLSelectElement; km: HTMLInputElement; auto: number | null }> = []; + const rows: Array<{ + road: HTMLSelectElement; + km: HTMLInputElement; + auto: number | null; + orig: number | null; // 안 고친 칸 = 원래 값 그대로(반올림 값으로 안 덮음) + shown: string; + }> = []; const add = (leg: Leg): void => { const road = createSelectField({ label: "도로 구분", @@ -56,13 +62,13 @@ export function legsEditor( }); const dist = createInputField({ label: "거리 (km)", type: "number", min: 0, compact: true }); road.select.value = roadOf(leg.road, kind); - dist.input.value = leg.km == null ? "" : String(leg.km); + dist.input.value = leg.km == null ? "" : fmtKm(leg.km); const auto = leg.auto_km ?? null; if (auto != null) { - dist.input.placeholder = String(auto); // 빈칸 = 자동 값 · 고치면 그 값 우선 - dist.input.title = `자동 값 ${auto} km`; + dist.input.placeholder = fmtKm(auto); // 빈칸 = 자동 값 · 고치면 그 값 우선 + dist.input.title = `자동 값 ${fmtKm(auto)} km`; } - const ref = { road: road.select, km: dist.input, auto }; + const ref = { road: road.select, km: dist.input, auto, orig: leg.km, shown: dist.input.value }; rows.push(ref); const drop = createButton({ label: "×", @@ -90,9 +96,9 @@ export function legsEditor( return { root: el("div", { children: [list, more] }), value: () => - rows.map(({ road, km: dist, auto }) => ({ + rows.map(({ road, km: dist, auto, orig, shown }) => ({ road: (ROADS as readonly string[]).includes(road.value) ? (road.value as Road) : null, - km: km(dist.value), + km: dist.value === shown ? orig : km(dist.value), auto_km: auto, })), }; @@ -117,8 +123,9 @@ function openMidLeg(projectId: string): void { road.select.value = roadOf(data.site.mid_road, "mid"); const dist = createInputField({ label: "거리 (km)", type: "number", min: 0, compact: true }); dist.input.value = - data.site.mid_station_m == null ? "" : String(data.site.mid_station_m / 1000); - dist.input.placeholder = site?.mid_default_m == null ? "" : String(site.mid_default_m / 1000); // 빈칸 = 노선 ÷ 2 + data.site.mid_station_m == null ? "" : fmtKm(data.site.mid_station_m / 1000); + dist.input.placeholder = site?.mid_default_m == null ? "" : fmtKm(site.mid_default_m / 1000); // 빈칸 = 노선 ÷ 2 + const shown = dist.input.value; // 안 고치면 저장 값(m) 그대로 dist.input.dataset.b08MilestoneLegKm = "start"; const save = createButton({ label: "수정", @@ -129,7 +136,7 @@ function openMidLeg(projectId: string): void { projectId, setLeg(fresh, "start", 0, { road: road.select.value as Road, - km: km(dist.input.value), + ...(dist.input.value === shown ? {} : { km: km(dist.input.value) }), }), ); close(); diff --git a/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts b/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts index 6d1bf33b5..0e3394a59 100644 --- a/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts +++ b/B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts @@ -64,6 +64,9 @@ export function toastKm(text: string): void { if (text) showToast(`길찾기 — ${text}`, text.includes(" — ") ? "warning" : "success"); } +/** 칸 · 글에 보이는 km — 3자리(1 m) 반올림 · 끝 0 지움(셈 값은 안 바꿈 · 261-1) */ +export const fmtKm = (km: number): string => String(Math.round(km * 1000) / 1000); + let listSeq = 0; /** 항목(손 입력 + 제안 목록 · 244-14) · 목적지 ▾ 한 줄 — 목록 밖 글도 받음 · 분류 바꾸면 제안만 바뀜 */ @@ -270,9 +273,10 @@ export function openSiteInfo(projectId: string): void { compact: true, }); mid.input.step = "0.001"; - mid.input.placeholder = site?.mid_default_m != null ? String(site.mid_default_m / 1000) : ""; + mid.input.placeholder = site?.mid_default_m != null ? fmtKm(site.mid_default_m / 1000) : ""; mid.input.value = - data.site.mid_station_m == null ? "" : String(data.site.mid_station_m / 1000); + data.site.mid_station_m == null ? "" : fmtKm(data.site.mid_station_m / 1000); + const shown = mid.input.value; // 안 고치면 저장 값(m) 그대로 · 반올림 값으로 안 덮음 mid.input.dataset.b08Milestone = "mid_station_m"; const road = createSelectField({ label: "시점 → 중점 도로", @@ -290,7 +294,10 @@ export function openSiteInfo(projectId: string): void { const fresh = liveMilestone(projectId); putMilestone( projectId, - setLeg(fresh, "start", 0, { road: road.select.value as Road, km }), + setLeg(fresh, "start", 0, { + road: road.select.value as Road, + ...(text === shown ? {} : { km }), + }), ); close(); }, diff --git a/resources/tester/test_255_1_b08_milestone_site_modal.py b/resources/tester/test_255_1_b08_milestone_site_modal.py index e944a6d05..51ce9d5b0 100644 --- a/resources/tester/test_255_1_b08_milestone_site_modal.py +++ b/resources/tester/test_255_1_b08_milestone_site_modal.py @@ -41,7 +41,7 @@ def test_시점_모달_km_카카오_지도(): modal = _read("B08_Quantity/B08_Quantity_UI_Milestone_Modal.ts") site = modal[modal.index("export function openSiteInfo") :] assert 'label: "중점 측점 (km)"' in site - assert "String(data.site.mid_station_m / 1000)" in site # 보임 = km + assert "fmtKm(data.site.mid_station_m / 1000)" in site # 보임 = km(261-1 끝수 정리) assert 'setLeg(fresh, "start", 0, {' in site # 받음 km → 저장 m # 설명 줄 · 등록 주소 · 브이월드 주소 줄 · B01 타일 지도 없음 for gone in ("시점 = 노선 첫 점", "프로젝트 등록 주소", "브이월드 주소", "buildAddressMap"): diff --git a/resources/tester/test_261_1_b08_milestone_km_fmt.py b/resources/tester/test_261_1_b08_milestone_km_fmt.py new file mode 100644 index 000000000..85ef4acf0 --- /dev/null +++ b/resources/tester/test_261_1_b08_milestone_km_fmt.py @@ -0,0 +1,71 @@ +"""261-1 거리이정표 km 칸 끝수 — 보이는 km = 3자리(1 m) 반올림 · 끝 0 지움 · 안 고치면 저장 m 그대로 · +고치면 km × 1000 → m(끝수 없이).""" + +import json +import re +import subprocess +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +IMPORTS = re.compile(r"^import[^;]*;\n", re.MULTILINE) +UI = "B08_Quantity/B08_Quantity_UI_Milestone" + + +def _read(rel): + return (ROOT / rel).read_text(encoding="utf-8") + + +def _node(src, expr): + result = subprocess.run( + ["node", "--experimental-strip-types", "--no-warnings", + "--input-type=module-typescript", "-e", + IMPORTS.sub("", src) + f"\nconsole.log(JSON.stringify({expr}));"], + capture_output=True, text=True, encoding="utf-8", timeout=60, + ) # fmt: skip + assert result.returncode == 0, result.stderr + return json.loads(result.stdout) + + +def _fmt(values): + modal = _read(f"{UI}_Modal.ts") + line = next(ln for ln in modal.splitlines() if ln.startswith("export const fmtKm")) + return _node(line.replace("export ", ""), f"{json.dumps(values)}.map(fmtKm)") + + +def test_km_글_3자리_끝0_지움(): + assert _fmt([576.595 / 1000, 0.3, 0.1 + 0.2, 12, 0, 1.2345, 0.0005, 2 / 3]) == [ + "0.577", "0.3", "0.3", "12", "0", "1.235", "0.001", "0.667", + ] # fmt: skip + + +def test_고치면_km_곱하기_1000_끝수_없이(): + layout = _read("B08_Quantity/B08_Quantity_Milestone_Layout.ts") + data = {"site": {"mid_station_m": 576.595, "mid_road": "비포장"}, "base": {}, "routes": []} + expr = f'setLeg({json.dumps(data)}, "start", 0, {{ km: 0.3 }}).site.mid_station_m' + assert _node(layout, expr) == 300 + # 안 고침 = km 키 없는 patch → 저장 m 그대로 + expr = f'setLeg({json.dumps(data)}, "start", 0, {{ road: "비포장" }}).site.mid_station_m' + assert _node(layout, expr) == 576.595 + + +def test_모든_km_칸_같은_규칙(): + modal = _read(f"{UI}_Modal.ts") + site = modal[modal.index("export function openSiteInfo") :] + assert "fmtKm(data.site.mid_station_m / 1000)" in site + assert "fmtKm(site.mid_default_m / 1000)" in site + assert "...(text === shown ? {} : { km })" in site # 안 고침 = 덮지 않음 + + leg = _read(f"{UI}_LegModal.ts") + assert "fmtKm(data.site.mid_station_m / 1000)" in leg + assert "fmtKm(site.mid_default_m / 1000)" in leg + assert "...(dist.input.value === shown ? {} : { km: km(dist.input.value) })" in leg + assert "dist.value === shown ? orig : km(dist.value)" in leg # 구간 줄 + assert "fmtKm(leg.km)" in leg and "fmtKm(auto)" in leg + + diagram = _read(f"{UI}_Diagram.ts") + assert "fmtKm(leg.km)" in diagram and "fmtKm(leg.auto_km)" in diagram + assert "km.value === shown" in diagram # 선 위 km 고치기 칸 · 안 고침 = 덮지 않음 + for src in (modal, leg, diagram): + assert "String(leg.km)" not in src and "mid_station_m / 1000)" not in src.replace( + "fmtKm(data.site.mid_station_m / 1000)", "" + ) # 맨 String 변환 없음