/* 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));