Files
Aislo/resources/tester/helper_442_2_page_settings.cjs
T
eomsangdonandClaude Opus 5.5 990832d629 refactor(472-5 5-C 값 주인 하나): 종단 편집 · 페이지 설정 = 설계 가게 칸 — ↶ ↷ 뒤 두 보기 · 폼 · 기울기 한도 같은 값(N6 · N7)
- 종단 편집: 모듈 사본 EditState 걷음 — 지금 값 = 가게 alignmentDraft(세션) ?? 새 칸 alignmentSaved(노선별 서버 저장분 · 메모리)
  · ↶ ↷ = 다시 읽기만(resync · 같은 값이면 다시 셈 없음) · [저장] 뒤 ↶ 가 저장 전 서버 값으로 안 돌아감 · 노선 없는 창구는 그 안 메모리
- 페이지 설정: 가게 칸 pageSettings(세션 pagesettings) · ↶ ↷ 훅이 가게로 다시 알림 → 폼(B05) · 기울기 한도 · 시작 측점(종단도 한 벌)이 들음(N6)
- 최소고 강제: 결과 통 사본 칸 profile-enforce-min-cover 지움 — 편집 가드가 페이지 설정(초안 ?? 저장 노선)을 바로 읽음(N7)
- 시험: test_472_5_value_owner(옛 코드 빨강 3) · 헬퍼 271 · 442_2 가게 창구 따라감 · 최소고 가드 글 검사 줄
- 위키 design_page 값 · 조작 · 세션 열쇠 · 규칙 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 09:35:58 +09:00

188 lines
7.4 KiB
JavaScript

/* PLAN 448(442-2) 단위검증 헬퍼 — 페이지 설정 저장 차례 · 초안 내보내기 · 기준표 거울 재료.
* ① DESIGN_SAVE_ORDER 에 settings(route 뒤 · pipes 앞) · 노선 바꾼 [저장]에도 settings 기본 창구가 돎
* ② flushPageSettings — 초안 없음 = 안 보냄 · 성공 = 최신 응답 캐시에 얹고 초안 비움 · 실패 = 초안 그대로
* ③ 끝 측점 계획고 편집(시 · 종점 조정) — 칸 값 = 첫 · 끝 측점 station_offsets
* ④ TS 기준표(PROFILE_CRITERIA · SPEED_CHOICES) — 거울 시험이 파이썬과 대조
* TS 를 그 자리에서 트랜스파일해 Node 로 돌림 · 서버 창구는 가짜. 짝 = `test_442_2_page_settings.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 = { setTimeout, clearTimeout };
const anything = () =>
new Proxy({}, { get: (_target, key) => (key === "__esModule" ? false : () => undefined) });
function load(file, stubs) {
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
new Function("exports", "module", "require", js)(box.exports, box, (r) => stubs[r] ?? anything());
return box.exports;
}
/** 상대 경로 import 를 실제 파일로 따라 읽는 로더(순수 logic 묶음용) · 그 밖은 가짜. */
const cache = new Map();
function loadReal(file) {
if (cache.has(file)) return cache.get(file);
const box = { exports: {} };
cache.set(file, box.exports);
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const req = (r) => {
if (!r.startsWith(".")) return anything();
const target = path.resolve(path.dirname(file), r);
return loadReal(fs.existsSync(`${target}.ts`) ? `${target}.ts` : target);
};
new Function("exports", "module", "require", js)(box.exports, box, req);
cache.set(file, box.exports);
return box.exports;
}
async function run() {
const out = {};
// ── ① 저장 차례 ──
const calls = [];
const save = load(path.join(ROOT, "A00_Common", "design", "design_save.ts"), {
"../../B05_Profile/B05_Profile_Api_PageSettings": {
flushPageSettings: async (projectId) => calls.push(`settings:${projectId}`),
},
"../../B05_Profile/B05_Profile_UI_RouteEdit_Apply": {
pendingRouteSave: async () => ({ draft: 1 }),
saveRouteDraft: async () => (calls.push("route"), { route_id: 9 }),
carryAfterRouteSave: async () => [],
routeSaveSummary: () => "",
rebuildAfterRouteSave: async () => undefined,
},
});
out.order = [...save.DESIGN_SAVE_ORDER];
const done = await save.saveDesign({
projectId: "p1",
routeId: () => 3,
steps: { settings: async () => calls.push("own-settings") }, // 노선 바뀌면 화면 몫 건너뜀
cross: async () => calls.push("cross"),
failed: (error) => calls.push(`failed:${error.message}`),
});
out.routeSaveDone = done;
out.routeSaveCalls = calls;
// ── ② 초안 내보내기 ──
const session = new Map();
const puts = [];
let mode = "ok";
let latest = {
route_params: { options: { grade_class: "trunk", design_speed_kph: 20 }, max_grade_pct: 15 },
};
const api = load(path.join(ROOT, "B05_Profile", "B05_Profile_Api_PageSettings.ts"), {
// 값 주인 = 설계 가게 칸(472-5) — 같은 세션 흉내 위에
"../A00_Common/design/design_store": {
useDesignStore: (p) => ({
get: () => (session.has(`pagesettings:${p}`) ? session.get(`pagesettings:${p}`) : null),
set: (_key, value) =>
value === null
? session.delete(`pagesettings:${p}`)
: session.set(`pagesettings:${p}`, value),
}),
draftProjectId: () => "p1",
},
"./B05_Profile_UI_Profile_History": { onPageHistory: () => () => undefined },
"../A00_Common/b_page_state": {
readState: (name, p) => (session.has(`${name}:${p}`) ? session.get(`${name}:${p}`) : null),
writeState: (name, value, p) => session.set(`${name}:${p}`, value),
clearState: (name, p) => session.delete(`${name}:${p}`),
},
"./B05_Profile_Api_Fetch": {
savePageSettings: async (projectId, body) => {
puts.push(body);
if (mode === "boom") throw new Error("서버 500");
return { warnings: [], profile_updated: true };
},
readRouteLatestCache: () => latest,
writeRouteLatestCache: (_p, value) => (latest = value),
},
});
await api.flushPageSettings("p1");
out.putsWithoutDraft = puts.length;
const draft = api.pageSettingsOf({
designSpeed: 30,
terrainType: "normal",
maxGradePct: null,
minVerticalRadius: 300,
minTangentLength: null,
enforcePipeClearance: true,
stationDisplay: { station: 12, cumulative: 240 },
});
out.draft = draft;
api.writePageSettings("p1", draft);
out.restoreFromDraft = api.panelValuesOfDraft(api.readPageSettings("p1"));
mode = "boom";
out.boom = await api.flushPageSettings("p1").then(
() => "ok",
(error) => error.message,
);
out.draftAfterBoom = api.readPageSettings("p1");
mode = "ok";
await api.flushPageSettings("p1");
out.draftAfterOk = api.readPageSettings("p1");
out.latestAfterOk = latest;
out.puts = puts.length;
// ── ③ 끝 측점 계획고 편집 ──
const logic = path.join(ROOT, "B05_Profile", "logic");
const alignment = loadReal(path.join(logic, "B05_Profile_UI_Profile_Alignment.ts"));
const ends = loadReal(path.join(logic, "B05_Profile_EndOffsets.ts"));
const base = {
policy: {
station_interval_m: 20,
curve_radius_ratio: 5,
default_curve_length_m: 20,
curve_tangent_max_ratio: 0.5,
curve_skip_legal_exception: true,
balance_tolerance_percent: 5,
edit_step_m: 0.1,
grade_violation_policy: "warn",
max_grade_pct: 14,
curve_skip_delta_pct: 5,
paved: false,
},
onlyExplicitCurves: false,
basePvi: [
{ chainage_m: 0, elevation_m: 100 },
{ chainage_m: 100, elevation_m: 105 },
{ chainage_m: 200, elevation_m: 108 },
],
chainage: [0, 100, 200],
ground: [100, 105, 108],
stations: [0, 20, 40, 60, 80, 100, 120, 140, 160, 180, 200].map((s) => ({
station_id: `s${s}`,
chainage_m: s,
})),
};
const empty = { station_offsets: {}, curve_radii: {} };
out.endsEmpty = ends.endOffsetsOf(base, empty);
const raised = ends.withEndOffset(base, empty, "start", 1.25);
out.endsRaised = ends.endOffsetsOf(base, raised);
const both = ends.withEndOffset(base, raised, "end", -0.5);
out.endsBoth = ends.endOffsetsOf(base, both);
const plan = alignment.buildAlignment(base, both);
out.planStart = plan.samples[0].elevation_m;
out.planEnd = plan.samples[plan.samples.length - 1].elevation_m;
out.backToZero = ends.endOffsetsOf(base, ends.withEndOffset(base, both, "start", 0));
// ── ④ 기준표 ──
const fields = load(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Panel_Fields.ts"), {});
out.criteria = fields.PROFILE_CRITERIA;
out.speedChoices = fields.SPEED_CHOICES;
process.stdout.write(JSON.stringify(out));
}
run().catch((error) => {
process.stderr.write(String(error.stack || error));
process.exit(1);
});