/* 346-4 떠남 확인 단위검증 헬퍼 — `A00_Common/design/design_leave.ts` 를 그 자리에서 트랜스파일 · * `b_page_state` 는 진짜 · 라우터 · 물음 창 · 문구는 흉내. 결과 JSON 을 stdout 으로. */ 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), clear: () => map.clear(), key: (index) => [...map.keys()][index] ?? null, get length() { return map.size; }, }; } const session = memoryStorage(); const win = new EventTarget(); win.sessionStorage = session; win.localStorage = memoryStorage(); win.setTimeout = setTimeout; globalThis.window = win; globalThis.sessionStorage = session; let route = "/b05-profile"; let guard = null; let guardSets = 0; let choice = "cancel"; const asked = []; const STUBS = { "@config/config_frontend": { ROUTES: { B05_PROFILE: "/b05-profile", B06_SECTION: "/b06-section" }, }, "@ui/ui_template_locale": { t: (key) => key }, "@ui/ui_template_leave_dialog": { leaveOrStay: async (message, hooks) => { asked.push(message); if (choice === "cancel") return "stay"; if (choice === "discard") { await hooks.discard(); return "discarded"; } await hooks.save(); return hooks.stillDirty() ? "stay" : "saved"; }, }, "../router": { currentRoute: () => route, setLeaveGuard: (fn) => { guard = fn; guardSets += 1; }, }, }; function loadTs(file) { const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const box = { exports: {} }; const localRequire = (request) => { if (request in STUBS) return STUBS[request]; if (request.startsWith(".")) return loadTs(path.join(path.dirname(file), `${request}.ts`)); return require(request); }; new Function("exports", "module", "require", js)(box.exports, box, localRequire); return box.exports; } const leave = loadTs(path.join(ROOT, "A00_Common", "design", "design_leave.ts")); const P = "p1"; const tick = () => new Promise((resolve) => setTimeout(resolve, 5)); /** 브라우저 BeforeUnloadEvent 처럼 returnValue 를 쓸 수 있게. */ class BeforeUnload extends Event { returnValue = true; } const unloadBlocked = () => { const event = new BeforeUnload("beforeunload", { cancelable: true }); win.dispatchEvent(event); return event.defaultPrevented; }; (async () => { const out = {}; const root = { isConnected: true }; let saveMarks = true; let saves = 0; leave.guardDesignLeave(P, root, async () => { saves += 1; if (saveMarks) leave.markDesignClean(P); }); out.emptyDirty = leave.designDirty(P); // 고침이 아닌 칸 · 다른 페이지 칸 · 표준 횡단면만 — 안 물음 session.setItem(`aislo:draft:selection:${P}`, '{"chainageM":20}'); session.setItem(`aislo:draft:std-cross:${P}`, '{"soil":{}}'); session.setItem(`aislo:draft:pipe-ids:${P}`, '{"next":3}'); session.setItem(`aislo:draft:b08-materials:${P}`, '{"units":1}'); session.setItem(`aislo:draft:crossdesign:${P}:7`, "{}"); // 빈 칸 out.nonEditDirty = leave.designDirty(P); out.nonEditUnload = unloadBlocked(); route = "/b08-quantity"; out.nonEditLeave = await guard(); out.askedNonEdit = asked.length; // 횡단 초안(노선 7) — 물음 session.setItem(`aislo:draft:crossdesign:${P}:7`, '{"20.000":{"ground_type":"soil"}}'); out.editDirty = leave.designDirty(P); out.editUnload = unloadBlocked(); // 같은 페이지(B05 → B06) — 안 묻고 한 틱 뒤 다시 걺 route = "/b06-section"; const setsBefore = guardSets; out.samePage = await guard(); await tick(); out.reArmed = guardSets === setsBefore + 1; out.askedSamePage = asked.length; // 다른 페이지 — 취소 = 머묾 route = "/b08-quantity"; choice = "cancel"; out.cancelLeave = await guard(); // 저장 실패(깨끗한 표 없음) = 머묾 · 저장 성공 = 넘어감 · 뒤로 안 물음 choice = "save"; saveMarks = false; out.saveFailLeave = await guard(); saveMarks = true; out.saveLeave = await guard(); out.saves = saves; out.afterSaveDirty = leave.designDirty(P); out.afterSaveUnload = unloadBlocked(); const askedAfterSave = asked.length; out.afterSaveLeave = await guard(); out.askedAfterSave = asked.length - askedAfterSave; // 저장 뒤 다시 고침 — 다시 물음 · 버리고 넘어감 = 고침 칸만 지움 session.setItem(`aislo:draft:alignment-draft:${P}:7`, '{"station_offsets":{"20.000":0.3}}'); session.setItem(`aislo:draft:route-draft:${P}`, '{"rev":2}'); out.reEditDirty = leave.designDirty(P); choice = "discard"; out.discardLeave = await guard(); out.left = { crossdesign: session.getItem(`aislo:draft:crossdesign:${P}:7`), alignment: session.getItem(`aislo:draft:alignment-draft:${P}:7`), routeDraft: session.getItem(`aislo:draft:route-draft:${P}`), selection: session.getItem(`aislo:draft:selection:${P}`), stdCross: session.getItem(`aislo:draft:std-cross:${P}`), b08: session.getItem(`aislo:draft:b08-materials:${P}`), }; out.afterDiscardDirty = leave.designDirty(P); // 페이지가 화면에서 빠짐 — 안 물음 session.setItem(`aislo:draft:crossdesign:${P}:7`, '{"40.000":{}}'); root.isConnected = false; const askedGone = asked.length; out.goneLeave = await guard(); out.goneUnload = unloadBlocked(); out.askedGone = asked.length - askedGone; process.stdout.write(JSON.stringify(out)); })();