Merge remote-tracking branches 'origin/main_laptop_1' and 'origin/sub_laptop_5' into sub_laptop_6
This commit is contained in:
@@ -137,6 +137,10 @@ const panelModule = loadTs(
|
||||
writePendingPipes() {},
|
||||
},
|
||||
"../B05_Profile/B05_Profile_UI_Drainage_Facility_Merge": { mergeFacilityOptions: (x) => x },
|
||||
"../B05_Profile/B05_Profile_UI_Drainage_Ford_Fill": {
|
||||
fordWaitsOf: () => new Set(),
|
||||
waitsFordHeight: () => false,
|
||||
},
|
||||
"../B04_PreProcess/B04_PreProcess_Api_Fetch": {
|
||||
fetchDetailPipePoints: async () => ({ pipe_points: PIPES, basins: [] }),
|
||||
},
|
||||
|
||||
@@ -156,6 +156,10 @@ const panelModule = loadTs(
|
||||
writePendingPipes() {},
|
||||
},
|
||||
"../B05_Profile/B05_Profile_UI_Drainage_Facility_Merge": { mergeFacilityOptions: (x) => x },
|
||||
"../B05_Profile/B05_Profile_UI_Drainage_Ford_Fill": {
|
||||
fordWaitsOf: () => new Set(),
|
||||
waitsFordHeight: () => false,
|
||||
},
|
||||
"../B04_PreProcess/B04_PreProcess_Api_Fetch": {
|
||||
fetchDetailPipePoints: async () => ({ pipe_points: PIPES, basins: [] }),
|
||||
},
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/* PLAN 298-1 헬퍼 — B06 [추가]가 적은 「높이 기다림」을 배수유역도 판이 같은 표(`fordWaitsOf`)로 보고
|
||||
* 다시 나눈 유량으로 빈 월류 높이를 채움(126-1) · 프로젝트마다 따로 · 실패 = 토스트 한 줄(`analyzeFailed`).
|
||||
* TS 로더는 `helper_126_ford_fill.cjs` 와 같다 · 결과 = 표준출력 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 cache = new Map();
|
||||
const toasts = [];
|
||||
const STUBS = {
|
||||
"@ui/ui_template_elements": { showToast: (message, kind) => toasts.push([message, kind]) },
|
||||
};
|
||||
|
||||
function loadTs(file) {
|
||||
const full = path.resolve(file);
|
||||
if (cache.has(full)) return cache.get(full);
|
||||
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const box = { exports: {} };
|
||||
cache.set(full, box.exports);
|
||||
const localRequire = (request) => {
|
||||
if (STUBS[request]) return STUBS[request];
|
||||
if (request === "@config/config_frontend")
|
||||
return loadTs(path.join(ROOT, "config", "config_frontend.ts"));
|
||||
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
|
||||
if (request.startsWith("@")) return new Proxy({}, { get: () => () => {} });
|
||||
return require(request);
|
||||
};
|
||||
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
|
||||
cache.set(full, box.exports);
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
const B05 = path.join(ROOT, "B05_Profile");
|
||||
const fill = loadTs(path.join(B05, "B05_Profile_UI_Drainage_Ford_Fill.ts"));
|
||||
const { fordHeightM } = loadTs(
|
||||
path.join(ROOT, "A00_Common", "design", "design_structure_ford_calc.ts"),
|
||||
);
|
||||
|
||||
const flow = 8.0;
|
||||
const points = () => [
|
||||
{ chainage_m: 185, facility: "ford_bridge", options: { ford_width_m: 10 } },
|
||||
{ chainage_m: 300, facility: "ford_pavement", options: {} },
|
||||
];
|
||||
const basins = [
|
||||
{ chainage_m: 185, design_flow_m3s: flow },
|
||||
{ chainage_m: 300, design_flow_m3s: flow },
|
||||
];
|
||||
|
||||
// ① B06 [추가] — 그 프로젝트 표에 측점을 적음(판과 같은 표)
|
||||
const b06Wants = fill.waitsFordHeight("ford_bridge", { ford_width_m: 10 });
|
||||
if (b06Wants) fill.fordWaitsOf("p1").add(185);
|
||||
const same = fill.fordWaitsOf("p1") === fill.fordWaitsOf("p1");
|
||||
// ② 판의 다시 나눔 응답 — 같은 표로 채움 · 기다리지 않던 300 은 빈칸 그대로
|
||||
const p1 = points();
|
||||
const filled = fill.fillFordHeights(p1, basins, fill.fordWaitsOf("p1"));
|
||||
// ③ 다른 프로젝트 같은 측점은 안 채움
|
||||
const p2 = points();
|
||||
const otherFilled = fill.fillFordHeights(p2, basins, fill.fordWaitsOf("p2"));
|
||||
|
||||
let failed = null;
|
||||
try {
|
||||
const parts = loadTs(path.join(B05, "B05_Profile_UI_Drainage_Parts.ts"));
|
||||
failed = {
|
||||
status: parts.analyzeFailed(new Error("502 Bad Gateway"), "세부유역 산정에 실패했습니다."),
|
||||
plain: parts.analyzeFailed("x", "세부유역 산정에 실패했습니다."),
|
||||
toasts: [...toasts],
|
||||
};
|
||||
} catch (error) {
|
||||
failed = { error: String(error) };
|
||||
}
|
||||
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
b06Wants,
|
||||
same,
|
||||
filled,
|
||||
heights: p1.map((p) => p.options.ford_height_m ?? null),
|
||||
left: [...fill.fordWaitsOf("p1")],
|
||||
otherFilled,
|
||||
otherHeights: p2.map((p) => p.options.ford_height_m ?? null),
|
||||
need: fordHeightM(flow, 10),
|
||||
failed,
|
||||
}),
|
||||
);
|
||||
@@ -95,7 +95,7 @@ def test_배수유역_패널_배선():
|
||||
# 채움은 보관소가 응답을 받기 전에 · 채웠으면 세션 초안에 다시 담음
|
||||
apply = src[src.index("function apply(") : src.index("async function run(")]
|
||||
order = [
|
||||
apply.index("fillFordHeights(response.pipe_points, response.basins, fordWaits)"),
|
||||
apply.index("fillFordHeights(response.pipe_points, response.basins, fordWaits())"),
|
||||
apply.index("facilityStore.replaceFromResponse(response.pipe_points)"),
|
||||
apply.index("if (fordFilled) rememberPipes()"),
|
||||
apply.index("callbacks.onPipesChanged?.("),
|
||||
@@ -103,5 +103,7 @@ def test_배수유역_패널_배선():
|
||||
assert order == sorted(order)
|
||||
add = src[src.index(" addPipe(chainageM, attributes) {") : src.index(" removePipe(")]
|
||||
assert "waitsFordHeight(attributes?.facility, attributes?.options)" in add
|
||||
assert add.index("fordWaits.add(chainageM)") < add.index("pipeEditor.addAtChainage(chainageM)")
|
||||
assert "if (fordWaits.delete(fromChainageM)) fordWaits.add(toChainageM);" in add
|
||||
assert add.index("fordWaits().add(chainageM)") < add.index(
|
||||
"pipeEditor.addAtChainage(chainageM)"
|
||||
)
|
||||
assert "if (fordWaits().delete(fromChainageM)) fordWaits().add(toChainageM);" in add
|
||||
|
||||
@@ -61,7 +61,7 @@ def test_고르던_항목만_실시간_반영():
|
||||
def test_새_세월교_물넘이_월류_높이는_추가_뒤_채움():
|
||||
drainage = _read("B05_Profile_UI_Drainage_Panel.ts")
|
||||
body = _body(drainage, " addPipe(chainageM, attributes) {", " updatePipeFacility(")
|
||||
assert "fordWaits.add(chainageM)" in body
|
||||
assert "fordWaits().add(chainageM)" in body
|
||||
|
||||
|
||||
def test_패널_700줄_안():
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""PLAN 298-1 — 신규 관 계열(배관 · 박스암거 · 세월교 · 물넘이포장) [추가] = 횡단도 생김 + 세부유역 뒤에서 다시 나눔.
|
||||
|
||||
B05 [추가] = 배수유역도 판 `addPipe` → 다시 나눔(이미 있던 길) · B06 [추가] = 관 초안 → 판 `reload`(같은 길).
|
||||
고친 것 — B06 길도 빈 월류 높이를 채움(126-1 · 판과 같은 기다림 표 `fordWaitsOf`) · 채운 높이를 B06 목록 · 폼이
|
||||
받음 · B06 알림 글이 「저장 뒤 계산」이라 틀리던 것 · 다시 나눔 실패 = 토스트 한 줄.
|
||||
Node(`helper_298_add_reanalyze.cjs`) + 배선 대조.
|
||||
"""
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
|
||||
|
||||
def _read(*parts: str) -> str:
|
||||
return ROOT.joinpath(*parts).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def _body(src: str, start: str, end: str) -> str:
|
||||
at = src.index(start)
|
||||
return src[at : src.index(end, at)]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def out():
|
||||
proc = subprocess.run(
|
||||
["node", str(HERE / "helper_298_add_reanalyze.cjs")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
timeout=60,
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
PANEL = _read("B05_Profile", "B05_Profile_UI_Drainage_Panel.ts")
|
||||
B06 = _read("B06_Section", "B06_Section_UI_Page_Structures_Panel.ts")
|
||||
CROSS = _read("A00_Common", "design", "design_view_cross.ts")
|
||||
|
||||
|
||||
# ── 126-1 채움 — B06 [추가]도 판과 같은 표 ──────────────────────────────────
|
||||
|
||||
|
||||
def test_B06_추가가_적은_기다림을_판이_채움(out):
|
||||
assert out["b06Wants"] is True and out["same"] is True
|
||||
assert out["filled"] is True
|
||||
assert out["heights"] == [out["need"], None] # 기다리던 측점만 · 옛 빈칸은 그대로
|
||||
assert out["left"] == []
|
||||
|
||||
|
||||
def test_다른_프로젝트_같은_측점은_안_채움(out):
|
||||
assert out["otherFilled"] is False
|
||||
assert out["otherHeights"] == [None, None]
|
||||
|
||||
|
||||
def test_B06_추가_순간_기다림_적고_초안_씀():
|
||||
body = _body(B06, " onPipeAdd: (chainageM, attributes) => {", " onPipeUpdate:")
|
||||
order = [
|
||||
body.index("pipeFacilities.push({"),
|
||||
body.index("if (waitsFordHeight(attributes.facility, attributes.options))"),
|
||||
body.index("fordWaitsOf(deps.projectId).add(at);"),
|
||||
body.index("persistPipes();"),
|
||||
body.index("deps.onStructuresChanged?.();"),
|
||||
]
|
||||
assert order == sorted(order)
|
||||
|
||||
|
||||
def test_판은_같은_표로_채움():
|
||||
assert "const fordWaits = () => fordWaitsOf(projectId);" in PANEL
|
||||
assert "new Set<number>()" not in _body(PANEL, "let requestSeq = 0;", "let canvasWidth")
|
||||
|
||||
|
||||
def test_채운_높이를_B06_목록_폼이_받음():
|
||||
adopt = _body(B06, " adoptFordHeights: () => {", " releasePipe:")
|
||||
assert "readPendingPipes(deps.projectId)" in adopt
|
||||
assert "if (entry.options?.ford_height_m !== undefined) return false;" in adopt # 빈 칸만
|
||||
assert "section.setPipeFacilities(pipeFacilities);" in adopt
|
||||
assert "persistPipes" not in adopt # 받기만 — 초안을 다시 안 씀(되돌이 없음)
|
||||
assert (
|
||||
"pipesChanged: () => (structuresPanel.adoptFordHeights(), void refreshDetailForStructures()),"
|
||||
in CROSS
|
||||
)
|
||||
view = _read("B06_Section", "B06_Section_UI_Section_View_Profile.ts")
|
||||
sig = _body(view, "const signature = JSON.stringify(", "const changed")
|
||||
assert "p.options?.ford_height_m ?? null" in sig # 늦게 온 유량으로 채운 높이도 알림
|
||||
|
||||
|
||||
# ── 다시 나눔 = 요청 한 번 · 저장 안 함 · 최신 응답만 · 잠금 없음 ────────────
|
||||
|
||||
|
||||
def test_B06_초안_쓰면_판이_한_번_다시_나눔():
|
||||
written = _body(CROSS, " onPipesWritten: () => {", " detail: () => sectionDetail,")
|
||||
assert "window.clearTimeout(pipeReloadTimer);" in written # 잇단 조작 = 한 번
|
||||
assert "shared.profile?.side?.reload()" in written
|
||||
persist = _body(B06, " function persistPipes(): void {", " /** 관 옮기기")
|
||||
assert persist.index("writePendingPipes(") < persist.index("deps.onPipesWritten?.();")
|
||||
|
||||
|
||||
def test_다시_나눔은_세션_초안으로_계산만_저장_안_함():
|
||||
load = _body(PANEL, "const loadSaved = ", "const resetPipes")
|
||||
assert "readPendingPipes(projectId as string)" in load
|
||||
assert "computeDetailBasins(projectId as string, pending)" in load
|
||||
analyze = _body(PANEL, "const analyze = ", "analyzeButton.addEventListener")
|
||||
assert "computeDetailBasins(" in analyze
|
||||
run = _body(PANEL, "async function run(", "/** 지금 관 목록")
|
||||
assert "saveDetailPipePoints" not in run + load + analyze
|
||||
# B05 [추가] = 판 addPipe → 배치 바뀜 → 초안 + 다시 나눔
|
||||
commit = _body(PANEL, "const pipeEditor = createPipeEditor(", "(fromM, toM) =>")
|
||||
assert commit.index("rememberPipes();") < commit.index("void analyze();")
|
||||
|
||||
|
||||
def test_최신_응답만_잠금_없이_지도_칸_표시():
|
||||
run = _body(PANEL, "async function run(", "/** 지금 관 목록")
|
||||
assert "const seq = ++requestSeq;" in run
|
||||
assert run.index("if (seq !== requestSeq) return;") < run.index("apply(response)")
|
||||
# 지도 칸 안 작은 표시(문구 · 지도 가운데 서클)만 — 화면 전체 잠금 없음
|
||||
assert 'status.textContent = L("B05_Drainage_Status_Analyzing");' in run
|
||||
assert 'showProgress(null, L("B05_Drainage_Status_Analyzing"));' in run
|
||||
assert "Overlay" not in run and "lock" not in run.lower()
|
||||
chrome = _read("B05_Profile", "B05_Profile_UI_Drainage_Chrome.ts")
|
||||
assert "viewport.append(" in chrome and "progress.root," in chrome
|
||||
|
||||
|
||||
def test_실패하면_토스트_한_줄(out):
|
||||
run = _body(PANEL, "async function run(", "/** 지금 관 목록")
|
||||
assert (
|
||||
'status.textContent = analyzeFailed(error, L("B05_Drainage_Status_AnalyzeFailed"));' in run
|
||||
)
|
||||
failed = out["failed"]
|
||||
assert failed["status"] == "502 Bad Gateway"
|
||||
assert failed["toasts"] == [
|
||||
["세부유역 산정에 실패했습니다. 502 Bad Gateway", "error"],
|
||||
["세부유역 산정에 실패했습니다.", "error"],
|
||||
]
|
||||
|
||||
|
||||
# ── 횡단도 생김 · 알림 글 ────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_추가_뒤_새_측점_횡단_미리_뜸():
|
||||
body = _body(B06, " onPipeAdd: (chainageM, attributes) => {", " onPipeUpdate:")
|
||||
assert "deps.onStructuresChanged?.();" in body
|
||||
assert "onStructuresChanged: () => void refreshDetailForStructures()," in CROSS
|
||||
drafts = _read("B06_Section", "B06_Section_Section_Drafts.ts")
|
||||
stations = _body(
|
||||
drafts, "export async function withDraftStations(", "parked.set(detail, stale);"
|
||||
)
|
||||
assert "readPendingPipes(projectId)" in stations
|
||||
assert "previewOf(projectId, routeId, missing)" in stations
|
||||
|
||||
|
||||
def test_B06_알림_글_뒤에서_다시_나눔():
|
||||
for name in ("PIPE_ADD_NOTICE", "PIPE_REMOVE_NOTICE", "PIPE_MOVE_NOTICE"):
|
||||
line = _body(B06, f"const {name} =", ";\n")
|
||||
assert "세부 배수유역은 뒤에서 다시 나눕니다." in line, name
|
||||
assert "[저장]·[확정] 뒤에" not in line, name
|
||||
|
||||
|
||||
def test_줄_수():
|
||||
assert len(PANEL.splitlines()) <= 700
|
||||
assert len(CROSS.splitlines()) <= 685
|
||||
Reference in New Issue
Block a user