/* PLAN 494-1 단위검증 헬퍼 — 화면 밖 횡단 카드 줄(`createIdleQueue`)이 쉬는 틈이 안 와도 그려지는가. * `B06_Section_UI_Section_Common.ts` 에서 그 함수만 꺼내 트랜스파일해 Node 로 돌린다(다른 import 안 끎). * 가짜 창 = 쉬는 틈이 영영 안 오는 브라우저(ORCA 내장 · 가려진 창) — 시간 제한을 준 부탁만 그 시간에 불림. * 결과는 표준출력에 JSON 한 벌. */ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const file = path.join(__dirname, "..", "..", "B06_Section", "B06_Section_UI_Section_Common.ts"); const src = fs.readFileSync(file, "utf-8").split("\r\n").join("\n"); const body = src.match(/export function createIdleQueue[\s\S]*?\n}\n/); if (!body) throw new Error("createIdleQueue 못 찾음"); // 함수가 쓰는 파일 머리 상수(대문자 이름 한 줄) const consts = [...src.matchAll(/^const [A-Z_]+ = [^;\n]+;$/gm)].map((m) => m[0]).join("\n"); const js = ts.transpileModule(`${consts}\n${body[0]}`, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; let now = 0; const timers = []; // { at, fn } const window = { requestIdleCallback: (fn, options) => { // 쉬는 틈 없음 — 시간 제한이 있을 때만 그 시간에 부름(didTimeout) if (options && Number.isFinite(options.timeout)) timers.push({ at: now + options.timeout, fn: () => fn({ didTimeout: true, timeRemaining: () => 0 }), }); return timers.length + 1; }, setTimeout: (fn, ms) => (timers.push({ at: now + (ms || 0), fn }), timers.length + 1), }; const mod = { exports: {} }; new Function("module", "exports", "window", js)(mod, mod.exports, window); const { createIdleQueue } = mod.exports; const ran = []; const q = createIdleQueue((m) => ran.push(m), 2); [145, 150, 155, 160, 165].forEach((m) => q.queue.add(m)); q.drain(); // 2 초 흐름 — 그 사이 불릴 것 모두 부름 for (;;) { timers.sort((a, b) => a.at - b.at); const next = timers[0]; if (!next || next.at > 2000) break; timers.shift(); now = next.at; next.fn(); } process.stdout.write(JSON.stringify({ ran, left: [...q.queue], elapsedMs: now }));