Files
Aislo/resources/tester/helper_design_store_save.cjs
T
eomsangdonandClaude Opus 5.5 47366d3740 feat(종단보기): 346-3 [노선 반영] 없앰 — [저장]이 노선부터(POST /route/replan · draft = 계약 RouteDraft · 안 바뀌면 안 보냄) · 뒤 차례는 새 노선 · 횡단 초안 측점 열쇠로 넘김 · 결과 한 줄
- design_save 차례 = 노선 → 관 → 구조물 → 계획고(옛 노선 칸 → 새 노선) → 암거 옵션 → 상단측 → 횡단(carryStationDrafts 뒤 저장) · 잠금 막 = [저장] 하나 · 실패 = 멈춤 · 초안 그대로 · 오류 한 줄
- [확정] 중 노선이 바뀌면 노선만 저장하고 다시 세움(새 노선으로 다시 [확정])
- _RouteEdit_Apply = 노선 저장 이음(편집기 등록부 · pendingRouteSave · saveRouteDraft · carryAfterRouteSave · routeSaveSummary · rebuildAfterRouteSave) · 단추 듣기 없음
- 결과 한 줄 = 관 지킴 · 새 교차 관 · 오목 보충 · 구조물 옮김 · 멀어짐 · 노선 밖 고침(서버 cross_dropped + 화면) · 배수유역 낡음
- RE_Chrome [노선 반영] 지움 · [반영 전으로] → [저장 전으로] · replanRoute 본문 = RouteSaveBody
- 시험: test_design_store_save 차례 넷 더함 · 꼴 시험 셋 고쳐 씀 · 323_9 대역

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VmC34fsxxbweEG3RUjs3Mn
2026-10-16 12:14:22 +09:00

247 lines
9.2 KiB
JavaScript

/* 설계 가게 · 저장 한 줄기(계획서 24-2a) 단위검증 헬퍼 —
* `A00_Common/design/design_store.ts` · `design_save.ts` · `B05_Profile_UI_Profile_Edit.ts` 를
* 그 자리에서 트랜스파일해 Node 로 돌린다. 서버 창구는 가짜로 두고 부른 차례만 적는다.
*/
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
function memoryStorage() {
const map = new Map();
return {
getItem: (key) => (map.has(key) ? map.get(key) : null),
setItem: (key, value) => map.set(key, String(value)),
removeItem: (key) => map.delete(key),
key: (index) => [...map.keys()][index] ?? null,
get length() {
return map.size;
},
};
}
const session = memoryStorage();
globalThis.window = { sessionStorage: session, localStorage: memoryStorage() };
globalThis.sessionStorage = session;
const calls = [];
const record = (name, fail) => async () => {
calls.push(name);
if (fail) throw new Error(`${name} 실패`);
};
let failStructures = false;
// 노선 저장(346-3) — 초안 · 실패를 시험이 정함
let routeBody = null;
let failRoute = false;
const routeStub = {
pendingRouteSave: async () => routeBody,
saveRouteDraft: async () => {
calls.push("route");
if (failRoute) throw new Error("노선 저장 실패");
return { route_id: 9, pipes: { kept: 3, crossing: 1, concave: 0 }, cross_dropped: 1 };
},
carryAfterRouteSave: async (_p, from, to) => (calls.push(`carry:${from}->${to}`), 2),
routeSaveSummary: (saved, outside) => (calls.push(`summary:${saved.route_id}:${outside}`), ""),
rebuildAfterRouteSave: async () => calls.push("rebuild"),
};
const STUBS = {
"../../B05_Profile/B05_Profile_UI_RouteEdit_Apply": routeStub,
"@ui/ui_template_elements": {
showToast: (text, kind) => calls.push(`toast:${kind}`),
showLoadingOverlay: () => calls.push("overlay:on"),
hideLoadingOverlay: () => calls.push("overlay:off"),
},
"../../B05_Profile/B05_Profile_Api_Pipes_Draft": {
flushPendingPipes: () => record("pipes")(),
},
"../../B05_Profile/B05_Profile_Api_Structures": {
flushPendingStructures: () => record("structures", failStructures)(),
},
"../../B05_Profile/B05_Profile_Api_Fetch": {
flushUphillOverrides: () => record("uphill")(),
saveProfileAlignment: async (_p, routeId, edits) =>
calls.push(`alignment:${routeId}:${JSON.stringify(edits.station_offsets)}`),
},
"../../B06_Section/B06_Section_Api_Culvert_Options": {
flushCulvertOptions: async (_p, key) => calls.push(`culvert:${key}`),
},
};
const cache = new Map();
function loadTs(file) {
const full = path.resolve(file);
if (cache.has(full)) return cache.get(full);
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
cache.set(full, box.exports);
const localRequire = (request) => {
if (STUBS[request]) return STUBS[request];
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
return require(request);
};
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
cache.set(full, box.exports);
return box.exports;
}
const { useDesignStore } = loadTs(path.join(ROOT, "A00_Common", "design", "design_store.ts"));
const edit = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Profile_Edit.ts"));
const { saveDesign, DESIGN_SAVE_ORDER } = loadTs(
path.join(ROOT, "A00_Common", "design", "design_save.ts"),
);
async function run() {
const out = {};
// ① 가게 — 등록 키 밑 · 같은 칸이면 두 가게가 같은 값 · 듣는 쪽
const a = useDesignStore("p1", 7);
const b = useDesignStore("p1", 99); // 프로젝트 칸은 노선과 무관
const heard = [];
const off = b.subscribe("pipes", (value) => heard.push(value.length));
a.set("pipes", [{ chainage_m: 10, source: "user" }]);
out.pipesShared = b.get("pipes");
out.pipesKey = [...Array(session.length).keys()].map((i) => session.key(i));
off();
a.set("pipes", [{ chainage_m: 10 }, { chainage_m: 20 }]);
out.heard = heard;
out.otherProject = useDesignStore("p2", 7).get("pipes");
out.noRoute = useDesignStore("p1", null).get("alignmentDraft");
a.set("selection", { chainageM: 12.5, structureId: "s-1" });
out.selection = b.get("selection");
a.set("pipes", null);
a.set("selection", null);
// ② 계획고 가게 한 벌 — B05 패널 · B06 계획선 편집이 같은 벌
const saved = { station_offsets: {}, curve_radii: { "40.000": 30 } };
const b05 = edit.createProfileEditStore(7, saved, () => undefined);
const b06 = edit.createProfileEditStore(7, saved, () => undefined);
b06.replace({ station_offsets: { "20.000": 0.5 }, curve_radii: saved.curve_radii });
out.b05SeesB06 = b05.edits().station_offsets;
out.b05Dirty = b05.dirty();
// B05 가 자기 손에 쥔 값으로 이어 고쳐도 B06 편집이 안 사라짐(옛 두 가게 흠)
b05.replace({
station_offsets: { ...b05.edits().station_offsets, "60.000": -0.2 },
curve_radii: b05.edits().curve_radii,
});
out.afterBoth = b06.edits().station_offsets;
out.draft = useDesignStore(null, 7).get("alignmentDraft").station_offsets;
out.pending = edit.pendingAlignment(7).station_offsets;
// 되돌리기가 초안을 바꾼 뒤 B05 가 다시 세움 — B06 쪽 손잡이도 같은 벌을 봄
useDesignStore(null, 7).set("alignmentDraft", {
station_offsets: { "20.000": 0.1 },
curve_radii: {},
});
edit.createProfileEditStore(7, saved, () => undefined);
out.rebuiltSeenByB06 = b06.edits().station_offsets;
// ③ 저장 한 줄기 — 차례 · 기본 창구 · 보기 몫 · 계획고 저장됨 표시
calls.length = 0;
const ok = await saveDesign({
projectId: "p1",
routeId: () => 7,
prepare: record("prepare"),
steps: { pipes: record("pipes:screen") },
cross: async (confirm) => calls.push(`cross:${confirm}`),
done: async (confirm) => calls.push(`done:${confirm}`),
failed: () => calls.push("failed"),
});
out.saveOk = ok;
out.saveCalls = [...calls];
out.afterSave = { dirty: b05.dirty(), draft: useDesignStore(null, 7).get("alignmentDraft") };
out.order = DESIGN_SAVE_ORDER;
// ④ 구조물이 실패해도 다음 차례로(알림) · 확정 · 노선 없으면 안 나감
calls.length = 0;
failStructures = true;
await saveDesign(
{
projectId: "p1",
routeId: () => 7,
steps: { uphill: record("uphill:screen") },
cross: async (confirm) => calls.push(`cross:${confirm}`),
failed: () => calls.push("failed"),
},
{ confirm: true },
);
failStructures = false;
out.tolerant = [...calls];
calls.length = 0;
out.noRouteSave = await saveDesign({
projectId: "p1",
routeId: () => null,
cross: async () => calls.push("cross"),
failed: () => calls.push("failed"),
});
out.noRouteCalls = [...calls];
// ⑥ 노선이 바뀜(346-3) — 노선 먼저 · 뒤 차례는 새 노선(9) · 화면 몫 건너뜀 · 횡단 초안 넘김 · 다시 세움
b05.replace({ station_offsets: { "30.000": 0.2 }, curve_radii: {} }); // 옛 노선(7) 계획고 초안
routeBody = {
draft: {
rev: 1,
nodes: [
[0, 0],
[10, 0],
],
},
};
calls.length = 0;
out.routeOk = await saveDesign({
projectId: "p1",
routeId: () => 7,
prepare: record("prepare"),
steps: { pipes: record("pipes:screen"), alignment: record("alignment:screen") },
cross: async (confirm) => calls.push(`cross:${confirm}`),
done: async (confirm) => calls.push(`done:${confirm}`),
failed: () => calls.push("failed"),
});
out.routeCalls = [...calls];
// ⑦ 노선 저장 실패 — 거기서 멈춤 · 다른 초안 그대로
b05.replace({ station_offsets: { "50.000": 0.3 }, curve_radii: {} });
failRoute = true;
calls.length = 0;
out.routeFailOk = await saveDesign({
projectId: "p1",
routeId: () => 7,
cross: async (confirm) => calls.push(`cross:${confirm}`),
done: async (confirm) => calls.push(`done:${confirm}`),
failed: () => calls.push("failed"),
});
out.routeFailCalls = [...calls];
out.routeFailDraft = edit.pendingAlignment(7)?.station_offsets ?? null;
failRoute = false;
// ⑧ [확정] 중 노선이 바뀜 — 노선 저장 뒤 다시 세움 · 확정 단계는 안 닫음(새 노선으로 다시)
calls.length = 0;
await saveDesign(
{
projectId: "p1",
routeId: () => 7,
cross: async (confirm) => calls.push(`cross:${confirm}`),
done: async (confirm) => calls.push(`done:${confirm}`),
failed: () => calls.push("failed"),
},
{ confirm: true },
);
out.routeConfirmCalls = [...calls];
routeBody = null;
// ⑤ [초기화] — 초안 · 한 벌 모두 버림
b05.replace({ station_offsets: { "80.000": 1 }, curve_radii: {} });
edit.clearAlignmentDrafts();
out.cleared = {
draft: useDesignStore(null, 7).get("alignmentDraft"),
pending: edit.pendingAlignment(7),
};
return out;
}
run().then(
(out) => console.log(JSON.stringify(out)),
(error) => {
console.error(error);
process.exit(1);
},
);