/* 298-3 옛 페이지 풀림 단위검증 헬퍼 — `ui_template_window_watch` · `ui_template_overlay_drag` · * `design_leave` 를 그 자리에서 트랜스파일 · 페이지를 다섯 번 다시 세운 뒤 창 듣개 수 · 옛 노드 풀림. * `node --expose-gc` 로 부름 · 결과 JSON 을 stdout 으로. */ const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const { makeLoader, memoryStorage } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const storage = memoryStorage(); const win = new EventTarget(); const doc = new EventTarget(); const counts = { window: 0, document: 0 }; for (const [name, target] of [ ["window", win], ["document", doc], ]) { const add = target.addEventListener.bind(target); target.addEventListener = (...args) => { counts[name] += 1; return add(...args); }; } Object.assign(win, { sessionStorage: storage, localStorage: storage, setTimeout, innerWidth: 1500, innerHeight: 900, }); globalThis.window = win; globalThis.document = doc; globalThis.sessionStorage = storage; globalThis.localStorage = storage; globalThis.requestAnimationFrame = (fn) => (fn(), 1); let guard = null; 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 () => "stay" }, "../router": { currentRoute: () => "/b05-profile", setLeaveGuard: (fn) => (guard = fn) }, }; const { loadTs } = makeLoader({ root: ROOT, target: ts.ScriptTarget.ES2022, requireFor: (full) => (request) => { if (request in STUBS) return STUBS[request]; if (request.startsWith("@ui/")) return loadTs(path.join(ROOT, "ui_template", `${request.slice(4)}.ts`)); if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); return require(request); }, }); const drag = loadTs(path.join(ROOT, "ui_template", "ui_template_overlay_drag.ts")); const leave = loadTs(path.join(ROOT, "A00_Common", "design", "design_leave.ts")); let settles = 0; function fakeElement() { const node = new EventTarget(); node.style = {}; node.isConnected = true; node.offsetWidth = 300; node.offsetHeight = 40; node.classList = { contains: () => false, add() {}, toggle() {}, remove: () => (settles += 1), // detach 가 부름 = 자리 다시 앉힘 }; node.querySelector = () => null; node.getBoundingClientRect = () => ({ left: 10, top: 80 }); return node; } /** 페이지 한 벌 — 떠남 확인 + 진행단계 패널 · 옛 페이지 노드의 약한 손잡이를 돌려줌. */ function mountPage() { const page = fakeElement(); const panel = fakeElement(); page.panel = panel; // 페이지 나무 = 패널을 품음 leave.guardDesignLeave("p1", page, async () => {}); page.dragHandle = drag.makePanelDraggable(panel, fakeElement(), "pos"); return { page: new WeakRef(page), panel: new WeakRef(panel), keep: page }; } (async () => { const out = {}; let live = mountPage(); out.afterFirst = { ...counts }; const old = []; for (let index = 0; index < 4; index += 1) { live.keep.isConnected = false; // 다시 세움 — 옛 페이지는 화면에서 빠지고 버려짐 old.push({ page: live.page, panel: live.panel }); live = mountPage(); } out.afterFive = { ...counts }; await new Promise((resolve) => setTimeout(resolve, 0)); globalThis.gc(); await new Promise((resolve) => setTimeout(resolve, 0)); globalThis.gc(); out.oldAlive = old.filter((pair) => pair.page.deref() || pair.panel.deref()).length; out.liveAlive = Boolean(live.page.deref() && live.panel.deref()); // 살아 있는 패널은 창 크기 바뀜을 여전히 들음 · 버려진 패널은 안 부름 settles = 0; win.dispatchEvent(new Event("resize")); out.resizeSettles = settles; // 떠남 확인 — 살아 있는 페이지만(고침 없음 = 안 막음) const event = new Event("beforeunload", { cancelable: true }); win.dispatchEvent(event); out.unloadBlocked = event.defaultPrevented; out.guardSet = typeof guard === "function"; process.stdout.write(JSON.stringify(out)); })();