From 7827625ba4551fe79976560dc57013fde61a133c Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 17 Oct 2026 18:48:37 +0900 Subject: [PATCH 1/2] =?UTF-8?q?=EF=BB=BFfix(B05=20=ED=8F=89=EB=A9=B4=20?= =?UTF-8?q?=EB=8F=84=EA=B5=AC=EC=A4=84):=20=EC=8A=A4=EB=83=85=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=A4=84=20=EA=B8=80=20=3D=20=EA=B3=A0=EB=A5=B8=20?= =?UTF-8?q?=EA=B0=84=EA=B2=A9=20=EB=94=B0=EB=9D=BC=20=E2=80=94=20=EC=B8=A1?= =?UTF-8?q?=EC=A0=90=20No.=20=3D=20=EC=9D=BC=EB=B0=98=EC=B8=A1=EC=A0=90=20?= =?UTF-8?q?=C2=B7=200.1=20=C2=B7=201=20=C2=B7=2010=20m=20=3D=20=EB=B0=B0?= =?UTF-8?q?=EC=88=98=20(390-4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD --- B05_Profile/B05_Profile_UI_RouteEdit.ts | 4 +- .../B05_Profile_UI_RouteEdit_SnapStep.ts | 16 ++++- resources/tester/test_390_4_snap_step_text.py | 63 +++++++++++++++++++ 3 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 resources/tester/test_390_4_snap_step_text.py diff --git a/B05_Profile/B05_Profile_UI_RouteEdit.ts b/B05_Profile/B05_Profile_UI_RouteEdit.ts index e74c3bf2e..641dd9c37 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit.ts @@ -263,7 +263,7 @@ export async function mountRouteEditor( const showMinorContours = bindMinorContourToggle(minorContourButton, () => draw()); const snapStepM = bindStationSnap(chrome, projectId, stationIntervalM, (toggle) => { if (toggle) setStationSnap(!stationSnap); - if (stationSnap && history) applyEdit("곡선 가운데(MC)를 일반측점에 맞췄습니다."); // 전체 · 한 걸음 + if (stationSnap && history) applyEdit(snapStepM.text()); // 전체 · 한 걸음 draw(); }); /** 보던 화면 기억(계획서 18 ③) — 자료를 읽고 화면을 되살린 **뒤부터** 적는다. */ @@ -535,7 +535,7 @@ export async function mountRouteEditor( curveHint, contourHint, commit: () => { - if (stationSnap) return applyEdit("곡선 가운데(MC)를 일반측점에 맞췄습니다."); // 놓을 때(366-3) + if (stationSnap) return applyEdit(snapStepM.text()); // 놓을 때(366-3) history?.commit(snapshotNow()); historyControls.sync(); }, diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_SnapStep.ts b/B05_Profile/B05_Profile_UI_RouteEdit_SnapStep.ts index 2520e30f1..c149ef32f 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_SnapStep.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_SnapStep.ts @@ -5,6 +5,7 @@ * 간격 = 0.1 m · 1 m · 10 m · 측점 No.(편집 창 측점 간격 · 기본) — 곡선 MC 를 그 간격 배수에 맞춤. * 고른 값은 프로젝트별 화면 기억(pref `routeedit-snap-step`) · [저장] 대상 아님(서버 · 추천은 측점 No. 그대로). * 스냅 끔이면 고르기는 흐림(CSS) · 고를 수는 있음. + * 상태줄 글은 고른 간격 따라(390-4) — 측점 No. = 「일반측점에」 · 그 밖 = 「1 m 배수에」 꼴. * ========================================================================== */ import { readState, writeState } from "../A00_Common/b_page_state"; @@ -18,16 +19,24 @@ export function paintStationSnap(button: HTMLButtonElement, on: boolean): void { button.setAttribute("aria-pressed", String(on)); } +/** 스냅 뒤 상태줄 글 — `step` = 고르기 값("0.1" · "1" · "10" · "station"). */ +export function snapFitText(step: string): string { + const where = step === "station" ? "일반측점" : `${step} m 배수`; + return `곡선 가운데(MC)를 ${where}에 맞췄습니다.`; +} + +export type SnapStep = { (): number; text: () => string }; + /** * 단추 · 고르기 배선 — `onChange(true)` = 단추 누름(켬끔 바꿈) · `onChange(false)` = 간격 바꿈. - * 돌려주는 함수 = 지금 스냅 간격(m). + * 돌려주는 함수 = 지금 스냅 간격(m) · `.text()` = 스냅 뒤 상태줄 글. */ export function bindStationSnap( chrome: Pick, projectId: string, stationIntervalM: number, onChange: (toggle: boolean) => void, -): () => number { +): SnapStep { const select = chrome.overlay.querySelector('[data-act="snap-step"]')!; const saved = readState("routeedit-snap-step", projectId); select.value = saved && STEPS.includes(saved) ? saved : "station"; @@ -36,5 +45,6 @@ export function bindStationSnap( onChange(false); }); chrome.stationSnapButton.addEventListener("click", () => onChange(true)); - return () => (select.value === "station" ? stationIntervalM : Number(select.value)); + const stepM = () => (select.value === "station" ? stationIntervalM : Number(select.value)); + return Object.assign(stepM, { text: () => snapFitText(select.value) }); } diff --git a/resources/tester/test_390_4_snap_step_text.py b/resources/tester/test_390_4_snap_step_text.py new file mode 100644 index 000000000..14c7ace46 --- /dev/null +++ b/resources/tester/test_390_4_snap_step_text.py @@ -0,0 +1,63 @@ +"""390-4 스냅 상태줄 글 = 고른 간격 따라 — `B05_Profile_UI_RouteEdit_SnapStep.ts` 의 `snapFitText` +를 Node 로 돌려 간격별 글 판정 + 편집기 두 자리(전체 · 놓을 때)가 그 글을 씀. + + ./venv/Scripts/python.exe -m pytest resources/tester/test_390_4_snap_step_text.py -q +""" + +import json +import subprocess +from pathlib import Path + +import pytest + +ROOT = Path(__file__).resolve().parents[2] +B05 = ROOT / "B05_Profile" +SNAP = B05 / "B05_Profile_UI_RouteEdit_SnapStep.ts" +TS = ROOT / "config" / "node_modules" / "typescript" +STEPS = ["0.1", "1", "10", "station"] + +NODE = """ +const fs = require("fs"); +const ts = require(process.argv[1]); +const js = ts.transpileModule(fs.readFileSync(process.argv[2], "utf8"), { + compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, +}).outputText; +const box = { exports: {} }; +new Function("exports", "module", "require", js)(box.exports, box, () => ({})); +const steps = JSON.parse(process.argv[3]); +console.log(JSON.stringify(Object.fromEntries(steps.map((s) => [s, box.exports.snapFitText(s)])))); +""" + + +@pytest.fixture(scope="module") +def texts() -> dict: + run = subprocess.run( # noqa: S603 — 고정 실행 파일 + ["node", "-e", NODE, str(TS), str(SNAP), json.dumps(STEPS)], + capture_output=True, + text=True, + encoding="utf-8", + timeout=60, + ) + assert run.returncode == 0, run.stderr + return json.loads(run.stdout) + + +@pytest.mark.parametrize( + ("step", "want"), + [ + ("station", "곡선 가운데(MC)를 일반측점에 맞췄습니다."), + ("0.1", "곡선 가운데(MC)를 0.1 m 배수에 맞췄습니다."), + ("1", "곡선 가운데(MC)를 1 m 배수에 맞췄습니다."), + ("10", "곡선 가운데(MC)를 10 m 배수에 맞췄습니다."), + ], +) +def test_간격별_상태줄_글(texts, step, want) -> None: + assert texts[step] == want + + +def test_편집기는_고른_간격_글을_씀() -> None: + edit = (B05 / "B05_Profile_UI_RouteEdit.ts").read_text(encoding="utf-8") + assert "일반측점에 맞췄" not in edit + assert edit.count("applyEdit(snapStepM.text())") == 2 + snap = SNAP.read_text(encoding="utf-8") + assert "text: () => snapFitText(select.value)" in snap From d1c863c05a5acc75da020808baaf84eba202c436 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sat, 17 Oct 2026 18:52:16 +0900 Subject: [PATCH 2/2] =?UTF-8?q?test(152-3):=20394-3=20=EC=84=B1=ED=86=A0?= =?UTF-8?q?=EB=A7=8C=20=EC=B8=A1=EC=A0=90=20=EA=B8=B0=EB=8C=80=20=EA=B3=A0?= =?UTF-8?q?=EC=B9=A8=20=E2=80=94=20371-1=20=EC=9D=98=EB=8F=84=EB=90=9C=20?= =?UTF-8?q?=EB=B0=94=EB=80=9C(=EB=B0=B0=EA=B4=80=2085.052=20=EB=81=9D=20?= =?UTF-8?q?=EA=B5=AC=EA=B0=84=20=EB=AC=BB=ED=9E=98=20=E2=86=92=20=EC=84=B1?= =?UTF-8?q?=ED=86=A0=20+=203=EB=8F=84=20=EC=A0=88=ED=86=A0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 까닭 = 6ba26ee61(371-1 다단 사이 시작 = 윗단 발끝) · 그 앞 커밋 트리에서 helper 그대로 = fill · fill / 띠 2 · 2 - 아랫단 0.5 m 내려가 끝 구간 시작이 원지반 아래 → 152-3 3도 절토선 · 관 거름 띠 2 그대로 · 성토만 거름 1 - 제품 코드 고침 없음 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf --- resources/tester/test_152_3_chute_cut.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/resources/tester/test_152_3_chute_cut.py b/resources/tester/test_152_3_chute_cut.py index e3870b7c6..5304d40e7 100644 --- a/resources/tester/test_152_3_chute_cut.py +++ b/resources/tester/test_152_3_chute_cut.py @@ -1,6 +1,7 @@ """152-3 도수로 · 돌붙임 띠 — 묻힌 유출 끝 3도 절토선(내림 · 수평)에도 띠 · 오르막 복합경사 절토엔 없음. 화면 TS 를 그 자리에서 돌린 값(`helper_152_3_chute_cut.cjs`) · B06 관 · 흙막이 띠와 3D 띠가 같은 거름. +배관 85.052 = 371-1 뒤 아랫단 0.5 m 내려가 끝 구간이 묻힘 → 성토 + 3도 절토(의도된 바뀜 · 394-3). """ import json @@ -48,7 +49,9 @@ def test_배관_묻힌_끝_내림이면_띠_오르막이면_없음(out: dict) -> def test_성토만인_측점은_그대로(out: dict) -> None: base = out["pipeBase"] - assert base["bands3d"] == base["bands3dFillOnly"] == 2 + assert [s["kind"] for s in base["segments"]] == ["fill", "cut"] + # 성토 조각 띠는 두 거름 같음 · 3도 절토 조각만 관 거름이 더함 + assert base["bands3d"] == 2 and base["bands3dFillOnly"] == 1 def test_거름_한_벌_배선() -> None: