Merge remote-tracking branches 'origin/sub_laptop_1', 'origin/sub_laptop_2', 'origin/sub_laptop_3', 'origin/sub_laptop_5' and 'origin/sub_laptop_7' into main_laptop_1
This commit is contained in:
@@ -46,11 +46,14 @@ if (process.argv[2] === "road") {
|
||||
same: spec === road.PAVEMENT_SPEC,
|
||||
containers: spec.containers.map((c) => [c.id, c.title, c.required, c.link]),
|
||||
choices: road.PAVE_SPEC_CHOICES.map((c) => c.value),
|
||||
items: spec.containers[0].items.map((i) => [i.id, i.label, i.kind]),
|
||||
state,
|
||||
edited: s.writeOptions(spec, edited),
|
||||
cleared: s.writeOptions(spec, cleared),
|
||||
keys: s.specKeys(spec),
|
||||
retired: spec.retired,
|
||||
dropped: spec.dropped,
|
||||
byRange: spec.byRange,
|
||||
}),
|
||||
);
|
||||
process.exit(0);
|
||||
|
||||
@@ -65,6 +65,8 @@ function pipeFit(edit) {
|
||||
const { ford, culvert } = computeStoredLayouts(section, list);
|
||||
const [right, left] = ford.pipe.outer.slice(2);
|
||||
const ends = { 1: left.offset, [-1]: right.offset };
|
||||
const [topLeft, topRight] = ford.pipe.outer;
|
||||
const tops = { 1: topLeft, [-1]: topRight };
|
||||
const slab = ford.slabBridge.map((p) => p.offset);
|
||||
const slabEnd = { 1: Math.max(...slab), [-1]: Math.min(...slab) };
|
||||
return {
|
||||
@@ -80,6 +82,13 @@ function pipeFit(edit) {
|
||||
role: side.role,
|
||||
guard: true,
|
||||
pipeGap: r3(ends[side.outward] - front),
|
||||
// 관 윗모서리 ↔ 그 높이의 흙막이 전면(사면 이용 · 89-1)
|
||||
topGap: r3(
|
||||
tops[side.outward].offset -
|
||||
(wall.points[2].offset +
|
||||
wall.outward * wall.lean * (wall.topJoint.elevation - tops[side.outward].elevation)),
|
||||
),
|
||||
lean: wall.lean,
|
||||
slabGap: r3(slabEnd[side.outward] - wall.bottomBack.offset),
|
||||
};
|
||||
}),
|
||||
|
||||
@@ -36,7 +36,15 @@ def test_명세_등록_필수_컨테이너_하나(out):
|
||||
|
||||
|
||||
def test_포장사양_선택지는_명세_한_곳(out):
|
||||
assert out["choices"] == ["콘크리트"]
|
||||
assert out["choices"] == ["콘크리트", "아스콘", "쇄석"]
|
||||
|
||||
|
||||
def test_상세내용_차례_포장사양_길이_출력_높이(out):
|
||||
assert out["items"] == [
|
||||
["spec", "포장사양", "select"],
|
||||
["length", "길이 (m)", "auto"],
|
||||
["height", "높이 (m)", "number"],
|
||||
]
|
||||
|
||||
|
||||
def test_저장_키_왕복_옛_칸은_명세_밖(out):
|
||||
@@ -44,7 +52,10 @@ def test_저장_키_왕복_옛_칸은_명세_밖(out):
|
||||
assert out["edited"] == {"pave_spec": "콘크리트", "height_m": 0.35}
|
||||
assert out["cleared"] == {"pave_spec": "콘크리트"}
|
||||
assert out["keys"] == {"keys": ["pave_spec", "height_m"], "prefixes": []}
|
||||
assert "length_m" not in out["retired"] # 구간 길이 · 전 · 후는 정의 칸 그대로
|
||||
# 길이 · 기준측점 전/후는 입력 없음 — 시작 · 종료만 받고 옛 값은 저장 때 뺌(87-1)
|
||||
assert out["byRange"] is True
|
||||
assert out["dropped"] == ["length_m", "before_m", "after_m"]
|
||||
assert "length_m" not in out["retired"]
|
||||
assert set(out["retired"]) == {
|
||||
"thickness_cm",
|
||||
"width_m",
|
||||
@@ -62,4 +73,7 @@ def test_기타_상세_종류_이름_글_칸():
|
||||
name = next(o for o in etc["options"] if o["key"] == "name")
|
||||
assert name["label"] == "상세 종류 이름" and name["input"] == "text"
|
||||
pave = next(t for t in doc["types"] if t["type_id"] == "pavement_concrete")
|
||||
assert [o["key"] for o in pave["options"]][:3] == ["length_m", "before_m", "after_m"]
|
||||
keys = [o["key"] for o in pave["options"]]
|
||||
assert not {"length_m", "before_m", "after_m"} & set(keys) # 87-1 시작 · 종료 직접 입력
|
||||
height = next(o for o in pave["options"] if o["key"] == "height_m")
|
||||
assert height["default"] == 0.2
|
||||
|
||||
@@ -47,6 +47,17 @@ def test_관은_흙막이_전면까지_바닥판은_배면까지(fit: dict) -> N
|
||||
assert side["slabGap"] == pytest.approx(0, abs=1e-3)
|
||||
|
||||
|
||||
def test_관_윗모서리도_흙막이_전면_사면에(fit: dict) -> None:
|
||||
"""89-1 — 흙막이 사면 이용: 윗모서리가 그 높이 전면 자리라 벽 밖으로 안 튀어나감."""
|
||||
leaned = 0
|
||||
for case in (fit[k] for k in ("both", "inletPlus1", "outletPlus2", "inletOnly")):
|
||||
for side in case["sides"]:
|
||||
if side["guard"]:
|
||||
assert side["topGap"] == pytest.approx(0, abs=1e-3)
|
||||
leaned += side["lean"] > 0
|
||||
assert leaned
|
||||
|
||||
|
||||
def test_흙막이를_옮기면_관이_그만큼_따라감(fit: dict) -> None:
|
||||
base = fit["both"]["lengthM"]
|
||||
assert fit["inletPlus1"]["lengthM"] == pytest.approx(base + 1, abs=1e-3)
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
"""노면공 포장 제원(88) — 파이썬·TS 짝 거울 시험.
|
||||
|
||||
`common_util/common_util_structure_pavement.py`(서버 · 저장분) 와 `.ts`(브라우저 · 초안)가
|
||||
같은 입력에 같은 `section.pavement` 를 내는지 대조. TS 는 프로젝트 tsc 로 옮겨 실제 코드 그대로 돌림.
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
TSC = PROJECT_ROOT / "config" / "node_modules" / "typescript" / "bin" / "tsc"
|
||||
|
||||
from common_util.common_util_structure_pavement import ( # noqa: E402
|
||||
attach_pavement_specs,
|
||||
pavement_specs_from,
|
||||
)
|
||||
|
||||
STRUCTURES = [
|
||||
{
|
||||
"type_id": "pavement_concrete",
|
||||
"placement": "interval",
|
||||
"chainage_m": None,
|
||||
"start_m": 100.0,
|
||||
"end_m": 160.0,
|
||||
"options": {"pave_spec": "콘크리트", "height_m": 0.25, "widening_off": "120-140, x-1,,"},
|
||||
},
|
||||
{
|
||||
# 뒤집힌 구간 · 두께 빔 · 사양 빔 — 두 벌 다 작은 값을 시작으로 · null 로.
|
||||
"type_id": "gravel_surfacing",
|
||||
"placement": "interval",
|
||||
"start_m": 150.0,
|
||||
"end_m": 130.0,
|
||||
"options": {"pave_spec": "", "height_m": "0.3"},
|
||||
},
|
||||
{"type_id": "pavement_concrete", "start_m": None, "end_m": None, "options": {}},
|
||||
{"type_id": "masonry_wet", "start_m": 0.0, "end_m": 500.0, "options": {}},
|
||||
{
|
||||
"type_id": "pavement_concrete",
|
||||
"chainage_m": 300.0,
|
||||
"options": {"pave_spec": "아스콘", "height_m": 0.05, "widening_off": "290-310"},
|
||||
},
|
||||
]
|
||||
TYPE_IDS = ["pavement_concrete", "gravel_surfacing"]
|
||||
CHAINAGES = [99.97, 100.0, 119.99, 130.0, 145.0, 160.02, 160.05, 300.0, 400.0]
|
||||
|
||||
_RUNNER = """
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { attachPavementSpecs, pavementSpecsFrom } from "./common_util_structure_pavement.js";
|
||||
const input = JSON.parse(readFileSync(process.argv[2], "utf8"));
|
||||
const sections = input.chainages.map((chainage_m) => ({ chainage_m }));
|
||||
attachPavementSpecs(sections, pavementSpecsFrom(input.structures, new Set(input.type_ids)));
|
||||
writeFileSync(process.argv[3], JSON.stringify(sections));
|
||||
"""
|
||||
|
||||
|
||||
def _python_result() -> list[dict]:
|
||||
sections = [{"chainage_m": value} for value in CHAINAGES]
|
||||
attach_pavement_specs(sections, pavement_specs_from(STRUCTURES, set(TYPE_IDS)))
|
||||
return sections
|
||||
|
||||
|
||||
def _ts_result(tmp_path: Path) -> list[dict]:
|
||||
out = tmp_path / "js"
|
||||
subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
[
|
||||
"node",
|
||||
str(TSC),
|
||||
str(PROJECT_ROOT / "common_util" / "common_util_structure_pavement.ts"),
|
||||
"--outDir",
|
||||
str(out),
|
||||
"--module",
|
||||
"esnext",
|
||||
"--target",
|
||||
"es2022",
|
||||
"--moduleResolution",
|
||||
"bundler",
|
||||
"--ignoreConfig",
|
||||
],
|
||||
cwd=str(PROJECT_ROOT),
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
(out / "runner.mjs").write_text(_RUNNER, encoding="utf-8")
|
||||
payload = tmp_path / "input.json"
|
||||
result = tmp_path / "output.json"
|
||||
payload.write_text(
|
||||
json.dumps({"structures": STRUCTURES, "type_ids": TYPE_IDS, "chainages": CHAINAGES}),
|
||||
encoding="utf-8",
|
||||
)
|
||||
subprocess.run( # noqa: S603
|
||||
["node", str(out / "runner.mjs"), str(payload), str(result)],
|
||||
cwd=str(PROJECT_ROOT),
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
return json.loads(result.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def test_pavement_specs_mirror():
|
||||
with tempfile.TemporaryDirectory() as workdir:
|
||||
ts = _ts_result(Path(workdir))
|
||||
py = _python_result()
|
||||
assert ts == py
|
||||
by = {section["chainage_m"]: section.get("pavement") for section in py}
|
||||
# 범위 밖 · 경계 오차(0.02 m) 안
|
||||
assert by[99.97] is None and by[160.05] is None and by[400.0] is None
|
||||
assert [entry["type_id"] for entry in by[100.0]] == ["pavement_concrete"]
|
||||
assert by[160.02][0]["thickness_m"] == 0.25
|
||||
# 확폭 끈 구간 120~140 (±0.02) — 끔 · 못 읽는 조각은 버림
|
||||
assert by[119.99][0]["widening_on"] is False
|
||||
assert by[100.0][0]["widening_on"] is True
|
||||
# 겹친 범위 = 둘 다 · 뒤집힌 구간 정렬 · 문자 두께 · 빈 사양 = null
|
||||
assert [entry["type_id"] for entry in by[145.0]] == ["pavement_concrete", "gravel_surfacing"]
|
||||
gravel = by[145.0][1]
|
||||
assert (gravel["start_m"], gravel["end_m"]) == (130.0, 150.0)
|
||||
assert gravel["thickness_m"] is None and gravel["spec"] is None
|
||||
# 기준 측점만 있는 옛 항목 = 그 측점 한 점
|
||||
assert by[300.0] == [
|
||||
{
|
||||
"type_id": "pavement_concrete",
|
||||
"spec": "아스콘",
|
||||
"thickness_m": 0.05,
|
||||
"start_m": 300.0,
|
||||
"end_m": 300.0,
|
||||
"widening_on": False,
|
||||
}
|
||||
]
|
||||
@@ -66,11 +66,14 @@ def test_알약은_전체_편집기와_같은_부품():
|
||||
def test_저장_안_한_카드_고침_물음():
|
||||
struct = _text(M02 / "M02_MasterTemplete_Structure.ts")
|
||||
show = struct.split("const show = ")[1].split("const pickPrice")[0]
|
||||
assert "cards.dirty()" in show and "showConfirmDialog(TEXT.discardCards)" in show
|
||||
assert "cards.dirty()" in show and "leaveOrStay(TEXT.discardCards," in show
|
||||
assert "opts.keepColumn?.(from)" in show
|
||||
main = _text(M02 / "M02_MasterTemplete_UI_Main.ts")
|
||||
assert "const busy = (): boolean => dirty || !!panel?.dirty() || toDrawing.waiting();" in main
|
||||
assert main.count("busy()") >= 3 # 떠나기 · 새로고침 막기 · 나무 고침(settle)
|
||||
assert main.count("busy()") >= 2 # 떠나기 · 새로고침 막기
|
||||
assert (
|
||||
'askUnsaved(L("M02_TreeUnsaved"))' in main
|
||||
) # 나무 고침(settle)도 같은 물음 · 카드 저장 실패까지 봄
|
||||
assert main.index("await panel.saveCards();") < main.index("await panel.reload();")
|
||||
assert "keepColumn: (colId) => sheet?.selectColumn(colId)" in main
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ def test_메뉴는_숨기고_저장은_도면_칸만():
|
||||
def test_저장_안_한_카드_고침은_물음():
|
||||
cards = _text("M02_MasterTemplete_ItemCards.ts")
|
||||
# 물음 한 번(31-7) — 어느 카드든(도면 · 엑셀) 고침이 있으면 · 틀의 저장 표시(saver)로 봄
|
||||
assert "if (!again && anyDirty() && col?.id !== column && !(await showConfirmDialog(" in cards
|
||||
assert "(await leaveOrStay(TEXT.discard, { save: saveAll, stillDirty: anyDirty })) === \"stay\"" in cards
|
||||
assert "const anyDirty = (): boolean => cards.some((c) => c.saver.dirty());" in cards
|
||||
assert "dirty: anyDirty," in cards
|
||||
assert "dirty: () => cards.dirty()," in _text("M02_MasterTemplete_Structure.ts")
|
||||
|
||||
@@ -70,6 +70,9 @@ const kind = { 이름: "종류", 고르기: ["막돌", "견치돌"] };
|
||||
const size = { 이름: "규격", 고르기: [25, 40] };
|
||||
const area = { 이름: "지역" };
|
||||
const inputs = [height, kind, size, area];
|
||||
const machine = {
|
||||
이름: "기계", 고르기: ["0201-0060", "9999"], 고르기이름: { "0201-0060": "굴착기(무한궤도) 0.6㎥" },
|
||||
};
|
||||
console.log(JSON.stringify({
|
||||
stored: [
|
||||
m.storedOf(height, ""), m.storedOf(height, "2.5"), m.storedOf(kind, "막돌"),
|
||||
@@ -82,6 +85,11 @@ console.log(JSON.stringify({
|
||||
known: m.keepKnown(inputs, { 높이: 2, 종류: "", 없앤것: 3 }),
|
||||
line: m.valueLine(inputs, { 높이: 2, 규격: 40 }),
|
||||
lineEmpty: m.valueLine(inputs, {}),
|
||||
named: [
|
||||
m.choicesOf(machine).map((c) => [c.값, c.이름]), m.valueHint(machine),
|
||||
m.valueLine([machine], { 기계: "0201-0060" }).text, m.labelOf(machine, "9999"),
|
||||
m.storedOf(machine, "0201-0060"),
|
||||
],
|
||||
}));
|
||||
"""
|
||||
|
||||
@@ -100,6 +108,27 @@ def test_값_셈() -> None:
|
||||
assert got["known"] == {"kept": {"높이": 2}, "lost": ["없앤것"]}
|
||||
assert got["line"] == {"text": "높이 2m · 규격 40", "empty": 2} # 종류 · 지역 = 자동값 없음
|
||||
assert got["lineEmpty"] == {"text": "", "empty": 3} # 높이는 자동값이 있어 빈 값 아님
|
||||
# 85-1 — 보이는 글 = 이름 규격 · 모르는 번호는 그대로 · 저장 값은 번호
|
||||
assert got["named"] == [
|
||||
[["0201-0060", "굴착기(무한궤도) 0.6㎥"], ["9999", "9999"]],
|
||||
"굴착기(무한궤도) 0.6㎥ / 9999",
|
||||
"기계 굴착기(무한궤도) 0.6㎥",
|
||||
"9999",
|
||||
"0201-0060",
|
||||
]
|
||||
|
||||
|
||||
def test_고르기_원문번호에_이름표(client: TestClient) -> None:
|
||||
"""85-1 — 기계 · 집게 = 「이름 규격」 · 직종(입력 이름에 「직종」) = 직종 이름 · 고르기는 번호 그대로."""
|
||||
got = client.get("/api/m02/logic-lib/inputs", params={"keys": "GC000260"}).json()
|
||||
specs = {i["이름"]: i for i in got["로직"][0]["입력"]}
|
||||
assert specs["기계"]["고르기이름"]["0201-0060"] == "굴착기(무한궤도) 0.6㎥"
|
||||
assert specs["집게"]["고르기이름"]["7206-0020"] == "부착용 집게 0.2㎥"
|
||||
assert "0201-0060" in specs["기계"]["고르기"]
|
||||
assert "고르기이름" not in specs["뒷길이"] and "고르기이름" not in specs["지역"]
|
||||
whole = labor_inputs.m01.loaded()[1]
|
||||
job = {"이름": "조종원직종", "고르기": ["1048", "없는번호"]}
|
||||
assert labor_inputs._labels(whole, job) == {"1048": "건설기계운전사"}
|
||||
|
||||
|
||||
ROWS_SCRIPT = r"""
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
"""저장 안 한 고침 3단추 물음(PLAN 86-1 · `ui_template_leave_dialog.ts`) — Node 로 TS 를 돌려 판정.
|
||||
|
||||
못박는 것
|
||||
- 단추 순서 [취소] [버리고 넘어감] [저장 후 넘어감] · 기본 초점 = 취소 · Esc = 취소.
|
||||
- 저장 후 넘어감: 저장 성공 + 고침 없음 = saved / 저장 던짐 · 고침 남음 = stay(머묾).
|
||||
- 버리고 넘어감: discard 갈고리 호출 · 취소: 아무것도 안 부름.
|
||||
- M02 자리(UI_Main · Structure · ItemCards)가 이 물음을 씀.
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
M02 = ROOT / "M02_MasterTemplete"
|
||||
SCRIPT = r"""
|
||||
const fs = require("fs");
|
||||
const ts = require(process.argv[1] + "/config/node_modules/typescript");
|
||||
const src = fs.readFileSync(process.argv[1] + "/ui_template/ui_template_leave_dialog.ts", "utf8");
|
||||
const js = ts.transpileModule(src, {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
let focused = null;
|
||||
let overlay = null;
|
||||
let buttons = [];
|
||||
const node = (tag, o = {}) => ({
|
||||
tag, ...o, kids: [], on: {},
|
||||
append(...x) { this.kids.push(...x); },
|
||||
addEventListener(k, f) { this.on[k] = f; },
|
||||
remove() { overlay = null; },
|
||||
});
|
||||
const stubs = {
|
||||
"./ui_template_elements": {
|
||||
el: node,
|
||||
createButton: (o) => {
|
||||
const b = node("button", { label: o.label, variant: o.variant });
|
||||
b.focus = () => { focused = b.label; };
|
||||
b.click = o.onClick;
|
||||
buttons.push(b);
|
||||
return b;
|
||||
},
|
||||
},
|
||||
"./ui_template_locale": { t: (k) => k },
|
||||
};
|
||||
globalThis.document = { activeElement: null, body: { append(x) { overlay = x; } } };
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", "require", js)(box.exports, box, (n) => stubs[n] ?? require(n));
|
||||
const { leaveOrStay } = box.exports;
|
||||
|
||||
const run = async (pick, hooks) => {
|
||||
buttons = [];
|
||||
focused = null;
|
||||
const calls = [];
|
||||
const wrap = {
|
||||
save: async () => { calls.push("save"); if (hooks.throws) throw new Error("x"); },
|
||||
stillDirty: () => !!hooks.left,
|
||||
discard: () => calls.push("discard"),
|
||||
};
|
||||
const p = leaveOrStay("msg", wrap);
|
||||
const labels = buttons.map((b) => b.label);
|
||||
const first = focused;
|
||||
if (pick === "esc") overlay.on.keydown({ key: "Escape", stopPropagation() {} });
|
||||
else buttons[pick].click();
|
||||
return { got: await p, calls, labels, first };
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const out = {
|
||||
cancel: await run(0, {}),
|
||||
esc: await run("esc", {}),
|
||||
discard: await run(1, {}),
|
||||
save: await run(2, {}),
|
||||
saveThrows: await run(2, { throws: true }),
|
||||
saveLeft: await run(2, { left: true }),
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
})();
|
||||
"""
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def got() -> dict:
|
||||
done = subprocess.run(
|
||||
["node", "-e", SCRIPT, str(ROOT)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
check=True,
|
||||
)
|
||||
return json.loads(done.stdout)
|
||||
|
||||
|
||||
def test_단추_순서_기본_초점_취소(got):
|
||||
row = got["cancel"]
|
||||
assert row["labels"] == ["M02_TreeCancel", "M02_TreeDiscardGo", "M02_TreeSaveGo"]
|
||||
assert row["first"] == "M02_TreeCancel"
|
||||
|
||||
|
||||
def test_취소_Esc는_머묾(got):
|
||||
assert got["cancel"]["got"] == "stay" and got["cancel"]["calls"] == []
|
||||
assert got["esc"]["got"] == "stay" and got["esc"]["calls"] == []
|
||||
|
||||
|
||||
def test_버리고_넘어감(got):
|
||||
assert got["discard"]["got"] == "discarded"
|
||||
assert got["discard"]["calls"] == ["discard"]
|
||||
|
||||
|
||||
def test_저장_후_넘어감과_실패_머묾(got):
|
||||
assert got["save"] == {**got["save"], "got": "saved", "calls": ["save"]}
|
||||
assert got["saveThrows"]["got"] == "stay" # 저장 던짐
|
||||
assert got["saveLeft"]["got"] == "stay" # 저장 뒤에도 고침이 남음(카드 저장 실패 등)
|
||||
|
||||
|
||||
def test_M02_자리가_3단추_물음을_씀():
|
||||
main = (M02 / "M02_MasterTemplete_UI_Main.ts").read_text(encoding="utf-8")
|
||||
assert "leaveOrStay(message, { save: doSave, stillDirty: busy" in main
|
||||
assert "askSettle" not in main
|
||||
assert "!(await showConfirmDialog(L(" not in main
|
||||
cards = (M02 / "M02_MasterTemplete_ItemCards.ts").read_text(encoding="utf-8")
|
||||
assert "leaveOrStay(TEXT.discard" in cards
|
||||
struct = (M02 / "M02_MasterTemplete_Structure.ts").read_text(encoding="utf-8")
|
||||
assert struct.count("leaveOrStay(TEXT.discardCards") == 2
|
||||
@@ -216,3 +216,11 @@ def test_측점이_없어진_손_값은_맨_아래_비고_줄로(project: Path)
|
||||
assert last["값"] == {"sta": "NO.27+15", "h_intake": 1, "memo": "설계에서 측점 없어짐"}
|
||||
kept = fill.strip_design(doc)
|
||||
assert kept["줄"][-1]["값"]["h_intake"] == 1 # 다음 저장에도 남음
|
||||
|
||||
|
||||
def test_집계_길이는_구간형이면_시작_종료가_먼저(project: Path) -> None:
|
||||
# 노면공 87-2 — 범위를 고쳐도 옛 length_m 이 안 앞섬
|
||||
moved = {"start_m": 200, "end_m": 245, "options": {"length_m": 30}}
|
||||
assert fill._length(moved) == 45
|
||||
assert fill._length({"options": {"length_m": 12}}) == 12 # 점형 · 구간 없음 = 입력 길이
|
||||
assert fill._length({"options": {}}) is None
|
||||
|
||||
Reference in New Issue
Block a user