feat(b06): 도수로 · 돌붙임 띠 고름 · 강조 — 관 고름 꼴(152-4)

- 그린 띠마다 투명 판정면(벽 · 관 판정면 아래 · 보라 선 위) · 고름 키 chute · 강조 setter
- 좌측 「도수로/돌붙임」 상자 강조 · 스크롤 · 상자 고치면 띠 고름 · 자기 측점 폼으로
- 조정창 없음 · B05 3D chute 키 → B06 고름 · 새 시험 test_152_4_chute_pick
- 받는 줄 문자열 시험 54-29 고침(152-2 OWN_HEIGHT_ROW)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F3mdb91125mPoC6tpsQx4P
This commit is contained in:
2026-10-09 19:17:41 +09:00
co-authored by Claude Opus 5.5
parent 1f638cd1c5
commit c924ee2042
12 changed files with 445 additions and 24 deletions
+21 -10
View File
@@ -47,10 +47,15 @@ const EXTRA_ROWS = [
".b06-structure-panel__struct--span",
].join(", ");
/** 유입구·유출구 칸은 폼에 없는 줄만 받는다 — 읽기(좌우 · 상하 · 구체 길이 · 표고) ·
* 단 수 · 옵션 · 집수정 내공(24-4a). */
/** 도수로 · 돌붙임 상자 — 배관 · 박스 · 세월교 chute_add · 물넘이 chute(152-4). */
const CHUTE_BOX =
'fieldset[data-spec-container="chute_add"], fieldset[data-spec-container="chute"]';
/** 연동을 푼 옆 카드의 「이 측점 높이」 줄 — 어느 칸이든 받음(152-2). */
const OWN_HEIGHT_ROW = ".b06-structure-panel__struct--own-height";
/** 유입구·유출구 칸은 폼에 없는 줄만 받는다 — 읽기(좌우 · 상하 · 구체 길이 · 표고) ·
* 단 수 · 옵션 · 집수정 내공(24-4a). */
const SIDE_ROWS = [
OWN_HEIGHT_ROW,
".b06-structure-panel__struct--readout",
@@ -159,14 +164,20 @@ function relocateSideRows(panelRoot: HTMLElement, scroll = true): void {
const side = panelRoot.dataset.side;
// 세월교 · BOX 본체 = 옮길 줄 없이 그 명세의 본체 상자만 강조(66-1) — 날개벽 칸이 든 명세 폼에서 찾음
// 관 = 배관 명세 폼의 「관」 상자만 강조(118-1)
if (side === "body" || side === "pipe") {
const body = (side === "pipe" ? sideSlots?.inlet : sideSlots?.wingInlet)
?.closest(".b05-structure__spec")
?.querySelector<HTMLElement>(
side === "pipe"
? 'fieldset[data-spec-container="pipe"]'
: 'fieldset[data-spec-container="ford"], fieldset[data-spec-container="body"]',
);
// 도수로 띠 = 보이는 명세의 「도수로/돌붙임」 상자(배관 · 박스 · 세월교 chute_add · 물넘이 chute · 152-4)
if (side === "body" || side === "pipe" || side === "chute") {
const body =
side === "chute"
? [...document.querySelectorAll<HTMLElement>(CHUTE_BOX)].find(
(box) => !box.closest("[hidden]"),
)
: (side === "pipe" ? sideSlots?.inlet : sideSlots?.wingInlet)
?.closest(".b05-structure__spec")
?.querySelector<HTMLElement>(
side === "pipe"
? 'fieldset[data-spec-container="pipe"]'
: 'fieldset[data-spec-container="ford"], fieldset[data-spec-container="body"]',
);
if (!body || body.closest("[hidden]")) return;
setGroupLabel(body, "");
body.classList.add("is-active");
+2 -2
View File
@@ -60,8 +60,8 @@ function polygon(
// "own" = 배관과 무관한 **독립 기슭막이**(구조물 정본 D군). 조정창·조작값 저장은
// 배관 벽과 같은 체계를 그대로 쓴다(2026-08-28 사용자: 배관측점처럼 이동).
export type RevetKey = "inlet" | "outlet" | "own" | `extra${number}` | `bextra${number}`;
/** 고름 전용 키 — 벽 키 + 관(118-1 · 조정창 없음 · 강조만). 조정 함수에는 `RevetKey` 만. */
export type RevetPick = RevetKey | "pipe";
/** 고름 전용 키 — 벽 키 + 관(118-1) · 도수로 띠(152-4) — 조정창 없음 · 강조만. 조정 함수에는 `RevetKey` 만. */
export type RevetPick = RevetKey | "pipe" | "chute";
/** 벽 강조를 카드 재생성 없이 갈아 끼우는 setter. */
export type RevetHighlightSetter = (key: RevetPick | null) => void;
@@ -496,11 +496,11 @@ export function buildStructurePanel(
return {
root,
show: (pick) => {
// 관 = 조정창 없음 · 좌측 「관」 상자만 강조(118-1 · 세월교 본체 꼴)
const key = pick === "pipe" ? null : pick;
// 관 · 도수로 띠 = 조정창 없음 · 좌측 「관」 · 「도수로/돌붙임」 상자만 강조(118-1 · 152-4)
const key = pick === "pipe" || pick === "chute" ? null : pick;
current = key;
root.classList.toggle("is-hidden", key === null);
root.dataset.side = pick === "pipe" ? "pipe" : "";
root.dataset.side = pick === "pipe" || pick === "chute" ? pick : "";
document.removeEventListener("keydown", onArrowKey);
drag.detach();
if (!key) return;
+14
View File
@@ -22,6 +22,7 @@ import {
} from "./B06_Section_UI_Cross_Ford_Pavement";
import { culvertWallsStandAt } from "./B06_Section_UI_Cross_Culvert_Wire";
import { raiseWalls } from "./B06_Section_UI_Cross_Wall";
import { attachChuteBandPick } from "./B06_Section_UI_Cross_Wall_Hatch";
import { appendSpoilFillOverlay } from "./B06_Section_UI_Cross_SpoilFill";
import { appendRevetmentOverlay, computeRevetmentLayout } from "./B06_Section_UI_Cross_Revetment";
import {
@@ -572,6 +573,19 @@ export function createCrossSectionCard(
if (highlightRevet) setRevetActive(highlightRevet);
}
raiseWalls(plotLayer); // 흙막이 · 옹벽 안을 지나는 다른 선 가림(66-5)
// 도수로 · 돌붙임 띠 고름 — 관 고름 꼴(152-4)
const setChuteActive = attachChuteBandPick(
plotLayer,
revetOffset && (() => toggleRevet("chute")),
);
if (setChuteActive) {
const baseSetActive = setRevetActive;
setRevetActive = (key) => {
baseSetActive(key);
setChuteActive(key === "chute");
};
setChuteActive(highlightRevet === "chute");
}
// 보라 선 고름 — 가림 · 판정면 차례가 정해진 뒤(137-2).
const lineDesign = section.design;
linePick = attachDesignLinePick({
@@ -424,3 +424,31 @@ export function appendChuteBand(
layer.append(band, clip, group);
}
}
/**
* 도수로 · 돌붙임 띠 고름(152-4 · 118-1 관 고름 꼴) — 그린 띠(관 · 박스 · 세월교 · 물넘이)마다 투명 판정면.
* 벽 판정면 아래 · 보라 선 판정 위(판정면 클래스를 같이 씀). 띠가 없으면 null · 강조 setter 반환.
*/
export function attachChuteBandPick(
layer: SVGElement,
onPick?: () => void,
): ((active: boolean) => void) | null {
const kids = [...layer.children];
const bands = kids.filter((el) => el.classList.contains("b06-chart__culvert-pitching"));
if (!bands.length) return null;
const firstHit = kids.find((el) => el.classList.contains("b06-chart__culvert-revet-hit"));
for (const band of onPick ? bands : []) {
const hit = document.createElementNS(SVG_NS, "polygon");
hit.setAttribute("points", band.getAttribute("points") ?? "");
hit.setAttribute("class", "b06-chart__culvert-revet-hit b06-chart__chute-hit");
const title = document.createElementNS(SVG_NS, "title");
title.textContent = "도수로 · 돌붙임 선택";
hit.append(title);
hit.addEventListener("click", (event) => {
event.stopPropagation();
onPick?.();
});
layer.insertBefore(hit, firstHit ?? null);
}
return (active) => bands.forEach((band) => band.classList.toggle("is-active", active));
}
@@ -23,8 +23,8 @@ import {
type SelectableStructure,
} from "../A00_Common/design/design_selection";
/** 벽 고름이 받는 부재키 — 관은 강조만(118-1) · 집수정·본체는 여기서 빠진다. */
const REVET_KEYS = /^(inlet|outlet|own|extra\d+|bextra\d+|pipe)$/;
/** 벽 고름이 받는 부재키 — 관 · 도수로 띠는 강조만(118-1 · 152-4) · 집수정·본체는 여기서 빠진다. */
const REVET_KEYS = /^(inlet|outlet|own|extra\d+|bextra\d+|pipe|chute)$/;
interface PickTargets {
focusStation: (stationId: string) => void;
@@ -73,10 +73,12 @@ export function wireStructureSelection(
// 푸는 것은 고름이 아님 — 표시를 켜면 뒤이은 카드 고름이 선택 칸에 안 적힘(53-1)
if (key) markWallSelecting();
origRevet(chainageM, key);
if (key) remember?.(ownerChainageOf(chainageM), key);
if (key) panel.showAtChainage(ownerChainageOf(chainageM));
// 도수로 띠는 자기 측점 시설 — 이웃 관 연장 안이어도 그 관 폼을 안 엶(152-4)
const owner = key === "chute" ? chainageM : ownerChainageOf(chainageM);
if (key) remember?.(owner, key);
if (key) panel.showAtChainage(owner);
else if (!nested) {
remember?.(ownerChainageOf(chainageM), ""); // 푼 부재가 B05 ↔ B06 오갈 때 다시 안 열림
remember?.(owner, ""); // 푼 부재가 B05 ↔ B06 오갈 때 다시 안 열림
panel.releasePipe();
}
if (key) syncInletStructure();
@@ -88,14 +90,20 @@ export function wireStructureSelection(
const keyOf = (box: HTMLElement): RevetPick | null => {
const id = box.dataset.specContainer;
if (id === "inlet" || id === "outlet" || id === "pipe") return id;
if (id === "chute_add" || id === "chute") return "chute"; // 도수로 띠(152-4)
return id === "soil_add" ? `extra${Number(box.dataset.specIndex)}` : null;
};
const pickBox = (box: HTMLElement): void => {
const chainageM = panel.currentChainage();
const target = chainageM === null ? undefined : sectionAt(chainageM);
const key = keyOf(box);
if (!key || !target?.culvert || stationControls.revetOffset.highlightFor(target) === key)
return;
const chute =
target?.culvert?.chute ??
target?.box?.chute ??
target?.ford?.chute ??
target?.ford_pavement?.chute;
if (!key || !target || !(key === "chute" ? chute : target.culvert)) return;
if (stationControls.revetOffset.highlightFor(target) === key) return;
stationControls.revetOffset.select(target.chainage_m, key);
refreshCard?.(target.chainage_m);
};
@@ -384,6 +384,18 @@
stroke-linejoin: round;
}
/* 도수로 · 돌붙임 띠 고름(152-4) — 관 고름과 같은 강조 · 판정면은 굵은 투명 테두리로 여유 */
.b06-chart__culvert-pitching.is-active {
fill: color-mix(in srgb, var(--color-danger) 30%, transparent);
stroke: var(--color-danger);
stroke-width: 2.2;
stroke-dasharray: none;
}
.b06-chart__culvert-revet-hit.b06-chart__chute-hit {
stroke: transparent;
stroke-width: 8;
}
/* 암 경계선 상/하/리셋 제어 (B05 측점 선 제어 ▲/▼/↺ 버튼 패턴 재활용) */
.b06-design__rockb-btn {
width: 22px;
@@ -0,0 +1,278 @@
/* PLAN 152-4 단위검증 헬퍼 — B06 도수로 · 돌붙임 띠 고름 · 강조(118-1 관 고름 꼴) · 조정창 숨김 · 3D chute 키.
* 화면 TS 를 그 자리에서 트랜스파일해 흉내 DOM 에서 돌림 · 결과 = 표준출력 JSON 한 벌.
* 입력 = `fixtures/b07_cross_overlay_sections.json` 배관 측점(도수로 상자를 붙인 사본). 짝 = `test_152_4_chute_pick.py`.
*/
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const ROOT = path.join(__dirname, "..", "..");
const cache = new Map();
let STUBS = {
"@ui/ui_template_toast": { showToast: () => undefined },
"@ui/ui_template_elements": { showToast: () => undefined },
};
// ── 화면 그릇 흉내 — 오버레이 · 조정창이 쓰는 만큼만 ─────────────────────────
class FakeNode {
constructor(tag) {
this.tagName = String(tag).toUpperCase();
this.children = [];
this.attrs = {};
this.dataset = {};
this.listeners = {};
this._text = "";
this._classes = new Set();
const self = this;
this.classList = {
add: (...names) => names.forEach((n) => self._classes.add(n)),
remove: (...names) => names.forEach((n) => self._classes.delete(n)),
toggle: (name, force) => {
const on = force === undefined ? !self._classes.has(name) : !!force;
on ? self._classes.add(name) : self._classes.delete(name);
return on;
},
contains: (name) => self._classes.has(name),
};
this.style = { setProperty: () => {} };
}
set className(value) {
this._classes = new Set(String(value).split(/\s+/).filter(Boolean));
}
get className() {
return [...this._classes].join(" ");
}
set textContent(value) {
this._text = String(value);
this.children = [];
}
get textContent() {
return this._text + this.children.map((c) => c.textContent).join("");
}
append(...kids) {
kids.forEach((k) => this.children.push(k));
}
insertBefore(kid, ref) {
const at = ref ? this.children.indexOf(ref) : -1;
at < 0 ? this.children.push(kid) : this.children.splice(at, 0, kid);
return kid;
}
appendChild(kid) {
this.children.push(kid);
return kid;
}
replaceChildren(...kids) {
this.children = kids;
}
setAttribute(name, value) {
this.attrs[name] = String(value);
if (name === "class") this.className = value;
}
getAttribute(name) {
return name === "class" ? this.className : (this.attrs[name] ?? null);
}
addEventListener(type, fn) {
(this.listeners[type] ??= []).push(fn);
}
removeEventListener() {}
click() {
let stopped = false;
const event = { stopPropagation: () => (stopped = true), preventDefault: () => {} };
(this.listeners.click ?? []).forEach((fn) => fn(event));
return stopped;
}
querySelectorAll(selector) {
const found = [];
const walk = (node) =>
node.children.forEach((kid) => {
if (kid.classList && kid.classList.contains(selector.slice(1))) found.push(kid);
walk(kid);
});
walk(this);
return found;
}
querySelector(selector) {
return this.querySelectorAll(selector)[0] ?? null;
}
getClientRects() {
return [];
}
}
globalThis.document = {
createElement: (tag) => new FakeNode(tag),
createElementNS: (_ns, tag) => new FakeNode(tag),
createComment: () => new FakeNode("#comment"),
addEventListener: () => {},
removeEventListener: () => {},
};
globalThis.window = { addEventListener: () => {}, removeEventListener: () => {} };
globalThis.requestAnimationFrame = (fn) => fn();
function loadTs(file) {
const full = path.resolve(file);
if (cache.has(full)) return cache.get(full);
if (full.endsWith(".json")) {
const data = JSON.parse(fs.readFileSync(full, "utf8"));
cache.set(full, data);
return data;
}
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
compilerOptions: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2020,
esModuleInterop: true,
},
}).outputText;
const box = { exports: {} };
cache.set(full, box.exports);
const localRequire = (request) => {
if (STUBS[request]) return STUBS[request];
const alias = { "@util/": "common_util", "@ui/": "ui_template", "@config/": "config" };
for (const [prefix, dir] of Object.entries(alias))
if (request.startsWith(prefix))
return loadTs(resolve(path.join(ROOT, dir, request.slice(prefix.length))));
return loadTs(resolve(path.join(path.dirname(full), request)));
};
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
cache.set(full, box.exports);
return box.exports;
}
const resolve = (base) =>
[base, `${base}.ts`, path.join(base, "index.ts")].find(
(p) => fs.existsSync(p) && fs.statSync(p).isFile(),
) ?? `${base}.ts`;
const B06 = (f) => path.join(ROOT, "B06_Section", `${f}.ts`);
const { computeStoredLayouts } = loadTs(B06("B06_Section_Structure_Layouts"));
const { appendCulvertOverlay } = loadTs(B06("B06_Section_UI_Cross_Culvert"));
const { attachChuteBandPick } = loadTs(B06("B06_Section_UI_Cross_Wall_Hatch"));
const { applyStructurePick } = loadTs(B06("B06_Section_UI_Page_Structure_Pick"));
const fixture = JSON.parse(
fs.readFileSync(path.join(__dirname, "fixtures", "b07_cross_overlay_sections.json"), "utf8"),
);
const sections = fixture.cross_sections;
const plain = sections.find((s) => s.culvert && !s.culvert.hidden_pipe);
const pipeSection = JSON.parse(JSON.stringify(plain));
pipeSection.culvert.chute = { structure: "돌붙임", thickness_m: 0.3 };
const listWith = sections.map((s) => (s === plain ? pipeSection : s));
const out = {};
const cls = (n) => n.className;
// ① 오버레이 + 띠 고름 — 판정면 차례 · 누름 · 강조
function draw(section, list, onPick) {
const layer = new FakeNode("g");
const layout = computeStoredLayouts(section, list).culvert;
const setter = appendCulvertOverlay(
layer,
layout,
(o) => -o * 10,
(e) => -e * 10,
() => {},
false,
);
const picked = [];
const setChute = attachChuteBandPick(layer, onPick ? () => picked.push("chute") : undefined);
return { layer, setter, setChute, picked };
}
const drawn = draw(pipeSection, listWith, true);
const kids = drawn.layer.children;
const bands = kids.filter((n) => n.classList.contains("b06-chart__culvert-pitching"));
const hits = kids.filter((n) => n.classList.contains("b06-chart__culvert-revet-hit"));
const chuteHits = hits.filter((n) => n.classList.contains("b06-chart__chute-hit"));
const stopped = chuteHits[0].click();
drawn.setChute(true);
const on = bands.map((b) => b.classList.contains("is-active"));
drawn.setChute(false);
const off = bands.map((b) => b.classList.contains("is-active"));
out.overlay = {
bands: bands.length,
chuteHits: chuteHits.length,
// 판정면 차례 — 띠 판정면이 관 · 벽 판정면보다 먼저(아래)
hitOrder: hits.map((n) => (n.classList.contains("b06-chart__chute-hit") ? "chute" : "other")),
sameShape: chuteHits.every((h, i) => h.attrs.points === bands[i].attrs.points),
picked: drawn.picked,
stopped,
on,
off,
};
// 고름 배선 없음(조작 없는 카드) — 판정면 없이 강조만 · 띠 없으면 null
const bare = draw(pipeSection, listWith, false);
out.bare = {
hits: bare.layer.children.filter((n) => n.classList.contains("b06-chart__chute-hit")).length,
setter: typeof bare.setChute,
};
out.noChute = draw(plain, sections, true).setChute === null;
// ② B05 3D 띠 고름(chute 키) → B06 고름 길로
const calls = [];
applyStructurePick(
{ at: pipeSection.chainage_m, key: "chute" },
{ cross_sections: [pipeSection] },
{
focusStation: () => {},
refreshCard: () => {},
revetSelect: (c, k) => calls.push(["revet", k]),
fordSelect: (c, k) => calls.push(["ford", k]),
boxSelect: (c, k) => calls.push(["box", k]),
},
);
out.pick = calls;
// ③ 조정창 — show("chute") = 숨김 · side "chute"
const base = loadTs(B06("B06_Section_UI_Cross_Panel_Base"));
STUBS = {
...STUBS,
"./B06_Section_UI_Section_Common": { L: (key) => key },
"./B06_Section_UI_Cross_Panel_Base": {
...base,
bindDragHandle: () => ({ attach: () => {}, detach: () => {} }),
},
};
const { buildStructurePanel } = loadTs(B06("B06_Section_UI_Cross_Structure_Panel"));
const touched = [];
const spy = (name, value) => () => {
touched.push(name);
return value;
};
const panel = buildStructurePanel({
scrollKey: "t152",
adjustFor: spy("adjustFor", { x: 0, d: null, h: null, m: null }),
nudge: spy("nudge"),
nudgeSlope: spy("nudgeSlope"),
nudgeHeight: spy("nudgeHeight"),
heightFor: spy("heightFor", 2),
formFor: spy("formFor", "돌쌓기(메)"),
setForm: spy("setForm"),
reset: spy("reset"),
close: () => {},
structureFor: () => "revet",
basinAdjustFor: () => ({ innerWidthM: 1, innerHeightM: 1.2, lateralM: 0, slopeM: 0 }),
updateBasin: () => {},
nudgeBasinLateral: () => {},
resetBasin: () => {},
canNudge: spy("canNudge", true),
fineMove: spy("fineMove", false),
dragAxes: spy("dragAxes", null),
extraState: () => ({ canAdd: false, count: 0 }),
isLinkedCard: () => false,
basinExtraState: () => ({ canAdd: false, count: 0 }),
hiddenPipe: () => false,
bodyGuard: () => false,
setExtraCount: () => {},
setBasinExtraCount: () => {},
equalizeExtras: () => {},
spanFor: spy("spanFor", null),
setSpan: () => {},
linkState: () => null,
setLinked: () => {},
followGrade: () => true,
setFollowGrade: () => {},
});
panel.show("chute");
out.panel = {
hidden: panel.root.classList.contains("is-hidden"),
side: panel.root.dataset.side,
touched,
};
process.stdout.write(JSON.stringify(out));
+1 -1
View File
@@ -92,7 +92,7 @@ def test_관은_조정창_없음(out) -> None:
def test_좌측_관_상자_강조_스크롤() -> None:
dock = _src("B06_Section/B06_Section_UI_Adjust_Dock.ts")
assert 'if (side === "body" || side === "pipe") {' in dock
assert 'if (side === "body" || side === "pipe" || side === "chute") {' in dock # 152-4 도수로
assert "'fieldset[data-spec-container=\"pipe\"]'" in dock
assert 'body.classList.add("is-active");' in dock
assert 'if (scroll) body.scrollIntoView({ block: "nearest" });' in dock
+66
View File
@@ -0,0 +1,66 @@
"""152-4 도수로 · 돌붙임 띠 고름 · 강조 — 118-1 관 고름 꼴(판정면 · setter · 좌측 상자 · 3D 키).
화면 TS 를 흉내 DOM 에서 돌린 값(`helper_152_4_chute_pick.cjs`) + 배선 문자열.
"""
import json
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[2]
HELPER = Path(__file__).parent / "helper_152_4_chute_pick.cjs"
NODE_MODULES = ROOT / "config" / "node_modules"
pytestmark = pytest.mark.skipif(not NODE_MODULES.is_dir(), reason="프론트엔드 의존성 미설치")
@pytest.fixture(scope="module")
def out() -> dict:
done = subprocess.run( # noqa: S603 — 고정 실행 파일
["node", str(HELPER)], cwd=str(ROOT), capture_output=True, text=True, encoding="utf-8"
)
assert done.returncode == 0, done.stderr
return json.loads(done.stdout)
def _src(relative: str) -> str:
return (ROOT / relative).read_text(encoding="utf-8")
def test_띠마다_판정면_벽_관_아래(out: dict) -> None:
overlay = out["overlay"]
assert overlay["bands"] == overlay["chuteHits"] == 2
assert overlay["sameShape"]
n = overlay["chuteHits"]
assert overlay["hitOrder"][:n] == ["chute"] * n # 벽 · 관 판정면이 위(겹치면 이김)
assert "chute" not in overlay["hitOrder"][n:]
def test_띠_누름_고름_강조(out: dict) -> None:
overlay = out["overlay"]
assert overlay["picked"] == ["chute"] and overlay["stopped"]
assert overlay["on"] == [True, True] and overlay["off"] == [False, False]
assert out["bare"] == {"hits": 0, "setter": "function"}
assert out["noChute"]
def test_조정창_없음_3D_키(out: dict) -> None:
assert out["panel"] == {"hidden": True, "side": "chute", "touched": []}
assert out["pick"] == [["revet", "chute"]]
def test_좌측_상자_강조_배선() -> None:
view = _src("B06_Section/B06_Section_UI_Cross_View.ts")
assert "attachChuteBandPick(" in view and 'toggleRevet("chute")' in view
assert 'setChuteActive(key === "chute");' in view
dock = _src("B06_Section/B06_Section_UI_Adjust_Dock.ts")
assert (
'fieldset[data-spec-container="chute_add"], fieldset[data-spec-container="chute"]' in dock
)
selection = _src("B06_Section/B06_Section_UI_Page_Structures_Selection.ts")
assert 'if (id === "chute_add" || id === "chute") return "chute";' in selection
assert 'const owner = key === "chute" ? chainageM : ownerChainageOf(chainageM);' in selection
css = _src("B06_Section/B06_Section_UI_Style_Cross_Areas.css")
assert ".b06-chart__culvert-pitching.is-active {" in css
@@ -25,7 +25,11 @@ def test_배관_칸은_옵션_읽기_줄을_안_받음() -> None:
assert inlet
assert "struct--options" not in inlet.group(1)
assert "struct--readout" not in inlet.group(1)
assert 'const PIPE_OUTLET_ROWS = ".b06-structure-panel__struct--basin-size";' in dock
# 152-2 — 풀린 연동 카드 「이 측점 높이」 줄만 더 받음
assert (
"const PIPE_OUTLET_ROWS = `${OWN_HEIGHT_ROW}, .b06-structure-panel__struct--basin-size`;"
in dock
)
def test_배관_칸_이름표는_그대로() -> None: