Files
Aislo/resources/tester/helper_b05_b06_selection.cjs
T
eomsangdonandClaude Sonnet 5.5 fdd93cdb2d test(508-1b 옛 헬퍼 공용 불러오기 6/7): 헬퍼 28 곳 loadTs 베낌 걷고 makeLoader 로
- 헬퍼 동작 그대로(풀이 함수 본문은 옮겨 담음) · 제품 코드 안 고침
- 확인: 이 묶음 헬퍼를 쓰는 시험 428 개 전 417 통과 → 후 417 통과 (시험별 junit 대조 · 어긋남 0)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 17:24:27 +09:00

173 lines
7.5 KiB
JavaScript

/* 측점 · 구조물 고름 한 곳 단위검증 헬퍼(PLAN 24-2d) — TS 를 그 자리에서 트랜스파일해 Node 로 돌린다.
* 결과는 표준출력에 JSON 한 벌. 화면 그릇은 쓰지 않는다(세션 저장소와 즉시 실행 프레임만 흉내).
*
* ⚠ 확장자가 `.cjs` 인 까닭은 `helper_b05_structures_form_summary.cjs` 머리와 같다.
*/
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();
const { loadTs } = makeLoader({
root: ROOT,
requireFor: (full) => (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 pickPage = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Page_Structure_Pick.ts"));
const pickSession = loadTs(
path.join(ROOT, "B05_Profile", "B05_Profile_UI_Structure_Pick_Session.ts"),
);
const out = { matchM: sel.PIPE_MATCH_M };
const P = "proj-1";
// ── 풀기: 관 → 구조물 순 · 한 거리 ───────────────────────────────────────
// 옹벽(구간 200~210) · 독립 기슭막이(구간 80~90) · BOX/세월교/배수관은 관 정본
const structures = [
{ structure_id: "wall", placement: "interval", start_m: 200, end_m: 210, chainage_m: 205 },
{ structure_id: "revet", placement: "interval", start_m: 80, end_m: 90, chainage_m: 85 },
];
const pipes = [{ chainage_m: 173.09 }, { chainage_m: 205.2 }, { chainage_m: 300 }];
const at = (chainageM, structureId = null) =>
sel.resolveSelection({ chainageM, structureId }, pipes, structures);
out.card = {
// 횡단 카드 측점이 관 측점에서 0.05 ~ 0.51 m 어긋나도 그 관을 찾는다(옛 B05 폼은 0.05 라 놓쳤다)
pipeNear: at(173.4),
pipeFar: at(173.09 + 0.6),
wallStart: at(200),
wallMiddle: at(207),
wallEdge: at(210.4),
revet: at(85),
none: at(250),
cleared: at(null),
// 같은 측점에 관과 구조물이 겹치면 관이 먼저(횡단도에 그려지는 것)
pipeBeatsStructure: at(205.0),
// 구조물 id 가 주어지고 목록에 있으면 측점보다 id 가 이긴다 · 없는 id 는 측점으로 되돌아간다
byId: at(300, "revet"),
staleId: at(200, "gone"),
};
// ── 세션 값 + 알림 ─────────────────────────────────────────────────────
const heard = [];
const off = sel.subscribeSelection(P, (s, from) => heard.push({ ...s, from }));
sel.selectDesign(P, { chainageM: 205, structureId: "wall" }, { partKey: "own", from: "b06" });
out.store1 = sel.readSelection(P);
sel.selectDesign(P, { chainageM: 205, structureId: "wall" }, { from: "b06" });
out.sameQuiet = heard.length; // 같은 값 · 부재키 생략 → 알리지 않음
sel.selectDesign(P, { chainageM: 205.2, structureId: "wall" }, { from: "b06" });
out.keyKept = sel.readSelection(P)?.partKey; // 같은 것을 다시 적으면 부재키를 지킨다
sel.selectDesign(P, { chainageM: 173.09, structureId: null }, { from: "b05" });
out.keyDropped = sel.readSelection(P)?.partKey ?? null; // 다른 구조물로 옮기면 버린다(53-1)
sel.selectDesign(P, sel.NO_SELECTION, { from: "b05" });
out.cleared = sel.readSelection(P);
out.heardFrom = heard.map((h) => [h.chainageM, h.structureId, h.from]);
off();
sel.selectDesign(P, { chainageM: 1, structureId: null });
out.afterOff = heard.length;
out.noProject = sel.readSelection(null);
// 알림 도중 다시 낸 값은 적기만 한다(되울림 방지)
let echoes = 0;
const off2 = sel.subscribeSelection(P, () => {
echoes += 1;
sel.selectDesign(P, { chainageM: 2, structureId: null });
});
sel.selectDesign(P, { chainageM: 3, structureId: null });
out.echoes = echoes;
out.lastWritten = sel.readSelection(P)?.chainageM;
off2();
sel.selectDesign(P, sel.NO_SELECTION);
// ── 넘김값 읽기 ────────────────────────────────────────────────────────
sel.selectDesign(P, { chainageM: 205, structureId: "wall" }, { partKey: "right" });
out.handoff = pickSession.readStructurePick(P);
sel.selectDesign(P, sel.NO_SELECTION);
out.handoffNone = pickSession.readStructurePick(P);
// ── B06 진입 때 카드 찾기 ──────────────────────────────────────────────
const cards = [80, 173.09, 200, 205.2, 220].map((c) => ({
chainage_m: c,
station_id: `s${c}`,
ford: null,
box: null,
}));
const detail = { cross_sections: cards };
const run = (handoff, list) => {
const calls = [];
calls.applied = pickPage.applyStructurePick(
handoff,
detail,
{
focusStation: (id) => calls.push(["focus", id]),
refreshCard: (c) => calls.push(["refresh", c]),
revetSelect: (c, k) => calls.push(["revet", c, k]),
fordSelect: (c, k) => calls.push(["ford", c, k]),
boxSelect: (c, k) => calls.push(["box", c, k]),
},
list,
);
return calls;
};
const midWall = { at: 207, structureId: "wall" }; // 구간 한가운데 — 카드 위치가 아님
out.retry = {
// 구조물 목록이 아직 안 실렸으면 카드를 못 찾고 false → 부르는 쪽이 나중에 다시 부른다
before: run({ at: 260 }, []).applied,
// 목록이 실린 뒤 같은 넘김값은 카드를 찾는다
after: run(midWall, structures).applied,
notFound: run({ at: 260 }, structures).applied,
emptyHandoff: run(null, structures).applied,
};
out.apply = {
pipeOffsetCard: run({ at: 173.4 }, structures),
wallByAnchor: run({ at: 205, structureId: "wall" }, structures),
wallNoId: run({ at: 207 }, structures),
revetCardFirst: run({ at: 85, structureId: "revet", key: "own" }, structures),
unknown: run({ at: 260 }, structures),
none: run(null, structures),
};
// ── B05 진입 때 되살리기 — 구조물 id 가 있을 때만 구조물로 연다 ─────────────
const restoreRun = (pickValue, stationOpens = false) => {
sel.selectDesign(P, sel.NO_SELECTION);
if (pickValue) sel.selectDesign(P, pickValue.selection, { partKey: pickValue.key });
const calls = [];
let opened = false;
const section = {
hasSelection: () => opened,
selectAt: (s) => {
calls.push(["selectAt", s.chainageM, s.structureId]);
opened = true;
},
};
const controls = { select: (p) => calls.push(["3d", p.chainageM, p.key ?? null]) };
const done = pickSession.restoreStructurePick(controls, P, section, (c) => {
calls.push(["station", c]);
opened = stationOpens; // 같은 측점 관이 먼저 열림
});
return { calls, done };
};
out.restore = {
pipeOnly: restoreRun({ selection: { chainageM: 85.05, structureId: null } }),
structure: restoreRun({ selection: { chainageM: 205, structureId: "wall" }, key: "own" }),
nothing: restoreRun(null),
structureOverPipe: restoreRun({ selection: { chainageM: 440.24, structureId: "mirror" } }, true),
};
sel.selectDesign(P, sel.NO_SELECTION);
console.log(JSON.stringify(out));