fix(493-2 손 고침 저장 그대로): 492-2 띠 · [저장] 확인 창 · 저장 뒤 초기화 알림 걷음 · 추천 [적용] 창 수 = 서버 지울 수
- 손 고침 [저장] = 492 이전처럼 바로 저장(확인 창 없음 · 세션 초안 통째 비움 없음) · 평면 · B06 평면 패널 띠 걷음 - 추천 [적용] 확인 창 = 서버 저장분(구조물 · 관 저장 파일 · 추가 측점) 수 — 응답 route_reset.dropped 와 같은 셈 · 세션 초안 안 셈 · 종단 편집 수 뺌 - 안 쓰게 된 것 걷음: routeShapeChanged(TS) · draftShapeChanged · askRouteReset · ROUTE_RESET_BAND · pageRoutePlan · 띠 CSS - 추가 측점 저장분 받기 fetchSavedPlusStations 한 줄 - 시험: test_283_route_reset_confirm 고쳐 씀(걷음 소스 · 창 수 = 알림 수 · 관 저장 파일 없음 0) · 저장 헬퍼 셋 확인 창 가짜 걷음 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,6 @@ import {
|
||||
dropStaleStructureTypes,
|
||||
fetchStructures,
|
||||
} from "../../B05_Profile/B05_Profile_Api_Structures";
|
||||
import type { RoutePlanResponse } from "../../B05_Profile/B05_Profile_Api_Replan";
|
||||
|
||||
const memo = new Map<string, Promise<unknown>>();
|
||||
|
||||
@@ -43,14 +42,6 @@ export const pageStructures = (projectId: string): ReturnType<typeof fetchStruct
|
||||
export const pagePipes = (projectId: string): ReturnType<typeof fetchDetailPipePoints> =>
|
||||
once(`pipes:${projectId}`, () => fetchDetailPipePoints(projectId));
|
||||
|
||||
/** 저장 노선(그린 선 · 기본 반경) — 노선 모양 바뀜 판정(492-2 띠 · [저장] 확인 창)이 조작마다 다시 안 물음 ·
|
||||
* 창구는 부를 때 불러옴(`pageSectionContext` 와 같음). */
|
||||
export const pageRoutePlan = (projectId: string): Promise<RoutePlanResponse> =>
|
||||
once(`plan:${projectId}`, async () => {
|
||||
const { fetchRoutePlan } = await import("../../B05_Profile/B05_Profile_Api_Replan");
|
||||
return fetchRoutePlan(projectId);
|
||||
});
|
||||
|
||||
/** 정의 판이 바뀌었으면 종류 캐시 버림 — 페이지마다 한 번(탭이 다시 보일 때는 `onStaleStructureTypes`). */
|
||||
export const pageTypesChecked = (projectId: string): Promise<boolean> =>
|
||||
once(`types:${projectId}`, () => dropStaleStructureTypes());
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
* 이월) → 관 · 구조물 초안 다시 얹기 → 새 노선으로 위 차례 → 횡단 = 세션 고침을 측점 열쇠로 넘긴 patch(같은 TS ·
|
||||
* Node). 이 때 조각은 기본 조각만(화면 몫 일 = 옛 노선 판이라 건너뜀) · 계획고 초안 = 노선 초안 칸 ?? 옛 노선 칸 ·
|
||||
* 손 상단측은 안 보냄(요구 274) · [확정]도 [저장]으로(새 노선을 본 뒤 다시 [확정]) · 다 저장하면 페이지 다시 세움.
|
||||
* 노선 모양이 바뀌면 = 초기화(492 · 요구 284 · 285) — 잠금 막 앞 확인 창(지워질 수 · [취소] = 요청 0) · 서버가
|
||||
* 시설 · 손 입력을 버리고 다시 셈(응답 `route_reset`) → 알림 한 줄 · 세션 초안을 통째 비우고 서버 정본으로 다시 세움.
|
||||
*
|
||||
* ⚠ 차례를 바꾸면 결과가 갈린다 — 관 목록은 스냅샷이라 구간값(암거 옵션)보다 먼저, 상단측은
|
||||
* 횡단 patch 보다 먼저 나가야 사용자 수정이 맨 위에 얹힌다(2026-09-06 · 09-12) — 차례 = 서버 한 곳.
|
||||
@@ -41,7 +39,7 @@ import {
|
||||
type SavePartKey,
|
||||
type SaveStepKey,
|
||||
} from "./design_save_api";
|
||||
import { clearDrafts, clearRouteDrafts, clearState, stateKey } from "../b_page_state";
|
||||
import { clearRouteDrafts, clearState, stateKey } from "../b_page_state";
|
||||
import {
|
||||
pendingRouteSave,
|
||||
rebuildAfterRouteSave,
|
||||
@@ -50,8 +48,6 @@ import {
|
||||
routeSaveSummary,
|
||||
} from "../../B05_Profile/B05_Profile_UI_RouteEdit_Apply";
|
||||
import type { RouteSaveBody } from "../../B05_Profile/B05_Profile_Api_Replan";
|
||||
import { confirmRouteReset } from "../../B05_Profile/B05_Profile_UI_RouteReset";
|
||||
import { routeResetNotice } from "../../B05_Profile/logic/B05_Profile_Route_Reset";
|
||||
|
||||
export interface DesignSaveContext {
|
||||
projectId: string;
|
||||
@@ -168,10 +164,8 @@ function oneBodyError(error: unknown): Error {
|
||||
return new Error(`${where}실패 — 아무것도 저장하지 않았습니다(모두 그대로). ${error.message}`);
|
||||
}
|
||||
|
||||
/** 노선 저장 결과 알림 — 초기화면 지운 수 한 줄(492) · 아니면 노선 밖 관 · 구조물 · 따라감 실패 · 결과 한 줄. */
|
||||
/** 노선 저장 결과 알림 — 노선 밖 관 · 구조물 · 따라감 실패 · 결과 한 줄(관 지킴 · 노선 밖 고침 …). */
|
||||
function noticeRouteSaved(saved: RouteReplanResponse, carryOutside: number): void {
|
||||
const reset = routeResetNotice(saved.route_reset, saved.pipes?.crossing ?? 0);
|
||||
if (reset) return showToast(reset, "info");
|
||||
const outside = (saved.pipes?.outside ?? 0) + (saved.structures?.outside ?? 0);
|
||||
if (outside) showToast(outsideNotice(outside), "warning");
|
||||
if (saved.follow_failed?.length)
|
||||
@@ -193,12 +187,10 @@ export async function saveDesign(
|
||||
const confirm = options.confirm ?? false;
|
||||
const routeId = ctx.routeId();
|
||||
if (routeId === null) return false;
|
||||
const route: RouteSaveBody | null = await pendingRouteSave(ctx.projectId);
|
||||
// 노선 모양이 바뀐 [저장] = 초기화(492-2) — 잠금 막 앞에서 지워질 수를 물음 · [취소] = 요청 0
|
||||
if (route && !(await confirmRouteReset(ctx.projectId, route.draft))) return false;
|
||||
showLoadingOverlay();
|
||||
saving.add(ctx.projectId);
|
||||
try {
|
||||
const route: RouteSaveBody | null = await pendingRouteSave(ctx.projectId);
|
||||
await ctx.prepare?.();
|
||||
const parts: Partial<Record<SavePartKey, SavePart | null>> = {};
|
||||
// 노선이 바뀌면 횡단 = 옛 노선 세션 고침 원본(서버가 새 측점에 넘김) · 화면 몫 조각은 옛 노선 판이라 안 씀
|
||||
@@ -230,8 +222,6 @@ export async function saveDesign(
|
||||
}
|
||||
const saved = (result.steps.route ?? {}) as unknown as RouteReplanResponse;
|
||||
noticeRouteSaved(saved, Number(result.steps.carry?.outside ?? 0));
|
||||
// 초기화 — 세션 초안(시설 · 계획고 · 횡단 캐시)을 비워 서버 정본으로 다시 세움(추천 [적용]과 같음)
|
||||
if (saved.route_reset?.reset) clearDrafts(ctx.projectId);
|
||||
// 새 노선 — [확정]은 새 노선을 본 뒤 다시(단계 닫기는 새 횡단 본문이 있어야 함) · 페이지 다시 세움.
|
||||
if (confirm) showToast("노선을 저장했습니다 — 새 노선을 확인한 뒤 다시 [확정]하세요.", "info");
|
||||
else await ctx.done?.(false);
|
||||
|
||||
@@ -20,7 +20,6 @@ import { createRouteProfilePanel } from "../../B05_Profile/B05_Profile_UI_Profil
|
||||
import { leaveForDashboard } from "../b_missing_data_guard";
|
||||
import { currentRoute, remountRoute } from "../router";
|
||||
import { mountRouteEditor } from "../../B05_Profile/B05_Profile_UI_RouteEdit";
|
||||
import { mountRouteResetBand } from "../../B05_Profile/B05_Profile_UI_RouteReset";
|
||||
import { routeDraftBridge } from "./design_pipeline_page";
|
||||
import { pageRouteId, pageSectionContext } from "./design_page_data";
|
||||
import { createSelectionSync, graphTargetAt } from "../../B05_Profile/B05_Profile_UI_Selection";
|
||||
@@ -549,9 +548,6 @@ export async function mountProfileView(
|
||||
// 계산 묶음(346-1) — 페이지가 세운 다리(365-1) · 노선을 고칠 때마다 종단이 따라옴(미리보기 · [저장]이 정본).
|
||||
draft: routeDraftBridge(activeProjectId, profilePanel),
|
||||
});
|
||||
// 모양 바뀐 노선 초안 띠(492-2) — 편집기 뼈대는 위 부름에서 바로 섬 · B06 평면 패널로 같이 옮겨짐
|
||||
const canvasWrap = mainContent.querySelector<HTMLElement>(".b05-routeedit__canvas-wrap");
|
||||
if (canvasWrap) host.onDispose(mountRouteResetBand(canvasWrap, activeProjectId));
|
||||
|
||||
// ③ 종단면·횡단 자료 — 하단 패널을 3D보다 먼저 채운다.
|
||||
const routeId = pageRouteId(activeProjectId); // 노선 id 한 칸(386-2b)
|
||||
|
||||
@@ -34,6 +34,9 @@ async function request(projectId: string, init: RequestInit = {}): Promise<numbe
|
||||
}
|
||||
}
|
||||
|
||||
/** 서버 저장분을 지금 받음(초안 · 받아 둔 값 안 봄) — 추천 [적용] 확인 창 지워질 수(493-2). */
|
||||
export const fetchSavedPlusStations = (projectId: string): Promise<number[]> => request(projectId);
|
||||
|
||||
export const readPlusDraft = (projectId: string): number[] | null =>
|
||||
useDesignStore(projectId, null).get("plusStations");
|
||||
|
||||
|
||||
@@ -1,101 +1,33 @@
|
||||
/* =============================================================================
|
||||
* B05_Profile_UI_RouteReset.ts
|
||||
* 노선 모양 바뀐 [저장] = 초기화(요구 283 · 284 · 285 · 492-2) — 화면 몫.
|
||||
* 노선 초기화 = 추천 [적용](요구 289 · 493-2) — 확인 창 글 몫.
|
||||
*
|
||||
* · 띠 — 평면(노선 편집) 위 한 줄 · 노선 초안이 저장 노선 모양을 바꾸면 보임(B06 평면 패널로 옮겨져도 같이 감).
|
||||
* · 확인 창 — [저장](`design_save`) · 추천 [적용](476-2) 앞 · 지워질 수 = 지금 캐시의 사용자 구조물 · 관 ·
|
||||
* 추가 측점 · 종단 편집 · [취소] = 요청 0.
|
||||
* · 저장 뒤 알림 — 서버 응답 `route_reset`(정본 판정) · 없으면(옛 서버) 건너뜀.
|
||||
* 셈 · 글 = `logic/B05_Profile_Route_Reset` · 저장 노선 = 페이지 자료 줄(`pageRoutePlan` · 조작마다 안 물음).
|
||||
* 지워질 수 = 서버 저장분(구조물 · 관 · 추가 측점) — 서버가 [적용]에서 지우는 것과 같음(응답
|
||||
* `route_reset.dropped`) · 세션 초안은 안 셈 · 관 = 저장 파일이 없으면 0(응답 관은 자동 제안).
|
||||
* 구조물 · 관 = 페이지 자료 줄(저장 · 적용 뒤 다시 받음) · 추가 측점 = 지금 받음.
|
||||
* 손 고침 [저장]은 묻지 않음(보이는 화면 그대로 저장).
|
||||
* 글 = `logic/B05_Profile_Route_Reset`.
|
||||
* ========================================================================== */
|
||||
|
||||
import { showConfirmDialog } from "@ui/ui_template_elements";
|
||||
import type { RouteDraft } from "../A00_Common/design/logic/design_pipeline_types";
|
||||
import { useDesignStore } from "../A00_Common/design/design_store";
|
||||
import {
|
||||
pagePipes,
|
||||
pageRouteId,
|
||||
pageRoutePlan,
|
||||
pageStructures,
|
||||
} from "../A00_Common/design/design_page_data";
|
||||
import { plusStationsOf } from "./B05_Profile_Api_PlusStations";
|
||||
import { pendingAlignment, routeAlignment } from "./B05_Profile_UI_Profile_Edit";
|
||||
import {
|
||||
askRouteReset,
|
||||
draftShapeChanged,
|
||||
ROUTE_RESET_BAND,
|
||||
routeResetConfirmText,
|
||||
type RouteResetCounts,
|
||||
} from "./logic/B05_Profile_Route_Reset";
|
||||
import { pagePipes, pageStructures } from "../A00_Common/design/design_page_data";
|
||||
import { fetchSavedPlusStations } from "./B05_Profile_Api_PlusStations";
|
||||
import { routeResetConfirmText } from "./logic/B05_Profile_Route_Reset";
|
||||
|
||||
/** 노선 초안 동안의 계획고 초안 칸 — `design_pipeline_page` 의 `DRAFT_ROUTE_ID` 와 같은 값. */
|
||||
const DRAFT_ROUTE_ID = 0;
|
||||
|
||||
/** 초안이 저장 노선 모양을 바꾸나(저장 노선을 못 받으면 바뀜으로 봄). */
|
||||
export async function draftResets(projectId: string, draft: RouteDraft | null): Promise<boolean> {
|
||||
if (!draft) return false;
|
||||
const plan = await pageRoutePlan(projectId).catch(() => null);
|
||||
return draftShapeChanged(plan?.planned ?? null, draft, plan?.min_radius_m ?? 0);
|
||||
}
|
||||
|
||||
/** 지금 캐시(초안 ?? 저장분)에서 지워질 수. */
|
||||
export async function routeResetCounts(projectId: string): Promise<RouteResetCounts> {
|
||||
const store = useDesignStore(projectId, null);
|
||||
const [structures, pipes] = await Promise.all([
|
||||
store.get("structures") ??
|
||||
pageStructures(projectId).then(
|
||||
(list) => list.structures,
|
||||
() => [],
|
||||
),
|
||||
store.get("pipes") ??
|
||||
pagePipes(projectId).then(
|
||||
(list) => list.pipe_points,
|
||||
() => [],
|
||||
),
|
||||
]);
|
||||
const routeId = pageRouteId(projectId);
|
||||
const edits =
|
||||
pendingAlignment(DRAFT_ROUTE_ID) ?? (routeId === null ? null : routeAlignment(routeId));
|
||||
return {
|
||||
structures: structures?.length ?? 0,
|
||||
pipes: pipes?.length ?? 0,
|
||||
plusStations: plusStationsOf(projectId).length,
|
||||
profileEdits:
|
||||
Object.keys(edits?.station_offsets ?? {}).length +
|
||||
Object.keys(edits?.curve_radii ?? {}).length,
|
||||
};
|
||||
}
|
||||
|
||||
/** [저장] 앞 — 모양 바뀐 초안이면 확인 창 · [취소] = false(요청 0) · 안 바뀜 = 묻지 않고 true. */
|
||||
export async function confirmRouteReset(projectId: string, draft: RouteDraft): Promise<boolean> {
|
||||
return askRouteReset(
|
||||
await draftResets(projectId, draft),
|
||||
() => routeResetCounts(projectId),
|
||||
(text) => showConfirmDialog(text, "저장"),
|
||||
);
|
||||
}
|
||||
|
||||
/** 추천 [적용] 확인 창 글 — [저장]과 같은 글 · 수. */
|
||||
/** 추천 [적용] 확인 창 글 — 서버 저장분에서 지워질 수(못 받은 칸 = 0). */
|
||||
export async function applyResetText(projectId: string): Promise<string> {
|
||||
return routeResetConfirmText(await routeResetCounts(projectId), "적용");
|
||||
}
|
||||
|
||||
/** 평면 위 띠를 세움 — 노선 초안이 바뀔 때마다 맞춤 · 돌려준 함수 = 떠날 때 풂. */
|
||||
export function mountRouteResetBand(parent: HTMLElement, projectId: string): () => void {
|
||||
const band = document.createElement("div");
|
||||
band.className = "b05-routeedit__reset-band";
|
||||
band.setAttribute("role", "status");
|
||||
band.textContent = ROUTE_RESET_BAND;
|
||||
band.hidden = true;
|
||||
parent.append(band);
|
||||
const store = useDesignStore(projectId, null);
|
||||
let turn = 0;
|
||||
const sync = async (draft: RouteDraft | null): Promise<void> => {
|
||||
const mine = ++turn; // 늦게 온 옛 판정이 새 판정을 덮지 않게
|
||||
const changed = await draftResets(projectId, draft);
|
||||
if (mine === turn) band.hidden = !changed;
|
||||
};
|
||||
void sync(store.get("routeDraft"));
|
||||
const stop = store.subscribe("routeDraft", (draft) => void sync(draft));
|
||||
return () => (stop(), band.remove());
|
||||
const [structures, pipes, plusStations] = await Promise.all([
|
||||
pageStructures(projectId).then(
|
||||
(list) => list.structures?.length ?? 0,
|
||||
() => 0,
|
||||
),
|
||||
pagePipes(projectId).then(
|
||||
(list) => (list.saved ? (list.pipe_points?.length ?? 0) : 0),
|
||||
() => 0,
|
||||
),
|
||||
fetchSavedPlusStations(projectId).then(
|
||||
(list) => list.length,
|
||||
() => 0,
|
||||
),
|
||||
]);
|
||||
return routeResetConfirmText({ structures, pipes, plusStations });
|
||||
}
|
||||
|
||||
@@ -41,28 +41,6 @@
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
/* 노선 모양 바뀐 초안 띠(492-2) — 도구줄 아래 가운데 · 클릭 통과 */
|
||||
.b05-routeedit__reset-band {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: calc(var(--spacing-16) + 34px + var(--spacing-8));
|
||||
left: 50%;
|
||||
width: max-content;
|
||||
max-width: calc(100% - 2 * var(--spacing-16));
|
||||
padding: var(--spacing-4) var(--spacing-12);
|
||||
transform: translateX(-50%);
|
||||
border: 1px solid var(--color-warning);
|
||||
border-radius: var(--radius-8, 6px);
|
||||
background: color-mix(in srgb, var(--color-warning) 16%, var(--color-surface-raised));
|
||||
color: var(--color-text-body);
|
||||
font-size: var(--text-caption);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.b05-routeedit__reset-band[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 지도 위에 얹는 판들 — 아래 정보행을 없애고 여기로 옮겼다(2026-09-12 사용자 지시 ⑩·⑫·⑬).
|
||||
글판은 **클릭을 통과시킨다** — 지도 조작을 가리면 안 된다(접기 단추만 받음 · 345-5 덧 3). */
|
||||
.b05-routeedit__hint-body,
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
* 짝 = 파이썬 `B05_Profile_Engine_Grade_Carry.py`(서버 정본 · 같은 규칙) · 거울 시험 =
|
||||
* resources/tester/test_400_profile_carry.py · test_344_2_route_pairs.py.
|
||||
* - `changedSpan` = 옛 · 새 정점열의 같은 앞머리 · 꼬리를 뺀 가운데 — 「노선 그대로?」 · 묶음 범위만.
|
||||
* - `routeShapeChanged` = 노선 모양 바뀜(492 — [저장] = 초기화) · 짝 = 파이썬 `route_shape_changed` ·
|
||||
* 거울 시험 = resources/tester/test_283_route_reset_confirm.py.
|
||||
* - 같은 자리(`lineMatcher`) = 새 선 점을 옛 선에 투영 · 옆 `SAME_LINE_TOL_M` 안 · 방향 30° 안
|
||||
* (측점 스냅이 뒤 교점을 밀어도 이어받음).
|
||||
* - 자동 꼭짓점(`carryBase`) · 편집(`carryEdits`)은 **새 측점 위에** 다시 놓음 — 같은 자리면 옛 선 그 자리
|
||||
@@ -87,11 +85,6 @@ export function changedSpan(
|
||||
};
|
||||
}
|
||||
|
||||
/** 노선 모양이 바뀌었나(492) — 저장 선 대 새 선 정점 1 mm(교점 · R · 곡선 켬끔이 선에 담김 · 스냅 · 간격은
|
||||
* 선 안 바꿈) · 파이썬 `route_shape_changed` 짝. */
|
||||
export const routeShapeChanged = (oldXy: ReadonlyArray<XY>, newXy: ReadonlyArray<XY>): boolean =>
|
||||
changedSpan(oldXy, newXy) !== null;
|
||||
|
||||
/** `np.interp` — 오름차순 xs · 범위 밖은 양 끝값. */
|
||||
function interp(xs: number[], ys: number[], value: number): number {
|
||||
if (value <= xs[0]) return ys[0];
|
||||
|
||||
@@ -1,69 +1,34 @@
|
||||
/* =============================================================================
|
||||
* B05_Profile/logic/B05_Profile_Route_Reset.ts
|
||||
* 노선 모양 바뀐 [저장] = 초기화(요구 283 · 284 · 285 · 492-2) — 브라우저 판정 · 띠 글 · 확인 창 글 · 저장 뒤 알림 글.
|
||||
* 노선 초기화 = 추천 [적용](요구 289 · 493-2) — 확인 창 글 · 적용 뒤 알림 글.
|
||||
*
|
||||
* 판정 = 저장 노선(그린 선) 대 초안이 그릴 선(`draftLine` · 서버가 쓰는 자릿수) — `routeShapeChanged`
|
||||
* (짝 = 파이썬 `B05_Profile_Engine_Grade_Carry.route_shape_changed` · 정점 1 mm · 거울 시험
|
||||
* resources/tester/test_283_route_reset_confirm.py). 스냅 켬끔 · 간격만 = 선 그대로 = 초기화 아님.
|
||||
* 정본은 서버 판정(응답 `route_reset`) — 여기는 저장 전에 무엇이 지워지는지 알리는 몫.
|
||||
* 손 고침 [저장]은 보이는 화면 그대로 저장(초기화 없음) — 초기화 기준 = 추천 [적용]만.
|
||||
* 지워질 수 = 서버 저장분(서버가 지우는 것 · 응답 `route_reset.dropped` 와 같은 셈).
|
||||
* ========================================================================== */
|
||||
|
||||
import type { RouteDraft } from "../../A00_Common/design/logic/design_pipeline_types";
|
||||
import { routeShapeChanged } from "./B05_Profile_Grade_Carry";
|
||||
import { draftLine } from "./B05_Profile_Pipeline_RouteGround";
|
||||
|
||||
type Vertex = [number, number];
|
||||
|
||||
/** 지금 캐시에서 지워질 수 — 사용자 구조물 · 관 · 추가 측점 · 종단 편집. */
|
||||
/** 서버 저장분에서 지워질 수 — 사용자 구조물 · 관 · 추가 측점(응답 `route_reset.dropped` 와 같은 칸). */
|
||||
export interface RouteResetCounts {
|
||||
structures: number;
|
||||
pipes: number;
|
||||
plusStations: number;
|
||||
profileEdits: number;
|
||||
}
|
||||
|
||||
/** 서버 응답 `route_reset`(sub4 약속) — 초기화했나 · 지운 수. */
|
||||
/** 서버 응답 `route_reset` — 초기화했나 · 지운 수. */
|
||||
export interface RouteReset {
|
||||
reset: boolean;
|
||||
dropped?: { structures?: number; pipes?: number; plus_stations?: number };
|
||||
}
|
||||
|
||||
export const ROUTE_RESET_BAND =
|
||||
"노선이 바뀌어 [저장] 하면 초기화 — 구조물 · 관 · 추가 측점 · 종단 · 횡단 손 입력이 지워지고 새 노선으로 다시 셈";
|
||||
|
||||
const xy = (line: ReadonlyArray<Vertex>) => line.map(([x, y]) => ({ x, y }));
|
||||
|
||||
/** 초안이 저장 노선 모양을 바꾸나 — 초안 없음 = 아님 · 저장 선을 모름 = 바뀜으로 봄(묻는 쪽이 안전). */
|
||||
export function draftShapeChanged(
|
||||
savedLine: ReadonlyArray<Vertex> | null,
|
||||
draft: RouteDraft | null,
|
||||
minRadiusM: number,
|
||||
): boolean {
|
||||
if (!draft?.nodes || draft.nodes.length < 2) return false;
|
||||
if (!savedLine || savedLine.length < 2) return true;
|
||||
return routeShapeChanged(xy(savedLine), xy(draftLine(draft, minRadiusM).line));
|
||||
}
|
||||
|
||||
/** 확인 창 글 — [저장](노선 고침) · [적용](추천 노선) 같은 글 · 수. */
|
||||
export function routeResetConfirmText(counts: RouteResetCounts, action: "저장" | "적용"): string {
|
||||
const head = action === "저장" ? "노선이 바뀌어 [저장]하면" : "추천 노선을 [적용]하면";
|
||||
/** 추천 [적용] 확인 창 글. */
|
||||
export function routeResetConfirmText(counts: RouteResetCounts): string {
|
||||
return (
|
||||
`${head} 초기화합니다 — 구조물 ${counts.structures} · 관 ${counts.pipes} · ` +
|
||||
`추가 측점 ${counts.plusStations} · 종단 편집 ${counts.profileEdits} · 횡단 손 입력이 지워지고 ` +
|
||||
`새 노선으로 다시 셉니다(세류선 교차 관 · 측점 · 초기 종단 · 횡단). ${action}할까요?`
|
||||
`추천 노선을 [적용]하면 초기화합니다 — 구조물 ${counts.structures} · 관 ${counts.pipes} · ` +
|
||||
`추가 측점 ${counts.plusStations} · 종단 · 횡단 손 입력이 지워지고 ` +
|
||||
`새 노선으로 다시 셉니다(세류선 교차 관 · 측점 · 초기 종단 · 횡단). 적용할까요?`
|
||||
);
|
||||
}
|
||||
|
||||
/** 모양 바뀐 초안이면 묻고 답(지워질 수는 물을 때만 셈) · 안 바뀌면 묻지 않고 true. */
|
||||
export async function askRouteReset(
|
||||
changed: boolean,
|
||||
counts: () => Promise<RouteResetCounts>,
|
||||
ask: (text: string) => Promise<boolean>,
|
||||
): Promise<boolean> {
|
||||
return !changed || ask(routeResetConfirmText(await counts(), "저장"));
|
||||
}
|
||||
|
||||
/** 저장 뒤 알림 한 줄 — 초기화가 아니면 null. */
|
||||
/** 적용 뒤 알림 한 줄 — 초기화가 아니면 null. */
|
||||
export function routeResetNotice(
|
||||
reset: RouteReset | null | undefined,
|
||||
crossing = 0,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* 283 노선 모양 바뀐 [저장] = 초기화 — 진짜 TS(`logic/B05_Profile_Route_Reset` · `logic/B05_Profile_Grade_Carry`
|
||||
* routeShapeChanged · `logic/B05_Profile_Pipeline_RouteGround` draftLine)를 Node 로 돌림.
|
||||
/* 283 · 493-2 추천 [적용] = 초기화 — 진짜 TS(`logic/B05_Profile_Route_Reset` · `B05_Profile_UI_RouteReset`
|
||||
* applyResetText)를 Node 로 돌림 · 서버 저장분 받기는 가짜(입력 JSON `server`).
|
||||
* 입력 argv[2] JSON · 표준출력 JSON 한 벌.
|
||||
*/
|
||||
const fs = require("fs");
|
||||
@@ -21,78 +21,31 @@ function loadTs(file, stubs = {}) {
|
||||
return box.exports;
|
||||
}
|
||||
|
||||
const straightJudge = loadTs("B05_Profile/logic/B05_Profile_Profile_Straight.ts");
|
||||
const curveLaw = loadTs("B05_Profile/logic/B05_Profile_Profile_CurveLaw.ts");
|
||||
const pivot = loadTs("B05_Profile/logic/B05_Profile_UI_Profile_Pivot.ts", {
|
||||
"./B05_Profile_Profile_Straight": straightJudge,
|
||||
});
|
||||
const alignment = loadTs("B05_Profile/logic/B05_Profile_UI_Profile_Alignment.ts", {
|
||||
"./B05_Profile_UI_Profile_Pivot": pivot,
|
||||
"./B05_Profile_Profile_Straight": straightJudge,
|
||||
"./B05_Profile_Profile_CurveLaw": curveLaw,
|
||||
});
|
||||
const carry = loadTs("B05_Profile/logic/B05_Profile_Grade_Carry.ts", {
|
||||
"./B05_Profile_UI_Profile_Alignment": alignment,
|
||||
});
|
||||
const curve = loadTs("B05_Profile/logic/B05_Profile_UI_RouteEdit_Curve.ts");
|
||||
const edits = loadTs("B05_Profile/logic/B05_Profile_UI_RouteEdit_Edits.ts", {
|
||||
"./B05_Profile_UI_RouteEdit_Curve": curve,
|
||||
});
|
||||
const routeGround = loadTs("B05_Profile/logic/B05_Profile_Pipeline_RouteGround.ts", {
|
||||
"../../common_util/common_util_surface_sampler": { heightAt: () => null },
|
||||
"./B05_Profile_UI_RouteEdit_Curve": curve,
|
||||
"./B05_Profile_UI_RouteEdit_Edits": edits,
|
||||
"./B05_Profile_Sections_Ground": {},
|
||||
});
|
||||
const reset = loadTs("B05_Profile/logic/B05_Profile_Route_Reset.ts", {
|
||||
"./B05_Profile_Grade_Carry": carry,
|
||||
"./B05_Profile_Pipeline_RouteGround": routeGround,
|
||||
});
|
||||
const reset = loadTs("B05_Profile/logic/B05_Profile_Route_Reset.ts");
|
||||
|
||||
const xy = (line) => line.map(([x, y]) => ({ x, y }));
|
||||
/** 서버 저장분 가짜 — 값 = 돌려줄 것 · "fail" = 받기 실패. */
|
||||
const answer = (value) =>
|
||||
value === "fail" ? Promise.reject(new Error("받기 실패")) : Promise.resolve(value);
|
||||
|
||||
async function applyText(server) {
|
||||
const ui = loadTs("B05_Profile/B05_Profile_UI_RouteReset.ts", {
|
||||
"../A00_Common/design/design_page_data": {
|
||||
pageStructures: () => answer(server.structures),
|
||||
pagePipes: () => answer(server.pipes),
|
||||
},
|
||||
"./B05_Profile_Api_PlusStations": { fetchSavedPlusStations: () => answer(server.plus) },
|
||||
"./logic/B05_Profile_Route_Reset": reset,
|
||||
});
|
||||
return ui.applyResetText("p");
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const input = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
||||
const minR = input.min_radius_m;
|
||||
const base = input.base;
|
||||
const saved = routeGround.draftLine(base, minR).line;
|
||||
const lines = {};
|
||||
const changed = {};
|
||||
for (const [name, draft] of Object.entries(input.drafts)) {
|
||||
lines[name] = routeGround.draftLine(draft, minR).line;
|
||||
changed[name] = reset.draftShapeChanged(saved, draft, minR);
|
||||
}
|
||||
const pairs = input.pairs.map(([a, b]) => carry.routeShapeChanged(xy(a), xy(b)));
|
||||
|
||||
// 확인 창 줄 — 안 바뀜 = 묻지 않음 · [취소] = false · [저장] = true
|
||||
const counts = input.counts;
|
||||
const gate = [];
|
||||
for (const [isChanged, answer] of [
|
||||
[false, false],
|
||||
[true, false],
|
||||
[true, true],
|
||||
]) {
|
||||
const asked = [];
|
||||
let counted = 0;
|
||||
const ok = await reset.askRouteReset(
|
||||
isChanged,
|
||||
async () => (counted++, counts),
|
||||
async (text) => (asked.push(text), answer),
|
||||
);
|
||||
gate.push({ changed: isChanged, answer, ok, asked, counted });
|
||||
}
|
||||
|
||||
const texts = {};
|
||||
for (const [name, server] of Object.entries(input.servers)) texts[name] = await applyText(server);
|
||||
process.stdout.write(
|
||||
JSON.stringify({
|
||||
saved,
|
||||
lines,
|
||||
changed,
|
||||
nullDraft: reset.draftShapeChanged(saved, null, minR),
|
||||
unknownSaved: reset.draftShapeChanged(null, base, minR),
|
||||
pairs,
|
||||
gate,
|
||||
band: reset.ROUTE_RESET_BAND,
|
||||
applyText: reset.routeResetConfirmText(counts, "적용"),
|
||||
texts,
|
||||
notice: reset.routeResetNotice(input.reset, 2),
|
||||
noticeNone: [
|
||||
reset.routeResetNotice(undefined),
|
||||
|
||||
@@ -64,8 +64,6 @@ async function run() {
|
||||
return { waited: false, steps: { route: { route_id: 9 } } };
|
||||
},
|
||||
},
|
||||
// 492-2 노선 모양 바뀜 확인 창 — 이 시험은 묻지 않음(test_283_route_reset_confirm)
|
||||
"../../B05_Profile/B05_Profile_UI_RouteReset": { confirmRouteReset: async () => true },
|
||||
"../../B05_Profile/B05_Profile_UI_RouteEdit_Apply": {
|
||||
pendingRouteSave: async () => ({ draft: 1 }),
|
||||
routeCarryPart: () => ({ carry: {}, cross: { body: {} } }),
|
||||
|
||||
@@ -37,8 +37,6 @@ async function once(newRouteId, fail) {
|
||||
return { waited: false, steps: { route: { route_id: newRouteId } } };
|
||||
},
|
||||
},
|
||||
// 492-2 노선 모양 바뀜 확인 창 — 이 시험은 묻지 않음(test_283_route_reset_confirm)
|
||||
"../../B05_Profile/B05_Profile_UI_RouteReset": { confirmRouteReset: async () => true },
|
||||
"../../B05_Profile/B05_Profile_UI_RouteEdit_Apply": {
|
||||
pendingRouteSave: async () => ({ draft: 1 }),
|
||||
routeCarryPart: () => ({ carry: {}, cross: { body: {} } }),
|
||||
|
||||
@@ -84,8 +84,6 @@ const saveApiStub = {
|
||||
},
|
||||
};
|
||||
const STUBS = {
|
||||
// 492-2 노선 모양 바뀜 확인 창 — 이 시험은 묻지 않음(test_283_route_reset_confirm)
|
||||
"../../B05_Profile/B05_Profile_UI_RouteReset": { confirmRouteReset: async () => true },
|
||||
"../../B05_Profile/B05_Profile_UI_RouteEdit_Apply": routeStub,
|
||||
"@ui/ui_template_elements": {
|
||||
showToast: (text, kind) => calls.push(`toast:${kind}`),
|
||||
|
||||
@@ -1,106 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""283 · 492-2 노선 모양 바뀐 [저장] = 초기화 — 브라우저 판정 · 띠 · 확인 창 · 저장 뒤 알림.
|
||||
"""283 · 493-2 B05 [저장] = 보이는 화면 그대로 · 초기화 기준 = 노선 추천 [적용](요구 289 · 290).
|
||||
|
||||
거울(파이썬 = 서버 정본 판정 · TS = 브라우저 띠 · 확인 창 · 같은 값):
|
||||
`B05_Profile_Engine_Grade_Carry.route_shape_changed` ↔ `logic/B05_Profile_Grade_Carry.ts` routeShapeChanged.
|
||||
판정 입력 = 저장 노선(그린 선) 대 초안이 그릴 선(`draftLine`) — 스냅 · 간격 · 지형만 바뀜 = 아님.
|
||||
[취소] = 요청 0 — `design_save` 가 잠금 막 · 서버 요청보다 먼저 묻는지(소스 차례).
|
||||
- 손 고침 [저장] — 띠 · 확인 창 · 저장 뒤 초기화 알림 · 세션 초안 통째 비움 없음(492-2 걷음 · 소스 확인).
|
||||
- 추천 [적용] 확인 창 — 지워질 수 = 서버 저장분(구조물 · 관 · 추가 측점) = 적용 뒤 알림 수
|
||||
(서버 `route_reset.dropped` = 저장 파일 길이) · 세션 초안은 안 셈 · 관 저장 파일 없음 = 0.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from B05_Profile import B05_Profile_Engine_Grade_Carry as grade_carry # noqa: E402
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not (ROOT / "config" / "node_modules" / "typescript").is_dir(),
|
||||
reason="프론트엔드 의존성 미설치",
|
||||
)
|
||||
|
||||
NODES = [[0.0, 0.0], [200.0, 0.0], [400.0, 150.0], [600.0, 150.0], [800.0, 0.0]]
|
||||
|
||||
|
||||
def _draft(nodes=NODES, **over) -> dict:
|
||||
n = len(nodes)
|
||||
draft = {
|
||||
"rev": 0,
|
||||
"nodes": copy.deepcopy(nodes),
|
||||
"curveOn": [True] * n,
|
||||
"curveRadius": [None] * n,
|
||||
"curveLock": [None] * n,
|
||||
"curveArc": [None] * n,
|
||||
"stationSnap": False,
|
||||
"stationSnapStep": None,
|
||||
"terrain": None,
|
||||
}
|
||||
draft.update(over)
|
||||
return draft
|
||||
|
||||
|
||||
def _moved(index: int, dx: float, dy: float = 0.0) -> list:
|
||||
nodes = copy.deepcopy(NODES)
|
||||
nodes[index] = [nodes[index][0] + dx, nodes[index][1] + dy]
|
||||
return nodes
|
||||
|
||||
|
||||
BASE = _draft()
|
||||
SAME = { # 선이 그대로 — 띠 · 창 없음
|
||||
"snap_on": _draft(stationSnap=True),
|
||||
"snap_step": _draft(stationSnap=True, stationSnapStep=10),
|
||||
"terrain": _draft(terrain="las"),
|
||||
"rev": _draft(rev=7),
|
||||
"tiny": _draft(_moved(2, 0.0004)), # 0.4 mm — 1 mm 안
|
||||
SERVERS = {
|
||||
"saved": {
|
||||
"structures": {"structures": [{"id": "s1"}]},
|
||||
"pipes": {"saved": True, "pipe_points": [{}, {}, {}]},
|
||||
"plus": [120],
|
||||
},
|
||||
"unsaved_pipes": { # 관 저장 파일 없음 — 응답 관은 자동 제안 · 서버가 지울 것 0
|
||||
"structures": {"structures": []},
|
||||
"pipes": {"saved": False, "pipe_points": [{}, {}]},
|
||||
"plus": [],
|
||||
},
|
||||
"fail": {"structures": "fail", "pipes": "fail", "plus": "fail"},
|
||||
}
|
||||
CHANGED = { # 모양 바뀜 — 띠 · 창
|
||||
"drag20": _draft(_moved(2, 20.0)),
|
||||
"radius": _draft(curveRadius=[None, 80.0, None, None, None]),
|
||||
"add": _draft(NODES[:1] + [[100.0, 30.0]] + NODES[1:]),
|
||||
"remove": _draft(NODES[:3] + NODES[4:]),
|
||||
"curve_off": _draft(curveOn=[True, False, True, True, True]),
|
||||
"end_drag": _draft(_moved(4, 0.0, 5.0)),
|
||||
}
|
||||
COUNTS = {"structures": 1, "pipes": 3, "plusStations": 1, "profileEdits": 2}
|
||||
RESET = {"reset": True, "dropped": {"structures": 1, "pipes": 3, "plus_stations": 1}}
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ts(tmp_path_factory):
|
||||
drafts = {**SAME, **CHANGED}
|
||||
payload = {
|
||||
"min_radius_m": 30.0,
|
||||
"base": BASE,
|
||||
"drafts": drafts,
|
||||
"counts": COUNTS,
|
||||
"reset": RESET,
|
||||
"pairs": [],
|
||||
}
|
||||
source = tmp_path_factory.mktemp("s283") / "in.json"
|
||||
source.write_text(json.dumps(payload), encoding="utf-8")
|
||||
first = _run(source)
|
||||
# 거울 짝 — 저장 선 대 각 초안 선 · 같은 선 · 2 mm 흔듦
|
||||
saved = first["saved"]
|
||||
jitter = [[x + (0.002 if i == 3 else 0.0), y] for i, (x, y) in enumerate(saved)]
|
||||
pairs = [[saved, line] for line in first["lines"].values()] + [[saved, saved], [saved, jitter]]
|
||||
payload["pairs"] = pairs
|
||||
source.write_text(json.dumps(payload), encoding="utf-8")
|
||||
out = _run(source)
|
||||
out["pair_inputs"] = pairs
|
||||
return out
|
||||
|
||||
|
||||
def _run(source: Path) -> dict:
|
||||
source.write_text(json.dumps({"servers": SERVERS, "reset": RESET}), encoding="utf-8")
|
||||
proc = subprocess.run( # noqa: S603 — 고정 실행 파일
|
||||
["node", str(HERE / "helper_283_route_reset_confirm.cjs"), str(source)],
|
||||
capture_output=True,
|
||||
@@ -113,79 +54,66 @@ def _run(source: Path) -> dict:
|
||||
return json.loads(proc.stdout)
|
||||
|
||||
|
||||
# ── 판정 ─────────────────────────────────────────────────────────────────────
|
||||
def _read(*parts: str) -> str:
|
||||
return ROOT.joinpath(*parts).read_text(encoding="utf-8")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", list(SAME))
|
||||
def test_스냅_간격_지형만_띠_창_없음(ts, name):
|
||||
assert ts["changed"][name] is False
|
||||
# ── 손 고침 [저장] = 보이는 화면 그대로 ───────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", list(CHANGED))
|
||||
def test_모양_바뀐_초안_띠_창(ts, name):
|
||||
assert ts["changed"][name] is True
|
||||
|
||||
|
||||
def test_초안_없음_아님_저장선_모름_바뀜(ts):
|
||||
assert ts["nullDraft"] is False
|
||||
assert ts["unknownSaved"] is True
|
||||
|
||||
|
||||
def test_거울_파이썬_판정_같은값(ts):
|
||||
judge = getattr(grade_carry, "route_shape_changed", None)
|
||||
if judge is None:
|
||||
pytest.skip("서버 판정 route_shape_changed 아직 없음(492-1 sub4 합침 전)")
|
||||
for (old, new), got in zip(ts["pair_inputs"], ts["pairs"]):
|
||||
assert judge(old, new) is got
|
||||
assert ts["pairs"][-2] is False # 같은 선
|
||||
assert ts["pairs"][-1] is True # 2 mm
|
||||
|
||||
|
||||
# ── 확인 창 · [취소] ──────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_확인창_안바뀜_묻지않음(ts):
|
||||
gate = ts["gate"][0]
|
||||
assert gate["ok"] is True and gate["asked"] == [] and gate["counted"] == 0
|
||||
|
||||
|
||||
def test_확인창_수_취소_false(ts):
|
||||
cancel, accept = ts["gate"][1], ts["gate"][2]
|
||||
assert cancel["ok"] is False and accept["ok"] is True
|
||||
for gate in (cancel, accept):
|
||||
assert len(gate["asked"]) == 1 and gate["counted"] == 1
|
||||
text = gate["asked"][0]
|
||||
assert text.startswith("노선이 바뀌어 [저장]하면 초기화")
|
||||
for part in ("구조물 1", "관 3", "추가 측점 1", "종단 편집 2", "횡단 손 입력"):
|
||||
assert part in text
|
||||
assert text.endswith("저장할까요?")
|
||||
|
||||
|
||||
def test_추천_적용_같은글_같은수(ts):
|
||||
text = ts["applyText"]
|
||||
assert text.startswith("추천 노선을 [적용]하면 초기화")
|
||||
tail = ts["gate"][2]["asked"][0].split("초기화", 1)[1].replace("저장할까요?", "")
|
||||
assert text.split("초기화", 1)[1].replace("적용할까요?", "") == tail
|
||||
|
||||
|
||||
def test_취소_요청0_잠금막_서버요청_앞(ts):
|
||||
src = (ROOT / "A00_Common" / "design" / "design_save.ts").read_text(encoding="utf-8")
|
||||
def test_저장_확인창_초기화알림_초안비움_없음():
|
||||
src = _read("A00_Common", "design", "design_save.ts")
|
||||
for gone in ("confirmRouteReset", "routeResetNotice", "route_reset", "clearDrafts("):
|
||||
assert gone not in src
|
||||
body = src[src.index("export async function saveDesign(") :]
|
||||
ask = body.index("confirmRouteReset(")
|
||||
assert ask < body.index("showLoadingOverlay()") < body.index("postDesignSave(")
|
||||
assert "return false" in body[ask : body.index("showLoadingOverlay()")]
|
||||
# 잠금 막 앞에 묻는 것 없음 — 노선 초안 읽기는 잠금 막 뒤(492 이전 차례)
|
||||
assert body.index("showLoadingOverlay()") < body.index("pendingRouteSave(")
|
||||
|
||||
|
||||
def test_띠_글(ts):
|
||||
assert ts["band"].startswith("노선이 바뀌어 [저장] 하면 초기화")
|
||||
for part in ("구조물", "관", "추가 측점", "종단", "횡단 손 입력"):
|
||||
assert part in ts["band"]
|
||||
def test_띠_걷음():
|
||||
assert "mountRouteResetBand" not in _read("A00_Common", "design", "design_view_profile.ts")
|
||||
assert "reset-band" not in _read("B05_Profile", "B05_Profile_UI_Style_RouteEdit.css")
|
||||
assert "pageRoutePlan" not in _read("A00_Common", "design", "design_page_data.ts")
|
||||
assert "routeShapeChanged" not in _read("B05_Profile", "logic", "B05_Profile_Grade_Carry.ts")
|
||||
logic = _read("B05_Profile", "logic", "B05_Profile_Route_Reset.ts")
|
||||
for gone in ("ROUTE_RESET_BAND", "draftShapeChanged", "askRouteReset"):
|
||||
assert gone not in logic
|
||||
|
||||
|
||||
# ── 저장 뒤 알림 ─────────────────────────────────────────────────────────────
|
||||
# ── 추천 [적용] 확인 창 = 서버가 지울 수 ─────────────────────────────────────
|
||||
|
||||
|
||||
def test_저장뒤_알림_dropped(ts):
|
||||
def test_적용_확인창_서버_저장분_수(ts):
|
||||
text = ts["texts"]["saved"]
|
||||
assert text.startswith("추천 노선을 [적용]하면 초기화")
|
||||
for part in ("구조물 1", "관 3", "추가 측점 1", "종단 · 횡단 손 입력"):
|
||||
assert part in text
|
||||
assert text.endswith("적용할까요?")
|
||||
|
||||
|
||||
def test_적용_확인창_수_적용뒤_알림_수_같음(ts):
|
||||
dropped = RESET["dropped"]
|
||||
text, notice = ts["texts"]["saved"], ts["notice"]
|
||||
for name, key in (("구조물", "structures"), ("관", "pipes"), ("추가 측점", "plus_stations")):
|
||||
assert f"{name} {dropped[key]} " in text and f"{name} {dropped[key]}" in notice
|
||||
|
||||
|
||||
def test_관_저장파일_없음_0_받기실패_0(ts):
|
||||
assert "구조물 0 · 관 0 · 추가 측점 0" in ts["texts"]["unsaved_pipes"]
|
||||
assert "구조물 0 · 관 0 · 추가 측점 0" in ts["texts"]["fail"]
|
||||
|
||||
|
||||
def test_적용_확인창_배선():
|
||||
initial = _read("B05_Profile", "B05_Profile_UI_RouteEdit_Initial.ts")
|
||||
assert "confirmText: () => applyResetText(projectId)" in initial
|
||||
ui = _read("B05_Profile", "B05_Profile_UI_RouteReset.ts")
|
||||
assert "useDesignStore" not in ui # 세션 초안 안 셈
|
||||
|
||||
|
||||
# ── 적용 뒤 알림 ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_적용뒤_알림_dropped(ts):
|
||||
assert ts["notice"] == (
|
||||
"노선이 바뀌어 초기화했습니다 — 지움: 구조물 1 · 관 3 · 추가 측점 1 · 새 교차 관 2"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user