Files
Aislo/resources/tester/helper_298_3_save_shape.cjs
T
eomsangdonandClaude Sonnet 5.5 156ce1cf52 test(508-1b 옛 헬퍼 공용 불러오기 3/7): 헬퍼 28 곳 loadTs 베낌 걷고 makeLoader 로
- 헬퍼 동작 그대로(풀이 함수 본문은 옮겨 담음) · 제품 코드 안 고침
- 확인: 이 묶음 헬퍼를 쓰는 시험 218 개 전 218 통과 → 후 218 통과 (시험별 junit 대조 · 어긋남 0)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 17:08:08 +09:00

147 lines
5.4 KiB
JavaScript

/* 요구 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);
});