Merge remote-tracking branches 'origin/sub_laptop_1', 'origin/sub_laptop_7' and 'origin/sub_laptop_8' into sub_laptop_4
This commit is contained in:
@@ -17,6 +17,7 @@ const STUBS = {
|
||||
showLoadingOverlay: () => calls.overlay++,
|
||||
hideLoadingOverlay: () => {},
|
||||
showToast: () => {},
|
||||
showConfirmDialog: async () => true, // 다시 만들기 확인창(446) — 이 시험은 늘 확인
|
||||
},
|
||||
"./B06_Section_Api_Fetch": {
|
||||
regenerateSections: async (_p, _r, width) => calls.regenerate.push(width),
|
||||
|
||||
@@ -95,15 +95,17 @@ async function toggles(projectId, shift) {
|
||||
const steps = [];
|
||||
const run = async (tag) => {
|
||||
const updated = [];
|
||||
let calls = 0; // 받는 쪽 = 카드 다시 그림 + 종단 · 유토 다시 그림(design_shared) — 한 걸음에 한 번
|
||||
const out = await m.refreshSavedCross(
|
||||
{ projectId, routeId: R, detail, edits: EDITS, alignment },
|
||||
stations,
|
||||
(rows) => updated.push(...rows),
|
||||
(rows) => (calls++, updated.push(...rows)),
|
||||
);
|
||||
steps.push({
|
||||
tag,
|
||||
out: out ? Object.keys(out.byKey).length : null,
|
||||
updated,
|
||||
calls,
|
||||
...pick(target.design),
|
||||
});
|
||||
};
|
||||
@@ -126,6 +128,9 @@ async function toggles(projectId, shift) {
|
||||
m.writeCrossDesignChoice(projectId, R, target.chainage_m, { ...choice });
|
||||
await run(tag);
|
||||
}
|
||||
// 사면 이동 한 걸음(B06 ◀▶ 0.1 m · 깎기 쪽 · 441 · 요구 268-4)
|
||||
m.writeShoulderExtend(projectId, R, target.chainage_m, job.mode.split("_")[0], 0.1);
|
||||
await run("slope");
|
||||
return { target: target.chainage_m, steps };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/* PLAN 439 시험 묶음 진입점 — 저장 노선 cross 차례(카드 단추 → 다시 셈 · 측점 어긋남) · 진짜 모듈 · vite ssr. */
|
||||
export { refreshSavedCross } from "../../A00_Common/design/design_shared";
|
||||
export { writeCrossDesignChoice } from "../../B06_Section/B06_Section_Cross_Design_Session";
|
||||
export {
|
||||
writeCrossDesignChoice,
|
||||
writeShoulderExtend,
|
||||
} from "../../B06_Section/B06_Section_Cross_Design_Session";
|
||||
export { rememberRockBoundaryDefault } from "../../B06_Section/B06_Section_UI_Standard_Panel";
|
||||
export { writeState } from "../../A00_Common/b_page_state";
|
||||
|
||||
@@ -78,4 +78,24 @@ out.move = {
|
||||
same: after.map((s) => byKey.get(s.key) === s),
|
||||
empty: run(null, null, null, null),
|
||||
};
|
||||
// 441 — 초안 없는 저장 노선 = 저장 스냅 설정 따름 · 초안이 있으면 초안 값(스냅 case = cases[1])
|
||||
const snapCase = job.cases[1];
|
||||
const savedRun = (route, saved) =>
|
||||
stage
|
||||
.createStationsStage({ types: () => job.types, saved: () => saved })({
|
||||
generation: 1,
|
||||
range: null,
|
||||
signal: new AbortController().signal,
|
||||
inputs: { route, pipes: job.pipes, structures: job.structures },
|
||||
out: { route: { line: snapCase.line, curves: snapCase.curves, chainage: [], lengthM: 0 } },
|
||||
before: {},
|
||||
prev: null,
|
||||
})
|
||||
.map((s) => s.chainageM);
|
||||
out.savedSnap = {
|
||||
saved: savedRun(null, { station_snap: true, station_snap_step: null }),
|
||||
savedOff: savedRun(null, { station_snap: false }),
|
||||
noPlan: savedRun(null, null),
|
||||
draftWins: savedRun({ stationSnap: false }, { station_snap: true }),
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
|
||||
@@ -128,6 +128,82 @@ const drag = Object.fromEntries(
|
||||
}),
|
||||
);
|
||||
|
||||
// ④ 끌기 훑기(441) — 편집 창 끌기 차례(`_Pointer`) 그대로: 하한 막힘 · 미리보기 측점 밖이 누를 때보다 늘면
|
||||
// 그 걸음 되돌림 · 놓을 때 정본 맞춤(`applyEdit`). 측점 No. · IP 셋 × 12 방향 × 3 거리 · 고정 R(곡선표 반경 못박음).
|
||||
const edits = loadTs(path.join(LOGIC, "B05_Profile_UI_RouteEdit_Edits.ts"));
|
||||
const copy = (c) => ({
|
||||
...c,
|
||||
planned: c.planned.map(([x, y]) => [x, y]),
|
||||
curveRadius: [...c.curveRadius],
|
||||
});
|
||||
function sweep(fixedR) {
|
||||
const start = columns(fit.fitCurveStations(columns(ROUTE), LIMITS, 1));
|
||||
if (fixedR)
|
||||
start.curveRadius = edits
|
||||
.rebuildLine(copy(start), LIMITS)
|
||||
.nodes.map((n) => (n.radius_m === null ? null : Math.round(n.radius_m * 100) / 100));
|
||||
const floor = (built) =>
|
||||
edits.curveShortfalls(built.nodes, LIMITS.limitRadiusM, LIMITS.limitArcM);
|
||||
const off = (built) => fit.offStationCount(built.vertices, built.curves, INTERVAL);
|
||||
const tally = { drags: 0, released: 0, offAfter: 0, strayed: 0, worst: [] };
|
||||
for (const K of [2, 3, 4])
|
||||
for (let deg = 0; deg < 360; deg += 30)
|
||||
for (const reach of [9, 17, 26]) {
|
||||
const hist = copy(start);
|
||||
let st = copy(hist);
|
||||
let built = edits.rebuildLine(st, LIMITS);
|
||||
const offBefore = off(built);
|
||||
let raw = null;
|
||||
let movedOnce = false;
|
||||
const preview = () => {
|
||||
raw = st.planned.map(([x, y]) => [x, y]);
|
||||
st = { ...st, planned: fit.fitDrag(st, hist.planned, K, LIMITS) };
|
||||
built = edits.rebuildLine(st, LIMITS);
|
||||
};
|
||||
const p0 = hist.planned[K];
|
||||
for (let t = 0.5; t <= reach + 1e-9; t += 0.5) {
|
||||
if (raw) raw.forEach((v, i) => (st.planned[i] = v));
|
||||
built = edits.rebuildLine(st, LIMITS);
|
||||
const before = floor(built);
|
||||
const previous = st.planned[K];
|
||||
st.planned[K] = [
|
||||
p0[0] + Math.cos((deg * Math.PI) / 180) * t,
|
||||
p0[1] + Math.sin((deg * Math.PI) / 180) * t,
|
||||
];
|
||||
built = edits.rebuildLine(st, LIMITS);
|
||||
if (edits.shortfallCrossed(before, floor(built))) {
|
||||
st.planned[K] = previous;
|
||||
preview();
|
||||
continue;
|
||||
}
|
||||
preview();
|
||||
if (off(built) > offBefore) {
|
||||
raw.forEach((v, i) => (st.planned[i] = v));
|
||||
st.planned[K] = previous;
|
||||
preview();
|
||||
tally.strayed += 1;
|
||||
continue;
|
||||
}
|
||||
movedOnce = true;
|
||||
}
|
||||
tally.drags += 1;
|
||||
if (!movedOnce) continue;
|
||||
built = edits.rebuildLine(st, LIMITS);
|
||||
if (edits.floorStop(hist, st.planned, built.nodes, LIMITS)) continue;
|
||||
const from = fit.fitStart(hist, st);
|
||||
st = { ...st, planned: fit.fitCurveStations(st, LIMITS, from) };
|
||||
built = edits.rebuildLine(st, LIMITS);
|
||||
tally.released += 1;
|
||||
const left = fit.offStationMcs(built.vertices, built.curves, INTERVAL);
|
||||
if (left.length) {
|
||||
tally.offAfter += 1;
|
||||
if (tally.worst.length < 5) tally.worst.push({ K, deg, reach, mc: left });
|
||||
}
|
||||
}
|
||||
return tally;
|
||||
}
|
||||
const swept = { auto: sweep(false), fixed: sweep(true) };
|
||||
|
||||
// ③ MC 측점 값 · 눈금 글
|
||||
const input = JSON.parse(process.argv[2] || '{"grid":[],"spans":[]}');
|
||||
const snapped = Object.fromEntries(
|
||||
@@ -147,5 +223,12 @@ const labels = {
|
||||
};
|
||||
|
||||
console.log(
|
||||
JSON.stringify({ keys, drag, snapped, labels, scale: [0.1, 1, 10, null].map(frame.mcScale) }),
|
||||
JSON.stringify({
|
||||
keys,
|
||||
drag,
|
||||
swept,
|
||||
snapped,
|
||||
labels,
|
||||
scale: [0.1, 1, 10, null].map(frame.mcScale),
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/* 카드 폭 넘김 다시 만들기 확인창(PLAN 446) 단위검증 헬퍼 — `createSampleWidener` 를 트랜스파일해 돌린다.
|
||||
* 취소 = 재생성 없음 · 오버레이 셈 그대로 · 확인 = 재생성 한 번 · 오버레이 올림 · 내림 짝. 결과는 JSON 한 벌.
|
||||
* 짝 = `test_446_widen_carry.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 calls = { regenerate: [], overlay: 0, asked: [] };
|
||||
let answer = false;
|
||||
const STUBS = {
|
||||
"@ui/ui_template_locale": {
|
||||
currentLanguageIndex: 0,
|
||||
ui_locales: new Proxy({}, { get: (_t, key) => [`${String(key)} {width}/{sampled}`] }),
|
||||
},
|
||||
"@ui/ui_template_elements": {
|
||||
showLoadingOverlay: () => calls.overlay++,
|
||||
hideLoadingOverlay: () => calls.overlay--,
|
||||
showToast: () => {},
|
||||
showConfirmDialog: async (message) => {
|
||||
calls.asked.push(message);
|
||||
return answer;
|
||||
},
|
||||
},
|
||||
"./B06_Section_Api_Fetch": {
|
||||
regenerateSections: async (_p, _r, width) => calls.regenerate.push(width),
|
||||
},
|
||||
"./B06_Section_Section_Store": { loadSectionDetail: async () => ({ cross_sections: [] }) },
|
||||
};
|
||||
|
||||
const file = path.join(ROOT, "B06_Section", "B06_Section_UI_Page_Common.ts");
|
||||
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", "require", js)(box.exports, box, (r) => STUBS[r]);
|
||||
|
||||
(async () => {
|
||||
const widen = box.exports.createSampleWidener({
|
||||
target: () => ({ projectId: "p", routeId: 1 }),
|
||||
sampledHalfWidth: () => 20,
|
||||
applyDetail: () => {},
|
||||
});
|
||||
const out = {};
|
||||
out.within = await widen(15); // 표본 안 = 묻지 않음
|
||||
out.withinAsked = calls.asked.length;
|
||||
out.cancel = await widen(21);
|
||||
out.cancelCalls = calls.regenerate.length;
|
||||
out.cancelOverlay = calls.overlay;
|
||||
answer = true;
|
||||
out.ok = await widen(21);
|
||||
out.okCalls = [...calls.regenerate];
|
||||
out.okOverlay = calls.overlay;
|
||||
out.asked = calls.asked;
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
})();
|
||||
@@ -4,6 +4,7 @@
|
||||
- 저장 노선 cross 차례(`refreshSavedCross`) = 카드 자료(상세 설계)를 **늘** 다시 셈 —
|
||||
복합경사 · 측구 · 지반 단추마다 그 측점 설계선 · 면적이 바뀜.
|
||||
- 묶음 측점 = 상세 측점이면 돌려줌 · 1 mm 넘게 다르면 null(묶음 엔진) — 그래도 카드 자료는 고침.
|
||||
- 사면 이동 한 걸음도 같음 · 걸음마다 받는 쪽(카드 · 종단 · 유토 다시 그림) 한 번(441 · 요구 268-2 · 4).
|
||||
- 서버 폴백(선형 저장분 없음)에도 카드 선택이 실림(열쇠 = 측점 자리) · 서버는 저장분 위에 얹음.
|
||||
짝: `helper_266_card_toggle.mjs` · 진입점 `helper_266_card_toggle_entry.ts` ·
|
||||
지형 · 노선 틀 = `test_346_2_pipeline_cross_page.py`.
|
||||
@@ -99,7 +100,8 @@ def _check_toggles(case: dict, returned) -> None:
|
||||
steps = {step["tag"]: step for step in case["steps"]}
|
||||
assert steps["open"]["cut"] > 0.5 # 깎기 측점
|
||||
before = steps["open"]
|
||||
for tag in ("two_stage", "ditch_off", "soil"):
|
||||
assert all(step["calls"] == 1 for step in case["steps"]) # 걸음마다 다시 그림 한 번(441)
|
||||
for tag in ("two_stage", "ditch_off", "soil", "slope"):
|
||||
step = steps[tag]
|
||||
assert case["target"] in step["updated"], tag # 카드 다시 그림 대상
|
||||
assert step["line"] != before["line"], tag # 카드 설계선 바뀜
|
||||
|
||||
@@ -264,6 +264,17 @@ def test_스냅_측점은_원래_격자_번호(r):
|
||||
assert moved and all(abs(a - b) <= 10 for a, b in moved.values())
|
||||
|
||||
|
||||
def test_초안_없으면_저장_스냅_따름(r):
|
||||
"""441 — 초안 없는 저장 노선 측점 단계 = 저장 station_snap · 초안이 있으면 초안 값."""
|
||||
snapped = [s["chainageM"] for s in r["cases"][1]]
|
||||
plain = [s["chainageM"] for s in r["cases"][0]]
|
||||
assert snapped != plain
|
||||
got = r["savedSnap"]
|
||||
assert got["saved"] == snapped
|
||||
assert got["savedOff"] == plain and got["noPlan"] == plain
|
||||
assert got["draftWins"] == plain
|
||||
|
||||
|
||||
def test_노선_한_점_옮김_앞_구간_열쇠_그대로(r):
|
||||
m = r["move"]
|
||||
from_m = _first_changed_m()
|
||||
|
||||
@@ -78,7 +78,9 @@ def test_중심점_좌우_없음_위아래만(ts):
|
||||
|
||||
def test_배선_걷음():
|
||||
order = (B06 / "B06_Section_UI_Cross_Pick_Order.ts").read_text(encoding="utf-8")
|
||||
assert "layer.append(...surfaces, ...edges, ...lines, ...centers);" in order
|
||||
assert (
|
||||
"layer.append(...rocks, ...surfaces, ...edges, ...lines, ...centers);" in order
|
||||
) # 암 = 판정면 아래(440)
|
||||
for gone in (
|
||||
ROOT / "B05_Profile" / "B05_Profile_UI_RouteEdit_Shift.ts",
|
||||
ROOT / "B05_Profile" / "logic" / "B05_Profile_RouteShift.ts",
|
||||
|
||||
@@ -80,7 +80,17 @@ def test_끄는_동안_MC_가_간격_배수(got, step) -> None:
|
||||
drag = got["drag"][step]
|
||||
assert drag["off"] == 0
|
||||
assert max(drag["residues"]) <= drag["tol"]
|
||||
assert drag["dragMoved"] <= float(step) / 2 + 1e-6 # 끈 IP 는 접선 따라 ±간격/2 안만
|
||||
assert drag["dragMoved"] <= float(step) + 1e-6 # 끈 IP 는 접선 따라 ±간격 안만(441)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ["auto", "fixed"])
|
||||
def test_끌기_훑기_놓은_뒤_측점_밖_0(got, mode) -> None:
|
||||
"""441 — 측점 No. 끌기(IP 셋 × 12 방향 × 3 거리) · 놓은 뒤 MC 가 다 측점 위 · 맞춤이 늘 닿음
|
||||
(반 간격 한도면 18+9 ~ 19+1 에서 못 맞춰 막힘 걸음이 생김)."""
|
||||
swept = got["swept"][mode]
|
||||
assert swept["drags"] == 108 and swept["released"] > 0
|
||||
assert swept["offAfter"] == 0, swept["worst"]
|
||||
assert swept["strayed"] == 0
|
||||
|
||||
|
||||
@pytest.mark.parametrize("step", ["0.1", "1", "10", "20"])
|
||||
@@ -101,6 +111,12 @@ def test_끌기_방향키_편집기_이음() -> None:
|
||||
# 하한 검사는 맞추기 전 노선으로(맞춤이 IP 를 ±간격/2 밀어 10 m · 측점 No. 에서 헛걸림)
|
||||
assert "dragRaw.forEach((vertex, index) => (state.planned[index] = vertex))" in pointer
|
||||
assert "now.raw.forEach((vertex, index) => (state.planned[index] = vertex))" in keys
|
||||
# 미리보기 측점 밖이 누를 때보다 늘면 그 걸음 되돌림 · 막힘 검사 뒤에 옮김 표시(441 · 433-14)
|
||||
assert "params.offStation() > dragOff" in pointer and "params.offStation() > now.off" in keys
|
||||
move = pointer[pointer.index("const previous = state.planned[dragNode];") :]
|
||||
assert move.index("shortfallCrossed(") < move.index("params.offStation() > dragOff")
|
||||
assert move.index("params.offStation() > dragOff") < move.index("dragMoved = true;")
|
||||
assert "offStation: () => (stationSnap ? fit.offStationCount(" in edit
|
||||
assert edit.count("\n") <= 695
|
||||
|
||||
|
||||
@@ -193,6 +209,9 @@ def test_서버_측점_셈이_저장_간격을_씀() -> None:
|
||||
page = (design / "design_pipeline_page.ts").read_text(encoding="utf-8")
|
||||
assert "stationSnapStep: routePlan.station_snap_step" in page
|
||||
stage = (design / "logic" / "design_pipeline_stations.ts").read_text(encoding="utf-8")
|
||||
assert "stationSnapStep: ctx.inputs.route?.stationSnapStep" in stage
|
||||
assert "...snapOptions(ctx.inputs.route, saved?.())" in stage # 초안 · 없으면 저장 노선(441)
|
||||
assert "stationSnapStep: draft.stationSnapStep" in stage
|
||||
assert "stationSnapStep: saved?.station_snap_step" in stage
|
||||
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 "stationSnap: true, stationSnapStep: null" in apply # 추천 = 측점 No.
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
(갈아끼운 캐시)에 모든 카드 번호가 있음 · 찾기 넷(카드 고름 · 목록 → 카드 · 넘김 · 풀기)은 지금 그린 상세.
|
||||
② 평면 패널 빈 곳 누름 = 평면 마커가 고른 것만 풂(`test_409` 의 deselectOther · deselectOwn).
|
||||
③ 바뀜 신호에서 B06 목록도 자리가 바뀌었으면 다시 실음.
|
||||
④ 암 경계선 판정(굵은 투명 선)은 구조물 판정면 아래 — 구조물 아랫부분 누름이 구조물을 고름.
|
||||
TS 셈은 `helper_440_draft_card_ids.cjs`.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_440_cross_draft_pick.py -q
|
||||
@@ -77,3 +78,12 @@ def test_평면_빈_곳_누름은_마커_고름만() -> None:
|
||||
marks = _read("B05_Profile", "B05_Profile_UI_RouteEdit_StructureMarks.ts")
|
||||
assert "deselect: () => planMarks(canvas)?.picked === ownPick && deselect()," in marks
|
||||
assert "ownPick = mark.id;" in marks
|
||||
|
||||
|
||||
def test_암_경계_판정은_구조물_판정면_아래() -> None:
|
||||
order = _read("B06_Section", "B06_Section_UI_Cross_Pick_Order.ts")
|
||||
assert "layer.append(...rocks, ...surfaces, ...edges, ...lines, ...centers);" in order
|
||||
lines = order[order.index("const lines = kids.filter(") :]
|
||||
assert "!el.classList.contains(ROCK_HIT_CLASS)" in lines[: lines.index(");")]
|
||||
pick = _read("B06_Section", "B06_Section_UI_Cross_Design_Pick.ts")
|
||||
assert 'export const ROCK_HIT_CLASS = "b06-chart__rock-hit";' in pick
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
"""446 카드 폭 넘김 다시 만들기 — 표본 폭을 넘겨 종 · 횡단을 다시 만들어도 사용자 값 그대로.
|
||||
|
||||
- 횡단 행: 측점 열쇠(0.01 m 안)로 옛 data(설계 · 메모 · CAD · 상세 표 · 조절) · 상태 · 새 요약 이김
|
||||
- 종단 행: 생성 요약 · 생성 옵션은 새것 · 표준 횡단면 · 유토(풍선) · 계획 입력은 옛것
|
||||
- 화면: 다시 만들기 전 확인창 · 취소 = 재생성 없음(`helper_446_widen_confirm.cjs`)
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from B06_Section.B06_Section_Router_Widen import carried_cross_rows, carried_longitudinal_data
|
||||
|
||||
HERE = Path(__file__).parent
|
||||
ROOT = HERE.parents[1]
|
||||
|
||||
DESIGN = {
|
||||
"ground_type": "soil",
|
||||
"section_mode": "right_cut",
|
||||
"structure_note": "메모",
|
||||
"structure_note_cad": {"lines": [1]},
|
||||
"detail_table": {"rows": 2},
|
||||
"display_half_width_m": 21,
|
||||
"basin_adjust": {"d": 0.3},
|
||||
}
|
||||
|
||||
|
||||
def _old(row_id: int, at: float, design: dict | None = None, status: str = "CONFIRMED") -> dict:
|
||||
data = {"sample_count": 41, "center_z": 100.0, "design": design or DESIGN}
|
||||
return {"id": row_id, "chainage_m": at, "data": json.dumps(data), "status": status}
|
||||
|
||||
|
||||
def _new(row_id: int, at: float) -> dict:
|
||||
return {"id": row_id, "chainage_m": at, "data": json.dumps({"sample_count": 43})}
|
||||
|
||||
|
||||
def test_횡단_행은_측점_열쇠로_설계_상태_그대로() -> None:
|
||||
old = [_old(1, 0.0), _old(2, 20.0), _old(3, 33.456)]
|
||||
new = [_new(11, 0.0), _new(12, 20.0), _new(13, 33.46), _new(14, 40.0)]
|
||||
updates, dropped = carried_cross_rows(old, new)
|
||||
assert dropped == 0
|
||||
by_id = {row_id: (json.loads(data), status) for data, status, row_id in updates}
|
||||
assert set(by_id) == {11, 12, 13} # 40 m = 새 측점 · 옛 값 없음
|
||||
data, status = by_id[13] # 0.004 m 어긋남도 같은 측점
|
||||
assert data["design"] == DESIGN and status == "CONFIRMED"
|
||||
assert data["sample_count"] == 43 and data["center_z"] == 100.0 # 새 요약이 이김 · 옛 칸 남김
|
||||
|
||||
|
||||
def test_같은_측점_둘이면_최신_행_못_옮긴_행은_셈() -> None:
|
||||
newer = {**DESIGN, "structure_note": "새 메모"}
|
||||
old = [_old(1, 20.0), _old(5, 20.0, newer, "DRAFT"), _old(6, 55.0)]
|
||||
updates, dropped = carried_cross_rows(old, [_new(12, 20.0)])
|
||||
data, status, _ = updates[0]
|
||||
assert json.loads(data)["design"]["structure_note"] == "새 메모" and status == "DRAFT"
|
||||
assert dropped == 1 # 55 m — 새 측점에 없음(로그로 알림)
|
||||
|
||||
|
||||
def test_종단_data_는_사용자_키를_옛것으로() -> None:
|
||||
old = {
|
||||
"length_m": 500.0,
|
||||
"options": {"cross_half_width_m": 20, "standard_cross_section": {"road_width_m": 3}},
|
||||
"grade_overrides": {"max_grade_pct": 12},
|
||||
"grade_summary": {"old": 1},
|
||||
"mass_haul": {"balloon_offsets": {"0": [3.0, -4.0]}},
|
||||
}
|
||||
new = {
|
||||
"length_m": 500.0,
|
||||
"options": {"cross_half_width_m": 21, "station_interval_m": 20},
|
||||
"grade_overrides": {},
|
||||
"grade_summary": {"new": 1},
|
||||
}
|
||||
merged = carried_longitudinal_data(old, new)
|
||||
assert merged["options"] == {
|
||||
"cross_half_width_m": 21,
|
||||
"station_interval_m": 20,
|
||||
"standard_cross_section": {"road_width_m": 3},
|
||||
}
|
||||
assert merged["grade_overrides"] == {"max_grade_pct": 12}
|
||||
assert merged["mass_haul"] == {"balloon_offsets": {"0": [3.0, -4.0]}}
|
||||
assert merged["grade_summary"] == {"new": 1} # 계획고 옮김이 다시 씀
|
||||
|
||||
|
||||
def test_반폭_요청은_옮겨_담는_길로() -> None:
|
||||
router = (ROOT / "B06_Section" / "B06_Section_Router.py").read_text(encoding="utf-8")
|
||||
at = router.index("async def regenerate_sections(")
|
||||
body = router[at : router.index("async def regenerate_route_sections(", at)]
|
||||
assert "widen_route_sections(" in body and "regenerate_route_sections(" not in body
|
||||
assert "await carry(connection)" in router # 같은 트랜잭션 안
|
||||
|
||||
|
||||
@pytest.mark.skipif(not (ROOT / "config" / "node_modules").is_dir(), reason="의존성 미설치")
|
||||
def test_다시_만들기_전_확인창_취소면_재생성_없음() -> None:
|
||||
done = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", str(HERE / "helper_446_widen_confirm.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
)
|
||||
assert done.returncode == 0, done.stderr
|
||||
out = json.loads(done.stdout)
|
||||
assert out["within"] is True and out["withinAsked"] == 0
|
||||
assert out["cancel"] is False and out["cancelCalls"] == 0 and out["cancelOverlay"] == 0
|
||||
assert out["ok"] is True and out["okCalls"] == [21] and out["okOverlay"] == 0
|
||||
assert out["asked"][0].endswith("21/20") # 요청 폭 · 표본 폭이 글에 들어감
|
||||
Reference in New Issue
Block a user