/* 467 덧 헬퍼 — 횡단 고름(단추 · 노견 확장 · 소단 깊이 · 암 경계)을 세션에 쓰고 다시 셈 → 세션 칸을 지움(↶) → * 다시 셈 = 고르기 전(정본) 설계와 같은가 · 다시 씀(↷) = 고른 값과 같은가. * node helper_272_choice_undo.mjs <묶음.mjs> * job = { standard, rockDefault, ground } · 짝 = `test_272_choice_undo.py` · 표준출력 JSON 한 벌. */ import { readFileSync } from "node:fs"; import { pathToFileURL } from "node:url"; const memory = () => { const m = new Map(); return { getItem: (k) => (m.has(k) ? m.get(k) : null), setItem: (k, v) => m.set(k, String(v)), removeItem: (k) => m.delete(k), key: (i) => [...m.keys()][i] ?? null, get length() { return m.size; }, clear: () => m.clear(), }; }; globalThis.window = globalThis; globalThis.sessionStorage = memory(); globalThis.localStorage = memory(); const any = new Proxy(function () {}, { get: (_t, k) => (k === Symbol.toPrimitive ? () => 0 : any), apply: () => any, construct: () => any, }); globalThis.document = any; globalThis.requestAnimationFrame = (run) => setTimeout(run, 0); globalThis.fetch = async () => new Response("{}", { status: 404 }); const m = await import(pathToFileURL(process.argv[2]).href); const job = JSON.parse(readFileSync(process.argv[3], "utf8")); const P = "p272undo"; const R = 6; const AT = 40; m.writeState("std-cross", job.standard, P); m.rememberRockBoundaryDefault(P, job.rockDefault); // 소단 사본 = 측점을 덮는 구간(소단 깊이가 먹게) m.writeState( "berm", [{ start_m: 0, end_m: 100, width_m: 1, interval_m: 3, slope_deg: 0, depth_m: 1 }], P, R, ); const canonDetail = () => ({ longitudinal: { length_m: 100, stations: [], profile_alignment: { policy: {} } }, cross_sections: [ { station_id: "station_000000040000", chainage_m: AT, label: "", kind: "regular", center_z: 100, azimuth_deg: 90, center_x: AT, center_y: 0, frame: { left_xy: [0, 1] }, uphill_side: "left", samples: job.ground, design: { design_elevation_m: 100, ground_type: "soil", section_mode: "left_cut", ditch_type: "standard", ditch_side: "left", ditch_choice: null, two_stage_slope: false, }, }, ], }); m.writeState("section-detail", canonDetail(), P, R); // 저장 정본(세션 상세) const alignment = { stations: [{ chainage_m: AT, plan_elevation_m: 100.4 }], samples: [ { chainage_m: 0, elevation_m: 100.4 }, { chainage_m: 100000, elevation_m: 100.4 }, ], balance: { cut_area_m2: 0, fill_area_m2: 0, net_area_m2: 0 }, }; const detail = canonDetail(); // 메모리 상세(가게가 쥔 객체) const refresh = async (target = detail) => { await m.refreshCrossDesigns({ projectId: P, routeId: R, detail: target, edits: { station_offsets: {}, curve_radii: {} }, alignment, }); return JSON.stringify(target.cross_sections[0].design); }; const clear = (name) => sessionStorage.removeItem(m.stateKey(name, P, R)); const pristine = await refresh(canonDetail()); // 고르기 전 = 정본으로 셈 await refresh(); // 메모리 상세 한 번(측점 열쇠 세움) const out = { pristineSame: (await refresh()) === pristine, cases: {} }; const CASES = { choice: [ () => m.writeCrossDesignChoice(P, R, AT, { ground_type: "blasting_rock", section_mode: "right_cut", ditch_side: "right", ditch_type: "standard", paved: false, two_stage_slope: true, ditch_choice: false, }), "crossdesign", ], shoulder: [() => m.writeShoulderExtend(P, R, AT, "left", 1.2), "shoulderext"], bermDepth: [() => m.writeBermDepth(P, R, AT, "left", 2.5), "bermdepth"], rock: [() => m.writeKeyed("rockb", P, R, new Map([["40.00", 3.5]]), ["40.00"]), "rockb"], }; for (const [name, [write, state]] of Object.entries(CASES)) { write(); const picked = await refresh(); clear(state); // ↶ = 세션 칸 지움 const undone = await refresh(); write(); // ↷ = 다시 고름 const redone = await refresh(); clear(state); const again = await refresh(); out.cases[name] = { changed: picked !== pristine, undoIsCanon: undone === pristine, redoIsPicked: redone === picked, undoAgain: again === pristine, }; } process.stdout.write(JSON.stringify(out));