fix(B06): 목록 고름도 늘 고름 기록 — 같은 카드 안 구조물도 오감 · 새로 고침 뒤 그 폼(231-2)

- 목록 · 알약 · 형상 고름은 노면공이 아니어도 그 구조물 id 를 고름 한 칸에 적음 · 측점 고름이 연 것은 측점만(189-1)
- 새 시험 test_231_2 · test_231_1 · test_177_4 기대값 맞춤

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STqEqztUthVFBmEP18wBSz
This commit is contained in:
2026-10-10 17:52:53 +09:00
co-authored by Claude Opus 5.5
parent 64c9470f95
commit d35743870d
5 changed files with 141 additions and 7 deletions
@@ -227,9 +227,10 @@ export function createB06StructuresPanel(deps: B06StructuresPanelDeps): B06Struc
preferredId = null;
// 알약은 카드 뒤에 — 카드 측점이 알약을 갈아 끼우거나 끄지 않게(53-2)
deps.markStructure?.(id, !openingAtStation); // 측점 고름이 연 것 = 카드 하나 · 범위 테두리 없음(231-1)
// 카드 고름 리스너를 안 거치니 세션 고름은 여기서(B05 로 가도 그 노면공)
// 목록 · 알약 · 형상 고름은 늘 고름 한 칸에 그 구조물 — 같은 카드라 카드 리스너가 안 돌아도
// 오감 · 새로 고침 뒤 그 폼(188-1 · 231-2) · 측점 고름이 연 것은 측점만(카드 리스너 몫 · 189-1)
const at = { chainageM: focusOf(structure), structureId: id };
if (pave) selectDesign(deps.projectId, at, { from: "b06" });
if (!openingAtStation) selectDesign(deps.projectId, at, { from: "b06" });
},
getInterval: deps.stationInterval,
// 관 추가 — 정본은 [저장]·[확정]에서 나간다. 화면 목록·알약은 바로 세운다.
+46 -2
View File
@@ -249,9 +249,10 @@ view.listener = (stationId) => {
view.selectMark(mark, false);
};
async function build(list) {
/** 새로 세움 — keep = 새로 고침(F5 · 세션은 남고 화면 · 폼은 처음부터). */
async function build(list, keep = false) {
current.structures = list;
store.clear();
if (!keep) store.clear();
form.id = null;
form.pipe = null;
view.selected = null;
@@ -305,6 +306,12 @@ async function roundTrip() {
restore();
}
/** 새로 고침(F5) — 첫 마운트 `load().then(restoreStructurePick)` 차례(231-2). */
async function refresh() {
await build(current.structures, true);
restore();
}
function snap() {
const mark = marks.find((entry) => entry.structure_id === view.spanMarkId);
const pave = mark && isPavement(mark.structure_id);
@@ -320,6 +327,11 @@ function snap() {
const cardPick = (at) => view.selectStation(cardAt(at).station_id);
const listPick = (id) => loadForm(structures.find((entry) => entry.structure_id === id));
/** 좌측 목록 관 줄 — B05 패널 `onSelectPipe`(loadPipeForm → onPipeSelect). */
const listPipe = (at) => {
loadPipeForm(at);
binding.onPipeSelect(at);
};
/** B05 3D 부재 고름 — 진짜 `wireStructurePick` 에 B05 와 같은 거름(design_view_profile). */
function pick3d(chainageM) {
@@ -378,6 +390,38 @@ function pick3d(chainageM) {
out.pick3dBare = { sid: snap().sid, form: snap().form };
await roundTrip();
out.pick3dAfter = snap();
// 231-2 목록 고름도 늘 고름 기록 — 카드 120 → 목록 관 · 표지 · 옹벽 → 오감 · 새로 고침
await build([PAVE, WALL, SIGN]);
cardPick(120);
listPipe(80.17);
out.listPipe = snap();
await roundTrip();
out.listPipeTrip = snap();
await refresh();
out.listPipeF5 = snap();
await build([PAVE, SIGN]);
cardPick(120);
listPick("sign"); // 같은 카드 120 — 카드 리스너 안 돎
out.listSign = snap();
await roundTrip();
out.listSignTrip = snap();
await refresh();
out.listSignF5 = snap();
await build([PAVE, WALL]);
cardPick(120);
listPick("wall");
out.listWall = snap();
await roundTrip();
out.listWallTrip = snap();
await build([PAVE, SIGN]);
cardPick(120);
listPick("pave");
await refresh();
out.listPaveF5 = snap();
process.stdout.write(JSON.stringify(out));
})().catch((error) => {
process.stderr.write(String(error?.stack ?? error));
@@ -71,4 +71,5 @@ def test_스크롤만_길은_리스너를_안_거침():
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
# 목록 고름은 노면공뿐 아니라 늘 고름 기록(231-2)
assert 'if (!openingAtStation) selectDesign(deps.projectId, at, { from: "b06" });' in panel
+2 -2
View File
@@ -57,9 +57,9 @@ def test_범위_안_카드_고름_오감해도_노면공_안_켬_폼_그대로(o
def test_같은_카드_목록_고름_뒤_오감도_노면공_안_켬(out) -> None:
assert out["signPick"]["form"] == "sign"
after = out["signAfter"]
assert after["form"] != "pave"
assert after["form"] == "sign" # 목록 고름도 기록 — 오감 뒤 그 폼(231-2)
assert after["paveLayer"] is None
assert after["span"] is None
assert after["span"] != "pave"
assert after["card"] == 120
@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*-
"""231-2 목록 고름도 늘 고름 기록 — 조사 AD 2안.
까닭: B06 목록 고름은 노면공일 때만 고름 한 칸에 적고 그 밖은 카드 리스너에 맡겼음 → 같은 카드 안 구조물을
목록으로 고르면 리스너가 안 돌아 기록이 「측점만」 그대로 → 오감 · 새로 고침 뒤 그 폼이 사라짐.
고침: 목록 · 알약 · 형상 고름은 늘 그 구조물 id 를 적음 · 측점 고름(카드 · 다시 세움)이 연 것은 측점만(189-1).
B05 목록은 원래 늘 적음(`rememberPick(…, structure_id)`). 헬퍼 = `helper_231_1_pave_reload.cjs`.
"""
from __future__ import annotations
import json
import os
import subprocess
import pytest
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.abspath(os.path.join(HERE, "..", ".."))
pytestmark = pytest.mark.skipif(
not os.path.isdir(os.path.join(ROOT, "config", "node_modules")),
reason="프론트엔드 의존성 미설치",
)
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() -> dict:
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
["node", os.path.join(HERE, "helper_231_1_pave_reload.cjs")],
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
)
assert proc.returncode == 0, proc.stderr
return json.loads(proc.stdout)
def test_카드_120_뒤_목록_관_오감_새로고침_관_폼(out) -> None:
for key in ("listPipe", "listPipeTrip", "listPipeF5"):
got = out[key]
assert got["form"] == "pipe-80.17", key
assert got["card"] == 80.17, key
assert got["paveLayer"] is None, key
def test_같은_카드_목록_표지는_기록되고_오감_새로고침_뒤_그대로(out) -> None:
sign = {"form": "sign", "paveLayer": None, "span": "sign", "card": 120, "sid": "sign"}
assert out["listSign"] == sign
assert out["listSignTrip"] == sign
assert out["listSignF5"] == sign
def test_목록_옹벽은_기준_카드_오감_뒤_그대로(out) -> None:
wall = {"form": "wall", "paveLayer": None, "span": "wall", "card": 100, "sid": "wall"}
assert out["listWall"] == wall
assert out["listWallTrip"] == wall
def test_목록_노면공은_새로고침_뒤도_범위_그대로(out) -> None:
assert out["listPaveF5"] == {
"form": "pave",
"paveLayer": "pave",
"span": "pave",
"card": None,
"sid": "pave",
}
def test_측점_고름이_연_것은_측점만(out) -> None:
assert out["wallCard"]["sid"] is None # 카드 하나(189-1)
assert out["wallAfter"]["sid"] is None # 다시 세움도
assert out["cardAfter"]["sid"] is None
def test_배선() -> None:
panel = _read("B06_Section", "B06_Section_UI_Page_Structures_Panel.ts")
assert 'if (!openingAtStation) selectDesign(deps.projectId, at, { from: "b06" });' in panel
assert "if (pave) selectDesign(" not in panel
profile = _read("A00_Common", "design", "design_view_profile.ts")
# B05 목록은 원래 늘 그 구조물 id 를 적음
assert "rememberPick(structureAnchorM(structure), structure.structure_id ?? null)" in profile