/* PLAN 219-1 단위검증 헬퍼 — 횡단 보기 방향키가 관을 몰래 옮기던 흠. * 실제 TS(`B05_Profile_UI_Profile_Panel_Tools` · `B06_Section_UI_Cross_Panel_Base`)를 그 자리에서 * 트랜스파일해 Node 로 돌림 · 결과 = 표준출력 JSON 한 벌. 짝 = `test_219_1_arrow_owner.py`. * 창 · 문서 듣개는 흉내(누름 = 문서 잡기 단계 · 키 = 문서 듣개(횡단 조정창) 뒤 창 듣개(종단도) 차례). */ const fs = require("fs"); const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); const listeners = { window: {}, document: {} }; const on = (where) => (type, fn) => (listeners[where][type] ??= new Set()).add(fn); const off = (where) => (type, fn) => listeners[where][type]?.delete(fn); const session = new Map(); globalThis.sessionStorage = { getItem: (k) => session.get(k) ?? null, setItem: (k, v) => session.set(k, String(v)), removeItem: (k) => session.delete(k), }; globalThis.window = { addEventListener: on("window"), removeEventListener: off("window"), setTimeout: () => 0, clearTimeout: () => undefined, setInterval: () => 0, clearInterval: () => undefined, }; globalThis.document = { addEventListener: on("document"), removeEventListener: off("document") }; // 화면 계산 · 이력 · 도구줄은 이 흠과 무관 — 쓰는 꼴만 흉내 const calls = { move: [], edits: 0 }; const STUBS = { "./logic/B05_Profile_UI_Profile_Alignment": { adjustStation: () => (calls.edits++, {}) }, "./B05_Profile_UI_Profile_Edit": { HOLD_DELAY_MS: 500, HOLD_INTERVAL_MS: 100 }, "./B05_Profile_UI_Profile_History": { createProfileHistory: () => ({ record: () => undefined, pause: () => undefined, resume: () => undefined, undo: () => undefined, redo: () => undefined, canUndo: () => false, canRedo: () => false, }), setPageHistory: () => undefined, onPageHistory: () => () => undefined, // ↶ ↷ 켜짐 맞춤(472 덧) · 이 흠과 무관 }, "./B05_Profile_UI_Profile_Tools": { createProfileTools: () => ({ mode: () => "none", clear: () => undefined }), }, "./logic/B05_Profile_UI_Profile_Straighten": {}, // [자유지정](215-1) — 화면 설정(@config) 줄기를 끌고 옴 · 이 흠과 무관 "../A00_Common/design/logic/design_profile_free": { createFreeDraw: () => ({ key: () => false, hint: () => "" }), }, "../A00_Common/design/design_profile_free_view": { createFreeView: () => ({ mount: () => undefined, redraw: () => undefined }), }, // 계산 자리 「서버」 길(511-A) — 판 both 그대로 · 이 흠과 무관 "../A00_Common/design/design_calc_sites": { calcOnServerNow: () => false }, "../A00_Common/design/design_calc_server": { calcChecked: (_key, _id, fn, args) => fn(...args), calcOnServerOnly: async () => undefined, trialRequest: () => ({}), }, }; const { loadTs } = makeLoader({ root: ROOT, requireFor: (full) => (request) => (CATALOG.isCatalogRequest(request) ? CATALOG : null) ?? STUBS[request] ?? (request.startsWith(".") ? loadTs(path.join(path.dirname(full), `${request}.ts`)) : require(request)), }); const tools = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Profile_Panel_Tools.ts")); const base = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Panel_Base.ts")); // 엄상준 263.21 관 — 횡단 보기에서 그 관 카드가 골라져 종단도도 그 관 측점을 고른 상태 const pipe = { id: "p1", chainage_m: 263.21, origin: "pipe" }; tools.createPanelTools({ root: { isConnected: true }, base: () => ({}), alignment: () => null, edits: () => ({}), savedRadii: () => ({}), applyEdits: () => undefined, selectedStationId: () => "irregular:p1", irregularStations: () => [pipe], stationIdOf: (s) => `irregular:${s.id}`, moveStation: (s, to) => calls.move.push([s.chainage_m, to]), drainage: { setPipeChainages: () => undefined }, restore: () => undefined, restoreSaved: () => undefined, canRestoreSaved: () => false, refresh: () => undefined, }); /** 누른 자리 — 종단도 안이면 closest 가 그 판을 줌(새 창 요소처럼 Element 가 아닌 것도). */ const spot = (inProfile) => ({ closest: (sel) => inProfile && sel.split(",").some((s) => s.trim() === ".b05-route-profile") ? {} : null, }); const press = (target) => listeners.document.pointerdown?.forEach((fn) => fn({ target })); /** 횡단 조정창 흉내 — 관 · 도수로 띠를 고른 동안 `swallowsArrows` 면 삼킴(Cross_Structure_Panel 배선과 같음). */ let side = ""; const crossPanel = (event) => { if (base.swallowsArrows(side, event.key)) event.preventDefault(); }; listeners.document.keydown = new Set([crossPanel]); function key(name) { const event = { key: name, target: null, repeat: false, defaultPrevented: false, preventDefault() { this.defaultPrevented = true; }, }; listeners.document.keydown.forEach((fn) => fn(event)); listeners.window.keydown?.forEach((fn) => fn(event)); listeners.window.keyup?.forEach((fn) => fn({ key: name })); return event.defaultPrevented; } function run(steps) { calls.move = []; calls.edits = 0; steps(); return { move: calls.move, edits: calls.edits }; } const out = { // 횡단 카드를 누른 뒤(조정창 없음) ← → = 안 옮김 — 옛 흠: 263.21 → 263.31 cardThenArrows: run(() => { press(spot(false)); key("ArrowRight"); key("ArrowLeft"); }), // 종단도를 누른 뒤 ← → = 지금처럼 0.1 m 이동 profileThenArrows: run(() => { press(spot(true)); key("ArrowRight"); }), // 새 창 종단도(다른 창 요소)도 종단도 안 popoutThenArrows: run(() => { press(Object.create(null, { closest: { value: spot(true).closest } })); key("ArrowLeft"); }), // 횡단에서 도수로 띠 고름 · 카드 쪽 누름 = 방향키 넷 다 아무것도 안 옮김(계획고도) chute: run(() => { side = "chute"; press(spot(false)); ["ArrowRight", "ArrowLeft", "ArrowUp", "ArrowDown"].forEach(key); }), pipeSide: run(() => { side = "pipe"; press(spot(false)); key("ArrowRight"); key("ArrowUp"); }), // 도수로 고른 채 종단도를 누르면 방향키는 종단도 몫 chuteThenProfile: run(() => { side = "chute"; press(spot(true)); key("ArrowRight"); }), // 조정창 있는 벽 고름 · 고름 없음 = 삼키지 않음(조정창 · 종단도 제 길) swallow: { wall: base.swallowsArrows("inlet", "ArrowRight"), none: base.swallowsArrows("", "ArrowRight"), letter: base.swallowsArrows("chute", "a"), }, }; side = ""; out.cardUpArrow = run(() => { press(spot(false)); key("ArrowUp"); // 고름 없는 카드 ▲ = 계획고(24-3b 그대로) }); process.stdout.write(JSON.stringify(out));