Merge remote-tracking branch 'origin/sub_laptop_5' into main_laptop_1
This commit is contained in:
@@ -622,12 +622,12 @@ export function createDesignProfile(ctx: DesignProfileContext) {
|
||||
syncDrainageSpans();
|
||||
draw();
|
||||
},
|
||||
/** 사이드 목록에서 고른 구조물을 그래프 알약·측점 세로선 선택에 함께 맞춘다. */
|
||||
setSelectedStructure(structureId: string | null) {
|
||||
// 지금 측점(B06 카드)이 그 구조물 자리면 측점선도 그대로(53-2)
|
||||
/** 고른 구조물에 알약 · 측점선을 맞춤 — 그 자리면 선 그대로(53-2) · keepStation(B06 카드 = 선 주인) = 선 없는 구조물 · 풂도 카드 선 지킴(177-3). */
|
||||
setSelectedStructure(structureId: string | null, keepStation = false) {
|
||||
selectedStructureId = structureId;
|
||||
const own = stationIdAtStructure(structureId) ?? (keepStation ? selectedStationId : null);
|
||||
const keep = structureId !== null && structureIdAtStation(selectedStationId) === structureId;
|
||||
selectedStationId = keep ? selectedStationId : stationIdAtStructure(structureId);
|
||||
selectedStationId = keep ? selectedStationId : own;
|
||||
draw();
|
||||
},
|
||||
/** 이어 공사 시작 기준(측점번호·누가거리 오프셋)을 반영해 측점 라벨·누가거리 표시를 옮긴다. */
|
||||
|
||||
@@ -115,13 +115,14 @@ export async function mountCrossView(
|
||||
const crossHalfWidthField = crossView.field;
|
||||
|
||||
/** 목록·폼에서 고른 시설의 측점 카드를 선택·스크롤 — 가장 가까운 카드로 간다. */
|
||||
const focusStationAt = (chainageM: number): void => {
|
||||
const focusStationAt = (chainageM: number, revealOnly = false): void => {
|
||||
const sections = sectionDetail?.cross_sections;
|
||||
if (!sections?.length) return;
|
||||
const nearest = sections.reduce((best, entry) =>
|
||||
Math.abs(entry.chainage_m - chainageM) < Math.abs(best.chainage_m - chainageM) ? entry : best,
|
||||
);
|
||||
sectionView.focusStation(nearest.station_id);
|
||||
if (revealOnly) sectionView.revealStation(nearest.station_id);
|
||||
else sectionView.focusStation(nearest.station_id);
|
||||
};
|
||||
/** 그 누가거리를 가진 측점 카드(관 찾는 거리 한 값). */
|
||||
const ownerAt = (chainageM: number) =>
|
||||
@@ -186,8 +187,8 @@ export async function mountCrossView(
|
||||
stationInterval: () => stationInterval ?? 20,
|
||||
// 벽·구체를 누르는 중이면 그 누름이 카드를 고른다 — 폼이 구조물을 열며 카드를 먼저
|
||||
// 고르면 뒤이은 카드 선택이 「같은 측점 재클릭 = 해제」로 먹힌다.
|
||||
focusChainage: (chainageM) => {
|
||||
if (!isWallSelecting()) focusStationAt(chainageM);
|
||||
focusChainage: (chainageM, revealOnly) => {
|
||||
if (!isWallSelecting()) focusStationAt(chainageM, revealOnly);
|
||||
},
|
||||
releaseCard: () => releaseCross(),
|
||||
markStructure: (structureId) => sectionView.selectMark(structureId),
|
||||
|
||||
@@ -154,9 +154,9 @@ export function createStructuresSection(
|
||||
}
|
||||
|
||||
/** 측점 범위 표시 갱신 — 시작 = 기준 − 전길이, 종료 = 시작 + 길이(2026-08-19
|
||||
* 사용자 지시: 전/후 분할, 후길이 = 길이 − 전길이 자동). 계산 불가면 "—". */
|
||||
* 사용자 지시: 전/후 분할, 후길이 = 길이 − 전길이 자동). 계산 불가면 "—" · 사면안정군(C)은 줄 숨김(177-5). */
|
||||
function syncRangeDisplay(): void {
|
||||
const computed = hasComputedRange(currentType());
|
||||
const computed = hasComputedRange(currentType()) && currentType()?.group !== "C";
|
||||
rangeWrap.hidden = !computed;
|
||||
if (!computed) return;
|
||||
const step = interval();
|
||||
|
||||
@@ -84,8 +84,8 @@ export interface B06StructuresPanelDeps {
|
||||
wallSideLabels?: (chainageM: number) => { inlet: "좌" | "우"; outlet: "좌" | "우" } | null;
|
||||
/** 측점 간격(m) — 측점번호+잔여거리 환산용. */
|
||||
stationInterval: () => number;
|
||||
/** 목록·폼에서 고른 시설의 측점 카드를 선택·스크롤한다. */
|
||||
focusChainage: (chainageM: number) => void;
|
||||
/** 목록·폼에서 고른 시설의 측점 카드를 선택·스크롤한다(revealOnly = 고르지 않고 스크롤만 · 177-4). */
|
||||
focusChainage: (chainageM: number, revealOnly?: boolean) => void;
|
||||
/** 목록에서 고른 줄을 다시 눌러 풂 — 횡단 카드 선택도 끔(54-8). */
|
||||
releaseCard?: () => void;
|
||||
/** 종단 알약 강조를 목록에서 고른 것에 맞춘다(null = 관 · 해제 — 측점선 짝을 따름). */
|
||||
@@ -216,11 +216,17 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
|
||||
}
|
||||
currentChainageM = null; // 폼 = 이 구조물 — 옛 관을 붙든 채 남지 않게(165-2)
|
||||
// 카드 고름이 측점으로 다시 풀면 같은 측점 관이 먼저 열린다 — 누른 구조물을 넘긴다.
|
||||
preferredId = structure.structure_id ?? null;
|
||||
deps.focusChainage(focusOf(structure));
|
||||
// 노면공 = 카드 하나 안 고름 · 범위 카드 전부 테두리 · 그 카드로 스크롤만 · 폼 유지(177-4)
|
||||
const pave = !coverable(structure);
|
||||
const id = structure.structure_id ?? null;
|
||||
preferredId = id;
|
||||
deps.focusChainage(focusOf(structure), pave);
|
||||
preferredId = null;
|
||||
// 알약은 카드 뒤에 — 카드 측점이 알약을 갈아 끼우거나 끄지 않게(53-2)
|
||||
deps.markStructure?.(structure.structure_id ?? null);
|
||||
deps.markStructure?.(id);
|
||||
// 카드 고름 리스너를 안 거치니 세션 고름은 여기서(B05 로 가도 그 노면공)
|
||||
const at = { chainageM: focusOf(structure), structureId: id };
|
||||
if (pave) selectDesign(deps.projectId, at, { from: "b06" });
|
||||
},
|
||||
getInterval: deps.stationInterval,
|
||||
// 관 추가 — 정본은 [저장]·[확정]에서 나간다. 화면 목록·알약은 바로 세운다.
|
||||
|
||||
@@ -235,7 +235,7 @@ export function createSectionView(
|
||||
|
||||
/**
|
||||
* 고른 구간형 구조물(옹벽 · 독립 흙막이 · 사토장)의 시작~종료 안 횡단 카드 전부에 테두리 강조(106-1).
|
||||
* 노면공(G군)은 테두리 대신 범위 카드 안 그 노면공의 포장층만 강조(152-5) — 겹친 노면공은 범위 표식으로 하나만.
|
||||
* 노면공(G군)도 테두리 + 범위 카드 안 그 노면공의 포장층 강조(152-5 · 177-4) — 겹친 노면공은 범위 표식으로 하나만.
|
||||
* 가장 가까운 한 장의 `--selected` 는 그대로 — 클래스만 갈아 끼워 줌·팬을 지킨다.
|
||||
*/
|
||||
let spanMarks: ReadonlyArray<StructureInstance> = [];
|
||||
@@ -257,7 +257,7 @@ export function createSectionView(
|
||||
: undefined;
|
||||
const span = inSpan(section, mark);
|
||||
const pave = span && isPavement(spanMarkId);
|
||||
card.classList.toggle("b06-cross-card--span", span && !pave);
|
||||
card.classList.toggle("b06-cross-card--span", span);
|
||||
highlightPavement(card, pave ? mark : undefined);
|
||||
};
|
||||
const syncSpanHighlights = (): void => {
|
||||
@@ -576,6 +576,15 @@ export function createSectionView(
|
||||
);
|
||||
}
|
||||
},
|
||||
revealStation(stationId) {
|
||||
// 노면공 고름 = 카드 하나 안 고름 · 옛 카드는 리스너 없이 풂(폼 유지) · 그 카드로 스크롤만(177-4)
|
||||
const previous = selectedStationId;
|
||||
selectedStationId = null;
|
||||
activeAreaKey = null;
|
||||
markCard(previous);
|
||||
profile.showCard(null);
|
||||
revealCard(stationId, "smooth");
|
||||
},
|
||||
selectMark: (structureId, span = true) => {
|
||||
// span 끔 = 알약만 · 카드는 고른 측점 하나(137-4 — 측점 고름 리스너) · 노면공 포장층은 테두리가 아니라 늘 켬(165-2)
|
||||
spanMarkId = span || isPavement(structureId) ? structureId : null;
|
||||
|
||||
@@ -265,7 +265,7 @@ export function createSectionProfile(
|
||||
},
|
||||
selectMark(structureId) {
|
||||
markedId = structureId;
|
||||
profile?.setSelectedStructure(structureId);
|
||||
profile?.setSelectedStructure(structureId, true); // 카드가 측점선 주인 — 선 없는 구조물 · 풂은 카드 선 지킴(177-3)
|
||||
},
|
||||
showCard(section) {
|
||||
if (!profile) return;
|
||||
@@ -280,7 +280,7 @@ export function createSectionProfile(
|
||||
// 일반 측점은 측점선 짝 알약이 없어 꺼짐 — 고른 구조물이 이 측점을 덮으면 그 알약을 지킴(53-1)
|
||||
const marked = markedId ? marks.find((entry) => entry.structure_id === markedId) : undefined;
|
||||
if (!pipe && marked && coversChainage(marked, section.chainage_m))
|
||||
profile.setSelectedStructure(markedId);
|
||||
profile.setSelectedStructure(markedId, true);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ export interface SectionViewController {
|
||||
/** 좌측 구조물 목록에서 고른 측점 카드를 선택하고 화면에 드러낸다 — 재클릭 토글 없음
|
||||
* (2026-08-29 B05/B06 일원화: 목록 클릭 → 해당 카드 스크롤·강조). null = 카드 선택 풂(54-8). */
|
||||
focusStation: (stationId: string | null) => void;
|
||||
/** 카드를 고르지 않고 스크롤만 — 고른 카드는 알림 없이 풂(노면공 목록 고름 · 177-4). */
|
||||
revealStation: (stationId: string) => void;
|
||||
/** 종단 알약 강조를 그 구조물로(null = 풀기 — 카드 측점선 짝을 따름).
|
||||
* `span` = 구간형 범위 안 카드 전부 테두리(기본) · 끄면 카드는 고른 측점 하나(137-4). */
|
||||
selectMark: (structureId: string | null, span?: boolean) => void;
|
||||
|
||||
@@ -202,6 +202,13 @@ const view = {
|
||||
profile.showCard(CARDS.find((card) => card.station_id === this.selected) ?? null);
|
||||
if (this.selected !== previous) this.listener(this.selected);
|
||||
},
|
||||
revealed: null,
|
||||
revealStation(id) {
|
||||
// 노면공 목록 고름 = 카드 하나 안 고름 · 리스너 없이 풂 · 스크롤만(177-4 · Section_View `revealStation`)
|
||||
this.selected = null;
|
||||
profile.showCard(null);
|
||||
this.revealed = id;
|
||||
},
|
||||
focusStation(id) {
|
||||
if (id === null) this.selected = null;
|
||||
else if (this.selected !== id) this.selectStation(id);
|
||||
@@ -214,11 +221,12 @@ const view = {
|
||||
};
|
||||
|
||||
let wallSelecting = false;
|
||||
const focusStationAt = (at) => {
|
||||
const focusStationAt = (at, revealOnly = false) => {
|
||||
const nearest = CARDS.reduce((best, card) =>
|
||||
Math.abs(card.chainage_m - at) < Math.abs(best.chainage_m - at) ? card : best,
|
||||
);
|
||||
view.focusStation(nearest.station_id);
|
||||
if (revealOnly) view.revealStation(nearest.station_id);
|
||||
else view.focusStation(nearest.station_id);
|
||||
};
|
||||
|
||||
const current = { structures: [PAVE] };
|
||||
@@ -248,6 +256,7 @@ async function build(list) {
|
||||
form.id = null;
|
||||
form.pipe = null;
|
||||
view.selected = null;
|
||||
view.revealed = null;
|
||||
view.spanMarkId = null;
|
||||
profile.markedId = null;
|
||||
profile.pill = null;
|
||||
@@ -256,8 +265,8 @@ async function build(list) {
|
||||
shared: { view: () => "cross" },
|
||||
detail: () => ({ cross_sections: CARDS }),
|
||||
stationInterval: () => 20,
|
||||
focusChainage: (at) => {
|
||||
if (!wallSelecting) focusStationAt(at);
|
||||
focusChainage: (at, revealOnly) => {
|
||||
if (!wallSelecting) focusStationAt(at, revealOnly);
|
||||
},
|
||||
markStructure: (id) => view.selectMark(id),
|
||||
onMarks: (next, types) => {
|
||||
@@ -308,7 +317,11 @@ const wallPick = (cardAtM, ownerM) => {
|
||||
await build([PAVE, { structure_id: "sign", type_id: "hazard_sign", chainage_m: 127.86 }]);
|
||||
listPick("pave");
|
||||
out.step1 = snap();
|
||||
cardPick(40); // 고름이 이미 연 범위 첫 카드 재누름(브레인 ORCA ②)
|
||||
out.reveal1 = view.revealed; // 고르지 않고 스크롤만 한 카드(177-4)
|
||||
out.pick1 = selection.readSelection(PID);
|
||||
cardPick(40); // 스크롤만 한 범위 첫 카드 누름
|
||||
out.step2first = snap();
|
||||
cardPick(40); // 고른 범위 카드 재누름(167-3 · 브레인 ORCA ②)
|
||||
out.step2re = snap();
|
||||
cardPick(140);
|
||||
out.step2 = snap();
|
||||
@@ -318,6 +331,7 @@ const wallPick = (cardAtM, ownerM) => {
|
||||
out.step3 = snap();
|
||||
pillPick("pave");
|
||||
out.step4 = snap();
|
||||
out.reveal4 = view.revealed;
|
||||
cardPick(80); // 관 자리 카드 = 관(지금처럼)
|
||||
out.pipeCard = snap();
|
||||
pillPick("pave");
|
||||
@@ -339,6 +353,7 @@ const wallPick = (cardAtM, ownerM) => {
|
||||
await build([PAVE, PAVE2]);
|
||||
listPick("pave2");
|
||||
out.step6 = snap();
|
||||
out.reveal6 = view.revealed;
|
||||
cardPick(160);
|
||||
out.step6b = snap();
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/* 177-3 횡단 카드 ↔ 종단 측점선 헬퍼 — 진짜 design_profile.ts 의 `setSelectedStation` · `setSelectedStructure`
|
||||
* 본문을 떼어(TS 는 그 자리 트랜스파일) 측점 · 구조물 짝 흉내에 붙이고, B06 카드 고름 차례
|
||||
* (showCard → setSelectedStation · 리스너 → selectMark → setSelectedStructure(mark, true))와 B05 기본 길을 돌려
|
||||
* 남은 측점선 id 를 표준출력 JSON 한 벌로.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
|
||||
|
||||
const ROOT = path.join(__dirname, "..", "..");
|
||||
const src = fs.readFileSync(path.join(ROOT, "A00_Common", "design", "design_profile.ts"), "utf8");
|
||||
const method = (name) => {
|
||||
const pattern = String.raw`\r?\n ${name}\([^\r\n]*\) \{\r?\n[\s\S]*?\r?\n \},`;
|
||||
const match = src.match(new RegExp(pattern));
|
||||
if (!match) throw new Error(`본문 없음: ${name}`);
|
||||
return match[0];
|
||||
};
|
||||
const body = `
|
||||
let selectedStationId = null;
|
||||
let selectedStructureId = null;
|
||||
// 관(pipe-80.17)만 측점선(irr-80)이 있음 — 벽 · 노면공은 선 없음(PST stationIdAtStructure)
|
||||
const structureIdAtStation = (stationId) => (stationId === "irr-80" ? "pipe-80.17" : null);
|
||||
const stationIdAtStructure = (structureId) => (structureId === "pipe-80.17" ? "irr-80" : null);
|
||||
const draw = () => {};
|
||||
module.exports = {
|
||||
line: () => selectedStationId,
|
||||
${method("setSelectedStation")}
|
||||
${method("setSelectedStructure")}
|
||||
};`;
|
||||
const js = ts.transpileModule(body, {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", js)(box.exports, box);
|
||||
const dp = box.exports;
|
||||
|
||||
/** B06 카드 고름 — showCard(카드 · 관 측점이면 관 선) 뒤 리스너가 selectMark(mark) = keepStation true. */
|
||||
const b06Card = (lineId, mark) => {
|
||||
dp.setSelectedStation(lineId);
|
||||
dp.setSelectedStructure(mark, true);
|
||||
return dp.line();
|
||||
};
|
||||
/** B05 목록 고름 — 기본값(keepStation 없음). */
|
||||
const b05Pick = (lineId, structureId) => {
|
||||
dp.setSelectedStation(lineId);
|
||||
dp.setSelectedStructure(structureId);
|
||||
return dp.line();
|
||||
};
|
||||
|
||||
const out = {
|
||||
plainCard: b06Card("s40", null), // 일반 카드 — 덮는 것 없음
|
||||
wallCard: b06Card("s100", "wall"), // 벽(선 없는 구조물) 카드
|
||||
paveCard: b06Card("s140", "pave"), // 노면공 범위 안 카드
|
||||
pipeCard: b06Card("irr-80", "pipe-80.17"), // 관 카드 = 관 선
|
||||
pipeFromPlain: b06Card("s60", "pipe-80.17"), // 관 알약 = 관 선으로 옮김
|
||||
// 반대 길 — 종단 측점선 누름 → 카드 고름 → 같은 리스너(selectMark null)
|
||||
profileClick: b06Card("s200", null),
|
||||
// 카드 풂 = showCard(null) → 선 끔
|
||||
release: (dp.setSelectedStation(null), dp.line()),
|
||||
b05Wall: b05Pick("s100", "wall"), // B05 = 지금 그대로(선 없는 구조물 → 선 끔)
|
||||
b05Pipe: b05Pick("s40", "pipe-80.17"),
|
||||
b05Keep: b05Pick("irr-80", "pipe-80.17"),
|
||||
};
|
||||
process.stdout.write(JSON.stringify(out));
|
||||
@@ -32,8 +32,8 @@ def test_범위_강조_클래스는_고름_목록갱신_카드재생성_세_길
|
||||
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
|
||||
# 152-5 노면공(G군) 포장층 · 177-4 범위 카드 테두리도 함께
|
||||
assert 'card.classList.toggle("b06-cross-card--span", span);' in VIEW
|
||||
|
||||
|
||||
def test_측점_고름은_카드_하나_목록_고름은_여럿():
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""165 B06 노면공 고름 일관성 — 조사 K 재현 차례 6단계.
|
||||
|
||||
못박는 것(단계마다 폼 · 알약 · 포장층 · 고른 카드 · 세션 sid 가 같은 것을 가리킴)
|
||||
1. 목록 노면공 = 범위 안 카드 · 포장층 · 알약 · 폼 · sid 모두 노면공(기준점이 범위 경계여도 범위 밖 카드로 안 감).
|
||||
1. 목록 노면공 = 포장층 · 알약 · 폼 · sid 모두 노면공 · 카드는 안 고름(스크롤만 · 177-4 · 기준점이 범위 경계여도 범위 밖 카드로 안 감).
|
||||
2. 범위 안 빈 카드(관 · 덮는 구조물 없음) = 노면공 그대로 · 카드만 옮김(165-1).
|
||||
3. 고른 카드의 벽 = 관 폼 + 관 알약(알약이 꺼진 채 폼만 남지 않음 · 165-2) · 포장층 풂.
|
||||
4. 알약 다시 = 1 과 같음. 관 자리 카드 = 관 · 범위 밖 카드 = 모두 풂(지금처럼).
|
||||
@@ -55,10 +55,12 @@ PIPE = {"form": "pipe-80.17", "pill": "pipe-80.17", "paveLayer": None, "paveCard
|
||||
|
||||
|
||||
def test_1_목록_노면공(out):
|
||||
assert out["step1"] == _pave(40) # 기준점 30 = 범위 경계 → 범위 안 첫 카드(20 아님)
|
||||
assert out["step1"] == _pave(None) # 카드 하나 안 고름(177-4)
|
||||
assert out["reveal1"] == "s40" # 기준점 30 = 범위 경계 → 범위 안 첫 카드(20 아님)
|
||||
|
||||
|
||||
def test_2_범위_안_빈_카드는_노면공_그대로(out):
|
||||
assert out["step2first"] == _pave(40)
|
||||
assert out["step2re"] == _pave(40) # 이미 고른 카드 재누름도 그대로(167-3)
|
||||
assert out["step2"] == _pave(140)
|
||||
assert out["step2b"] == _pave(100)
|
||||
@@ -69,7 +71,7 @@ def test_3_벽은_관_폼과_관_알약_함께(out):
|
||||
|
||||
|
||||
def test_4_알약_다시_관_카드_범위_밖(out):
|
||||
assert out["step4"] == _pave(40)
|
||||
assert out["step4"] == _pave(None)
|
||||
assert out["pipeCard"] == {**PIPE, "card": 80, "sid": None}
|
||||
assert out["outside"] == {
|
||||
"form": None,
|
||||
@@ -83,17 +85,17 @@ def test_4_알약_다시_관_카드_범위_밖(out):
|
||||
|
||||
|
||||
def test_5_B05_복귀(out):
|
||||
assert out["step5"] == _pave(40)
|
||||
assert out["step5"] == _pave(None)
|
||||
|
||||
|
||||
def test_6_겹친_노면공(out):
|
||||
assert out["step6"] == _pave(100, "pave2", 6)
|
||||
assert out["step6"] == _pave(None, "pave2", 6)
|
||||
assert out["step6b"] == _pave(160, "pave2", 6)
|
||||
|
||||
|
||||
def test_배선():
|
||||
panel = _read("B06_Section", "B06_Section_UI_Page_Structures_Panel.ts")
|
||||
assert "deps.focusChainage(focusOf(structure));" in panel
|
||||
assert "deps.focusChainage(focusOf(structure), pave);" in panel
|
||||
assert "return { chainageM, structureId: openId };" in panel
|
||||
# 관 알약은 폼 뒤(폼이 연 구조물을 닫으며 알약을 끔)
|
||||
at = panel.index("section.selectPipeByChainage(hit.pipeChainageM);")
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""177-3 B06 횡단 카드 고름 → 종단 측점 세로선 유지(반대 방향도).
|
||||
|
||||
못박는 것
|
||||
- 카드 고름 뒤 리스너의 selectMark(mark) 가 측점선을 끄지 않음 — 일반 · 벽 · 노면공 카드 = 그 카드 선.
|
||||
- 관 카드 · 관 알약 = 관 측점선 · 카드 풂 = 선 끔.
|
||||
- 종단 측점선 누름(반대 길)도 같은 리스너 — 누른 선 그대로.
|
||||
- B05(keepStation 기본값) = 지금 그대로(선 없는 구조물 → 선 끔).
|
||||
|
||||
Node 헬퍼 `helper_177_3_station_line_keep.cjs` 가 진짜 design_profile.ts 본문을 돌림. 화면은 사용자 확인.
|
||||
"""
|
||||
|
||||
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_177_3_station_line_keep.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["plainCard"] == "s40"
|
||||
assert out["wallCard"] == "s100"
|
||||
assert out["paveCard"] == "s140"
|
||||
|
||||
|
||||
def test_관은_관_측점선(out):
|
||||
assert out["pipeCard"] == "irr-80"
|
||||
assert out["pipeFromPlain"] == "irr-80"
|
||||
|
||||
|
||||
def test_반대_길_종단_측점선_누름도_유지(out):
|
||||
assert out["profileClick"] == "s200"
|
||||
assert out["release"] is None
|
||||
|
||||
|
||||
def test_B05_기본_길은_그대로(out):
|
||||
assert out["b05Wall"] is None
|
||||
assert out["b05Pipe"] == "irr-80"
|
||||
assert out["b05Keep"] == "irr-80"
|
||||
|
||||
|
||||
def test_배선():
|
||||
svp = _read("B06_Section", "B06_Section_UI_Section_View_Profile.ts")
|
||||
assert "profile?.setSelectedStructure(structureId, true);" in svp
|
||||
assert "profile.setSelectedStructure(markedId, true);" in svp
|
||||
cross = _read("A00_Common", "design", "design_view_cross.ts")
|
||||
assert "sectionView.selectMark(mark, false);" in cross
|
||||
b05 = _read("A00_Common", "design", "design_view_profile.ts")
|
||||
assert ", true)" not in "".join(
|
||||
line for line in b05.splitlines() if "setSelectedStructure(" in line
|
||||
)
|
||||
@@ -0,0 +1,74 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""177-4 B06 노면공 목록 · 알약 고름 = 범위 카드 전부 테두리(박스) · 카드 하나 안 고름.
|
||||
|
||||
못박는 것
|
||||
- 노면공 고름 = 범위 안 카드 전부 `b06-cross-card--span`(다른 구간형과 같음) + 포장층 강조 그대로.
|
||||
- 카드 하나 고르기(`--selected`) 없음 · 범위 첫 카드로 스크롤만 · 옛 고른 카드는 리스너 없이 풂(폼 · 알약 유지).
|
||||
- 세션 고름은 노면공 + 스크롤한 카드 측점(B05 로 가도 그 노면공).
|
||||
|
||||
Node 헬퍼 `helper_165_pavement_flow.cjs`(진짜 SPN) 결과를 같이 씀. 화면은 사용자 확인.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
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_165_pavement_flow.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):
|
||||
for step, reveal, layer, cards in (
|
||||
("step1", "reveal1", "pave", 12),
|
||||
("step4", "reveal4", "pave", 12),
|
||||
("step6", "reveal6", "pave2", 6),
|
||||
):
|
||||
snap = out[step]
|
||||
assert snap["card"] is None # --selected 0
|
||||
assert snap["form"] == layer and snap["pill"] == layer # 폼 · 알약 유지
|
||||
assert snap["paveCards"] == cards # 범위 카드 전부
|
||||
assert out[reveal] in ("s40", "s100")
|
||||
assert out["pick1"] == {"chainageM": 40, "structureId": "pave"}
|
||||
|
||||
|
||||
def test_범위_카드_테두리는_노면공도():
|
||||
view = _read("B06_Section", "B06_Section_UI_Section_View.ts")
|
||||
assert 'card.classList.toggle("b06-cross-card--span", span);' in view
|
||||
assert "highlightPavement(card, pave ? mark : undefined);" in view
|
||||
|
||||
|
||||
def test_스크롤만_길은_리스너를_안_거침():
|
||||
view = _read("B06_Section", "B06_Section_UI_Section_View.ts")
|
||||
body = re.search(r"revealStation\(stationId\) \{(.*?)\n \},", view, re.S)
|
||||
assert body, "revealStation 없음"
|
||||
text = body.group(1)
|
||||
assert "selectedStationId = null;" in text
|
||||
assert "profile.showCard(null);" in text
|
||||
assert 'revealCard(stationId, "smooth");' in text
|
||||
assert "stationSelectListener" not in text # 폼을 닫지 않음
|
||||
cross = _read("A00_Common", "design", "design_view_cross.ts")
|
||||
assert "if (revealOnly) sectionView.revealStation(nearest.station_id);" in cross
|
||||
panel = _read("B06_Section", "B06_Section_UI_Page_Structures_Panel.ts")
|
||||
assert "const pave = !coverable(structure);" in panel
|
||||
assert 'if (pave) selectDesign(deps.projectId, at, { from: "b06" });' in panel
|
||||
@@ -0,0 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""177-5 좌측 폼 「측점 범위」 줄 — 사면안정군(C군)만 숨김.
|
||||
|
||||
못박는 것
|
||||
- PNL5 `syncRangeDisplay` 가 C군이면 `rangeWrap.hidden` · 다른 구간형(B 소단배수로 · E 대피소 등)은 그대로.
|
||||
- `hasComputedRange` 자체는 그대로(시작 · 종료 칸 숨김에도 씀).
|
||||
- C군 = 사면안정 6종(정의 기준) · 그 밖 범위 종류는 C 가 아님.
|
||||
|
||||
화면은 사용자 확인.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from B05_Profile.B05_Profile_Structures_Schema import load_structure_types
|
||||
|
||||
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()
|
||||
|
||||
|
||||
def test_범위_줄은_C군만_숨김():
|
||||
panel = _read("B05_Profile", "B05_Profile_UI_Structures_Panel.ts")
|
||||
at = panel.index("function syncRangeDisplay(): void {")
|
||||
body = panel[at : panel.index("\n }", at)]
|
||||
assert (
|
||||
'const computed = hasComputedRange(currentType()) && currentType()?.group !== "C";' in body
|
||||
)
|
||||
assert "rangeWrap.hidden = !computed;" in body
|
||||
|
||||
|
||||
def test_hasComputedRange_는_그대로():
|
||||
types = _read("B05_Profile", "B05_Profile_UI_Structures_Panel_Types.ts")
|
||||
assert "export function hasComputedRange(" in types
|
||||
assert '"C"' not in types[types.index("export function hasComputedRange(") :][:600]
|
||||
|
||||
|
||||
def test_C군은_사면안정_6종_다른_범위_종류는_C_아님():
|
||||
groups = {t.type_id: t.group for t in load_structure_types()}
|
||||
slope = {
|
||||
"retaining_wall",
|
||||
"masonry_wet",
|
||||
"masonry_dry",
|
||||
"soil_guard",
|
||||
"boulder_masonry",
|
||||
"berm",
|
||||
}
|
||||
assert {k for k, g in groups.items() if g == "C"} == slope
|
||||
for kept in ("ditch_berm", "refuge", "work_yard", "guardrail"):
|
||||
assert groups.get(kept) not in (None, "C"), kept
|
||||
Reference in New Issue
Block a user