/* 계획노선 편집 창 **측점 반폭 선 길이**(계획서 22-1) 단위검증 헬퍼 — * `B05_Profile_UI_RouteEdit_Cross.ts` 를 트랜스파일해 Node 로 돌린다. * 서버 묻기 · 그림 조각은 가짜로 채운다 — 「무슨 반폭을 묻고 · 무슨 반폭으로 선을 잡나」만 본다. */ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const ROOT = path.join(__dirname, "..", ".."); function memoryStorage() { const map = new Map(); return { getItem: (key) => (map.has(key) ? map.get(key) : null), setItem: (key, value) => map.set(key, String(value)), removeItem: (key) => map.delete(key), key: (index) => [...map.keys()][index] ?? null, get length() { return map.size; }, }; } const session = memoryStorage(); Object.defineProperty(session, "length", { get: () => session._keys().length }); session._keys = () => { const keys = []; for (let i = 0; i < 1000; i += 1) { const key = session.key(i); if (key === null) break; keys.push(key); } return keys; }; // `Object.keys(sessionStorage)` 가 저장된 키를 돌려주도록 실제 브라우저처럼 속성으로도 둔다. const sessionProxy = new Proxy(session, { ownKeys: () => [...new Set(session._keys())], getOwnPropertyDescriptor: (target, key) => session._keys().includes(key) ? { enumerable: true, configurable: true, value: target.getItem(key) } : Reflect.getOwnPropertyDescriptor(target, key), }); globalThis.window = { localStorage: memoryStorage(), sessionStorage: sessionProxy }; const fakeEl = () => ({ className: "", textContent: "", innerHTML: "", querySelector: () => fakeEl(), replaceChildren() {}, append() {}, getBoundingClientRect: () => ({ width: 300, height: 200 }), }); globalThis.document = { createElement: fakeEl }; const asked = []; const STUBS = { "../A00_Common/b_svg_zoom_pan": { attachSvgZoomPan: () => {} }, "../common_util/common_util_station": { stationLabel: (m) => String(m) }, "./B05_Profile_UI_RouteEdit_Cross_Draw": { buildCrossSvg: () => ({ svg: {}, layer: {}, width: 1, height: 1, content: {} }), CROSS_PLOT_PAD: 0, summarizeCross: () => "", }, // 서버 흉내 — 요청 반폭을 그대로 되돌려 주고, 없으면 기본 20. "./B05_Profile_Api_Replan": { fetchCrossPreview: async (_project, request) => { asked.push(request.cross_half_width_m ?? null); return { chainage_m: request.chainage_m, cross_half_width_m: request.cross_half_width_m ?? 20, samples: [], design: null, }; }, }, }; const cache = new Map(); 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) => { if (STUBS[request]) return STUBS[request]; if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); return require(request); }; new Function("exports", "module", "require", js)(box.exports, box, localRequire); cache.set(full, box.exports); return box.exports; } const { createCrossPreview } = loadTs( path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_Cross.ts"), ); async function run() { const cross = createCrossPreview({ projectId: "p-1", side: { append() {} }, request: () => ({ vertices: [], min_radius_m: 12, station_interval_m: 20 }), }); const out = {}; out.before = cross.watched(); await cross.open(100); out.empty = { asked: asked.at(-1), watched: cross.watched() }; session.setItem("aislo:draft:cross-display:p-1:3", "15"); await cross.refresh(); out.applied15 = { asked: asked.at(-1), watched: cross.watched() }; session.setItem("aislo:draft:cross-display:p-1:12", "8.5"); session.setItem("aislo:draft:cross-display:p-2:99", "40"); await cross.refresh(); out.latestRoute = { asked: asked.at(-1), watched: cross.watched() }; session.setItem("aislo:draft:cross-display:p-1:12", "abc"); await cross.refresh(); out.badValue = { asked: asked.at(-1), watched: cross.watched() }; return out; } run().then((out) => console.log(JSON.stringify(out)));