/* PLAN 398-1 단위검증 헬퍼 — 진짜 `design_user_pick.ts` + 진짜 `design_page_data.ts`(묶음 차례 · 「계산 중」)로 * 좌측 패널 펴기 한 곳(`design_shared` onReveal 과 같은 식)을 길마다 셈. 짝 = `test_398_1_reveal_user_pick.py`. * 브라우저가 처리 중인 이벤트(`window.event`)는 `globalThis.event` 로 흉내. */ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const ROOT = path.join(__dirname, "..", ".."); const DESIGN = path.join(ROOT, "A00_Common", "design"); function load(file, stubs) { const js = ts.transpileModule(fs.readFileSync(path.join(DESIGN, file), "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const box = { exports: {} }; new Function("exports", "module", "require", js)(box.exports, box, (name) => { if (!stubs[name]) throw new Error(`stub 없음: ${name}`); return stubs[name]; }); return box.exports; } const data = load("design_page_data.ts", { "./design_page_route": { setPageRouteId: () => undefined, pageRouteId: () => null }, "../../B04_PreProcess/B04_PreProcess_Api_Fetch": { fetchDetailPipePoints: async () => ({}) }, "../../B05_Profile/B05_Profile_Api_Structures": { fetchStructures: async () => ({}), dropStaleStructureTypes: async () => false, }, }); const pick = load("design_user_pick.ts", { "./design_page_data": data }); const P = "p1"; let reveals = 0; /** 폼이 고른 것을 실을 때마다 부르는 펴기(`design_shared` onReveal 그대로). */ const onReveal = () => pick.isUserPick(P) && (reveals += 1); /** 브라우저가 그 이벤트를 처리하는 동안(그 마이크로태스크까지) — 끝나면 이벤트 없음. */ async function during(event, body) { globalThis.event = event; try { await body(); } finally { globalThis.event = undefined; } } const trusted = (type) => ({ type, isTrusted: true }); const count = async (event, body) => { const before = reveals; await during(event, body); return reveals - before; }; (async () => { const out = {}; // 사용자 고름 — 종단 알약 · 횡단 카드(click) · 3D 부재(pointerup) · 우클릭 추가(contextmenu) out.click = await count(trusted("click"), () => onReveal()); out.pointerup = await count(trusted("pointerup"), () => onReveal()); out.contextmenu = await count(trusted("contextmenu"), () => onReveal()); // 누름 밖 — 배수유역 다시 나눔 응답 · 횡단 다시 그림(서버 응답 · 타이머) out.outside = await count(undefined, () => onReveal()); // 방향키로 교점 옮김 · 끌기 도중(pointermove) out.arrow = await count(trusted("keydown"), () => onReveal()); out.drag = await count(trusted("pointermove"), () => onReveal()); // 스크립트가 낸 가짜 누름 out.synthetic = await count({ type: "click", isTrusted: false }, () => onReveal()); // 교점 끌기 놓음 · 곡선 지우기 · 되돌리기 단추 — 묶음 차례가 떠 그 마이크로태스크에서 관 · 구조물 따라가며 다시 칠함 let finish; out.pipelineRepaint = await count(trusted("pointerup"), async () => { data.trackPageBusy(P, new Promise((resolve) => (finish = resolve))); await Promise.resolve(); onReveal(); // follow → 폼 다시 실음 }); out.busyPick = await count(trusted("click"), () => onReveal()); // 차례가 도는 사이 고름은 안 폄 finish(); await new Promise((resolve) => setTimeout(resolve, 0)); out.afterRun = await count(trusted("click"), () => onReveal()); // 차례가 끝나면 다시 폄 process.stdout.write(JSON.stringify(out)); })().catch((error) => { console.error(error); process.exit(1); });