Files
Aislo/resources/tester/helper_188_1_last_pick.cjs
T
eomsangdonandClaude Opus 5.5 9e69bec512 fix(B06·B05): 노면공 고름 세 갈래 · 형상 누름 = 목록 고름 · 카드 하나 = 그 측점(189-1)
- ② 카드 안 노면공 포장층 누름 = 목록 고름과 같은 길(범위 카드 전부 · 포장층 강조) — 카드 고름보다 먼저 가로챔
- ③ 카드 하나 누름 = 연 노면공 · 범위 강조 풂 · 그 카드 하나(165-1 노면공 지킴 · 167-3 재누름 그대로 없앰)
- 카드 고름 세션 = 그 측점(구조물 id 는 목록 · 알약 · 넘김이 고른 것만)
- 종단: 구조물 넘김이면 측점선 · 3D 띠 안 켬
- 새 시험 test_189_1_pave_pick · test_165 · test_188_1 기대값 41번 규칙으로

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
2026-10-10 13:21:23 +09:00

239 lines
8.3 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 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) => {
// 구조물 고름은 측점선 없음(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));