Merge remote-tracking branch 'origin/sub_laptop_8' into main_laptop_1
This commit is contained in:
@@ -25,13 +25,13 @@ import { createRouteProfilePanel } from "../../B05_Profile/B05_Profile_UI_Profil
|
||||
import { leaveForDashboard } from "../b_missing_data_guard";
|
||||
import { currentRoute, remountRoute } from "../router";
|
||||
import { openRouteEditModal } from "../../B05_Profile/B05_Profile_UI_RouteEdit";
|
||||
import { createSelectionSync } from "../../B05_Profile/B05_Profile_UI_Selection";
|
||||
import { createSelectionSync, graphTargetAt } from "../../B05_Profile/B05_Profile_UI_Selection";
|
||||
import {
|
||||
readStructurePick,
|
||||
restoreStructurePick,
|
||||
wireStructurePick,
|
||||
} from "../../B05_Profile/B05_Profile_UI_Structure_Pick_Session";
|
||||
import { NO_SELECTION, PIPE_MATCH_M, nearestWithin, selectDesign } from "./design_selection";
|
||||
import { NO_SELECTION, nearestWithin, selectDesign } from "./design_selection";
|
||||
import type { DesignPageHost } from "./design_page";
|
||||
import {
|
||||
bindDesignView,
|
||||
@@ -123,14 +123,17 @@ export async function mountProfileView(
|
||||
profilePanel.setMinCoverPipes(pipes);
|
||||
restorePick(); // 관 목록이 실린 뒤라야 폼이 그 시설을 찾는다.
|
||||
};
|
||||
/** 측점선 강조 — 측점선이 없는 관 시설(물넘이포장 · 비정규 측점 아님)은 알약을 id 로 켬(53-2). */
|
||||
/** 측점선 강조 — 측점선이 없는 관 시설(물넘이포장 · 비정규 측점 아님)은 알약을 id 로 켬(53-2) ·
|
||||
* 관도 없는 자리면 그 측점선 · 3D 띠(횡단에서 고른 일반 측점 · 188-1). */
|
||||
const selectGraphAt = (id: string | null, chainageM: number | null): void => {
|
||||
const bare =
|
||||
id === null && chainageM !== null
|
||||
? lastPipes?.find((pipe) => Math.abs(pipe.chainage_m - chainageM) < PIPE_MATCH_M)
|
||||
: undefined;
|
||||
if (bare) profilePanel.setSelectedStructure(`pipe-${bare.chainage_m.toFixed(2)}`);
|
||||
else profilePanel.setSelectedStation(id);
|
||||
const sections = currentSectionDetail?.cross_sections ?? [];
|
||||
const target = graphTargetAt(id, chainageM, lastPipes ?? [], sections);
|
||||
if ("pipeChainageM" in target) {
|
||||
profilePanel.setSelectedStructure(`pipe-${target.pipeChainageM.toFixed(2)}`);
|
||||
return;
|
||||
}
|
||||
profilePanel.setSelectedStation(target.stationId);
|
||||
if (id === null && target.stationId !== null) viewer.markers.selectStation(target.stationId);
|
||||
};
|
||||
// 종단도 한 벌 — 종단 보기가 보일 때 받는 알림만 건다(계획서 24-3b).
|
||||
const profilePanel = createRouteProfilePanel(
|
||||
@@ -223,6 +226,10 @@ export async function mountProfileView(
|
||||
selectPipeForm: (chainageM) => profilePanel.drainage.selectPipeAtChainage(chainageM),
|
||||
markStation: (chainageM) => profilePanel.drainage.markStation(chainageM),
|
||||
selectStructure3D: (chainageM) => viewer.structurePick.selectAtChainage(chainageM),
|
||||
regularChainage: (stationId) =>
|
||||
currentSectionDetail?.cross_sections.find((entry) => entry.station_id === stationId)
|
||||
?.chainage_m,
|
||||
remember: (chainageM) => rememberPick(chainageM, null),
|
||||
});
|
||||
|
||||
/** 세션에 남은 구조물 선택을 되살린다 — 횡단 보기에서 고른 것도 그대로 이어 받는다
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
* 구조물(`origin: "pipe"`)일 때만 붙는다 — 다른 구조물에는 대응하는 유역이 없다.
|
||||
* ========================================================================== */
|
||||
|
||||
import { PIPE_MATCH_M } from "../A00_Common/design/design_selection";
|
||||
import { PIPE_MATCH_M, nearestWithin } from "../A00_Common/design/design_selection";
|
||||
import {
|
||||
irregularStationId,
|
||||
isPipeStation,
|
||||
@@ -38,6 +38,10 @@ export interface SelectionSyncPorts {
|
||||
/** 3D 코리도 구조물 강조(누가거리 기준) — 좌측 폼·종단에서 골라도 3D가 따라온다
|
||||
* (2026-09-04). 3D에서 부재를 집어 이미 그 측점을 고른 상태면 그대로 둔다. */
|
||||
selectStructure3D?: (chainageM: number | null) => void;
|
||||
/** 일반 측점 id → 누가거리(없으면 undefined — 고름 한 칸을 안 건드림) — 일반 측점 고름도 고름 한 칸에 남기는 데 씀(188-1). */
|
||||
regularChainage?: (stationId: string) => number | undefined;
|
||||
/** 사용자 고름을 고름 한 칸(`structure-pick`)에 적음 — 마지막 고름 하나만 이김(188-1). */
|
||||
remember?: (chainageM: number | null) => void;
|
||||
}
|
||||
|
||||
export interface SelectionSync {
|
||||
@@ -71,9 +75,13 @@ export function createSelectionSync(ports: SelectionSyncPorts): SelectionSync {
|
||||
syncIrregularSelection(stationId) {
|
||||
if (ports.isSyncing()) return;
|
||||
syncBasinHighlight(stationId);
|
||||
const station = irregularOf(stationId);
|
||||
// 측점선 · 3D 띠 · 표 고름도 고름 한 칸에 — 일반 측점을 안 적어 옛 배관 고름이 이겼음(188-1)
|
||||
const at =
|
||||
stationId === null ? null : (station?.chainage_m ?? ports.regularChainage?.(stationId));
|
||||
if (at !== undefined) ports.remember?.(at);
|
||||
// 규칙 측점을 고른 것이면 구조물 폼은 건드리지 않는다(고를 항목이 없다).
|
||||
if (stationId !== null && !stationId.startsWith(prefix)) return;
|
||||
const station = irregularOf(stationId);
|
||||
ports.setSyncing(true);
|
||||
try {
|
||||
ports.selectSidebar(station ? station.chainage_m : null);
|
||||
@@ -113,3 +121,18 @@ export function createSelectionSync(ports: SelectionSyncPorts): SelectionSync {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** 종단 그래프가 켤 것 — 측점 id 가 있으면 그 선 · 없으면 그 자리 관 알약(53-2) · 관도 없으면
|
||||
* 그 자리 측점선(188-1 · 횡단에서 고른 일반 측점이 종단으로 이어짐). */
|
||||
export function graphTargetAt(
|
||||
stationId: string | null,
|
||||
chainageM: number | null,
|
||||
pipes: ReadonlyArray<{ chainage_m: number }>,
|
||||
sections: ReadonlyArray<{ station_id: string; chainage_m: number }>,
|
||||
): { pipeChainageM: number } | { stationId: string | null } {
|
||||
if (stationId !== null || chainageM === null) return { stationId };
|
||||
const pipe = nearestWithin(pipes, chainageM, (entry) => entry.chainage_m);
|
||||
if (pipe) return { pipeChainageM: pipe.chainage_m };
|
||||
const card = nearestWithin(sections, chainageM, (entry) => entry.chainage_m);
|
||||
return { stationId: card?.station_id ?? null };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
/* PLAN 188-1 단위검증 헬퍼 — 종단(B05) ↔ 횡단(B06) 오갈 때 마지막 고름 하나만 이김.
|
||||
* 화면 TS(`B05_Profile_UI_Selection` · `design_selection` · 넘김 · 카드 찾기)를 그 자리에서
|
||||
* 트랜스파일해 Node 로 돌림 · 결과 = 표준출력 JSON 한 벌. 짝 = `test_188_1_last_pick.py`.
|
||||
* 포트 배선은 `design_view_profile.ts` 와 같은 꼴(remember = 고름 한 칸 · 3D 띠 리스너 = 선택 동기화).
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const cache = new Map();
|
||||
|
||||
const session = new Map();
|
||||
const fakeStorage = {
|
||||
getItem: (k) => session.get(k) ?? null,
|
||||
setItem: (k, v) => session.set(k, String(v)),
|
||||
removeItem: (k) => session.delete(k),
|
||||
};
|
||||
globalThis.window = { sessionStorage: fakeStorage, localStorage: fakeStorage };
|
||||
globalThis.sessionStorage = fakeStorage;
|
||||
globalThis.localStorage = fakeStorage;
|
||||
globalThis.requestAnimationFrame = (fn) => fn();
|
||||
|
||||
// 비정규 측점 모듈은 설정 별칭(@config)을 끌어오므로 쓰는 두 함수만 같은 꼴로 흉내
|
||||
const STUBS = {
|
||||
"./B05_Profile_UI_IrregularStations": {
|
||||
irregularStationId: (id) => `irregular:${id}`,
|
||||
isPipeStation: (s) => s.origin === "pipe",
|
||||
},
|
||||
};
|
||||
|
||||
function loadTs(file) {
|
||||
const full = path.resolve(file);
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) =>
|
||||
STUBS[request] ??
|
||||
(request.startsWith(".")
|
||||
? loadTs(path.join(path.dirname(full), `${request}.ts`))
|
||||
: require(request));
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
cache.set(full, box.exports);
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
const sel = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts"));
|
||||
const sync = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Selection.ts"));
|
||||
const pickSession = loadTs(
|
||||
path.join(ROOT, "B05_Profile", "B05_Profile_UI_Structure_Pick_Session.ts"),
|
||||
);
|
||||
const pickPage = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Page_Structure_Pick.ts"));
|
||||
|
||||
const P = "proj-188";
|
||||
// 카드(횡단) = 일반 측점 + 배관 투영 측점(irregular:p1 · 33.4) · 옹벽 60~80 · 물넘이포장(측점선 없는 관 50)
|
||||
const cards = [
|
||||
["s0", 0],
|
||||
["s20", 20],
|
||||
["irregular:p1", 33.4],
|
||||
["s40", 40],
|
||||
["s60", 60],
|
||||
["s80", 80],
|
||||
["s100", 100],
|
||||
].map(([station_id, chainage_m]) => ({ station_id, chainage_m, ford: null, box: null }));
|
||||
const detail = { cross_sections: cards };
|
||||
const irregular = [{ id: "p1", chainage_m: 33.4, origin: "pipe", structure: "배관" }];
|
||||
const pipes = [{ chainage_m: 33.4 }, { chainage_m: 50 }];
|
||||
const structures = [
|
||||
{ structure_id: "wall", placement: "interval", start_m: 60, end_m: 80, chainage_m: 60 },
|
||||
];
|
||||
|
||||
// ── B05 종단 보기 흉내 — design_view_profile 의 포트 배선 그대로 ─────────────────
|
||||
let syncing = false;
|
||||
const graph = { station: null, mark: null };
|
||||
let marker = null;
|
||||
const sb = sync.createSelectionSync({
|
||||
stations: () => irregular,
|
||||
isSyncing: () => syncing,
|
||||
setSyncing: (v) => {
|
||||
syncing = v;
|
||||
},
|
||||
// 3D 띠 고름은 리스너로 선택 동기화를 되부름(onStationSelectionChange)
|
||||
selectMarker: (id) => {
|
||||
marker = id;
|
||||
sb.syncIrregularSelection(id);
|
||||
},
|
||||
selectGraph: (id, c) => {
|
||||
const target = sync.graphTargetAt(id, c ?? null, pipes, cards);
|
||||
if ("pipeChainageM" in target) graph.mark = `pipe-${target.pipeChainageM.toFixed(2)}`;
|
||||
else {
|
||||
graph.station = target.stationId;
|
||||
// 관도 없는 자리 = 그 측점선 · 3D 띠(view 의 selectGraphAt 과 같음)
|
||||
if (id === null && target.stationId !== null) {
|
||||
marker = target.stationId;
|
||||
sb.syncIrregularSelection(target.stationId);
|
||||
}
|
||||
}
|
||||
},
|
||||
selectSidebar: () => undefined,
|
||||
selectBasin: () => undefined,
|
||||
regularChainage: (id) => cards.find((c) => c.station_id === id)?.chainage_m,
|
||||
remember: (c) =>
|
||||
sel.selectDesign(P, c === null ? sel.NO_SELECTION : { chainageM: c, structureId: null }, {
|
||||
from: "b05",
|
||||
}),
|
||||
});
|
||||
|
||||
/** B05 사용자 고름 — 측점선 · 3D 띠 · 표(선택 동기화) · 알약 · 목록(직접 적음). */
|
||||
const b05 = {
|
||||
line: (id) => sb.syncIrregularSelection(id),
|
||||
pill: (c) => sel.selectDesign(P, { chainageM: c, structureId: null }, { from: "b05" }),
|
||||
structure: (sid, c) => sel.selectDesign(P, { chainageM: c, structureId: sid }, { from: "b05" }),
|
||||
};
|
||||
|
||||
/** 횡단 탭으로 — 넘김값으로 연 카드 id(없으면 null). */
|
||||
function toB06() {
|
||||
let focused = null;
|
||||
const handoff = pickSession.readStructurePick(P);
|
||||
pickPage.applyStructurePick(
|
||||
handoff,
|
||||
detail,
|
||||
{
|
||||
focusStation: (id) => {
|
||||
focused = id;
|
||||
},
|
||||
refreshCard: () => undefined,
|
||||
revetSelect: () => undefined,
|
||||
fordSelect: () => undefined,
|
||||
boxSelect: () => undefined,
|
||||
},
|
||||
structures,
|
||||
);
|
||||
return focused;
|
||||
}
|
||||
|
||||
/** 종단 탭으로 — 넘김값을 되살린 뒤 그래프 · 3D · 고름 한 칸. */
|
||||
function toB05() {
|
||||
graph.station = null;
|
||||
graph.mark = null;
|
||||
marker = null;
|
||||
const opened = [];
|
||||
pickSession.restoreStructurePick(
|
||||
{ select: () => undefined },
|
||||
P,
|
||||
{
|
||||
hasSelection: () => opened.length > 0,
|
||||
selectAt: (s) => opened.push(s.structureId),
|
||||
},
|
||||
sb.selectStationOfPipe,
|
||||
);
|
||||
return { graph: { ...graph }, marker, opened, pick: sel.readSelection(P) };
|
||||
}
|
||||
|
||||
const reset = () => sel.selectDesign(P, sel.NO_SELECTION);
|
||||
const out = {};
|
||||
|
||||
// ① 배관 → 일반 측점 → 횡단 = 일반 측점(사용자 재현)
|
||||
reset();
|
||||
b05.line("irregular:p1");
|
||||
b05.line("s20");
|
||||
out.pipeThenStation = toB06();
|
||||
reset();
|
||||
b05.pill(33.4); // 알약으로 고른 배관도 같음
|
||||
b05.line("s20");
|
||||
out.pillThenStation = toB06();
|
||||
|
||||
// ② 일반 측점 → 배관 → 횡단 = 배관
|
||||
reset();
|
||||
b05.line("s20");
|
||||
b05.line("irregular:p1");
|
||||
out.stationThenPipe = toB06();
|
||||
|
||||
// ③ 구조물 끼움 — 배관 → 구조물 → 일반 측점 = 일반 측점 · 일반 측점 → 구조물 = 구조물 시작 카드
|
||||
reset();
|
||||
b05.pill(33.4);
|
||||
b05.structure("wall", 60);
|
||||
b05.line("s100");
|
||||
out.structureThenStation = toB06();
|
||||
reset();
|
||||
b05.line("s100");
|
||||
b05.structure("wall", 60);
|
||||
out.stationThenStructure = toB06();
|
||||
reset();
|
||||
b05.structure("wall", 60);
|
||||
b05.line("irregular:p1");
|
||||
out.structureThenPipe = toB06();
|
||||
|
||||
// ④ 고름 풂 · 모르는 측점 id
|
||||
reset();
|
||||
b05.line("irregular:p1");
|
||||
b05.line(null);
|
||||
out.released = pickSession.readStructurePick(P);
|
||||
reset();
|
||||
b05.line("s20");
|
||||
b05.line("ghost");
|
||||
out.unknownKeeps = pickSession.readStructurePick(P)?.at ?? null;
|
||||
|
||||
// ⑤ 횡단 → 종단 — B06 카드가 적은 값을 종단이 그대로 켬 · 되살리기는 고름 한 칸을 안 덮음
|
||||
reset();
|
||||
sel.selectDesign(P, { chainageM: 40, structureId: null }, { from: "b06" });
|
||||
out.b06Station = toB05();
|
||||
reset();
|
||||
sel.selectDesign(P, { chainageM: 33.4, structureId: null }, { from: "b06" });
|
||||
out.b06Pipe = toB05();
|
||||
reset();
|
||||
sel.selectDesign(P, { chainageM: 50, structureId: null }, { from: "b06" });
|
||||
out.b06BarePipe = toB05();
|
||||
reset();
|
||||
sel.selectDesign(P, { chainageM: 60, structureId: "wall" }, { partKey: "own", from: "b06" });
|
||||
out.b06Structure = toB05();
|
||||
|
||||
// ⑥ 오가기 — 종단 일반 측점 → 횡단 → 횡단 카드 → 종단 → 종단 배관 → 횡단
|
||||
reset();
|
||||
b05.line("s20");
|
||||
const trip = [toB06()];
|
||||
sel.selectDesign(P, { chainageM: 80, structureId: null }, { from: "b06" });
|
||||
trip.push(toB05().graph.station);
|
||||
b05.line("irregular:p1");
|
||||
trip.push(toB06());
|
||||
b05.line("s0");
|
||||
trip.push(toB06());
|
||||
out.roundTrip = trip;
|
||||
|
||||
// ⑦ 그래프 표적 — 측점 id 우선 · 관 알약 · 그 자리 측점선 · 아무것도 없음
|
||||
out.graphTarget = {
|
||||
byId: sync.graphTargetAt("s20", 33.4, pipes, cards),
|
||||
bare: sync.graphTargetAt(null, 50.2, pipes, cards),
|
||||
station: sync.graphTargetAt(null, 100.3, pipes, cards),
|
||||
far: sync.graphTargetAt(null, 120, pipes, cards),
|
||||
none: sync.graphTargetAt(null, null, pipes, cards),
|
||||
};
|
||||
reset();
|
||||
console.log(JSON.stringify(out));
|
||||
@@ -0,0 +1,101 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""PLAN 188-1 — 종단(B05) ↔ 횡단(B06) 오갈 때 마지막 고름 하나만 이김.
|
||||
|
||||
원인: 종단 측점선 · 3D 띠 · 표에서 고른 일반 측점은 고름 한 칸(`structure-pick`)에 안 적혀
|
||||
(`syncIrregularSelection` 이 일반 측점이면 바로 끝남) 옛 배관 고름이 남아 횡단이 배관 카드로 감.
|
||||
횡단 → 종단은 관 · 구조물이 없는 자리면 그래프 측점선을 풀어 버림(`selectGraphAt` = null).
|
||||
실제 TS 를 `helper_188_1_last_pick.cjs` 가 Node 로 돌린다.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.abspath(os.path.join(HERE, "..", ".."))
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not os.path.isdir(os.path.join(ROOT, "config", "node_modules")),
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def out() -> dict:
|
||||
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", os.path.join(HERE, "helper_188_1_last_pick.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def _read(*parts: str) -> str:
|
||||
with open(os.path.join(ROOT, *parts), encoding="utf-8") as handle:
|
||||
return handle.read()
|
||||
|
||||
|
||||
def test_배관_다음_일반_측점이면_횡단은_일반_측점(out) -> None:
|
||||
assert out["pipeThenStation"] == "s20"
|
||||
assert out["pillThenStation"] == "s20" # 알약으로 고른 배관도
|
||||
|
||||
|
||||
def test_일반_측점_다음_배관이면_횡단은_배관(out) -> None:
|
||||
assert out["stationThenPipe"] == "irregular:p1"
|
||||
|
||||
|
||||
def test_구조물을_끼워도_마지막_고름이_이김(out) -> None:
|
||||
assert out["structureThenStation"] == "s100"
|
||||
assert out["stationThenStructure"] == "s60" # 옹벽 = 시작 측점 카드
|
||||
assert out["structureThenPipe"] == "irregular:p1"
|
||||
|
||||
|
||||
def test_고름을_풀면_비우고_모르는_측점은_안_건드림(out) -> None:
|
||||
assert out["released"] is None
|
||||
assert out["unknownKeeps"] == 20
|
||||
|
||||
|
||||
def test_횡단_일반_측점은_종단_측점선과_3D_띠로(out) -> None:
|
||||
station = out["b06Station"]
|
||||
assert station["graph"] == {"station": "s40", "mark": None}
|
||||
assert station["marker"] == "s40"
|
||||
assert station["pick"] == {"chainageM": 40, "structureId": None}
|
||||
|
||||
|
||||
def test_횡단_배관은_종단_배관_측점선_측점선_없는_관은_알약(out) -> None:
|
||||
assert out["b06Pipe"]["graph"]["station"] == "irregular:p1"
|
||||
assert out["b06BarePipe"]["graph"] == {"station": None, "mark": "pipe-50.00"}
|
||||
|
||||
|
||||
def test_횡단_구조물은_종단에서_그_구조물을_열고_고름_한_칸을_안_덮음(out) -> None:
|
||||
structure = out["b06Structure"]
|
||||
assert structure["opened"] == ["wall"]
|
||||
# 되살리며 3D 띠 리스너가 되불러도(동기화 중) 구조물 id · 부재키가 남음
|
||||
assert structure["pick"] == {"chainageM": 60, "structureId": "wall", "partKey": "own"}
|
||||
|
||||
|
||||
def test_종단_횡단을_오가도_마지막_고름(out) -> None:
|
||||
assert out["roundTrip"] == ["s20", "s80", "irregular:p1", "s0"]
|
||||
|
||||
|
||||
def test_그래프_표적_차례(out) -> None:
|
||||
target = out["graphTarget"]
|
||||
assert target["byId"] == {"stationId": "s20"}
|
||||
assert target["bare"] == {"pipeChainageM": 50}
|
||||
assert target["station"] == {"stationId": "s100"}
|
||||
assert target["far"] == {"stationId": None}
|
||||
assert target["none"] == {"stationId": None}
|
||||
|
||||
|
||||
def test_종단_보기_배선() -> None:
|
||||
page = _read("A00_Common", "design", "design_view_profile.ts")
|
||||
assert "regularChainage:" in page
|
||||
assert "remember: (chainageM) => rememberPick(chainageM, null)" in page
|
||||
assert "graphTargetAt(id, chainageM, lastPipes ?? [], sections)" in page
|
||||
Reference in New Issue
Block a user