/* 요구 298-3 · 302 D1 — [저장] 모양 단위검증 헬퍼. * `A00_Common/design/design_save.ts` 를 그 자리에서 트랜스파일해 Node 로 돌린다 · 둘레 모듈은 가짜 · * 부른 차례만 적는다(잠금 · 조각 정리 · 깨끗한 표 · 알림 · 줄 세움). */ const fs = require("fs"); const path = require("path"); const { makeLoader } = require("./helper_ts_stub_loader.cjs"); const ROOT = path.join(__dirname, "..", ".."); const calls = []; let signature = "s0"; let routeBody = null; let routeResponse = {}; let onPost = () => undefined; const STUBS = { "@ui/ui_template_elements": { showToast: (text, kind) => calls.push(`toast:${kind}:${text}`), showLoadingOverlay: () => calls.push("overlay:on"), hideLoadingOverlay: () => calls.push("overlay:off"), }, "../../B05_Profile/B05_Profile_Api_Pipes_Draft": { pipesPart: () => ({ body: { points: [] }, after: () => calls.push("pipes:after") }), }, "../../B05_Profile/B05_Profile_Api_Structures": { structuresPart: () => null }, "../../B05_Profile/B05_Profile_Api_PlusStations": { plusStationsPart: () => null }, "../../B05_Profile/B05_Profile_Api_Fetch": { routePartsBody: async () => null }, "../../B05_Profile/B05_Profile_UI_Profile_Edit": { markAlignmentSaved: () => undefined, pendingAlignment: () => null, }, "../../B06_Section/B06_Section_Api_Culvert_Options": { culvertOptionsPart: () => null }, "../../B05_Profile/B05_Profile_Api_PageSettings": { pageSettingsPart: () => null }, "./design_save_api": { SAVE_PART_KEYS: ["settings", "pipes", "cross"], DesignSaveError: class extends Error {}, postDesignSave: async (_p, _r, confirm, _parts, extra = {}) => { calls.push(`post:${confirm}${extra.route ? ":route" : ""}`); await onPost(); return { waited: false, steps: extra.route ? { route: routeResponse } : {} }; }, }, "../b_page_state": { clearRouteDrafts: () => undefined, clearState: () => undefined, stateKey: () => "k", }, "../../B05_Profile/B05_Profile_UI_RouteEdit_Apply": { pendingRouteSave: async () => routeBody, routeCarryPart: () => ({ carry: {}, cross: { body: {}, after: () => calls.push("carry:after") }, }), routeSaved: () => calls.push("routeSaved"), rebuildAfterRouteSave: async () => calls.push("rebuild"), }, "./design_leave": { designSignature: () => signature, markDesignClean: () => calls.push("clean"), }, }; const { loadTs } = makeLoader({ root: ROOT, cache: false, requireFor: (full) => (request) => { if (STUBS[request]) return STUBS[request]; throw new Error(`stub 없음: ${request}`); }, }); const save = loadTs(path.join(ROOT, "A00_Common", "design", "design_save.ts")); const ctx = (extra = {}) => ({ projectId: "p1", routeId: () => 7, crossPart: async () => ({ body: {}, after: () => calls.push("cross:after") }), done: async (confirm, reload) => calls.push(`done:${confirm}:${reload}`), failed: (error) => calls.push(`failed:${error.message}`), ...extra, }); const deferred = () => { let resolve; const promise = new Promise((done) => (resolve = done)); return { promise, resolve }; }; const take = () => calls.splice(0); async function run() { const out = {}; // ① 노선 모양 안 바뀐 [저장] — 잠금 없음 · 조각 정리 · 깨끗한 표 · 다시 읽기 out.plain = { ok: await save.saveDesign(ctx()), calls: take() }; // ② 노선 모양 바뀐 [저장] — 잠금 · 다시 세움 · 헛 다시 읽기 없음 · 알림 = 따라감 실패 · 새 교차 관만 routeBody = { draft: { nodes: [] } }; routeResponse = { route_id: 9, pipes: { kept: 3, crossing: 2, outside: 1 }, structures: { moved: 1, far: 0, outside: 1 }, cross_dropped: 4, drainage_stale: true, follow_failed: ["구조물"], }; out.route = { ok: await save.saveDesign(ctx()), calls: take() }; routeResponse = { route_id: 9, pipes: { kept: 3, crossing: 0 } }; out.routeQuiet = { ok: await save.saveDesign(ctx()), calls: take() }; routeBody = null; // 저장 도중 고침 — 조각 정리 · 깨끗한 표 건너뜀(고침이 남음) · 화면 다시 읽기는 함 onPost = () => (signature = "s1"); out.touched = { ok: await save.saveDesign(ctx()), calls: take() }; onPost = () => undefined; // ⑤ 저장 중 [저장] — 막음 + 알림 · ④ 저장 중 확정 — 잠그고 앞 저장 끝을 기다린 뒤 · 확정 중 확정 = 막음 const first = deferred(); const firstRun = save.queueDesignSave("p1", false, async () => { calls.push("job1:start"); await first.promise; calls.push("job1:end"); return "one"; }); out.busyWhile = save.designSaveBusy("p1"); out.second = await save.queueDesignSave("p1", false, async () => calls.push("job2")); const confirmRun = save.queueDesignSave("p1", true, async () => { calls.push("job3"); return "three"; }); out.thirdBlocked = await save.queueDesignSave("p1", true, async () => calls.push("job4")); await new Promise((done) => setTimeout(done, 10)); calls.push("release"); first.resolve(); out.firstResult = await firstRun; out.confirmResult = await confirmRun; out.queueCalls = take(); out.busyAfter = save.designSaveBusy("p1"); out.otherProject = await save.queueDesignSave("p2", false, async () => "p2"); out.otherCalls = take(); return out; } run() .then((out) => process.stdout.write(JSON.stringify(out))) .catch((error) => { console.error(error); process.exit(1); });