feat(B06): 노면공 고름 = 범위 카드 테두리 대신 포장층 강조 · 카드 누름으로 노면공 열기 끊음(152-5)
- 포장층 범위 표식(종류 · 시작 · 끝) · 고른 노면공 층만 is-active(겹침은 하나만) - 다른 구간형은 --span 그대로 - resolveSelection ③ 덮는 구조물에 거르개 · B06 은 G군 뺌 · 덮는 것 없으면 알약 · 강조 풂 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TNMXVCZXMpHpVvqmFkeChL
This commit is contained in:
@@ -78,10 +78,12 @@ export function coversChainage(structure: SelectableStructure, chainageM: number
|
||||
* ① 구조물 id 가 있고 목록에 있으면 그 구조물 ② 측점 근처 관(계곡 통과 시설이 먼저 —
|
||||
* 같은 측점에 구조물이 겹쳐도 횡단도에 그려지는 것은 관이다) ③ 측점을 덮는 구조물.
|
||||
*/
|
||||
export function resolveSelection(
|
||||
export function resolveSelection<T extends SelectableStructure>(
|
||||
selection: DesignSelection,
|
||||
pipes: ReadonlyArray<{ chainage_m: number }>,
|
||||
structures: ReadonlyArray<SelectableStructure>,
|
||||
structures: ReadonlyArray<T>,
|
||||
/** ③ 덮는 구조물로 열어도 되나 — B06 은 노면공을 뺌(152-5 · ① id 길은 그대로). */
|
||||
coverable: (entry: T) => boolean = () => true,
|
||||
): ResolvedSelection {
|
||||
if (selection.structureId) {
|
||||
const byId = structures.find((entry) => entry.structure_id === selection.structureId);
|
||||
@@ -91,7 +93,7 @@ export function resolveSelection(
|
||||
if (chainageM === null) return { pipeChainageM: null, structureId: null };
|
||||
const pipe = nearestWithin(pipes, chainageM, (entry) => entry.chainage_m);
|
||||
if (pipe) return { pipeChainageM: pipe.chainage_m, structureId: null };
|
||||
const hit = structures.find((entry) => coversChainage(entry, chainageM));
|
||||
const hit = structures.find((entry) => coverable(entry) && coversChainage(entry, chainageM));
|
||||
return { pipeChainageM: null, structureId: hit?.structure_id ?? null };
|
||||
}
|
||||
|
||||
|
||||
@@ -399,7 +399,7 @@ export async function mountCrossView(
|
||||
// 알약이 꺼지므로 연 것으로 다시 세움(관 알약 id = pipe-측점 · 53-1)
|
||||
const pipeAt = structuresPanel.currentChainage();
|
||||
const mark = opened.structureId ?? (pipeAt === null ? null : `pipe-${pipeAt.toFixed(2)}`);
|
||||
if (mark) sectionView.selectMark(mark, false); // 측점 고름 = 카드 하나(137-4) · 목록 길은 뒤에 여럿
|
||||
sectionView.selectMark(mark, false); // 측점 고름 = 카드 하나(137-4) · 덮는 것 없으면 알약 · 강조 풂(152-5)
|
||||
structureSelection.syncInletStructure();
|
||||
// 카드만 고른 경우엔 구조물(벽·구체) 선택을 비운다 — 조정창이 저절로 뜨면 안 된다
|
||||
// (2026-08-29 사용자). 벽을 찍어 카드가 딸려 선택된 경우는 건드리지 않는다.
|
||||
|
||||
@@ -64,6 +64,26 @@ function standardEdges(edges: { left: Edge; right: Edge }, design: CrossDesign)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 고른 노면공의 포장층만 `is-active`(152-5) — 범위 표식(종류 · 시작 · 끝)이 맞는 층. 겹친 노면공은 하나만.
|
||||
* `mark` 없음 = 모두 풂.
|
||||
*/
|
||||
export function highlightPavement(
|
||||
root: ParentNode,
|
||||
mark?: { type_id: string; start_m?: number | null; end_m?: number | null },
|
||||
): void {
|
||||
const [low, high] = [mark?.start_m ?? 0, mark?.end_m ?? 0].sort((a, b) => a - b);
|
||||
for (const layer of root.querySelectorAll<SVGElement>(".b06-chart__pavement")) {
|
||||
const { paveType, paveStart, paveEnd } = layer.dataset;
|
||||
const same =
|
||||
!!mark &&
|
||||
paveType === mark.type_id &&
|
||||
Math.abs(Number(paveStart) - low) < 0.01 &&
|
||||
Math.abs(Number(paveEnd) - high) < 0.01;
|
||||
layer.classList.toggle("is-active", same);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 포장층 — 노면선(= 포장 윗면) 아래로 두께만큼. 노면공 범위(`section.pavement`)면 그 두께 · 무늬 ·
|
||||
* 확폭 켬/끔(여러 건은 아래로 차례로 쌓음) · 없으면 카드 포장 켬일 때 표준 두께 채움만.
|
||||
@@ -92,6 +112,12 @@ export function appendPavementOverlay(
|
||||
const polygon = document.createElementNS(SVG_NS, "polygon");
|
||||
polygon.setAttribute("points", points);
|
||||
polygon.setAttribute("class", "b06-chart__pavement");
|
||||
if (entry) {
|
||||
// 범위 표식 — 고른 노면공의 포장층만 강조(152-5 · 겹친 노면공 가름)
|
||||
polygon.dataset.paveType = entry.type_id;
|
||||
polygon.dataset.paveStart = String(entry.start_m);
|
||||
polygon.dataset.paveEnd = String(entry.end_m);
|
||||
}
|
||||
const spec = entry?.spec;
|
||||
const mark = spec ? PAVE_MARKS[spec] : undefined;
|
||||
if (spec && mark) {
|
||||
|
||||
@@ -159,6 +159,9 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
||||
);
|
||||
|
||||
let markTypes: StructureType[] = [];
|
||||
/** 카드 누름이 덮어 여는 구조물 — 노면공(G군)은 뺌(152-5 · 목록 · 알약 고름은 id 길로 그대로). */
|
||||
const coverable = (entry: StructureInstance): boolean =>
|
||||
markTypes.find((type) => type.type_id === entry.type_id)?.group !== "G";
|
||||
/** 다음 카드 고름이 열 구조물 — 같은 측점에 관이 있어도 누른 것이 먼저다(52-15). */
|
||||
let preferredId: string | null = null;
|
||||
/** 종단 알약 레인에 올릴 목록 — 구조물 정본 + 관 정본(가상 구조물). */
|
||||
@@ -516,7 +519,10 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
||||
},
|
||||
selectionAt: (chainageM) => {
|
||||
const selection = { chainageM, structureId: null };
|
||||
return { chainageM, structureId: section.resolveAt(selection).structureId };
|
||||
return {
|
||||
chainageM,
|
||||
structureId: resolveSelection(selection, pipeFacilities, structures, coverable).structureId,
|
||||
};
|
||||
},
|
||||
prefer: (structureId) => {
|
||||
preferredId = structureId;
|
||||
@@ -527,13 +533,15 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
||||
const selection: DesignSelection = { chainageM, structureId: preferredId };
|
||||
// 계곡 통과 시설이 먼저다 — 같은 측점에 구조물 정본 항목이 겹쳐도 횡단도에
|
||||
// 그려지는 것은 이쪽이다(순서 · 거리는 `resolveSelection` 한 곳).
|
||||
const hit = resolveSelection(selection, pipeFacilities, structures);
|
||||
const hit = resolveSelection(selection, pipeFacilities, structures, coverable);
|
||||
if (hit.pipeChainageM !== null) {
|
||||
currentChainageM = hit.pipeChainageM;
|
||||
refreshSpecDefaults(hit.pipeChainageM);
|
||||
deps.markStructure?.(`pipe-${hit.pipeChainageM.toFixed(2)}`); // 측점선 없는 물넘이포장도(53-2)
|
||||
}
|
||||
section.selectAt(selection);
|
||||
// 폼은 위에서 푼 값으로 엶 — 공용 `selectAt` 은 노면공을 덮어 엶(152-5)
|
||||
if (hit.pipeChainageM !== null) section.selectPipeByChainage(hit.pipeChainageM);
|
||||
else section.selectById(hit.structureId);
|
||||
return { chainageM, structureId: hit.structureId };
|
||||
},
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ import type {
|
||||
HaulEquipmentLimit,
|
||||
SectionDetailResponse,
|
||||
} from "./B06_Section_Api_Fetch";
|
||||
import type { StructureInstance } from "../B05_Profile/B05_Profile_Api_Structures";
|
||||
import type { StructureInstance, StructureType } from "../B05_Profile/B05_Profile_Api_Structures";
|
||||
import type { RockBoundaryControl } from "./B06_Section_UI_Cross_Design";
|
||||
import type { CrossAreaKey } from "./B06_Section_UI_Cross_Areas";
|
||||
import type { FordControl } from "./B06_Section_UI_Cross_Ford_Panel";
|
||||
@@ -65,6 +65,7 @@ import {
|
||||
} from "./B06_Section_UI_Section_Common";
|
||||
import type { SectionViewController } from "./B06_Section_UI_Section_View_Types";
|
||||
import { createFillSlopeNotice } from "./B06_Section_UI_Cross_FillSlope_Notice";
|
||||
import { highlightPavement } from "./B06_Section_UI_Cross_Design_Surface";
|
||||
import { createSectionProfile } from "./B06_Section_UI_Section_View_Profile";
|
||||
import type { DesignPageShared } from "../A00_Common/design/design_shared";
|
||||
|
||||
@@ -231,23 +232,33 @@ export function createSectionView(
|
||||
if (revetOffset) revetOffset.onSelectionChange = syncRevetHighlights;
|
||||
|
||||
/**
|
||||
* 고른 구간형 구조물(노면공 등)의 시작~종료 안 횡단 카드 전부에 테두리 강조(106-1).
|
||||
* 고른 구간형 구조물(옹벽 · 독립 흙막이 · 사토장)의 시작~종료 안 횡단 카드 전부에 테두리 강조(106-1).
|
||||
* 노면공(G군)은 테두리 대신 범위 카드 안 그 노면공의 포장층만 강조(152-5) — 겹친 노면공은 범위 표식으로 하나만.
|
||||
* 가장 가까운 한 장의 `--selected` 는 그대로 — 클래스만 갈아 끼워 줌·팬을 지킨다.
|
||||
*/
|
||||
let spanMarks: ReadonlyArray<StructureInstance> = [];
|
||||
let spanTypes: ReadonlyArray<StructureType> = [];
|
||||
let spanMarkId: string | null = null;
|
||||
const inSpan = (section: CrossSection): boolean => {
|
||||
const mark = spanMarkId ? spanMarks.find((entry) => entry.structure_id === spanMarkId) : null;
|
||||
const inSpan = (section: CrossSection, mark: StructureInstance | undefined): boolean => {
|
||||
const { start_m: start, end_m: end } = mark ?? {};
|
||||
if (start == null || end == null || start === end) return false;
|
||||
const at = section.chainage_m;
|
||||
return at >= Math.min(start, end) - 0.01 && at <= Math.max(start, end) + 0.01;
|
||||
};
|
||||
const applySpan = (card: HTMLElement, section: CrossSection): void => {
|
||||
const mark = spanMarkId
|
||||
? spanMarks.find((entry) => entry.structure_id === spanMarkId)
|
||||
: undefined;
|
||||
const span = inSpan(section, mark);
|
||||
const pave = span && spanTypes.find((type) => type.type_id === mark?.type_id)?.group === "G";
|
||||
card.classList.toggle("b06-cross-card--span", span && !pave);
|
||||
highlightPavement(card, pave ? mark : undefined);
|
||||
};
|
||||
const syncSpanHighlights = (): void => {
|
||||
for (const section of currentDetail?.cross_sections ?? [])
|
||||
document
|
||||
.getElementById(`cross-${section.station_id}`)
|
||||
?.classList.toggle("b06-cross-card--span", inSpan(section));
|
||||
for (const section of currentDetail?.cross_sections ?? []) {
|
||||
const card = document.getElementById(`cross-${section.station_id}`);
|
||||
if (card) applySpan(card, section);
|
||||
}
|
||||
};
|
||||
|
||||
/** 카드 선택 변경 수신자(좌측 구조물 폼). 페이지가 등록한다. */
|
||||
@@ -349,7 +360,7 @@ export function createSectionView(
|
||||
plotBase,
|
||||
cutSlope,
|
||||
);
|
||||
card.classList.toggle("b06-cross-card--span", inSpan(section));
|
||||
applySpan(card, section);
|
||||
return card;
|
||||
};
|
||||
|
||||
@@ -543,6 +554,7 @@ export function createSectionView(
|
||||
},
|
||||
setStructureMarks(structures, types) {
|
||||
spanMarks = structures;
|
||||
spanTypes = types;
|
||||
syncSpanHighlights();
|
||||
profile.setMarks(structures, types);
|
||||
// 검증용 훅 — 알약 레인이 무엇을 받았는지 화면 밖에서 수치로 본다
|
||||
|
||||
@@ -200,6 +200,13 @@
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
/* 고른 노면공의 포장층(152-5) — 범위 카드마다 · 무늬 채움은 inline 이라 무늬 없는 층만 채움이 바뀌고 테두리로 강조 */
|
||||
.b06-chart__pavement.is-active {
|
||||
fill: color-mix(in srgb, var(--color-danger) 30%, transparent);
|
||||
stroke: var(--color-danger);
|
||||
stroke-width: 2.2;
|
||||
}
|
||||
|
||||
/* 노면공 포장사양 무늬(88): 포장 박스 채움 = 바탕 + 무늬 — 콘크리트 빗금 · 아스콘 점 · 쇄석 돌 */
|
||||
.b06-chart__pave-bg {
|
||||
fill: color-mix(in srgb, var(--color-text-secondary) 30%, transparent);
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
/* 152-5 노면공 강조 헬퍼 — 포장층 범위 표식 · 고른 노면공 포장층만 is-active · 카드 누름(③ 덮는 구조물)에서 노면공 뺌을
|
||||
* Node 로 돌려 표준출력 JSON 한 벌로. TS 는 그 자리에서 트랜스파일 · SVG 그릇은 쓰는 만큼만 흉내.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
|
||||
class FakeNode {
|
||||
constructor(tag) {
|
||||
this.tagName = tag;
|
||||
this.children = [];
|
||||
this.dataset = {};
|
||||
this.attrs = {};
|
||||
this.style = {};
|
||||
this._classes = new Set();
|
||||
const self = this;
|
||||
this.classList = {
|
||||
contains: (name) => self._classes.has(name),
|
||||
toggle: (name, on) => (on ? self._classes.add(name) : self._classes.delete(name)),
|
||||
};
|
||||
}
|
||||
setAttribute(key, value) {
|
||||
this.attrs[key] = String(value);
|
||||
if (key === "class") this._classes = new Set(String(value).split(/\s+/).filter(Boolean));
|
||||
}
|
||||
append(...kids) {
|
||||
this.children.push(...kids);
|
||||
}
|
||||
querySelectorAll(selector) {
|
||||
const name = selector.slice(1);
|
||||
const all = (node) => node.children.flatMap((kid) => [kid, ...all(kid)]);
|
||||
return all(this).filter((kid) => kid.classList.contains(name));
|
||||
}
|
||||
}
|
||||
globalThis.document = { createElementNS: (_, tag) => new FakeNode(tag) };
|
||||
|
||||
function loadTs(file, stubs = {}) {
|
||||
const js = ts.transpileModule(fs.readFileSync(file, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
const localRequire = (request) => {
|
||||
if (stubs[request]) return stubs[request];
|
||||
throw new Error(`stub 없음: ${request}`);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
const surface = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Design_Surface.ts"));
|
||||
const selection = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts"), {
|
||||
"../b_page_state": { readState: () => null, writeState: () => {} },
|
||||
});
|
||||
|
||||
const out = {};
|
||||
|
||||
// 포장층 — 같은 측점 노면공 둘(겹침) · 범위 표식
|
||||
const svg = new FakeNode("svg");
|
||||
const edge = (offset) => ({ offset_m: offset, elevation_m: 100 });
|
||||
const design = { carriageway_edges: { left: edge(-2), right: edge(2) }, pavement_thickness_m: 0.2 };
|
||||
const entry = (start, end) => ({
|
||||
type_id: "pavement_concrete",
|
||||
spec: null,
|
||||
thickness_m: 0.2,
|
||||
start_m: start,
|
||||
end_m: end,
|
||||
widening_on: true,
|
||||
});
|
||||
surface.appendPavementOverlay(
|
||||
svg,
|
||||
design,
|
||||
(v) => v,
|
||||
(v) => v,
|
||||
[entry(0, 40), entry(20, 60)],
|
||||
);
|
||||
const layers = () => svg.querySelectorAll(".b06-chart__pavement");
|
||||
const active = () => layers().map((layer) => layer.classList.contains("is-active"));
|
||||
out.marks = layers().map((layer) => ({ ...layer.dataset }));
|
||||
surface.highlightPavement(svg, { type_id: "pavement_concrete", start_m: 60, end_m: 20 });
|
||||
out.pickSecond = active();
|
||||
surface.highlightPavement(svg, { type_id: "gravel_surfacing", start_m: 20, end_m: 60 });
|
||||
out.otherType = active();
|
||||
surface.highlightPavement(svg, { type_id: "pavement_concrete", start_m: 0, end_m: 40 });
|
||||
out.pickFirst = active();
|
||||
surface.highlightPavement(svg, undefined);
|
||||
out.cleared = active();
|
||||
|
||||
// 카드 누름 — ③ 덮는 구조물에서 노면공 뺌 · ① id 길은 그대로
|
||||
const pave = { structure_id: "pave", type_id: "pavement_concrete", start_m: 0, end_m: 40 };
|
||||
const wall = { structure_id: "wall", type_id: "retaining_wall", start_m: 10, end_m: 30 };
|
||||
const coverable = (s) => s.type_id !== "pavement_concrete";
|
||||
const resolve = (sel, list, fn) => selection.resolveSelection(sel, [], list, fn);
|
||||
out.cover = {
|
||||
default: resolve({ chainageM: 5, structureId: null }, [pave]).structureId,
|
||||
skipped: resolve({ chainageM: 5, structureId: null }, [pave], coverable).structureId,
|
||||
wallFirst: resolve({ chainageM: 20, structureId: null }, [pave, wall], coverable).structureId,
|
||||
byId: resolve({ chainageM: 5, structureId: "pave" }, [pave], coverable).structureId,
|
||||
};
|
||||
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -22,19 +22,24 @@ VIEW = _read("B06_Section", "B06_Section_UI_Section_View.ts")
|
||||
|
||||
|
||||
def test_범위_강조_클래스는_고름_목록갱신_카드재생성_세_길에서_칠한다():
|
||||
assert VIEW.count("b06-cross-card--span") == 2 # syncSpanHighlights · buildCrossCard
|
||||
# applySpan 한 곳 — syncSpanHighlights · buildCrossCard 가 부름
|
||||
assert VIEW.count("b06-cross-card--span") == 1
|
||||
assert re.search(
|
||||
r"selectMark: \(structureId, span = true\) => \{\s+(//[^\n]*\s+)?spanMarkId = span \? structureId : null;",
|
||||
VIEW,
|
||||
)
|
||||
assert re.search(r"spanMarks = structures;\s+syncSpanHighlights\(\);", VIEW)
|
||||
assert 'card.classList.toggle("b06-cross-card--span", inSpan(section))' in VIEW
|
||||
assert re.search(
|
||||
r"spanMarks = structures;\s+spanTypes = types;\s+syncSpanHighlights\(\);", VIEW
|
||||
)
|
||||
assert VIEW.count("applySpan(card, section);") == 2
|
||||
# 152-5 노면공(G군)은 테두리 대신 포장층
|
||||
assert 'card.classList.toggle("b06-cross-card--span", span && !pave);' in VIEW
|
||||
|
||||
|
||||
def test_측점_고름은_카드_하나_목록_고름은_여럿():
|
||||
"""137-4 — 측점 고름 리스너는 span 끔 · 목록(노면공) 길 · 알약은 기본(여럿) 그대로."""
|
||||
cross = _read("A00_Common", "design", "design_view_cross.ts")
|
||||
assert "if (mark) sectionView.selectMark(mark, false);" in cross
|
||||
assert "sectionView.selectMark(mark, false);" in cross # 152-5 덮는 것 없으면 null = 풂
|
||||
assert "markStructure: (structureId) => sectionView.selectMark(structureId)," in cross
|
||||
types = _read("B06_Section", "B06_Section_UI_Section_View_Types.ts")
|
||||
assert "selectMark: (structureId: string | null, span?: boolean) => void;" in types
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""152-5 B06 노면공 강조.
|
||||
|
||||
못박는 것
|
||||
- 포장층마다 범위 표식(종류 · 시작 · 끝) — 고른 노면공의 층만 is-active · 겹친 노면공은 하나만 · 없음 = 모두 풂.
|
||||
- 노면공(G군)은 범위 카드 테두리(--span) 대신 포장층 · 다른 구간형은 --span 그대로.
|
||||
- 카드 누름(③ 덮는 구조물)은 노면공을 안 엶 · 목록 · 알약(① id 길)은 그대로 · 덮는 것 없으면 알약 · 강조 풂.
|
||||
|
||||
Node 헬퍼 `helper_152_5_pavement_pick.cjs` 가 SVG 그릇 흉내로 돌림. 화면은 사용자 확인.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.abspath(os.path.join(HERE, "..", ".."))
|
||||
|
||||
|
||||
def _read(*parts: str) -> str:
|
||||
with open(os.path.join(ROOT, *parts), encoding="utf-8") as handle:
|
||||
return handle.read()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def out():
|
||||
proc = subprocess.run(
|
||||
["node", os.path.join(HERE, "helper_152_5_pavement_pick.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_포장층_범위_표식(out):
|
||||
assert out["marks"] == [
|
||||
{"paveType": "pavement_concrete", "paveStart": "0", "paveEnd": "40"},
|
||||
{"paveType": "pavement_concrete", "paveStart": "20", "paveEnd": "60"},
|
||||
]
|
||||
|
||||
|
||||
def test_고른_노면공_층만_강조(out):
|
||||
assert out["pickSecond"] == [False, True] # 시작 · 끝 거꾸로 와도 같은 범위
|
||||
assert out["otherType"] == [False, False]
|
||||
assert out["pickFirst"] == [True, False]
|
||||
assert out["cleared"] == [False, False]
|
||||
|
||||
|
||||
def test_카드_누름은_노면공을_안_엶(out):
|
||||
cover = out["cover"]
|
||||
assert cover["default"] == "pave" # 거르개 없으면 옛 길 그대로(B05)
|
||||
assert cover["skipped"] is None
|
||||
assert cover["wallFirst"] == "wall"
|
||||
assert cover["byId"] == "pave" # 목록 · 알약 고름
|
||||
|
||||
|
||||
def test_화면_배선():
|
||||
view = _read("B06_Section", "B06_Section_UI_Section_View.ts")
|
||||
assert '?.group === "G"' in view
|
||||
assert "highlightPavement(card, pave ? mark : undefined);" in view
|
||||
panel = _read("B06_Section", "B06_Section_UI_Page_Structures_Panel.ts")
|
||||
assert panel.count("resolveSelection(selection, pipeFacilities, structures, coverable)") == 2
|
||||
assert "section.selectAt(selection)" not in panel # 공용 selectAt 은 노면공을 덮어 엶
|
||||
css = _read("B06_Section", "B06_Section_UI_Style_Cross_Areas.css")
|
||||
assert ".b06-chart__pavement.is-active" in css
|
||||
Reference in New Issue
Block a user