- 헬퍼 동작 그대로(풀이 함수 본문은 옮겨 담음) · 제품 코드 안 고침 - 확인: 이 묶음 헬퍼를 쓰는 시험 218 개 전 218 통과 → 후 218 통과 (시험별 junit 대조 · 어긋남 0) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
147 lines
5.4 KiB
JavaScript
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);
|
|
});
|