Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_2
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/* 두 보기 좌측 패널 **같음 지킴** 헬퍼(계획서 24-2f) — 공용 등록부(`design_panel` · `design_dock` ·
|
||||
* `design_sections`)와 종단 보기 칸 정의를 트랜스파일해 가짜 DOM 위에 두 보기를 같은 견본으로 세운다.
|
||||
* 결과는 표준출력에 JSON 한 벌.
|
||||
/* 두 보기 좌측 패널 **한 벌 지킴** 헬퍼(계획서 24-2f · 24-3b) — 공용 등록부(`design_panel` · `design_dock` ·
|
||||
* `design_sections`)와 두 보기 칸 정의를 트랜스파일해 가짜 DOM 위에 **패널 하나**를 세우고
|
||||
* `setView` 로 보기를 오가며 같은 노드인지 본다(한 벌 `design_shared` 가 하는 그대로). 결과는 JSON 한 벌.
|
||||
*
|
||||
* ⚠ 확장자가 `.cjs` 인 까닭은 `helper_b05_curve_radius_floor.cjs` 머리와 같다.
|
||||
*/
|
||||
@@ -141,6 +141,11 @@ const STUBS = {
|
||||
button.addEventListener("click", () => onClick && onClick());
|
||||
return button;
|
||||
},
|
||||
createInputField: () => ({ root: new FakeNode("div"), input: new FakeNode("input") }),
|
||||
},
|
||||
"./B06_Section_UI_Page_Common": {
|
||||
L: (key) =>
|
||||
({ B06_View_Title: "횡단 보기 설정", B06_Std_Title: "표준 횡단면 설정" })[key] ?? key,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -166,8 +171,9 @@ function loadTs(file) {
|
||||
const panel = loadTs(path.join(ROOT, "A00_Common", "design", "design_panel.ts"));
|
||||
loadTs(path.join(ROOT, "A00_Common", "design", "design_sections.ts"));
|
||||
loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_Panel_Sections.ts"));
|
||||
loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Page_Panel.ts"));
|
||||
|
||||
// ── 같은 견본 — 두 보기가 **같은 구조물 배치 부품**을 받는다 ─────────────────────
|
||||
// ── 견본 — 두 보기 부품을 한 `parts` 에 모은다(한 벌 `design_shared` 와 같은 모양) ──────────
|
||||
function sample() {
|
||||
const root = new FakeNode("section");
|
||||
root.className =
|
||||
@@ -182,9 +188,11 @@ function sample() {
|
||||
return { root, listRoot };
|
||||
}
|
||||
const text = (t) => Object.assign(new FakeNode("div"), { _text: t });
|
||||
function context(view, calls) {
|
||||
const run = (name) => () => calls.push(`${view}:${name}`);
|
||||
return {
|
||||
const calls = [];
|
||||
let current = "profile";
|
||||
const run = (name) => () => calls.push(`${current}:${name}`);
|
||||
const built = panel.createDesignPanel(
|
||||
{
|
||||
projectId: "p",
|
||||
parts: {
|
||||
structures: sample(),
|
||||
@@ -197,44 +205,72 @@ function context(view, calls) {
|
||||
root.hidden = true;
|
||||
},
|
||||
},
|
||||
crossView: { row: text("cross-row") },
|
||||
standardSlot: text("std"),
|
||||
},
|
||||
dock: {
|
||||
reset: { run: run("reset") },
|
||||
save: { run: run("save") },
|
||||
confirm: { run: run("confirm"), enabled: view === "cross" },
|
||||
confirm: { run: run("confirm") },
|
||||
switchView: { run: run("switch") },
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
"profile",
|
||||
);
|
||||
|
||||
const calls = [];
|
||||
const views = {};
|
||||
for (const view of ["profile", "cross"]) {
|
||||
const built = panel.createDesignPanel(context(view, calls), view);
|
||||
const titleOf = (node) => node.children.find((c) => c.tagName === "h3")?.textContent ?? "";
|
||||
const nodes = () => {
|
||||
const dockRoot = built.dock.root;
|
||||
const buttons = dockRoot.querySelectorAll(".ui-btn");
|
||||
return {
|
||||
root: built.root,
|
||||
dock: dockRoot,
|
||||
buttons: dockRoot.querySelectorAll(".ui-btn"),
|
||||
structures: built.root.children.find((c) => c.classList.contains("b05-structure-section")),
|
||||
};
|
||||
};
|
||||
function snapshot() {
|
||||
const { dock, buttons, structures } = nodes();
|
||||
const shown = built.root.children.filter((c) => c !== dock && c.style.display !== "none");
|
||||
buttons.forEach((b) => b.click());
|
||||
const structures = built.root.children.find((c) => c.classList.contains("b05-structure-section"));
|
||||
views[view] = {
|
||||
return {
|
||||
rootClass: built.root.className,
|
||||
sectionIds: panel.panelSectionsFor(view).map((d) => d.id),
|
||||
shownTitles: shown.map(titleOf),
|
||||
structuresFirst: shown[0] === structures,
|
||||
structuresHtml: html(structures),
|
||||
structuresFirst: built.root.children[0] === structures,
|
||||
dockFirstChild: html(dockRoot.children[0]),
|
||||
dockClass: dockRoot.className,
|
||||
dockFirstChild: html(dock.children[0]),
|
||||
dockIsLast: built.root.children[built.root.children.length - 1] === dock,
|
||||
dockLabels: buttons.map((b) => b.textContent),
|
||||
dockActions: buttons.map((b) => b.dataset.dockAction),
|
||||
dockDisabled: buttons.map((b) => b.disabled),
|
||||
dockRowClass: dockRoot.children[dockRoot.children.length - 1].className,
|
||||
dockIsLast: built.root.children[built.root.children.length - 1] === dockRoot,
|
||||
};
|
||||
}
|
||||
// 보기 전환 — 글자가 따라 바뀐다.
|
||||
const switched = panel.createDesignPanel(context("profile", []), "profile");
|
||||
switched.setView("cross");
|
||||
const switchLabelAfter = switched.dock.root
|
||||
.querySelectorAll(".ui-btn")
|
||||
.map((b) => b.textContent)
|
||||
.slice(-1)[0];
|
||||
|
||||
process.stdout.write(JSON.stringify({ views, calls, switchLabelAfter }));
|
||||
const first = nodes();
|
||||
const views = { profile: snapshot() };
|
||||
current = "cross";
|
||||
built.setView("cross");
|
||||
views.cross = snapshot();
|
||||
const afterCross = nodes();
|
||||
current = "profile";
|
||||
built.setView("profile");
|
||||
views.back = snapshot();
|
||||
const afterBack = nodes();
|
||||
const same = (a, b) =>
|
||||
a.root === b.root &&
|
||||
a.dock === b.dock &&
|
||||
a.structures === b.structures &&
|
||||
a.buttons.length === b.buttons.length &&
|
||||
a.buttons.every((button, index) => button === b.buttons[index]);
|
||||
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
views,
|
||||
calls,
|
||||
sameNodes: { cross: same(first, afterCross), back: same(first, afterBack) },
|
||||
childCount: built.root.children.length,
|
||||
sectionIds: {
|
||||
profile: panel.panelSectionsFor("profile").map((d) => d.id),
|
||||
cross: panel.panelSectionsFor("cross").map((d) => d.id),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -57,7 +57,8 @@ def test_거리_정의는_한_곳뿐이고_옛_0_05는_없다():
|
||||
assert definitions == ["design_selection.ts"]
|
||||
for name, folder in (
|
||||
("B05_Profile_UI_Structures_Panel.ts", "B05_Profile"),
|
||||
("B05_Profile_UI_Page.ts", "B05_Profile"),
|
||||
("design_view_profile.ts", "A00_Common/design"),
|
||||
("design_view_cross.ts", "A00_Common/design"),
|
||||
("B05_Profile_UI_Selection.ts", "B05_Profile"),
|
||||
("B06_Section_UI_Page_Structures_Panel.ts", "B06_Section"),
|
||||
("B06_Section_UI_Page_Structure_Pick.ts", "B06_Section"),
|
||||
@@ -178,14 +179,14 @@ def test_횡단도_벽_구체_눌러도_폼은_같은_길_하나():
|
||||
assert panel.count("panel.showAtChainage(") == 3 # 기슭막이 · 세월교 · BOX
|
||||
assert panel.count("panel.releasePipe()") == 3 # 벽 선택을 풀 때는 관 폼만 비움
|
||||
assert "section.selectAt(" in panel
|
||||
page = _read("B06_Section", "B06_Section_UI_Page.ts")
|
||||
page = _read("A00_Common/design", "design_view_cross.ts")
|
||||
assert "writeStructurePick" not in page
|
||||
assert "selectDesign(" in page
|
||||
|
||||
|
||||
def test_고름은_selectDesign_한_길로_적는다():
|
||||
for folder, name in (
|
||||
("B05_Profile", "B05_Profile_UI_Page.ts"),
|
||||
("A00_Common/design", "design_view_profile.ts"),
|
||||
("B05_Profile", "B05_Profile_UI_Structure_Pick_Session.ts"),
|
||||
("B06_Section", "B06_Section_UI_Page_Structures_Panel.ts"),
|
||||
):
|
||||
|
||||
@@ -23,9 +23,12 @@ B05_PANEL = (PROJECT_ROOT / "A00_Common" / "design" / "design_profile.ts").read_
|
||||
encoding="utf-8"
|
||||
)
|
||||
# 설계 재계산 자리는 700줄 제한으로 `_UI_Page_Design_Sync` 로 떨어져 나갔다(2026-09-13).
|
||||
B06_PAGE = (PROJECT_ROOT / "B06_Section" / "B06_Section_UI_Page.ts").read_text(encoding="utf-8") + (
|
||||
PROJECT_ROOT / "B06_Section" / "B06_Section_UI_Page_Design_Sync.ts"
|
||||
).read_text(encoding="utf-8")
|
||||
# 횡단 보기 본체는 한 페이지 보기 파일로 옮겼다(계획서 24-3b).
|
||||
B06_PAGE = (PROJECT_ROOT / "A00_Common" / "design" / "design_view_cross.ts").read_text(
|
||||
encoding="utf-8"
|
||||
) + (PROJECT_ROOT / "B06_Section" / "B06_Section_UI_Page_Design_Sync.ts").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
|
||||
def _stale(profile_samples, sections, tol=1e-3):
|
||||
|
||||
@@ -21,7 +21,8 @@ from B05_Profile.B05_Profile_Structures_Schema import load_structure_types # no
|
||||
_PANEL = PROJECT_ROOT / "B05_Profile" / "B05_Profile_UI_Structures_Panel.ts"
|
||||
# 옵션 칸 그리기는 700줄 제한으로 이 조각으로 옮겨 감(2026-09-29 · PLAN 21-4)
|
||||
_PANEL_OPTIONS = PROJECT_ROOT / "B05_Profile" / "B05_Profile_UI_Structures_Panel_Options.ts"
|
||||
_B06 = PROJECT_ROOT / "B06_Section" / "B06_Section_UI_Page_Structures_Panel.ts"
|
||||
# 폼은 두 보기가 한 벌을 쓴다 — 상세 칸은 한 벌이 보기에 따라 그린다(계획서 24-3b).
|
||||
_SHARED = PROJECT_ROOT / "A00_Common" / "design" / "design_shared.ts"
|
||||
_B05 = PROJECT_ROOT / "B05_Profile" / "B05_Profile_UI_Panel.ts"
|
||||
|
||||
|
||||
@@ -30,7 +31,9 @@ def _type(type_id: str):
|
||||
|
||||
|
||||
def test_B06_은_상세_칸까지_받는다():
|
||||
assert "includeDetail: true" in _B06.read_text(encoding="utf-8")
|
||||
assert 'includeDetail: () => shared.view() === "cross"' in _SHARED.read_text(encoding="utf-8")
|
||||
# 그릴 때마다 지금 보기로 가린다 — 만들 때 한 번 굳히면 한 벌 폼이 보기를 못 따라간다.
|
||||
assert 'typeof detail === "function" ? detail()' in _PANEL.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_B05_는_종전대로_유무_종류_위치만():
|
||||
|
||||
@@ -88,11 +88,16 @@ def test_저장이_창구를_다섯_다_거친다():
|
||||
|
||||
|
||||
def test_두_페이지가_저장_한_줄기를_탄다():
|
||||
"""B05 [임시저장] · B06 [저장] · [확정]이 창구를 따로 부르지 않는다(계획서 24-2a)."""
|
||||
"""[저장] · [확정]은 한 페이지 한 벌이 두 보기 몫을 모아 저장 한 줄기 한 번으로 보낸다
|
||||
(계획서 24-2a · 24-3b) — 보기 몫은 창구를 따로 부르지 않는다."""
|
||||
shared = (ROOT / "A00_Common" / "design" / "design_shared.ts").read_text(encoding="utf-8")
|
||||
actions = (ROOT / "B05_Profile" / "B05_Profile_UI_Page_Actions.ts").read_text(encoding="utf-8")
|
||||
persist = (ROOT / "B06_Section" / "B06_Section_UI_Page_Persist.ts").read_text(encoding="utf-8")
|
||||
for name, text in (("B05", actions), ("B06", persist)):
|
||||
assert "saveDesign(" in text, f"{name} 가 저장 한 줄기를 안 탐"
|
||||
assert shared.count("await saveDesign(") == 1, "저장 한 줄기를 한 자리에서만 부름"
|
||||
assert "saveSharedDesign(shared, true)" in shared, "[확정]이 한 줄기를 안 탐"
|
||||
assert 'await shared.ensureView("cross")' in shared, "종단 보기 [확정]이 횡단 자료를 못 얻음"
|
||||
for name, text in (("종단 보기", actions), ("횡단 보기", persist), ("한 벌", shared)):
|
||||
for token in ("flushPendingPipes(", "flushPendingStructures(", "flushUphillOverrides("):
|
||||
assert token not in text, f"{name} 가 {token} 를 따로 부름 — 차례가 갈림"
|
||||
assert "{ confirm: true }" in persist, "[확정]이 한 줄기를 안 탐"
|
||||
assert "export function profileSaveParts(" in actions
|
||||
assert "export function crossSaveParts(" in persist
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
안 만진 측점이 옛 값 그대로 정본이 된다.
|
||||
(같은 입력을 주면 파이썬·TS 는 같은 값을 낸다 — 거울 문제가 아니라 **입력이 갈린 것**이다.)
|
||||
|
||||
⇒ `saveCurrentSections`·`confirmCurrentSections` 가 `collectSectionEdits` **앞에서**
|
||||
`ctx.reconcileDesigns()` 를 부른다. 순서가 뒤집히면 옛 면적이 그대로 실린다.
|
||||
(계획서 24-2a — 둘 다 저장 한 줄기 `saveDesign` 을 탄다: B06 창구 `sectionSaveContext` 의
|
||||
`prepare` 가 재계산, `cross` 가 면적 수집이고, `saveDesign` 이 `prepare` 를 먼저 부른다.)
|
||||
⇒ [저장]·[확정]이 `collectSectionEdits` **앞에서** `ctx.reconcileDesigns()` 를 부른다. 순서가
|
||||
뒤집히면 옛 면적이 그대로 실린다. (계획서 24-2a · 24-3b — 한 벌(`design_shared`)이 저장 한 줄기
|
||||
`saveDesign` 을 부르고, 횡단 보기 몫 `crossSaveParts` 의 `prepare` 가 재계산 · `cross` 가 면적
|
||||
수집이며, `saveDesign` 이 `prepare` 를 먼저 부른다.)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -18,7 +18,8 @@ from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
PERSIST = ROOT / "B06_Section" / "B06_Section_UI_Page_Persist.ts"
|
||||
PAGE = ROOT / "B06_Section" / "B06_Section_UI_Page.ts"
|
||||
PAGE = ROOT / "A00_Common" / "design" / "design_view_cross.ts"
|
||||
SHARED = ROOT / "A00_Common" / "design" / "design_shared.ts"
|
||||
SAVE = ROOT / "A00_Common" / "design" / "design_save.ts"
|
||||
|
||||
|
||||
@@ -29,16 +30,11 @@ def _body(source: str, marker: str) -> str:
|
||||
|
||||
def test_저장이_면적을_모으기_전에_다시_계산한다() -> None:
|
||||
text = PERSIST.read_text(encoding="utf-8")
|
||||
for marker in (
|
||||
"export async function saveCurrentSections",
|
||||
"export async function confirmCurrentSections",
|
||||
):
|
||||
assert "saveDesign(sectionSaveContext(" in _body(text, marker)[:400], (
|
||||
f"{marker} 가 B06 창구로 저장 한 줄기를 안 탄다"
|
||||
)
|
||||
body = _body(text, "function sectionSaveContext")
|
||||
assert "prepare: () => ctx.reconcileDesigns()" in body, "B06 창구가 재계산을 안 넘긴다"
|
||||
body = _body(text, "export function crossSaveParts")
|
||||
assert "prepare: () => ctx.reconcileDesigns()" in body, "횡단 보기 몫이 재계산을 안 넘긴다"
|
||||
assert "collectSectionEdits(ctx)" in body[body.index("cross:") :], "면적 수집이 횡단 차례 밖"
|
||||
shared = _body(SHARED.read_text(encoding="utf-8"), "export async function saveSharedDesign")
|
||||
assert "prepare: crossSave?.prepare" in shared, "한 벌이 재계산을 저장 한 줄기에 안 넘긴다"
|
||||
save = SAVE.read_text(encoding="utf-8")
|
||||
run = _body(save, "export async function saveDesign")
|
||||
assert run.index("ctx.prepare?.()") < run.index("ctx.cross(confirm)"), (
|
||||
|
||||
@@ -32,24 +32,28 @@ def test_같은_페이지면_셸도_다시_안_그림():
|
||||
assert "if (within) return;" in main
|
||||
|
||||
|
||||
def test_보기_약속_두_길():
|
||||
def test_보기_약속_가운데만():
|
||||
"""끝 모습(24-3b) — 보기는 가운데 위 칸만 채우고 좌측 패널 · 단추 · 종단도는 한 벌을 쓴다."""
|
||||
page = _read("A00_Common/design/design_page.ts")
|
||||
assert "export interface DesignPageView" in page
|
||||
assert "mount?: (root: HTMLElement, host: DesignPageHost)" in page
|
||||
assert "mountCenter?: (" in page and "shared: DesignPageShared" in page
|
||||
assert "mount?: (root" not in page # 보기가 틀을 통째로 그리던 중간 길은 걷음
|
||||
assert "mountCenter: (" in page and "shared: DesignPageShared" in page
|
||||
assert 'import("./design_view_profile")).mountProfileView' in page
|
||||
assert 'import("./design_view_cross")).mountCrossView' in page
|
||||
for hook in ("onShow:", "onHide:", "onDispose:"):
|
||||
assert hook in page
|
||||
# 보기는 처음 보일 때 한 번만 만든다 — 있으면 보이기만.
|
||||
assert "if (slot) slot.show();" in page
|
||||
assert "if (slot) {\n slot.show();" in page
|
||||
|
||||
|
||||
def test_페이지가_숨김_떠남을_건다():
|
||||
life = _read("B05_Profile/B05_Profile_UI_Page_Life.ts")
|
||||
assert "deps.viewer.setActive(false)" in life and "deps.viewer.dispose()" in life
|
||||
assert "bindProfileViewLife(host," in _read("B05_Profile/B05_Profile_UI_Page.ts")
|
||||
b06 = _read("B06_Section/B06_Section_UI_Page.ts")
|
||||
assert "host?.onHide(() => sectionView.setActive(false));" in b06
|
||||
assert "host?.onDispose(() => sectionView.dispose());" in b06
|
||||
assert "bindProfileViewLife(host," in _read("A00_Common/design/design_view_profile.ts")
|
||||
cross = _read("A00_Common/design/design_view_cross.ts")
|
||||
assert "sectionView.dispose();" in cross[cross.index("host.onDispose(") :]
|
||||
# 종단도는 한 벌 — 보기가 숨어도 그대로 두고, 페이지를 떠날 때 한 번 정리한다.
|
||||
assert "shared.profile?.dispose();" in _read("A00_Common/design/design_page.ts")
|
||||
|
||||
|
||||
def test_3D_멈춤과_문맥_놓기():
|
||||
@@ -74,3 +78,31 @@ def test_창_키_듣기를_뗌():
|
||||
def test_겹친_알약은_누른_것을_그대로():
|
||||
profile = _read("A00_Common/design/design_profile.ts")
|
||||
assert "stationIdAtStructure(selectedStructureId) === stationId" in profile
|
||||
|
||||
|
||||
def test_진행_단계_두_칸이_보기를_따라감():
|
||||
"""종단설계 · 횡단설계 두 칸은 같은 페이지 — 누르면 주소만 바꿔 보기만 바뀐다(계획서 24-3b)."""
|
||||
page = _read("A00_Common/design/design_page.ts")
|
||||
assert "const VIEW_STEP: Record<DesignView, number> = { profile: 2, cross: 3 };" in page
|
||||
assert "layout?.setActiveStep(VIEW_STEP[view]);" in page
|
||||
assert "layout?.setTitle(" in page
|
||||
click = page[page.index("onStepClick: (stepIndex) => {") :][:260]
|
||||
assert "if (designViewOf(route)) navigateTo(route);" in click
|
||||
layout = _read("ui_template/ui_template_workflow_layout.ts")
|
||||
assert "setActiveStep: (stepIndex) =>" in layout and "setTitle: (title) =>" in layout
|
||||
|
||||
|
||||
def test_초기화_계획노선_편집은_다시_세움():
|
||||
"""같은 페이지 안 이동은 보기만 바꾸므로 [초기화] · 계획노선 편집은 페이지를 다시 세운다."""
|
||||
router = _read("A00_Common/router.ts")
|
||||
assert "export function remountRoute(" in router
|
||||
assert "remountRoute(ROUTES.B05_PROFILE)" in _read("A00_Common/design/design_reset.ts")
|
||||
assert "remountRoute(ROUTES.B05_PROFILE)" in _read("A00_Common/design/design_view_profile.ts")
|
||||
|
||||
|
||||
def test_종단_보기_확정은_횡단_보기를_숨긴_채_세움():
|
||||
page = _read("A00_Common/design/design_page.ts")
|
||||
assert "ensureView: (view) => (slots.get(view) ?? mountView(view, false)).ready," in page
|
||||
cross = _read("A00_Common/design/design_view_cross.ts")
|
||||
# 숨긴 채 섰으면 한 벌 폼은 종단 보기 몫 — 보일 때 싣는다.
|
||||
assert "if (host.visible()) void structuresPanel.load()" in cross
|
||||
|
||||
@@ -36,13 +36,16 @@ def results():
|
||||
|
||||
|
||||
def test_두_보기가_같은_종단도와_같은_판():
|
||||
"""종단도는 한 페이지 한 벌(계획서 24-3b) — 두 보기는 같은 노드에 제 알림만 건다."""
|
||||
shared = _read("A00_Common/design/design_shared.ts")
|
||||
assert shared.count("createDesignProfile(") == 1
|
||||
assert "side: drainageSide" in shared and "sideTool: drainageSideTool" in shared
|
||||
b05 = _read("B05_Profile/B05_Profile_UI_Profile_Panel.ts")
|
||||
b06 = _read("B06_Section/B06_Section_UI_Section_View_Profile.ts")
|
||||
for source in (b05, b06):
|
||||
assert "createDesignProfile(" in source
|
||||
assert "side: drainageSide" in source
|
||||
assert "sideTool: drainageSideTool" in source
|
||||
assert 'view: "profile"' in b05 and 'view: "cross"' in b06
|
||||
for source, view in ((b05, "profile"), (b06, "cross")):
|
||||
assert "createDesignProfile(" not in source
|
||||
assert "sharedProfile(shared)" in source
|
||||
assert f'bindDesignView(shared, "{view}"' in source
|
||||
|
||||
|
||||
def test_옛_B06_위쪽_종단이_남지_않음():
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""B05 · B06 한 페이지 두 보기 — **좌측 패널 같음 지킴**(계획서 24-2f).
|
||||
"""B05 · B06 한 페이지 두 보기 — **좌측 패널 한 벌 지킴**(계획서 24-2f · 24-3b).
|
||||
|
||||
① 두 보기의 좌측 패널을 같은 견본으로 세워 공용 칸(구조물 배치)의 DOM · 아래 고정 영역이 같은지 본다
|
||||
(Node 헬퍼 `helper_design_view_parity.cjs` 가 등록부를 트랜스파일해 가짜 DOM 위에서 돌린다).
|
||||
② 페이지 파일(`B05_Profile_UI_Page.ts` · `B06_Section_UI_Page.ts`)이 좌측 칸 · 아래 단추 · 저장 단추를
|
||||
직접 만들면 빨강 — 칸과 단추는 등록부(`A00_Common/design/`)만 만든다.
|
||||
① 좌측 패널을 **하나** 세우고 보기를 오가도 뿌리 · 구조물 배치 · 아래 고정 영역 · 단추 넷이 같은 노드인지,
|
||||
보기마다 제 칸만 보이는지, [보기 전환] 글자가 따라가는지 본다(Node 헬퍼 `helper_design_view_parity.cjs`
|
||||
가 등록부를 트랜스파일해 가짜 DOM 위에서 돌린다).
|
||||
② 보기 파일(`design_view_profile.ts` · `design_view_cross.ts`)이 좌측 칸 · 아래 단추 · 저장 단추를
|
||||
직접 만들면 빨강 — 칸 · 단추 · 저장은 한 벌(`design_shared`)만 만든다.
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -18,8 +19,8 @@ import pytest
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = Path(HERE).parents[1]
|
||||
PAGE_FILES = (
|
||||
ROOT / "B05_Profile" / "B05_Profile_UI_Page.ts",
|
||||
ROOT / "B06_Section" / "B06_Section_UI_Page.ts",
|
||||
ROOT / "A00_Common" / "design" / "design_view_profile.ts",
|
||||
ROOT / "A00_Common" / "design" / "design_view_cross.ts",
|
||||
)
|
||||
# 페이지 파일에 나오면 안 되는 것 — 좌측 칸 · 아래 단추 · 저장 단추를 직접 만든다는 표시.
|
||||
FORBIDDEN_IN_PAGES = (
|
||||
@@ -48,72 +49,68 @@ def results():
|
||||
["node", os.path.join(HERE, "helper_design_view_parity.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
def test_공용_칸_구조물_배치는_두_보기가_같은_DOM(results):
|
||||
profile, cross = results["views"]["profile"], results["views"]["cross"]
|
||||
assert profile["structuresHtml"] == cross["structuresHtml"]
|
||||
assert profile["structuresFirst"] and cross["structuresFirst"] # 맨 위 칸
|
||||
def test_보기를_오가도_좌측_패널_단추_구조물_배치는_같은_노드(results):
|
||||
assert results["sameNodes"] == {"cross": True, "back": True}
|
||||
# 보기마다 새로 만들지 않는다 — 칸은 처음 보일 때 한 번만 서고 뒤로는 숨김 · 보임만.
|
||||
assert results["childCount"] == 1 + 5 + 2 # 아래 고정 영역 + 종단 칸 다섯 + 횡단 칸 둘
|
||||
|
||||
|
||||
def test_구조물_배치는_두_보기_모두_맨_위_같은_DOM(results):
|
||||
views = results["views"]
|
||||
assert views["profile"]["structuresHtml"] == views["cross"]["structuresHtml"]
|
||||
assert all(views[name]["structuresFirst"] for name in ("profile", "cross", "back"))
|
||||
|
||||
|
||||
def test_구조물_목록은_두_보기_모두_아래_고정_영역_맨_위(results):
|
||||
profile, cross = results["views"]["profile"], results["views"]["cross"]
|
||||
assert profile["dockFirstChild"] == cross["dockFirstChild"]
|
||||
assert profile["dockClass"] == cross["dockClass"]
|
||||
assert profile["dockIsLast"] and cross["dockIsLast"]
|
||||
views = results["views"]
|
||||
assert views["profile"]["dockFirstChild"] == views["cross"]["dockFirstChild"]
|
||||
assert all(views[name]["dockIsLast"] for name in ("profile", "cross", "back"))
|
||||
|
||||
|
||||
def test_아래_단추는_네_개_두_보기_같은_차례(results):
|
||||
profile, cross = results["views"]["profile"], results["views"]["cross"]
|
||||
assert (
|
||||
profile["dockActions"]
|
||||
== cross["dockActions"]
|
||||
== [
|
||||
"reset",
|
||||
"save",
|
||||
"confirm",
|
||||
"switchView",
|
||||
]
|
||||
)
|
||||
assert profile["dockLabels"][:3] == cross["dockLabels"][:3] == ["초기화", "저장", "확정"]
|
||||
|
||||
|
||||
def test_보기_전환_단추는_가려는_쪽_이름(results):
|
||||
assert results["views"]["profile"]["dockLabels"][3] == "횡단 보기"
|
||||
assert results["views"]["cross"]["dockLabels"][3] == "종단 보기"
|
||||
assert results["switchLabelAfter"] == "종단 보기" # setView("cross") 뒤
|
||||
|
||||
|
||||
def test_꺼진_단추는_안_눌리고_켜진_단추는_동작을_부름(results):
|
||||
assert results["views"]["profile"]["dockDisabled"] == [False, False, True, False]
|
||||
assert results["views"]["cross"]["dockDisabled"] == [False, False, False, False]
|
||||
assert results["calls"] == [
|
||||
"profile:reset",
|
||||
"profile:save",
|
||||
"profile:switch",
|
||||
"cross:reset",
|
||||
"cross:save",
|
||||
"cross:confirm",
|
||||
"cross:switch",
|
||||
def test_보기마다_제_칸만_보인다(results):
|
||||
views = results["views"]
|
||||
assert views["profile"]["shownTitles"] == [
|
||||
"구조물 배치",
|
||||
"페이지 설정",
|
||||
"계획노선",
|
||||
"경로 계산 설정",
|
||||
"선택 포인트 상세 설정",
|
||||
]
|
||||
assert views["cross"]["shownTitles"] == ["구조물 배치", "횡단 보기 설정", "표준 횡단면 설정"]
|
||||
assert views["back"]["shownTitles"] == views["profile"]["shownTitles"]
|
||||
assert results["sectionIds"]["cross"] == ["structures", "cross-view", "standard-cross"]
|
||||
|
||||
|
||||
def test_칸_차례는_보기마다_등록부_순서(results):
|
||||
assert results["views"]["profile"]["sectionIds"] == [
|
||||
"structures",
|
||||
"page-settings",
|
||||
"planned-route",
|
||||
"route-calc",
|
||||
"selected-point",
|
||||
def test_아래_단추는_네_개_보기_전환_글자만_따라감(results):
|
||||
views = results["views"]
|
||||
for name in ("profile", "cross", "back"):
|
||||
assert views[name]["dockActions"] == ["reset", "save", "confirm", "switchView"]
|
||||
assert views[name]["dockLabels"][:3] == ["초기화", "저장", "확정"]
|
||||
# [확정]은 종단 보기에서도 켜진다(한 벌 저장이 횡단 자료를 같은 페이지에서 얻음).
|
||||
assert views[name]["dockDisabled"] == [False, False, False, False]
|
||||
assert views["profile"]["dockLabels"][3] == "횡단 보기"
|
||||
assert views["cross"]["dockLabels"][3] == "종단 보기"
|
||||
assert views["back"]["dockLabels"][3] == "횡단 보기"
|
||||
|
||||
|
||||
def test_단추는_한_벌_동작을_부름(results):
|
||||
names = ["reset", "save", "confirm", "switch"]
|
||||
assert results["calls"] == [
|
||||
*(f"profile:{n}" for n in names),
|
||||
*(f"cross:{n}" for n in names),
|
||||
*(f"profile:{n}" for n in names),
|
||||
]
|
||||
|
||||
|
||||
def test_횡단_보기_칸_정의와_차례():
|
||||
"""B06 칸 정의는 화면 부품(입력 칸 · 표준 패널)을 물어 Node 에서 못 읽는다 — 소스로 본다."""
|
||||
"""B06 칸 정의는 소스로도 본다 — 차례가 바뀌면 빨강."""
|
||||
source = (ROOT / "B06_Section" / "B06_Section_UI_Page_Panel.ts").read_text(encoding="utf-8")
|
||||
found = re.findall(
|
||||
r'id: "([\w-]+)",\s*title:[^\n]*\n\s*views: \["cross"\],\s*order: (\d+)', source
|
||||
@@ -125,14 +122,34 @@ def test_횡단_보기_칸_정의와_차례():
|
||||
|
||||
|
||||
@pytest.mark.parametrize("page", PAGE_FILES, ids=lambda path: path.name)
|
||||
def test_페이지_파일이_좌측_칸_단추_저장을_직접_만들지_않는다(page):
|
||||
def test_보기_파일이_좌측_칸_단추_저장을_직접_만들지_않는다(page):
|
||||
text = page.read_text(encoding="utf-8")
|
||||
found = [marker for marker in FORBIDDEN_IN_PAGES if marker in text]
|
||||
assert not found, f"{page.name} 이(가) 좌측 칸 · 단추를 직접 만든다: {found}"
|
||||
assert "createDesignPanel(" not in text and "saveDesign(" not in text
|
||||
# 좌측 패널은 한 벌을 쓴다 — 종단 보기는 칸 재료 모듈(`createRoutePanel`)이 대신 부른다.
|
||||
assert "sharedPanel(shared)" in text or "createRoutePanel(" in text
|
||||
route_panel = (ROOT / "B05_Profile" / "B05_Profile_UI_Panel.ts").read_text(encoding="utf-8")
|
||||
assert "sharedPanel(shared)" in route_panel
|
||||
|
||||
|
||||
def test_두_보기_패널은_같은_등록부로_세운다():
|
||||
b05 = (ROOT / "B05_Profile" / "B05_Profile_UI_Panel.ts").read_text(encoding="utf-8")
|
||||
b06 = (ROOT / "B06_Section" / "B06_Section_UI_Page.ts").read_text(encoding="utf-8")
|
||||
assert "createDesignPanel(" in b05 and '"profile"' in b05
|
||||
assert "createDesignPanel(" in b06 and '"cross"' in b06
|
||||
def test_한_벌이_패널_종단도_폼을_한_번만_만든다():
|
||||
shared = (ROOT / "A00_Common" / "design" / "design_shared.ts").read_text(encoding="utf-8")
|
||||
for maker in ("createDesignPanel(", "createDesignProfile(", "createStructuresSection("):
|
||||
assert shared.count(maker) == 1, maker
|
||||
for guard in (
|
||||
"if (shared.panel) return shared.panel;",
|
||||
"if (shared.profile) return shared.profile;",
|
||||
"if (shared.structures) return shared.structures;",
|
||||
):
|
||||
assert guard in shared
|
||||
# 보기 쪽은 만들지 않고 한 벌을 쓴다.
|
||||
for relative in (
|
||||
"B05_Profile/B05_Profile_UI_Panel.ts",
|
||||
"B05_Profile/B05_Profile_UI_Profile_Panel.ts",
|
||||
"B06_Section/B06_Section_UI_Section_View_Profile.ts",
|
||||
"B06_Section/B06_Section_UI_Page_Structures_Panel.ts",
|
||||
):
|
||||
text = (ROOT / relative).read_text(encoding="utf-8")
|
||||
for maker in ("createDesignPanel(", "createDesignProfile(", "createStructuresSection("):
|
||||
assert maker not in text, f"{relative} 가 {maker} 를 따로 부름"
|
||||
|
||||
@@ -20,7 +20,7 @@ B06_ROUTER = _read("B06_Section/B06_Section_Router.py")
|
||||
REFRESH = _read("B06_Section/B06_Section_Cross_Refresh.ts")
|
||||
B05_PREVIEW = _read("B05_Profile/B05_Profile_UI_Profile_Preview.ts")
|
||||
# 설계 재계산 자리는 700줄 제한으로 `_UI_Page_Design_Sync` 로 떨어져 나갔다(2026-09-13).
|
||||
B06_PAGE = _read("B06_Section/B06_Section_UI_Page.ts") + _read(
|
||||
B06_PAGE = _read("A00_Common/design/design_view_cross.ts") + _read(
|
||||
"B06_Section/B06_Section_UI_Page_Design_Sync.ts"
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user