test(B05): 48-3 · 48-4 화면 시나리오 — 판 1 전후 대조 · 정의 사본 따라감

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BVPp2o6Mh2FZJr72yW3ofZ
This commit is contained in:
2026-10-05 16:22:18 +09:00
co-authored by Claude Opus 5.5
parent fc54a385df
commit 0a9979e107
@@ -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);
})();