/* M02 구조물 정의 편집 화면 자료 손질(`M02_SO_Def_Model.ts` · PLAN 48-2) 단위검증 헬퍼 — * TS 를 그 자리에서 트랜스파일해 Node 로 돌리고 결과를 JSON 으로 냄(pytest 가 판정). */ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const source = fs.readFileSync( path.join(__dirname, "..", "..", "M02_MasterTemplete", "StructOptions", "M02_SO_Def_Model.ts"), "utf8", ); const js = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const box = { exports: {} }; new Function("exports", "module", "require", js)(box.exports, box, require); const m = box.exports; const sel = (key, choices) => ({ key, label: key, input: "select", choices: [...choices] }); const type = () => ({ type_id: "k1", group: "A", name: "배수관", options: [sel("a", ["x", "y"]), sel("b", ["p", "q"]), { key: "c", label: "c", input: "number" }], }); const defOf = (over = {}) => ({ 판: 3, 구조물군: { A: "A군", B: "B군" }, 동작: {}, types: [ type(), { ...type(), type_id: "k2", name: "둘째" }, { ...type(), type_id: "k3", group: "B", name: "셋째" }, ], 종류: {}, 표시: {}, 그룹: {}, 배치: {}, 규칙: [], ...over, }); const cells = (t, spots) => Object.fromEntries(m.placeAll(t.options, spots)); const out = {}; // 자리 안 정한 옵션 = 차례대로 채움 · 두 칸이면 셋째는 둘째 줄 out.auto = cells(type()); // 정한 자리 먼저 · 남은 것은 빈 칸으로 · 너비는 칸 수를 안 넘음 out.fixed = cells(type(), { c: { 줄: 1, 칸: 1, 너비: 5 } }); // 안 씀은 격자에서 빠짐 const off = type(); off.options[0].enabled = false; out.off = cells(off); // 겹침 out.overlap = m .overlaps( m.placeAll(type().options, { a: { 줄: 1, 칸: 1, 너비: 2 }, b: { 줄: 1, 칸: 2, 너비: 1 } }), ) .sort(); // 규칙 — 맞는 줄 · 없는 값 · 자기 자신 · 없는 하위 선택지 · 그러면 비었음 · 고르기 아닌 상위 const rd = defOf(); const R = (만약, 그러면) => ({ 종류: "k1", 만약, 그러면 }); out.rules = [ R({ 옵션: "a", 값들: ["x"] }, { 옵션: "b", 선택지: ["p"], 기본값: "p" }), R({ 옵션: "a", 값들: ["zz"] }, { 옵션: "b", 보이기: false }), R({ 옵션: "a", 값들: ["x"] }, { 옵션: "a", 보이기: false }), R({ 옵션: "a", 값들: ["y"] }, { 옵션: "b", 선택지: ["nope"] }), R({ 옵션: "a", 값들: ["x"] }, { 옵션: "b" }), R({ 옵션: "c", 값들: ["3"] }, { 옵션: "b", 보이기: false }), R({ 옵션: "a", 값들: ["x"] }, { 옵션: "b", 선택지: ["p"], 기본값: "q" }), ].map((r) => m.ruleBad(rd, r)); // 이름 — 빈 이름 · 겹침 out.names = m.badNames(["가", " ", "가"]); // 저장 전 검사 — 깨끗한 정의는 빈 목록 · 겹침 · 이름 틀림 · 빈 선택지는 까닭 out.clean = m.problems(defOf()); const broken = defOf({ 배치: { k1: { a: { 줄: 1, 칸: 1, 너비: 2 }, b: { 줄: 1, 칸: 2, 너비: 1 } } }, }); broken.types[0].options[1].label = "a"; broken.types[1].options[0].choices = []; out.broken = m.problems(broken); // 선택지 이름 바꿈 · 뺌 — 기본값 · 규칙 같이 const rn = defOf({ 규칙: [R({ 옵션: "a", 값들: ["x", "y"] }, { 옵션: "b", 선택지: ["p"], 기본값: "p" })], }); const a = rn.types[0].options[0]; const b = rn.types[0].options[1]; a.default = "x"; m.renameChoice(rn, "k1", a, "x", "엑스"); m.renameChoice(rn, "k1", b, "p", "피"); out.rename = [ a.choices, a.default, rn.규칙[0].만약.값들, rn.규칙[0].그러면.선택지, rn.규칙[0].그러면.기본값, ]; m.dropChoice(rn, "k1", b, "피"); out.drop = [b.choices, rn.규칙[0].그러면.선택지, "기본값" in rn.규칙[0].그러면]; // 순서 — 구조물군 키 차례 · 종류는 같은 군 안에서만 const od = defOf(); out.family = [m.stepFamily(od, "A", -1), m.stepFamily(od, "A", 1), Object.keys(od.구조물군)]; out.type = [m.stepType(od, "k2", 1), m.stepType(od, "k1", 1), od.types.map((t) => t.type_id)]; out.base = [ m.baseShown(defOf(), "k1"), m.baseShown(defOf({ 종류: { k1: { 기준측점: false } } }), "k1"), ]; // 목록 순서 바꿈 · 끝은 그대로 const list = [1, 2, 3]; out.step = [m.step(list, 0, -1), m.step(list, 0, 1), list]; // 새 키 — 머리글 · 안 겹침 · 영문 소문자 숫자 const k = m.newKey("opt_", ["opt_aaaaaa"]); out.newKey = [/^opt_[0-9a-z]{4,}$/.test(k), k !== "opt_aaaaaa"]; process.stdout.write(JSON.stringify(out));