- 헬퍼 동작 그대로(풀이 함수 본문은 옮겨 담음) · 제품 코드 안 고침 - 확인: 이 묶음 헬퍼를 쓰는 시험 279 개 전 279 통과 → 후 279 통과 (시험별 junit 대조 · 어긋남 0) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
229 lines
7.8 KiB
JavaScript
229 lines
7.8 KiB
JavaScript
/* 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 { makeLoader } = require("./helper_ts_stub_loader.cjs");
|
|
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
|
|
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",
|
|
},
|
|
};
|
|
|
|
const { loadTs } = makeLoader({
|
|
root: ROOT,
|
|
requireFor: (full) => (request) =>
|
|
STUBS[request] ??
|
|
(request.startsWith(".")
|
|
? loadTs(path.join(path.dirname(full), `${request}.ts`))
|
|
: require(request)),
|
|
});
|
|
|
|
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) => {
|
|
// 구조물 고름은 측점선 없음(189-1 · view 의 selectGraphAt 과 같음)
|
|
const sections = sel.readSelection(P)?.structureId ? [] : cards;
|
|
const target = sync.graphTargetAt(id, c ?? null, pipes, sections);
|
|
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));
|