diff --git a/resources/tester/ui_checks/48-3_구조물배치_정의그리기.js b/resources/tester/ui_checks/48-3_구조물배치_정의그리기.js new file mode 100644 index 000000000..1d40bb1a3 --- /dev/null +++ b/resources/tester/ui_checks/48-3_구조물배치_정의그리기.js @@ -0,0 +1,168 @@ +/* 48-3 · 48-4 — B05 · B06 좌측 「구조물 배치」 칸 = 구조물 정의대로 그림 + * 시작 화면: B05 종단설계(#/b05-profile) · 구조물 목록이 있는 프로젝트 · 폼 비어 있음(아무것도 안 고름) + * 부르는 법: orca eval --expression "$(cat 이 파일)" (async 식 · JSON 글을 돌려줌 · 60초 안) + * 쓰는 것: 없음 — 목록 항목은 고르고 다시 눌러 풀기만 · 정의 사본은 **메모리 안 종류 객체만** 고침 + * (파일 · 서버 쓰기 없음) · 끝나면 화면을 다시 읽어(reload) 사본을 버림 + * 기대 수치: + * - 판 1 전후 대조(목록 항목마다): 칸 키 차례 = 정의 옵션 차례(B05 몫 · 보임만) · 격자 = 2열 기본 · + * 규칙 가림 0 · 숨긴 보기 0 · 「지난 정의 값」 상자 숨김 · 기준 측점 보임 · 선택지 밖 옛 값 0 → sameAll = true + * - 정의 사본(레지스트리 종류): 이름 바꿈 · 순서(배치 줄) · 안 씀 · 선택지 줄임 · 규칙(보이기 · 선택지 · 기본값)이 + * 칸에 그대로 → copy.* = true + * - 정의 사본(배수관 · 손코딩 시설 폼): 규칙 선택지 · 기본값 · 가림 → pipe.* = true + */ +(async () => { + const w = (ms) => new Promise((r) => setTimeout(r, ms)); + const sec = document.querySelector(".b05-structure-section"); + if (!sec) return JSON.stringify({ error: "구조물 배치 칸 없음" }); + const url = performance + .getEntriesByType("resource") + .map((e) => e.name) + .filter((n) => n.includes("/B05_Profile/B05_Profile_Api_Structures.ts")) + .pop(); + const api = await import(url); + const types = await api.fetchStructureTypes(true); + const byId = new Map(types.map((t) => [t.type_id, t])); + const [groupSel, typeSel] = sec.querySelectorAll(".b05-structure__grid select"); + const optionRow = [...sec.querySelectorAll(".b05-structure__grid")][1]; + const vis = (n) => n && n.offsetParent !== null; + const facility = sec.querySelector(".b05-drainage__facility"); + const stale = sec.querySelector(".b05-structure__stale"); + const capture = () => { + const typeId = typeSel.value; + const t = byId.get(typeId); + const keys = [...optionRow.querySelectorAll("[data-option-keys]")] + .filter((n) => !n.classList.contains("is-rule-hidden")) + .map((n) => n.dataset.optionKeys); + const groupKeys = [...sec.querySelectorAll(".b05-structure__optgroup [data-option-keys]")].map( + (n) => n.dataset.optionKeys, + ); + const want = t.managed_by + ? [] + : t.options + .filter((o) => o.phase !== "detail" && o.visible !== false && o.enabled !== false) + .map((o) => o.key) + .filter((k) => !groupKeys.includes(k)); + return { + typeId, + keysSame: JSON.stringify(keys) === JSON.stringify(want), + grid: optionRow.style.gridTemplateColumns === "", + ruleHidden: sec.querySelectorAll(".is-rule-hidden").length, + hiddenOptions: sec.querySelectorAll("option[hidden]").length, + stale: !stale.hidden, + anchor: !sec.querySelectorAll(".b05-structure__position-row > label")[2].hidden, + facility: !!t.managed_by && vis(facility), + // 같은 값 — 정의 선택지 밖 옛 값을 남겨 보인 칸 수(판 1 이면 0 = 옛 화면과 같은 값) + keptOld: [...optionRow.querySelectorAll("[data-option-keys] select")].filter((s) => { + const o = t.options.find( + (x) => x.key === s.closest("[data-option-keys]").dataset.optionKeys, + ); + return s.value && o && !o.choices.includes(s.value); + }).length, + }; + }; + const out = { items: [], copy: {}, pipe: {} }; + // ── 판 1 전후 대조 — 목록 항목마다 고르고 · 재고 · 다시 눌러 풂 ── + const items = [...document.querySelectorAll(".b05-route__irregular-list li")]; + for (const li of items) { + (li.querySelector("button") ?? li).click(); + await w(250); + if (typeSel.value) out.items.push(capture()); + (li.querySelector("button") ?? li).click(); // 재선택 = 해제 + await w(150); + } + out.count = out.items.length; + out.sameAll = out.items.every( + (r) => + r.keysSame && + r.grid && + !r.ruleHidden && + !r.hiddenOptions && + !r.stale && + r.anchor && + !r.keptOld, + ); + out.bad = out.items.filter( + (r) => !(r.keysSame && r.grid && !r.ruleHidden && !r.hiddenOptions && !r.stale && r.anchor), + ); + // ── 정의 사본 — 레지스트리 종류(고르기 칸 둘 이상) · 새 폼에서만(저장 없음) ── + const pickType = (t) => { + groupSel.value = t.group; + groupSel.dispatchEvent(new Event("change", { bubbles: true })); + typeSel.value = t.type_id; + typeSel.dispatchEvent(new Event("change", { bubbles: true })); + }; + // 흙막이 — 고르기 칸 하나(설치 측) + 숫자 칸 여럿 + const reg = byId.get("soil_guard"); + const opt = (k) => reg.options.find((o) => o.key === k); + const side = opt("side"); + opt("tiers").label = "바꾼 이름"; + opt("shift_m").enabled = false; + side.choices = side.choices.slice(0, 2); + reg.spots = { tiers: { 줄: 1, 칸: 1, 너비: 2 } }; + reg.rules = [ + { + 종류: "soil_guard", + 만약: { 옵션: "side", 값들: [side.choices[0]] }, + 그러면: { 옵션: "tiers", 기본값: 3 }, + }, + { + 종류: "soil_guard", + 만약: { 옵션: "side", 값들: [side.choices[1]] }, + 그러면: { 옵션: "lift_m", 보이기: false }, + }, + ]; + pickType(reg); + await w(250); + const wrapOf = (k) => sec.querySelector(`[data-option-keys="${k}"]`); + const first = optionRow.querySelector("[data-option-keys]"); + out.copy.type = reg.type_id; + out.copy.rename = wrapOf("tiers")?.querySelector("span")?.textContent.startsWith("바꾼 이름"); + out.copy.order = first?.dataset.optionKeys === "tiers" && first.style.gridColumn === "1 / span 2"; + out.copy.unused = !wrapOf("shift_m"); + const c1 = wrapOf("side").querySelector("select"); + out.copy.choices = + [...c1.children] + .slice(1) + .map((o) => o.value) + .join(",") === side.choices.join(","); + c1.value = side.choices[0]; + c1.dispatchEvent(new Event("change", { bubbles: true })); + await w(100); + out.copy.ruleDefault = wrapOf("tiers").querySelector("input").value === "3"; + c1.value = side.choices[1]; + c1.dispatchEvent(new Event("change", { bubbles: true })); + await w(100); + out.copy.ruleHide = wrapOf("lift_m").classList.contains("is-rule-hidden"); + // ── 정의 사본 — 배수관(손코딩 시설 폼 · 동작 계곡통과시설) ── + const pipe = byId.get("pipe"); + pipe.rules = [ + { + 종류: "pipe", + 만약: { 옵션: "pipe_kind", 값들: ["흄관"] }, + 그러면: { 옵션: "pipe_diameter_mm", 선택지: ["800", "1000"], 기본값: "800" }, + }, + { + 종류: "pipe", + 만약: { 옵션: "pipe_kind", 값들: ["VR관"] }, + 그러면: { 옵션: "wing_wall_type", 보이기: false }, + }, + ]; + pickType(pipe); + await w(300); + const pk = sec.querySelector('[data-option-keys="pipe_kind"] select'); + const pd = () => sec.querySelector('[data-option-keys="pipe_diameter_mm"] select'); + pk.value = "흄관"; + pk.dispatchEvent(new Event("change", { bubbles: true })); + await w(150); + out.pipe.choices = [...pd().options].filter((o) => o.value && !o.hidden).map((o) => o.value); + out.pipe.defaultOk = pd().value === "800"; + pk.value = "VR관"; + pk.dispatchEvent(new Event("change", { bubbles: true })); + await w(150); + out.pipe.wingHidden = sec + .querySelector('[data-option-keys="wing_wall_type"]') + .classList.contains("is-rule-hidden"); + out.pipe.temp = + document.querySelectorAll(".b05-route__irregular-list li").length === items.length; + return JSON.stringify(out); +})();