Files
Aislo/resources/tester/helper_429_save_path.cjs
T
eomsangdonandClaude Opus 5.5 857b2d89d4 fix(445 B06 저장 patch): 바탕 = 캐시 설계 사용자 칸 + 세션 얹음 · 되돌림 null · 옮긴 측점 입력 · 집수정 범위 · 버린 수
- H3 — 횡단 patch 바탕을 캐시 설계 사용자 칸(측점마다 통째)으로 · 세션 고침은 벽 · 단 · 쪽 열쇠마다 합침(mergeCrossPatches)
- M2 — 벽 ↺ · 연동 풂 = 세션 null(안쪽 열쇠 지움) · 추가 벽 0 · 빈 전체 폭 0 을 세션에 남김 · 새 탭 − 가 저장분 단 수에서 출발
- H2 — 옮긴 관 · 구조물(정본 행 없음) = 지반 등 입력(CARRY_INPUT_KEYS)까지 실음 · 화면도 들고 감 · 서버 기본 설계가 그 입력으로 섬
- H5 — 노선 바꾼 [저장] 본문에 카드 선택 · 암선 · 암 경사 · 표준 횡단면 · 풍선 자리
- M1 — 집수정 내공 화면 범위 = 서버 1.0~2.0
- M5 — /save · /confirm 응답에 버린 patch 측점 · 기본 설계 실패 · 측점 맞춤 실패 · 화면 알림
- M7 — kindreset 을 측점 옮김 · 노선 저장에 따라 옮김
- 시험 test_445_save_patch · 기존 시험 꼴 맞춤(318 · 384 · 428 · 429 · fills_empty)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
2026-10-18 18:55:25 +09:00

139 lines
5.5 KiB
JavaScript

/* PLAN 429 단위검증 헬퍼 — [저장] 길 정합.
* ④ 노선 저장 뒤 횡단 저장 = 고침이 없어도 한 번(`saveCachedCrossPatches`)
* ⑨ 추가 측점 = 계산 묶음 입력(`plusStations` · stations 부터) · 가게 초안 바뀜 → 묶음에 넣음(`feedFacilities`)
* TS 를 그 자리에서 트랜스파일해 Node 로 돌림 · 서버 창구 · 화면 모듈은 가짜. 짝 = `test_429_save_path.py`. */
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
globalThis.window = { sessionStorage: null, localStorage: null, setTimeout, clearTimeout };
/** 아무 이름이나 받는 가짜 모듈 — 부르면 undefined. */
const anything = () =>
new Proxy({}, { get: (_target, key) => (key === "__esModule" ? false : () => undefined) });
function loader(stubs, real = () => false) {
const cache = new Map();
const load = (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];
const target = path.join(path.dirname(full), `${request}.ts`);
if (request.startsWith(".") && real(target)) return load(target);
return anything();
};
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
cache.set(full, box.exports);
return box.exports;
};
return load;
}
async function run() {
const out = {};
// ④ 고침이 없어도 횡단 저장 한 번 — 서버가 지금 종단으로 횡단을 다시 셈
const sent = [];
const store = loader({
"./B06_Section_Api_Fetch": {
saveSections: async (...args) => (sent.push(args.map((a) => a ?? null)), {}),
fetchSectionDetail: async () => null,
},
// 445 H5 — 노선 저장 뒤 본문 = 횡단 보기와 같은 칸(세션 고침 · 표준 횡단면 · 풍선)
"./B06_Section_Cross_Design_Session": { crossDesignChoices: () => new Map() },
"./B06_Section_UI_Page_Persist": {
readRockBoundarySession: () => ({}),
readCutSlopeSession: () => ({}),
},
"./logic/B06_Section_UI_Page_Patches": { mergeCrossPatches: (a, b) => [...a, ...b] },
"@util/common_util_mass_haul_balance_view": { balloonOffsetsPayload: () => ({}) },
})(path.join(ROOT, "B06_Section", "B06_Section_Section_Store.ts"));
out.noPatchReturn = await store.saveCachedCrossPatches("p1", 9);
out.noPatchSent = sent;
// ⑨ 묶음 — 추가 측점이 바뀌면 stations 부터 · 같은 값이면 안 셈
const DESIGN = path.join(ROOT, "A00_Common", "design");
const pipeMod = loader({}, () => true)(path.join(DESIGN, "logic", "design_pipeline.ts"));
out.stationsDeps = pipeMod.STAGE_DEPS.stations;
const ran = [];
const stages = {};
for (const name of [...pipeMod.BROWSER_STAGES, ...pipeMod.AFTER_STAGES])
stages[name] = (ctx) => (ran.push(name), { name, plus: ctx.inputs.plusStations ?? null });
stages.follow = () => (ran.push("follow"), null); // 입력 고침 없음
const pipe = pipeMod.createDesignPipeline({ stages });
await pipe.start({
route: null,
alignment: null,
cross: null,
pipes: [],
structures: [],
plusStations: [],
});
ran.length = 0;
await pipe.update("plus", [40]);
out.plusRan = [...ran];
out.plusSeen = pipe.output("stations")?.plus ?? null;
ran.length = 0;
out.sameValue = pipe.update("plus", [40]); // 같은 값 = 차례 안 띄움(null)
await new Promise((resolve) => setTimeout(resolve, 0));
out.sameRan = [...ran];
// ⑨ 먹이기 — 가게 추가 측점 초안이 바뀌면 묶음에 · 비면 저장분 · 되돌리기는 입력과 견줘 다를 때만
let restore = null;
const listeners = new Map();
const values = new Map();
const fakeStore = {
get: (key) => values.get(key) ?? null,
set: (key, value) => (
values.set(key, value),
(listeners.get(key) ?? []).forEach((fn) => fn(value))
),
subscribe: (key, fn) => {
listeners.set(key, [...(listeners.get(key) ?? []), fn]);
return () => undefined;
},
};
const fed = [];
const inputNow = { plusStations: [12] };
const feeder = loader({
"../../B05_Profile/B05_Profile_UI_Profile_History": {
onPageHistory: (hooks) => ((restore = hooks.restore), () => undefined),
},
"./design_save": { savingDesign: () => false },
})(path.join(DESIGN, "design_pipeline_facilities.ts"));
feeder.feedFacilities({
projectId: "p1",
store: fakeStore,
ready: () => true,
gone: () => false,
saved: (key) => (key === "plusStations" ? [12] : []),
input: (key) => inputNow[key] ?? null,
routed: () => false,
feed: (source, value) => fed.push([source, value]),
resync: async () => undefined,
});
fakeStore.set("plusStations", [12, 30]);
fakeStore.set("plusStations", null); // 초안 비움 = 저장분
out.fed = [...fed];
fed.length = 0;
values.set("plusStations", [12, 55]); // 되돌리기 = 세션만 되씀(알림 없음)
restore?.();
out.restoreFed = fed.filter(([source]) => source === "plus");
return out;
}
run().then(
(out) => console.log(JSON.stringify(out)),
(error) => {
console.error(error);
process.exit(1);
},
);