Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_7' into sub_laptop_1
This commit is contained in:
@@ -166,7 +166,8 @@ export function pipelineCrossSource(
|
||||
stored?.find((row) => Math.abs(row.chainage_m - chainageM) < SAME_M)?.design;
|
||||
const put = (key: string, section: CrossSection, at = section.chainage_m): void => {
|
||||
byKey[key] = carried(section, storedAt(at));
|
||||
const own: Record<string, unknown> = {};
|
||||
// 카드 번호 = 저장 카드 번호(같은 측점 열쇠 · 옮긴 관도 · 찾기 · 고름이 같은 id · 440)
|
||||
const own: Record<string, unknown> = { station_id: section.station_id };
|
||||
for (const name of SPEC_KEYS) if (section[name] !== undefined) own[name] = section[name];
|
||||
specs.set(key, own as SectionSpecs);
|
||||
};
|
||||
|
||||
@@ -103,6 +103,8 @@ export async function mountCrossView(
|
||||
const projectId = shared.projectId || null;
|
||||
let currentRouteId: number | null = null;
|
||||
let sectionDetail: SectionDetailResponse | null = null;
|
||||
/** 지금 그린 상세(노선 초안 동안 = 묶음 새 노선 상세) — 카드 찾기 · 고름 · 넘김 · 풀기가 이것으로(440). */
|
||||
let drawnDetail: SectionDetailResponse | null = null;
|
||||
let stationInterval: number | undefined;
|
||||
|
||||
// 표준 횡단면 설정 패널 자리. context 로드 후 config 기본값으로 채운다.
|
||||
@@ -115,7 +117,7 @@ export async function mountCrossView(
|
||||
|
||||
/** 목록·폼에서 고른 시설의 측점 카드를 선택·스크롤 — 가장 가까운 카드로 간다. */
|
||||
const focusStationAt = (chainageM: number, revealOnly = false): void => {
|
||||
const sections = sectionDetail?.cross_sections;
|
||||
const sections = drawnDetail?.cross_sections;
|
||||
if (!sections?.length) return;
|
||||
const nearest = sections.reduce((best, entry) =>
|
||||
Math.abs(entry.chainage_m - chainageM) < Math.abs(best.chainage_m - chainageM) ? entry : best,
|
||||
@@ -379,7 +381,7 @@ export async function mountCrossView(
|
||||
sectionView.setPavementSelectListener((id) => structuresPanel.selectStructure(id));
|
||||
sectionView.setStationSelectListener((stationId) => {
|
||||
const target = stationId
|
||||
? sectionDetail?.cross_sections.find((section) => section.station_id === stationId)
|
||||
? drawnDetail?.cross_sections.find((section) => section.station_id === stationId)
|
||||
: null;
|
||||
if (!target) {
|
||||
structuresPanel.showAtChainage(null);
|
||||
@@ -458,7 +460,7 @@ export async function mountCrossView(
|
||||
* 적용한다 — 재렌더마다 다시 돌면 사용자가 옮긴 스크롤이 튄다(2026-09-06). */
|
||||
let appliedPick: string | null = null;
|
||||
function restoreStructurePick(): void {
|
||||
if (!sectionDetail) return;
|
||||
if (!drawnDetail) return;
|
||||
const handoff = readStructurePick(projectId);
|
||||
const signature = handoff
|
||||
? `${handoff.at}|${handoff.key ?? ""}|${handoff.structureId ?? ""}`
|
||||
@@ -468,7 +470,7 @@ export async function mountCrossView(
|
||||
// `load().then(restoreStructurePick)` 가 다시 부른다.
|
||||
const applied = applyStructurePick(
|
||||
handoff,
|
||||
sectionDetail,
|
||||
drawnDetail,
|
||||
{
|
||||
// 넘겨받은 구조물 id 가 먼저(52-15) · 폼은 id 로 직접(53-1) · 측점 없는 구조물은 카드 · 스크롤 없음(307)
|
||||
focusStation: (stationId) =>
|
||||
@@ -487,7 +489,7 @@ export async function mountCrossView(
|
||||
|
||||
// 횡단 보기 강조 = 고름 한 칸 구독 · 다른 곳(Esc 등)이 바꾼 값으로 다시 세움(233-2)
|
||||
const crossPick = bindCrossPick(projectId, host, {
|
||||
sections: () => sectionDetail?.cross_sections ?? [],
|
||||
sections: () => drawnDetail?.cross_sections ?? [],
|
||||
controls: stationControls,
|
||||
releaseForm: () => structuresPanel.showAtChainage(null),
|
||||
view: sectionView,
|
||||
@@ -508,8 +510,9 @@ export async function mountCrossView(
|
||||
if (!sectionDetail) return;
|
||||
showSectionView();
|
||||
// 높이 배율은 항상 1(2026-08-06 사용자 지시 — 옵션 폐지).
|
||||
drawnDetail = shownDetail(projectId, sectionDetail); // 노선 초안 동안 = 새 노선 횡단(346-2 덧 3)
|
||||
sectionView.render(
|
||||
shownDetail(projectId, sectionDetail), // 노선 초안 동안 = 새 노선 횡단(346-2 덧 3)
|
||||
drawnDetail,
|
||||
1,
|
||||
crossHalfWidth(),
|
||||
stationInterval,
|
||||
@@ -564,7 +567,14 @@ export async function mountCrossView(
|
||||
const reloadPanel = (): void => void structuresPanel.load().then(restoreStructurePick);
|
||||
host.onDispose(onStaleStructureTypes(() => host.visible() && reloadPanel()));
|
||||
// 바뀜 신호 하나(323-6 · `watchDrafts`) — 초안이 어느 길로 바뀌든 보일 때 카드 · 그림 다시(옛 되부름 갈음)
|
||||
host.onDispose(watchDrafts(projectId, () => host.visible() && void refreshDetailForStructures()));
|
||||
// 목록도 — 관 · 구조물 자리가 바뀌었으면(노선 초안 따라감 · 평면 마커 옮김) 다시 실음(440)
|
||||
host.onDispose(
|
||||
watchDrafts(projectId, () => {
|
||||
if (!host.visible()) return;
|
||||
if (structuresPanel.listStale()) reloadPanel();
|
||||
void refreshDetailForStructures();
|
||||
}),
|
||||
);
|
||||
host.onDispose(followSwaps(projectId, () => currentRouteId, host, refreshDetailForStructures));
|
||||
host.onDispose(() => {
|
||||
window.clearTimeout(pipeReloadTimer);
|
||||
|
||||
@@ -266,7 +266,7 @@ export interface StructureMarkInput {
|
||||
release: () => boolean;
|
||||
/** 마커 위인가 — 커서 몫. */
|
||||
over: (px: number, py: number) => boolean;
|
||||
/** 고른 마커가 있으면 풂 · true(409 — 마커 아닌 곳 누름 · Esc). */
|
||||
/** 이 평면 마커가 고른 것이면 풂 · true(409 — 마커 아닌 곳 누름 · 다른 화면이 고른 것은 그대로 · 440). */
|
||||
deselect: () => boolean;
|
||||
}
|
||||
|
||||
@@ -277,6 +277,8 @@ export function bindStructureMarks(
|
||||
): StructureMarkInput {
|
||||
const { canvas, status } = params;
|
||||
const projectId = draftProjectId();
|
||||
/** 이 평면 마커 누름이 고른 것(빈 곳 누름이 풀 것 · 440). */
|
||||
let ownPick: string | null = null;
|
||||
const list = redraws.get(projectId) ?? new Set<() => void>();
|
||||
redraws.set(projectId, list);
|
||||
const redraw = (): void => void (canvas.isConnected ? params.draw() : list.delete(redraw));
|
||||
@@ -335,6 +337,7 @@ export function bindStructureMarks(
|
||||
const deselect = (): boolean => {
|
||||
if (!planMarks(canvas)?.picked) return false;
|
||||
selectDesign(projectId, NO_SELECTION, { from: PLAN_PICK_FROM });
|
||||
ownPick = null;
|
||||
params.draw();
|
||||
return true;
|
||||
};
|
||||
@@ -402,6 +405,7 @@ export function bindStructureMarks(
|
||||
if (!mark) return false;
|
||||
release();
|
||||
planMarks(canvas)?.source.select(mark.id); // 알약 고름과 같은 길(공용 고름)
|
||||
ownPick = mark.id;
|
||||
start(mark);
|
||||
params.draw();
|
||||
return true;
|
||||
@@ -422,6 +426,7 @@ export function bindStructureMarks(
|
||||
},
|
||||
release: () => !holdKey && finish(),
|
||||
over: (px, py) => markAt(px, py) !== null,
|
||||
deselect,
|
||||
// 빈 곳 누름 = 이 마커가 고른 것만 풂 — B06 카드 · 목록 · 알약이 고른 것은 그대로(440) · Esc 는 앞 그대로
|
||||
deselect: () => planMarks(canvas)?.picked === ownPick && deselect(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -28,10 +28,12 @@ import { toDesignProfile } from "./B05_Profile_UI_Profile_Data";
|
||||
import { stationLabel } from "../../common_util/common_util_station";
|
||||
import { stationIdOf } from "./B05_Profile_Pipeline_Grade";
|
||||
|
||||
/** 측점에 얹힌 시설 제원(저장 상세에서 측점 열쇠로 옮김) — 낡음 판정 · 그림이 읽음. */
|
||||
/** 측점에 얹힌 시설 제원(저장 상세에서 측점 열쇠로 옮김) — 낡음 판정 · 그림이 읽음 · 카드 번호(`station_id` ·
|
||||
* 있으면 저장 카드 번호를 이어 씀 · 440). */
|
||||
export type SectionSpecs = Partial<
|
||||
Pick<
|
||||
CrossSection,
|
||||
| "station_id"
|
||||
| "structure"
|
||||
| "culvert"
|
||||
| "ford"
|
||||
|
||||
@@ -117,6 +117,8 @@ export interface B06StructuresPanel {
|
||||
listRoot: HTMLElement;
|
||||
/** 타입 레지스트리·구조물 정본(미저장분 우선)·관 지점을 받아 채운다. */
|
||||
load: () => Promise<void>;
|
||||
/** 가게(초안 · 없으면 저장분) 관 · 구조물 자리가 목록과 다른가 — 다시 실을 때(노선 초안 따라감 · 평면 마커 옮김 · 440). */
|
||||
listStale: () => boolean;
|
||||
/** 기하가 한계로 자른 실제 값을 폼에 되돌린다 — 입력 숫자만 커지고 그림은 그대로인
|
||||
* 상태를 막는다(2026-08-29 사용자: 재질 높이 한계에 걸린 값). */
|
||||
overrideOptions: (chainageM: number, patch: Record<string, string | number>) => void;
|
||||
@@ -162,9 +164,19 @@ export interface B06StructuresPanel {
|
||||
};
|
||||
}
|
||||
|
||||
type PlaceOf = { chainage_m?: number | null; start_m?: number | null; end_m?: number | null };
|
||||
/** 목록 자리 지문 — 누가거리 · 구간만(옵션 고침은 안 셈 · 440). */
|
||||
const placeKey = (list: ReadonlyArray<PlaceOf>): string =>
|
||||
JSON.stringify(
|
||||
list.map((entry) => [entry.chainage_m ?? null, entry.start_m ?? null, entry.end_m ?? null]),
|
||||
);
|
||||
|
||||
export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06StructuresPanel {
|
||||
let structures: StructureInstance[] = [];
|
||||
let pipeFacilities: PipeFacilityItem[] = [];
|
||||
/** 마지막으로 실은 저장분(초안이 비면 목록이 견줄 자리 · 440). */
|
||||
let storedStructures: StructureInstance[] = [];
|
||||
let storedPipes: ReadonlyArray<PlaceOf> = [];
|
||||
/** 지금 폼에 올라온 계곡 통과 시설의 누가거리 — 유입측 형식 반영 대상. */
|
||||
let currentChainageM: number | null = null;
|
||||
|
||||
@@ -385,6 +397,8 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
||||
section.setTypes(types, listPick("b06")); // 표 목록 both + b06(322-1)
|
||||
markTypes = types;
|
||||
// 저장하지 않고 나갔던 조작분이 있으면 그것으로 화면을 세운다(B05와 같은 규칙).
|
||||
storedStructures = stored.structures;
|
||||
storedPipes = pipeResponse.pipe_points;
|
||||
structures = readPendingStructures(projectId) ?? stored.structures;
|
||||
const note = staleDefinitionNotice(projectId, stored.definition_version); // 322-2
|
||||
if (note) showToast(note);
|
||||
@@ -558,6 +572,15 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
||||
root: section.root,
|
||||
listRoot: section.listRoot,
|
||||
load,
|
||||
listStale: () => {
|
||||
if (!deps.projectId) return false;
|
||||
const shopStructures = readPendingStructures(deps.projectId) ?? storedStructures;
|
||||
const shopPipes = readPendingPipes(deps.projectId) ?? storedPipes;
|
||||
return (
|
||||
placeKey(shopStructures) !== placeKey(structures) ||
|
||||
placeKey(shopPipes) !== placeKey(pipeFacilities)
|
||||
);
|
||||
},
|
||||
currentChainage: () => currentChainageM,
|
||||
// 종단 우클릭 → 좌측 폼·목록과 같은 함수. 폼이 열리고 목록·알약도 함께 선다.
|
||||
addStructureAt: (chainageM, typeId) => section.addAt(chainageM, typeId),
|
||||
|
||||
@@ -141,7 +141,11 @@ function loadSelection() {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
const stubs = { "./B06_Section_UI_Adjust_Dock": { refreshAdjustSlots: () => {} } };
|
||||
const stubs = {
|
||||
"./B06_Section_UI_Adjust_Dock": { refreshAdjustSlots: () => {} },
|
||||
"./B06_Section_UI_Cross_Design_Pick": { setBermPickHost: () => {} }, // 소단 고름 다리(440 덤)
|
||||
"./B06_Section_UI_Page_Persist": { isBermType: () => false },
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, (r) => stubs[r]);
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
@@ -336,6 +336,8 @@ out.redrawnOnChange = redrawn - was;
|
||||
// ⑥ 고름 풀기(409) — 고른 것 없으면 아무 일 없음 · 있으면 공용 고름을 b05-plan 으로 비움 · Esc 도
|
||||
selection = null;
|
||||
out.deselectNone = [input.deselect(), written.length];
|
||||
input.press(80, 0); // 이 마커로 고름(440 — 빈 곳 누름이 풀 것)
|
||||
input.release();
|
||||
selection = { chainageM: 40, structureId: "A" };
|
||||
out.deselectPicked = [input.deselect(), ...written.slice(-1), selection];
|
||||
selection = { chainageM: 80.17, structureId: null }; // 관 고름(누가거리만)
|
||||
@@ -345,10 +347,15 @@ out.escapePicked = [...written.slice(-1), selection, prevented];
|
||||
const writes = written.length;
|
||||
key("keydown", "Escape"); // 고른 것 없음 = 공용 Esc 몫(안 막음)
|
||||
out.escapeNone = [written.length - writes, prevented];
|
||||
// 440 — 다른 화면(B06 카드 · 목록 · 알약)이 고른 것 = 빈 곳 누름이 안 풂 · 이 마커로 고른 것만 풂
|
||||
selection = { chainageM: 40, structureId: "A" };
|
||||
const before440 = written.length;
|
||||
out.deselectOther = [input.deselect(), written.length - before440, selection !== null];
|
||||
// 마커 누름 = 고름(공용 고름 길) · 법선 · 창 — 그린 차례는 `_Render` 맨 끝(시험이 원본으로 봄)
|
||||
input.press(80, 0);
|
||||
input.release();
|
||||
out.pressAfterRelease = selects[selects.length - 1];
|
||||
out.deselectOwn = [input.deselect(), ...written.slice(-1), selection]; // 440 — 마커로 고른 것은 풂
|
||||
|
||||
// ⑦ 순수 셈
|
||||
out.logic = {
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
// 440 — 노선 초안 동안 B06 카드(묶음 새 상세) 번호가 찾기 상세(갈아끼운 캐시)에 있음.
|
||||
// 저장 상세 → cross 입력 모음(`pipelineCrossSource` · 진짜) → 새 상세(`draftSectionDetail` · 진짜) 대 갈아끼움(`swapCrossSections` · 진짜).
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ROOT = path.resolve(__dirname, "..", "..");
|
||||
const ts = require(path.join(ROOT, "config", "node_modules", "typescript"));
|
||||
const idOf = (m) => `station_${String(Math.round(m * 1000)).padStart(12, "0")}`;
|
||||
const REAL = /(B05_Profile_Pipeline_Detail|B06_Section_Station_Swap|design_pipeline_cross)$/;
|
||||
const STUBS = {
|
||||
B05_Profile_Pipeline_Grade: { stationIdOf: idOf },
|
||||
common_util_station: { stationLabel: (m) => String(m) },
|
||||
B05_Profile_UI_Profile_Data: { toDesignProfile: () => ({}) },
|
||||
b_page_state: { readState: () => null },
|
||||
B06_Section_Section_Canon: { savedDesigns: () => null },
|
||||
B06_Section_UI_Standard_Panel: {
|
||||
effectiveStandardCross: () => ({ road_width_m: 3 }),
|
||||
readRockBoundaryDefault: () => null,
|
||||
},
|
||||
};
|
||||
const cache = new Map();
|
||||
function load(full) {
|
||||
if (cache.has(full)) return cache.get(full).exports;
|
||||
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);
|
||||
const req = (r) => {
|
||||
const name = r.split("/").pop();
|
||||
if (STUBS[name]) return STUBS[name];
|
||||
if (REAL.test(r)) return load(path.resolve(path.dirname(full), r + ".ts"));
|
||||
throw new Error(`stub 없음: ${r}`);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, req);
|
||||
return box.exports;
|
||||
}
|
||||
const detailMod = load(path.join(ROOT, "B05_Profile/logic/B05_Profile_Pipeline_Detail.ts"));
|
||||
const swapMod = load(path.join(ROOT, "B06_Section/logic/B06_Section_Station_Swap.ts"));
|
||||
const crossMod = load(path.join(ROOT, "A00_Common/design/design_pipeline_cross.ts"));
|
||||
|
||||
const sec = (m, kind, extra = {}) => ({
|
||||
station_id: idOf(m),
|
||||
chainage_m: m,
|
||||
label: "",
|
||||
kind,
|
||||
center_x: m,
|
||||
center_y: 0,
|
||||
samples: [],
|
||||
design: { design_elevation_m: 100 },
|
||||
...extra,
|
||||
});
|
||||
// 저장 상세: 일반 340 · 관 351(배수관) · 일반 360
|
||||
const saved = {
|
||||
longitudinal: { stations: [], design_profiles: [], length_m: 400, samples: [] },
|
||||
cross_sections: [
|
||||
sec(340, "regular"),
|
||||
sec(351, "irregular", { culvert: { chainage_m: 351, diameter_m: 0.8 } }),
|
||||
sec(360, "regular"),
|
||||
],
|
||||
};
|
||||
const st = (key, m, kind) => ({
|
||||
key,
|
||||
chainageM: m,
|
||||
kind,
|
||||
x: m + 0.5,
|
||||
y: 3,
|
||||
left: [0, 1],
|
||||
azimuthDeg: 0,
|
||||
ground: null,
|
||||
});
|
||||
const savedStations = [
|
||||
st("grid:340", 340, "grid"),
|
||||
st("pipe:p35100", 351, "pipe"),
|
||||
st("grid:360", 360, "grid"),
|
||||
];
|
||||
// 노선 초안: 교점 끌기 → 관이 좌표 따라 354 로(410) · 일반 측점은 같은 m
|
||||
const draftStations = [
|
||||
st("grid:340", 340, "grid"),
|
||||
st("pipe:p35100", 354, "pipe"),
|
||||
st("grid:360", 360, "grid"),
|
||||
];
|
||||
const oldKeys = savedStations.map((s) => s.key);
|
||||
|
||||
const source = crossMod.pipelineCrossSource("p1", saved, savedStations, null);
|
||||
const draft = detailMod.draftSectionDetail(
|
||||
saved,
|
||||
{ stations: draftStations, longitudinal: { chainageM: [0, 400], z: [0, 0] } },
|
||||
{},
|
||||
null,
|
||||
source.specs,
|
||||
20,
|
||||
);
|
||||
const swapped = swapMod.swapCrossSections(saved, draftStations, oldKeys, 20).detail;
|
||||
const ids = (detail) => detail.cross_sections.map((s) => s.station_id);
|
||||
const swappedIds = new Set(ids(swapped));
|
||||
const pipeCard = draft.cross_sections[1];
|
||||
// 새 측점(초안에서 처음 생긴 일반 측점) = 누가거리 번호
|
||||
const added = detailMod.draftSectionDetail(
|
||||
saved,
|
||||
{
|
||||
stations: [...draftStations, st("grid:380", 380, "grid")],
|
||||
longitudinal: { chainageM: [0, 400], z: [0, 0] },
|
||||
},
|
||||
{},
|
||||
null,
|
||||
source.specs,
|
||||
20,
|
||||
);
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
draftIds: ids(draft),
|
||||
allFoundInSwapped: ids(draft).every((id) => swappedIds.has(id)),
|
||||
sameOrder: JSON.stringify(ids(draft)) === JSON.stringify(ids(swapped)),
|
||||
pipeCard: [
|
||||
pipeCard.station_id,
|
||||
pipeCard.chainage_m,
|
||||
pipeCard.culvert && pipeCard.culvert.chainage_m,
|
||||
],
|
||||
longIds: draft.longitudinal.stations.map((s) => s.station_id),
|
||||
addedId: added.cross_sections[3].station_id,
|
||||
}),
|
||||
);
|
||||
@@ -151,9 +151,8 @@ def test_추가_뒤_새_측점_횡단_미리_뜸():
|
||||
body = _body(B06, " onPipeAdd: (chainageM, attributes) => {", " onPipeUpdate:")
|
||||
assert "persistPipes();" in body # 초안 씀 → 구독(`watchDrafts`)이 카드 다시(323-6)
|
||||
# 카드 다시 = 바뀜 신호 하나(323-6 · 관 · 구조물 초안 구독 · 옛 되부름 갈음)
|
||||
assert (
|
||||
"watchDrafts(projectId, () => host.visible() && void refreshDetailForStructures())" in CROSS
|
||||
)
|
||||
watch = CROSS[CROSS.index("watchDrafts(projectId, () => {") :]
|
||||
assert "void refreshDetailForStructures();" in watch[: watch.index("}),")] # 목록도(440)
|
||||
drafts = _read("B06_Section", "B06_Section_Section_Drafts.ts")
|
||||
stations = _body(
|
||||
drafts, "export async function withDraftStations(", "parked.set(detail, stale);"
|
||||
|
||||
@@ -52,6 +52,12 @@ def test_마커_누름은_그대로_고름(got) -> None:
|
||||
assert got["pressAfterRelease"] == "A"
|
||||
|
||||
|
||||
def test_빈_곳_누름은_마커가_고른_것만_풂(got) -> None:
|
||||
# 440 — B06 카드 · 목록 · 알약이 고른 것은 평면 빈 곳 누름이 안 풂(B06 고름 그대로)
|
||||
assert got["deselectOther"] == [False, 0, True]
|
||||
assert got["deselectOwn"] == [True, [None, "b05-plan"], None]
|
||||
|
||||
|
||||
def test_누름_마커_먼저_교각점_풂() -> None:
|
||||
pointer = _read("B05_Profile", "B05_Profile_UI_RouteEdit_Pointer.ts")
|
||||
down = pointer[pointer.index("if (marks.press(px, py)) {") :]
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"""440 구조물 횡단 · 내부 구조물 하이라이트(요구 267).
|
||||
|
||||
① 노선 초안 동안 B06 카드(묶음 새 상세) 번호 = 저장 카드 번호(같은 측점 열쇠 · 옮긴 관도) — 찾기 상세
|
||||
(갈아끼운 캐시)에 모든 카드 번호가 있음 · 찾기 넷(카드 고름 · 목록 → 카드 · 넘김 · 풀기)은 지금 그린 상세.
|
||||
② 평면 패널 빈 곳 누름 = 평면 마커가 고른 것만 풂(`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
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
HELPER = Path(__file__).with_name("helper_440_draft_card_ids.cjs")
|
||||
SAVED_PIPE = "station_000000351000"
|
||||
|
||||
|
||||
def _read(*parts: str) -> str:
|
||||
return ROOT.joinpath(*parts).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def got() -> dict:
|
||||
run = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", str(HELPER)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=120,
|
||||
)
|
||||
assert run.returncode == 0, run.stderr
|
||||
return json.loads(run.stdout)
|
||||
|
||||
|
||||
def test_초안_카드_번호가_찾기_상세에_모두_있음(got) -> None:
|
||||
assert got["allFoundInSwapped"] is True
|
||||
assert got["sameOrder"] is True
|
||||
|
||||
|
||||
def test_옮긴_관_카드는_저장_번호_새_자리(got) -> None:
|
||||
assert got["pipeCard"] == [SAVED_PIPE, 354, 354]
|
||||
assert got["longIds"] == got["draftIds"] # 종단 측점 머리도 같은 번호
|
||||
|
||||
|
||||
def test_초안_새_측점은_누가거리_번호(got) -> None:
|
||||
assert got["addedId"] == "station_000000380000"
|
||||
|
||||
|
||||
def test_찾기_넷은_지금_그린_상세() -> None:
|
||||
cross = _read("A00_Common", "design", "design_view_cross.ts")
|
||||
assert "drawnDetail = shownDetail(projectId, sectionDetail);" in cross
|
||||
focus = cross[cross.index("const focusStationAt") : cross.index("const ownerAt")]
|
||||
assert "drawnDetail?.cross_sections" in focus and "sectionDetail" not in focus
|
||||
pick = cross[cross.index("sectionView.setStationSelectListener") :]
|
||||
assert "drawnDetail?.cross_sections.find" in pick[: pick.index("if (!target)")]
|
||||
restore = cross[cross.index("function restoreStructurePick") : cross.index("// 횡단 보기 강조")]
|
||||
assert "sectionDetail" not in restore and "drawnDetail," in restore
|
||||
assert "sections: () => drawnDetail?.cross_sections ?? []," in cross
|
||||
|
||||
|
||||
def test_바뀜_신호에서_목록_다시_실음() -> None:
|
||||
cross = _read("A00_Common", "design", "design_view_cross.ts")
|
||||
watch = cross[cross.index("watchDrafts(projectId, () => {") :]
|
||||
watch = watch[: watch.index("}),")]
|
||||
assert "if (structuresPanel.listStale()) reloadPanel();" in watch
|
||||
panel = _read("B06_Section", "B06_Section_UI_Page_Structures_Panel.ts")
|
||||
assert "readPendingStructures(deps.projectId) ?? storedStructures" in panel
|
||||
assert "readPendingPipes(deps.projectId) ?? storedPipes" in panel
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user