- 판정 = 저장 노선 대 초안이 그릴 선 정점 1 mm(`routeShapeChanged` · 파이썬 `route_shape_changed` 짝) · 스냅 · 간격 · 지형만 = 아님 - 띠 = 평면(노선 편집) 위 한 줄 · B06 평면 패널로 같이 옮겨짐 · 저장 노선 = 페이지 자료 줄 `pageRoutePlan`(조작마다 안 물음) - [저장] 확인 창 = 잠금 막 · 서버 요청 앞 · 구조물 · 관 · 추가 측점 · 종단 편집 수 · [취소] = 요청 0 · 추천 [적용] 같은 글 · 수 - 저장 뒤 = 응답 `steps.route.route_reset` 지운 수 한 줄 · 초기화면 세션 초안 통째 비움 → 서버 정본으로 다시 세움 · 옛 서버(칸 없음) = 알림 건너뜀 - 시험: 새 test_283_route_reset_confirm(띠 · 확인 창 수 · [취소] · 스냅만 · 거울) · 저장 헬퍼 셋 확인 창 가짜 · 476-2 확인 글 기대 고침 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
205 lines
8.3 KiB
JavaScript
205 lines
8.3 KiB
JavaScript
/* PLAN 448(442-2) 단위검증 헬퍼 — 페이지 설정 저장 차례 · 초안 내보내기 · 기준표 거울 재료.
|
|
* ① 노선 바꾼 [저장](472-6 6-B 한 본문)에도 settings 기본 조각이 실림(화면 몫 아님)
|
|
* ② pageSettingsPart — 초안 없음 = 안 보냄 · 성공 = 최신 응답 캐시에 얹고 초안 비움 · 실패 = 초안 그대로
|
|
* ③ 끝 측점 계획고 편집(시 · 종점 조정) — 칸 값 = 첫 · 끝 측점 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 KEYS = ["settings", "pipes", "structures", "plusStations", "alignment"];
|
|
const save = load(path.join(ROOT, "A00_Common", "design", "design_save.ts"), {
|
|
"../../B05_Profile/B05_Profile_Api_PageSettings": {
|
|
pageSettingsPart: (projectId) => ({
|
|
body: { projectId },
|
|
after: () => calls.push(`settings:${projectId}`),
|
|
}),
|
|
},
|
|
"./design_save_api": {
|
|
SAVE_PART_KEYS: [...KEYS, "cross"],
|
|
DesignSaveError: Error,
|
|
postDesignSave: async (_p, _routeId, _confirm, parts, extra = {}) => {
|
|
const sent = KEYS.filter((key) => parts[key]?.body);
|
|
calls.push(`post:${extra.route ? "route:" : ""}${sent.join(",")}`);
|
|
return { waited: false, steps: { route: { route_id: 9 } } };
|
|
},
|
|
},
|
|
// 492-2 노선 모양 바뀜 확인 창 — 이 시험은 묻지 않음(test_283_route_reset_confirm)
|
|
"../../B05_Profile/B05_Profile_UI_RouteReset": { confirmRouteReset: async () => true },
|
|
"../../B05_Profile/B05_Profile_UI_RouteEdit_Apply": {
|
|
pendingRouteSave: async () => ({ draft: 1 }),
|
|
routeCarryPart: () => ({ carry: {}, cross: { body: {} } }),
|
|
routeSaved: () => undefined,
|
|
routeSaveSummary: () => "",
|
|
rebuildAfterRouteSave: async () => undefined,
|
|
},
|
|
});
|
|
const done = await save.saveDesign({
|
|
projectId: "p1",
|
|
routeId: () => 3,
|
|
parts: { settings: () => (calls.push("own-settings"), null) }, // 노선 바뀌면 화면 몫 건너뜀
|
|
crossPart: async () => (calls.push("cross"), null),
|
|
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": {
|
|
readRouteLatestCache: () => latest,
|
|
writeRouteLatestCache: (_p, value) => (latest = value),
|
|
},
|
|
});
|
|
// 472-6 — 서버 한 창구가 settings 칸을 받고(가짜) 다 저장한 뒤에만 조각 정리(옛 창구 `flushPageSettings` 걷음)
|
|
const flushPageSettings = async (projectId) => {
|
|
const part = api.pageSettingsPart(projectId);
|
|
if (!part) return;
|
|
puts.push(part.body);
|
|
if (mode === "boom") throw new Error("서버 500");
|
|
await part.after?.({ warnings: [], profile_updated: true });
|
|
};
|
|
await 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 flushPageSettings("p1").then(
|
|
() => "ok",
|
|
(error) => error.message,
|
|
);
|
|
out.draftAfterBoom = api.readPageSettings("p1");
|
|
mode = "ok";
|
|
await 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);
|
|
});
|