- 손 고침 [저장] = 492 이전처럼 바로 저장(확인 창 없음 · 세션 초안 통째 비움 없음) · 평면 · B06 평면 패널 띠 걷음 - 추천 [적용] 확인 창 = 서버 저장분(구조물 · 관 저장 파일 · 추가 측점) 수 — 응답 route_reset.dropped 와 같은 셈 · 세션 초안 안 셈 · 종단 편집 수 뺌 - 안 쓰게 된 것 걷음: routeShapeChanged(TS) · draftShapeChanged · askRouteReset · ROUTE_RESET_BAND · pageRoutePlan · 띠 CSS - 추가 측점 저장분 받기 fetchSavedPlusStations 한 줄 - 시험: test_283_route_reset_confirm 고쳐 씀(걷음 소스 · 창 수 = 알림 수 · 관 저장 파일 없음 0) · 저장 헬퍼 셋 확인 창 가짜 걷음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
322 lines
12 KiB
JavaScript
322 lines
12 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;
|
|
// 6-B — 노선 저장 · 다시 얹기 · 넘김은 서버 한 본문 · 여기서는 옛 노선 세션 칸 원본 조각만
|
|
const routeStub = {
|
|
pendingRouteSave: async () => routeBody,
|
|
routeCarryPart: (_p, from) => ({
|
|
carry: { from },
|
|
cross: { body: { carried: from }, after: () => calls.push("cross:carried:after") },
|
|
}),
|
|
routeSaved: () => calls.push("routeSaved"),
|
|
routeSaveSummary: (saved, outside) => (calls.push(`summary:${saved.route_id}:${outside}`), ""),
|
|
rebuildAfterRouteSave: async () => calls.push("rebuild"),
|
|
};
|
|
class DesignSaveError extends Error {
|
|
constructor(message, step, restored) {
|
|
super(message);
|
|
this.step = step;
|
|
this.restored = restored;
|
|
}
|
|
}
|
|
const SAVE_PART_KEYS = [
|
|
"settings",
|
|
"pipes",
|
|
"structures",
|
|
"plusStations",
|
|
"alignment",
|
|
"culvertOptions",
|
|
"uphill",
|
|
"cross",
|
|
];
|
|
const saveApiStub = {
|
|
SAVE_PART_KEYS,
|
|
DesignSaveError,
|
|
postDesignSave: async (_p, routeId, confirm, parts, extra = {}) => {
|
|
const sent = SAVE_PART_KEYS.filter((key) => parts[key] && parts[key].body != null);
|
|
const moved = extra.route ? `:route(carry ${extra.carry?.from})` : "";
|
|
calls.push(`post:${routeId}:${confirm}:${sent.join(",")}${moved}`);
|
|
if (extra.route && parts.alignment?.body) {
|
|
const body = parts.alignment.body; // 서버가 새 노선 번호로 고쳐 씀(test_472_6_route_save)
|
|
calls.push(`alignment:${body.route_id}:${JSON.stringify(body.station_offsets)}`);
|
|
}
|
|
if (failStructures) throw new DesignSaveError("structures 실패", "structures", true);
|
|
if (extra.route && failRoute) throw new DesignSaveError("노선 저장 실패", "route", true);
|
|
const route = extra.route
|
|
? {
|
|
route: { route_id: 9, pipes: { kept: 3, crossing: 1, concave: 0 }, cross_dropped: 1 },
|
|
carry: { outside: 2 },
|
|
}
|
|
: {};
|
|
return { waited: false, steps: { alignment: { dropped_edit_chainages: [] }, ...route } };
|
|
},
|
|
};
|
|
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": {
|
|
pipesPart: () => ({ body: { points: [] }, after: () => calls.push("pipes:after") }),
|
|
},
|
|
"../../B05_Profile/B05_Profile_Api_Structures": {
|
|
structuresPart: async () => ({
|
|
body: { base_revision: 0, structures: [] },
|
|
after: () => calls.push("structures:after"),
|
|
}),
|
|
},
|
|
"../../B05_Profile/B05_Profile_Api_PlusStations": {
|
|
outsideNotice: (count) => `노선 밖 ${count}`,
|
|
plusStationsPart: () => ({ body: null, after: () => calls.push("plusStations:after") }),
|
|
},
|
|
"../../B05_Profile/B05_Profile_Api_Fetch": {
|
|
routePartsBody: async () => ({ irregular_stations: [] }), // 상단측 차례 = 종단 보기 몫과 같은 본문(463)
|
|
},
|
|
"../../B06_Section/B06_Section_Api_Culvert_Options": {
|
|
culvertOptionsPart: (key) => ({
|
|
body: { "10.00": {} },
|
|
after: () => calls.push(`culvert:after:${key}`),
|
|
}),
|
|
},
|
|
// 448 페이지 설정 — 초안 없음(차례만 · 내보내기는 test_442_2_page_settings)
|
|
"../../B05_Profile/B05_Profile_Api_PageSettings": {
|
|
pageSettingsPart: () => null,
|
|
},
|
|
// 472-6 한 본문 창구 — 보낸 칸만 적음 · 구조물 실패 = 서버가 그 차례에서 되돌림
|
|
"./design_save_api": saveApiStub,
|
|
};
|
|
|
|
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 } = 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"),
|
|
parts: {
|
|
pipes: (base) => ({
|
|
body: base.body,
|
|
after: async (response) => {
|
|
await base.after(response);
|
|
calls.push("pipes:screen");
|
|
},
|
|
}),
|
|
},
|
|
crossPart: async (confirm) => ({
|
|
body: { confirm },
|
|
after: () => calls.push(`cross:after:${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") };
|
|
|
|
// ④ 구조물이 실패하면 그 차례에서 멈춤 · 차례 이름째 알림(429 ⑨) · 노선 없으면 안 나감
|
|
calls.length = 0;
|
|
failStructures = true;
|
|
out.tolerantOk = await saveDesign(
|
|
{
|
|
projectId: "p1",
|
|
routeId: () => 7,
|
|
crossPart: async (confirm) => ({ body: { confirm } }),
|
|
failed: (error) => (calls.push("failed"), (out.tolerantError = error.message)),
|
|
},
|
|
{ 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];
|
|
// ⑨ 노선 초안 동안 계획고 = 노선 0 칸(sub7 DRAFT_ROUTE_ID) — 그 칸이 새 노선에 나가고 두 칸 다 비움
|
|
b05.replace({ station_offsets: { "30.000": 0.2 }, curve_radii: {} }); // 옛 노선(7) 초안
|
|
useDesignStore(null, 0).set("alignmentDraft", {
|
|
station_offsets: { "35.000": 0.4 },
|
|
curve_radii: {},
|
|
});
|
|
calls.length = 0;
|
|
await saveDesign({
|
|
projectId: "p1",
|
|
routeId: () => 7,
|
|
cross: async (confirm) => calls.push(`cross:${confirm}`),
|
|
failed: () => calls.push("failed"),
|
|
});
|
|
out.draftRouteCalls = calls.filter((c) => c.startsWith("alignment:"));
|
|
out.draftRouteLeft = { draft: edit.pendingAlignment(0), old: edit.pendingAlignment(7) };
|
|
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);
|
|
},
|
|
);
|