feat(B05·B06): 「구조물 옵션 보이기」에서 끈 옵션을 배수관 · BOX암거 · 세월교 · 기슭막이 하위 폼에서도 가림(21-10 흠 8)

- 시설 폼 칸에 옵션 키 표식(data-option-keys) · 끈 키의 칸은 가림 · 칸이 다 가려진 줄은 줄째
- 키가 여럿인 칸(본체 규격 · 유입구 구조)은 키가 다 꺼졌을 때만 가림 · 독립 기슭막이는 유입 · 유출 채널 키로 넓힘
- 숨긴 칸은 칸 값(제안값 채우기 포함)이 아니라 폼이 받은 옛 값을 저장에 그대로 실음 · 이 폼에는 필수 검사가 없음
- 가리기 코드는 새 파일(_Facility_Hidden)로 분리해 Facility.ts 700줄 아래 유지
- 시험 3개 추가

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TULoa94ZFL26KU6ZqVpjkF
This commit is contained in:
2026-09-29 14:20:06 +09:00
co-authored by Claude Sonnet 5.5
parent 8cd4a78efc
commit eb387d5f84
9 changed files with 260 additions and 27 deletions
@@ -66,6 +66,20 @@ class FakeNode {
(this.listeners.click || []).forEach((h) => h({ target: this }));
}
scrollIntoView() {}
// 시험에 필요한 두 가지 선택자만 — `[data-option-keys]` · `.클래스`
querySelectorAll(selector) {
const match = selector.startsWith("[")
? (n) => n.dataset && n.dataset.optionKeys !== undefined
: (n) => n.classList.contains(selector.slice(1));
const found = [];
const walk = (node) =>
node.children.forEach((kid) => {
if (kid.classList && match(kid)) found.push(kid);
walk(kid);
});
walk(this);
return found;
}
}
globalThis.document = { createElement: (tag) => new FakeNode(tag) };
globalThis.Option = function Option(text, value) {
@@ -101,6 +115,9 @@ function loadTs(file) {
if (STUBS[request]) return STUBS[request];
// 화면 공용 부품(@ui/…) — 이 시험이 안 쓰는 것이라 뭘 불러도 아무 일 안 하는 함수로 막아 둠
if (request.startsWith("@ui/")) return new Proxy({}, { get: () => () => {} });
if (request.startsWith(".") && request.endsWith(".json")) {
return JSON.parse(fs.readFileSync(path.join(path.dirname(full), request), "utf8"));
}
return request.startsWith(".")
? loadTs(path.join(path.dirname(full), `${request}.ts`))
: require(request);
@@ -118,6 +135,8 @@ const { buildSummaryRow } = B05("B05_Profile_UI_Structures_Summary_Row");
const { renderStructureList } = B05("B05_Profile_UI_Structures_List");
const { renderOptionFields } = B05("B05_Profile_UI_Structures_Panel_Options");
const commitModule = B05("B05_Profile_UI_Structures_Panel_Commit");
const { createHiddenOptions } = B05("B05_Profile_UI_Drainage_Facility_Hidden");
const { labeled } = B05("B05_Profile_UI_Drainage_Facility_Fields");
const out = {};
const A = {
@@ -358,6 +377,58 @@ const prev = () => ({
summary_item: A,
});
// 시설 폼 끈 옵션 가리기(PLAN 21-10 흠 8)
{
const tagged = labeled("관종", new FakeNode("select"), "pipe_kind");
const multi = labeled("구조", new FakeNode("select"), ["inlet_type", "inlet_structure"]);
const plain = labeled("메모", new FakeNode("select"));
out.labelTag = [tagged.dataset.optionKeys, multi.dataset.optionKeys, plain.dataset.optionKeys];
const root = new FakeNode("div");
const rowA = new FakeNode("div");
rowA.className = "b05-structure__grid";
const kind = labeled("관종", new FakeNode("select"), "pipe_kind");
const dia = labeled("관경", new FakeNode("select"), "pipe_diameter_mm");
rowA.append(kind, dia);
const rowB = new FakeNode("div");
rowB.className = "b05-structure__grid";
const inlet = labeled("구조", new FakeNode("select"), ["inlet_type", "inlet_structure"]);
const free = labeled("메모", new FakeNode("select")); // 표식 없는 칸은 안 가림
rowB.append(inlet, free);
root.append(rowA, rowB);
const hiddenOf = () => ({
kind: kind.classList.contains("is-option-hidden"),
dia: dia.classList.contains("is-option-hidden"),
rowA: rowA.classList.contains("is-option-hidden"),
inlet: inlet.classList.contains("is-option-hidden"),
free: free.classList.contains("is-option-hidden"),
rowB: rowB.classList.contains("is-option-hidden"),
});
const hidden = createHiddenOptions(root);
hidden.set("pipe", ["pipe_kind"]);
out.hideOne = hiddenOf();
hidden.set("pipe", ["pipe_kind", "pipe_diameter_mm"]);
out.hideRow = hiddenOf(); // 줄의 칸이 다 가려지면 줄째
hidden.set("pipe", ["inlet_structure"]);
out.hideInletPartial = hiddenOf(); // 키가 여럿인 칸은 하나만 꺼지면 그대로
hidden.set("pipe", ["inlet_type"]);
out.hideInlet = hiddenOf(); // 유입구 「구조」는 inlet_type 을 끄면 함께 가려짐(구조 키 포함)
hidden.set("pipe", []);
out.showAll = hiddenOf(); // [모두 보임] 뒤 되돌아옴
// 숨긴 칸은 폼이 받은 옛 값 그대로 — 칸에서 읽은 값(제안값 채우기 포함)이 아니라
hidden.remember({ pipe_kind: "흄관", pipe_diameter_mm: 1000, other: 1 });
hidden.set("pipe", ["pipe_kind", "inlet_type"]);
const read = { pipe_kind: "VR관", pipe_diameter_mm: 800, inlet_type: "제안", other: 2 };
hidden.restore(read);
out.restored = read;
hidden.remember({}); // 옛 값이 없던 숨긴 칸은 값이 안 실림
const read2 = { pipe_kind: "VR관", pipe_diameter_mm: 800 };
hidden.restore(read2);
out.restoredEmpty = read2;
out.revetKeys = [...merge.hiddenFormKeys("revetment", ["form", "height_m", "side"])].sort();
out.pipeKeys = [...merge.hiddenFormKeys("pipe", ["inlet_type"])].sort();
out.otherKeys = [...merge.hiddenFormKeys("box_culvert", ["form"])].sort();
}
async function main() {
const fresh = await saveWith({ previous: null, summary: null, formOptions: { H: 3 } });
out.commitNew = { hasKey: "summary_item" in fresh, options: fresh.options };
@@ -136,3 +136,39 @@ def test_배수유역_화면은_들어올_때_세션_초안_관을_먼저_씀():
body = source.split("const loadSaved", 1)[1].split("/**", 1)[0]
assert "readPendingPipes(" in body and "computeDetailBasins(" in body
assert body.index("readPendingPipes(") < body.index("fetchDetailPipePoints(")
def test_시설_폼_끈_옵션_가리기(out):
# 칸 표식 — 키 하나 · 여럿 · 없음
assert out["labelTag"] == ["pipe_kind", "inlet_type inlet_structure", None]
none = dict(kind=False, dia=False, rowA=False, inlet=False, free=False, rowB=False)
assert out["hideOne"] == {**none, "kind": True}
# 줄의 칸이 다 가려지면 줄째 · 표식 없는 칸은 그대로
assert out["hideRow"] == {**none, "kind": True, "dia": True, "rowA": True}
assert out["hideInletPartial"] == none
assert out["hideInlet"] == {**none, "inlet": True}
assert out["showAll"] == none # [모두 보임] 뒤 되돌아옴
def test_숨긴_칸은_옛_값_그대로_저장에_실림(out):
# 칸에서 읽은 값(VR관 · 제안)이 아니라 폼이 받은 옛 값 — 안 숨긴 칸은 칸 값
assert out["restored"] == {"pipe_kind": "흄관", "pipe_diameter_mm": 800, "other": 2}
# 옛 값이 없던 숨긴 칸은 값이 안 실림(칸의 제안값이 몰래 확정되지 않음)
assert out["restoredEmpty"] == {"pipe_diameter_mm": 800}
def test_끈_키를_폼_칸_키로_넓힘(out):
# 독립 기슭막이는 유입 · 유출 채널 키로 · 유입구 구조는 inlet_structure 까지
assert out["revetKeys"] == sorted(
[
"form",
"height_m",
"side",
"inlet_revet_form",
"outlet_revet_form",
"inlet_revet_height_m",
"outlet_revet_height_m",
]
)
assert out["pipeKeys"] == ["inlet_structure", "inlet_type"]
assert out["otherKeys"] == ["form"]