diff --git a/B05_Profile/B05_Profile_UI_Panel_Route.ts b/B05_Profile/B05_Profile_UI_Panel_Route.ts index 4adc6d72b..a21185ec8 100644 --- a/B05_Profile/B05_Profile_UI_Panel_Route.ts +++ b/B05_Profile/B05_Profile_UI_Panel_Route.ts @@ -19,12 +19,9 @@ export interface RoutePartCallbacks { export function createRoutePart(callbacks: RoutePartCallbacks) { // 계획노선 편집 — 노선을 고치는 유일한 입구(2026-09-06 사용자 확정). 자동탐색 // (「경로 계산 설정」)은 사용 중단이라 이 자리가 노선을 바꾸는 길이다. - const plannedRouteBtn = document.createElement("button"); - plannedRouteBtn.type = "button"; - plannedRouteBtn.className = "b05-route__btn"; - plannedRouteBtn.textContent = "계획노선 편집"; + const plannedRouteBtn = button("계획노선 편집", () => callbacks.onEditPlannedRoute()); + plannedRouteBtn.classList.add("b05-route__btn"); plannedRouteBtn.title = "예상노선 위에서 계획노선을 고칩니다. [확인] 때만 다시 계산합니다."; - plannedRouteBtn.addEventListener("click", () => callbacks.onEditPlannedRoute()); const selectedName = document.createElement("strong"); const radius = numberField("회피/금지 반경 (m)", "25"); diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts b/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts index 2788805ea..eb9f18ab7 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_Chrome.ts @@ -1,3 +1,5 @@ +import { createButton } from "@ui/ui_template_elements"; + /* ============================================================================= * B05_Profile_UI_RouteEdit_Chrome.ts * 계획노선 편집 모달의 **뼈대** — 창·단추·오버레이 판을 만들고 자주 쓰는 요소를 집어 준다. @@ -61,7 +63,7 @@ export function createRouteEditChrome(): RouteEditChrome { - +
@@ -79,8 +81,7 @@ export function createRouteEditChrome(): RouteEditChrome {
노선을 읽는 중… @@ -101,6 +102,17 @@ export function createRouteEditChrome(): RouteEditChrome {
`; document.body.append(overlay); + const closeButton = createButton({ label: "✕", variant: "ghost" }); + closeButton.classList.add("b05-routeedit__close"); + closeButton.setAttribute("aria-label", "닫기"); + overlay.querySelector('[data-slot="close"]')!.replaceWith(closeButton); + + const measureCloseButton = createButton({ label: "✕", variant: "ghost" }); + measureCloseButton.classList.add("b05-routeedit__measure-close"); + measureCloseButton.setAttribute("aria-label", "닫기"); + measureCloseButton.title = "닫기"; + overlay.querySelector('[data-slot="measure-close"]')!.replaceWith(measureCloseButton); + const canvas = overlay.querySelector(".b05-routeedit__canvas")!; const status = overlay.querySelector(".b05-routeedit__status")!; const measureBox = overlay.querySelector(".b05-routeedit__measure")!; diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_Label.ts b/B05_Profile/B05_Profile_UI_RouteEdit_Label.ts index 60269cd99..c0e8fa11c 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_Label.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_Label.ts @@ -20,6 +20,8 @@ * 다른 꺾임점을 고르면 자동 자리로 돌아간다. * ========================================================================== */ +import { createButton } from "@ui/ui_template_elements"; +import { numberField, toggleButton } from "./B05_Profile_UI_Panel_Fields"; import { CURVE_OPTIONAL_INNER_ANGLE_DEG, type CurveLock } from "./B05_Profile_UI_RouteEdit_Edits"; // `deflectionRad` 는 화면을 안 만지는 셈이라 `_Edits.ts` 로 옮겼다(2026-09-12) — 순수 @@ -113,6 +115,12 @@ function boxReach(dx: number, dy: number, width: number, height: number): number return Math.min(byX, byY); } +function unitLabel(text: string): HTMLSpanElement { + const span = document.createElement("span"); + span.textContent = text; + return span; +} + /** 패널을 만든다. 몸통은 `document.body` 에 붙어 모달 밖으로도 넘어간다. */ export function createCurveLabel(handlers: CurveLabelHandlers): CurveLabel { const root = document.createElement("div"); @@ -121,40 +129,64 @@ export function createCurveLabel(handlers: CurveLabelHandlers): CurveLabel { root.innerHTML = `
- - + +
- - + +
교각점 - +
`; document.body.append(root); const head = root.querySelector(".b05-routeedit__label-head")!; const seatText = root.querySelector(".b05-routeedit__curve-label")!; - const toggle = root.querySelector('[data-act="curve-toggle"]')!; - const radius = root.querySelector(".b05-routeedit__curve-radius")!; - const arc = root.querySelector(".b05-routeedit__curve-arc")!; - const lockRadius = root.querySelector('[data-act="lock-radius"]')!; - const lockArc = root.querySelector('[data-act="lock-arc"]')!; - const lockApex = root.querySelector('[data-act="lock-apex"]')!; const info = root.querySelector(".b05-routeedit__curve-info")!; const lockName = root.querySelector(".b05-routeedit__lock-name")!; + + const toggle = createButton({ label: "", variant: "ghost", size: "sm" }); + toggle.dataset.act = "curve-toggle"; + root.querySelector('[data-slot="curve-toggle"]')!.replaceWith(toggle); + + const closeBtn = createButton({ label: "✕", variant: "ghost", size: "sm" }); + closeBtn.dataset.act = "curve-close"; + closeBtn.setAttribute("aria-label", "닫기"); + closeBtn.title = "닫기"; + root.querySelector('[data-slot="curve-close"]')!.replaceWith(closeBtn); + + const radiusField = numberField("반지름"); + radiusField.wrapper.className = "b05-routeedit__curve-field"; + radiusField.classList.add("b05-routeedit__curve-radius"); + radiusField.min = "1"; + radiusField.step = "0.5"; + const radius = radiusField as HTMLInputElement; + const lockRadius = toggleButton("고정", false, (active) => + handlers.onLock(active ? "radius" : null), + ); + lockRadius.dataset.act = "lock-radius"; + lockRadius.title = "반지름 고정 — 노드를 옮겨도 안 바뀝니다"; + radiusField.wrapper.append(unitLabel("m"), lockRadius); + root.querySelector('[data-slot="radius-field"]')!.replaceWith(radiusField.wrapper); + + const arcField = numberField("곡선 길이"); + arcField.wrapper.className = "b05-routeedit__curve-field"; + arcField.classList.add("b05-routeedit__curve-arc"); + arcField.min = "1"; + arcField.step = "0.5"; + const arc = arcField as HTMLInputElement; + const lockArc = toggleButton("고정", false, (active) => handlers.onLock(active ? "arc" : null)); + lockArc.dataset.act = "lock-arc"; + lockArc.title = "곡선 길이 고정 — 노드를 옮겨도 안 바뀝니다"; + arcField.wrapper.append(unitLabel("m"), lockArc); + root.querySelector('[data-slot="arc-field"]')!.replaceWith(arcField.wrapper); + + const lockApex = toggleButton("고정", false, (active) => handlers.onApexLock(active)); + lockApex.dataset.act = "lock-apex"; + root.querySelector('[data-slot="lock-apex"]')!.replaceWith(lockApex); + /** 끝점에서는 숨길 곡선 칸 — 곡선 지우기 단추 · 반지름 줄 · 곡선 길이 줄 · 내각 글. */ - const curveOnly = [toggle, radius.closest("label")!, arc.closest("label")!, info]; + const curveOnly = [toggle, radiusField.wrapper, arcField.wrapper, info]; // 패널 위에서 누른 것이 캔버스로 새어 나가면 노드가 딸려 움직인다. for (const type of ["pointerdown", "dblclick", "contextmenu", "wheel"] as const) { @@ -188,13 +220,10 @@ export function createCurveLabel(handlers: CurveLabelHandlers): CurveLabel { radius.addEventListener("change", () => handlers.onRadius(numberOf(radius, limitRadius))); arc.addEventListener("change", () => handlers.onArcLength(numberOf(arc, limitArc))); toggle.addEventListener("click", () => handlers.onCurveOn(!curveOn)); - root - .querySelector('[data-act="curve-close"]')! - .addEventListener("click", () => handlers.onClose()); - lockRadius.addEventListener("click", () => handlers.onLock(lock === "radius" ? null : "radius")); - lockArc.addEventListener("click", () => handlers.onLock(lock === "arc" ? null : "arc")); + closeBtn.addEventListener("click", () => handlers.onClose()); + // 고정 단추 셋은 만들 때(`toggleButton`) 이미 눌림을 배선했다 — 반지름 · 곡선 길이는 서로 + // 배타적(한쪽을 누르면 다른 쪽은 풀림)이라 `onLock` 쪽에서 그 규칙을 맞춘다. // 교각점 고정은 **곡선 유무와 무관**하다 — 곡선을 지운 자리도 꺾임점 자리는 못 박을 수 있다. - lockApex.addEventListener("click", () => handlers.onApexLock(!apexLocked)); // ── 머리를 잡아 옮기기 — 곡선을 가리면 손으로 치울 수 있어야 한다 ── let dragFrom: { x: number; y: number; left: number; top: number } | null = null; @@ -297,9 +326,12 @@ export function createCurveLabel(handlers: CurveLabelHandlers): CurveLabel { arc.disabled = !state.curveOn; lockRadius.disabled = !state.curveOn; lockArc.disabled = !state.curveOn; - lockRadius.classList.toggle("is-on", lock === "radius"); - lockArc.classList.toggle("is-on", lock === "arc"); - lockApex.classList.toggle("is-on", apexLocked); + lockRadius.classList.toggle("is-active", lock === "radius"); + lockRadius.setAttribute("aria-pressed", String(lock === "radius")); + lockArc.classList.toggle("is-active", lock === "arc"); + lockArc.setAttribute("aria-pressed", String(lock === "arc")); + lockApex.classList.toggle("is-active", apexLocked); + lockApex.setAttribute("aria-pressed", String(apexLocked)); radius.value = state.radiusShown === null ? "" : String(Math.round(state.radiusShown * 10) / 10); arc.value = diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_Recommend.ts b/B05_Profile/B05_Profile_UI_RouteEdit_Recommend.ts index 1fa8d2e44..6d8a4f854 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_Recommend.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_Recommend.ts @@ -15,7 +15,8 @@ * ========================================================================== */ import { openModal } from "@ui/ui_template_modal"; -import { showToast } from "@ui/ui_template_elements"; +import { createButton, createInputField, showToast } from "@ui/ui_template_elements"; +import { toggleButton } from "./B05_Profile_UI_Panel_Fields"; import type { RouteInitialCriteria, RouteInitialLogic, @@ -78,11 +79,6 @@ const WEIGHT_NAME: Record = { grade: "기울기", }; -/** 고정 단추 그림 — 자물쇠(글자 색을 따라 밝음 · 어두움에서 보인다). */ -const LOCK_ICON = ``; - /** 공통 제약 칸 — 표시 이름 · 단위 · 서버가 준 값(`InitialCriteria`)에서 그 칸을 꺼내는 법. */ const CRITERIA_FIELD: Record< CriteriaKey, @@ -181,43 +177,20 @@ export function openRecommendModal(params: RecommendParams): void {
-
- ${PILLS.map( - (key) => ``, - ).join("")} -
+
@@ -248,6 +221,74 @@ export function openRecommendModal(params: RecommendParams): void {
${recommendHelpHtml()}`; + // 알약 · 비중 막대 · 제약 칸 · 목표 기울기 칸은 공용 부품(createButton · createInputField · + // PF toggleButton)으로 짓는다(PLAN 117) — 글 틀은 위에 자리만 남기고 여기서 채운다. + const pillsBox = body.querySelector('[data-slot="pills"]')!; + for (const key of PILLS) { + const pill = createButton({ label: LOGIC_LABEL[key], variant: "pill" }); + pill.classList.add("b05-routerec__pill"); + pill.setAttribute("role", "radio"); + pill.dataset.logic = key; + pill.title = PILL_TITLE[key]; + pillsBox.append(pill); + } + + const barsBox = body.querySelector('[data-slot="bars"]')!; + for (const key of WEIGHT_KEYS) { + const bar = document.createElement("div"); + bar.className = "b05-routerec__bar"; + const numField = createInputField({ type: "number", min: 0, max: 100, compact: true }); + numField.input.step = "1"; + numField.input.inputMode = "numeric"; + numField.input.classList.add("b05-routerec__num"); + numField.input.dataset.weightNum = key; + numField.input.setAttribute("aria-label", `${WEIGHT_NAME[key]} 비중 값`); + const slider = document.createElement("input"); + slider.type = "range"; + slider.min = "0"; + slider.max = "100"; + slider.step = "1"; + slider.dataset.weight = key; + slider.setAttribute("aria-label", `${WEIGHT_NAME[key]} 비중`); + const lock = toggleButton("고정", false, (active) => { + locks[key] = active; + invalid.clear(); + render(); + }); + lock.classList.add("b05-routerec__lock"); + lock.dataset.weightLock = key; + lock.setAttribute("aria-label", `${WEIGHT_NAME[key]} 비중 고정`); + const name = document.createElement("span"); + name.textContent = WEIGHT_NAME[key]; + bar.append(numField.input, slider, lock, name); + barsBox.append(bar); + } + + const criteriaBox = body.querySelector('[data-slot="criteria"]')!; + for (const key of CRITERIA_KEYS) { + const field = CRITERIA_FIELD[key]; + const row = document.createElement("label"); + row.title = field.title; + const name = document.createElement("span"); + name.textContent = field.label; + const input = createInputField({ type: "number", min: 0, compact: true }); + input.input.inputMode = "decimal"; + input.input.step = "0.1"; + input.input.dataset.criteria = key; + input.input.placeholder = "기본값"; + const unit = document.createElement("span"); + unit.textContent = field.unit; + row.append(name, input.input, unit); + criteriaBox.append(row); + } + + const targetField = createInputField({ type: "number", min: 0, max: 100, compact: true }); + targetField.input.inputMode = "decimal"; + targetField.input.step = "0.1"; + targetField.input.dataset.criteria = TARGET_GRADE_KEY; + targetField.input.placeholder = "상한"; + body.querySelector('[data-slot="target-input"]')!.replaceWith(targetField.input); + const q = (sel: string): T => body.querySelector(sel)!; // [적용] · [닫기]를 제목줄 오른쪽 위로 — 공용 겉틀의 닫기 단추와 갈아 끼운다(PLAN 77-3). const actions = q(".b05-routerec__actions"); @@ -297,7 +338,7 @@ export function openRecommendModal(params: RecommendParams): void { pills.forEach((pill) => { const on = pill.dataset.logic === logic; pill.setAttribute("aria-checked", String(on)); - pill.classList.toggle("is-on", on); + pill.classList.toggle("is-active", on); pill.disabled = busy; }); weightsBox.hidden = logic !== "weighted"; @@ -316,7 +357,7 @@ export function openRecommendModal(params: RecommendParams): void { num.classList.toggle("is-invalid", invalid.has(key)); num.setAttribute("aria-invalid", String(invalid.has(key))); lock.setAttribute("aria-pressed", String(shut)); - lock.classList.toggle("is-on", shut); + lock.classList.toggle("is-active", shut); lock.classList.toggle("is-auto", derived === key); // 셋 다 고정은 막는다 — 마지막 하나는 고정 단추가 꺼지고 잠김으로만 보인다. lock.disabled = busy || !canLockWeight(locks, key) || derived === key; @@ -488,16 +529,8 @@ export function openRecommendModal(params: RecommendParams): void { if (!invalid.has(input.dataset.weightNum as WeightKey)) render(); }); }); - // 고정 단추 — 지금 값을 붙든다. 셋 다 고정은 막혀 있다(단추가 꺼짐). - body.querySelectorAll("[data-weight-lock]").forEach((button) => { - button.addEventListener("click", () => { - const key = button.dataset.weightLock as WeightKey; - if (!canLockWeight(locks, key)) return; - locks[key] = !locks[key]; - invalid.clear(); - render(); - }); - }); + // 고정 단추 — 눌림은 만들 때(`toggleButton`) 이미 배선했다. 셋 다 고정은 `render()` 가 + // disabled 로 막아 클릭 자체가 안 먹는다. criteriaInputs.forEach((input) => { input.addEventListener("input", () => { dirty = preview !== null; diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Profile.ts b/B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Profile.ts index 218ae6ddf..63ac6d57b 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Profile.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_Recommend_Profile.ts @@ -8,6 +8,8 @@ * (계획고 = 지반고)으로 다시 세운다. 계산 자리 = 서버 단독 — 여기는 받은 값을 그리기만 한다. * ========================================================================== */ +import { renderDataTableHtml, type DataColumn } from "@ui/ui_template_data_table"; + /** 후보 하나의 균형 종단 — 서버 `metrics.profile`. */ export interface BalanceProfile { stationsM: number[]; @@ -118,17 +120,25 @@ export function profileSvg( `; } +const CANDIDATE_COLUMNS: DataColumn[] = [ + { key: "name", label: "후보" }, + { key: "cut", label: "절토" }, + { key: "fill", label: "성토" }, + { key: "mass", label: "누가토량" }, + { key: "grade", label: "최대 종단경사" }, +]; + /** 이 모달에서 셈한 후보들 — 갈래 이름 · 절 · 성 · 누가토량(±) · 최대 종단경사 표. 비면 빈 글. */ export function candidatesTable(rows: Array<[string, BalanceProfile]>): string { if (!rows.length) return ""; - const body = rows - .map( - ([name, b]) => - `${name}${m3(b.cutM3)}${m3(b.fillM3)}` + - `${signedM3(b.massEndM3)}${pct(b.maxGradePct)}`, - ) - .join(""); - return ` - - ${body}
후보절토성토누가토량최대 종단경사
`; + return renderDataTableHtml({ + columns: CANDIDATE_COLUMNS, + rows: rows.map(([name, b]) => ({ + name, + cut: m3(b.cutM3), + fill: m3(b.fillM3), + mass: signedM3(b.massEndM3), + grade: pct(b.maxGradePct), + })), + }); } diff --git a/B05_Profile/B05_Profile_UI_Style_RouteEdit.css b/B05_Profile/B05_Profile_UI_Style_RouteEdit.css index e142c56a4..45cb046a5 100644 --- a/B05_Profile/B05_Profile_UI_Style_RouteEdit.css +++ b/B05_Profile/B05_Profile_UI_Style_RouteEdit.css @@ -49,15 +49,6 @@ gap: var(--spacing-8); } -.b05-routeedit__close { - flex: none; - border: none; - background: none; - color: var(--color-text-secondary); - font-size: 16px; - cursor: pointer; -} - .b05-routeedit__canvas-wrap { position: relative; flex: 1 1 auto; @@ -284,14 +275,6 @@ line-height: 1.5; } -.b05-routeedit__measure-close { - flex: none; - border: none; - background: none; - color: var(--color-text-secondary); - cursor: pointer; -} - /* 재계산 중에는 화면 전체를 덮는다 — 결과를 기다릴 수밖에 없는 조작(CLAUDE.md 5장). */ .b05-routeedit__busy { position: absolute; @@ -344,57 +327,11 @@ touch-action: none; } -.b05-routeedit__label-close { - border: none; - background: none; - color: var(--color-text-secondary); - font-size: 13px; - line-height: 1; - cursor: pointer; -} - -/* 고정 단추 — 켜지면 색이 찬다. 켠 값은 노드를 옮겨도 안 바뀐다. */ -.b05-routeedit__lock { - padding: 1px 6px; - border: 1px solid var(--color-border); - border-radius: var(--radius-4, 4px); - background: var(--color-surface); - color: var(--color-text-secondary); - font: inherit; - cursor: pointer; -} - -.b05-routeedit__lock.is-on { - border-color: transparent; - background: var(--color-primary, #7c3aed); - color: #fff; -} - -.b05-routeedit__lock:disabled { - color: var(--color-text-secondary); - cursor: default; -} - /* 「칸을 비우면 자동」 — 상태 설명과 줄을 나눈다(2026-09-07 사용자 지시 4). */ .b05-routeedit__curve-note { color: var(--color-text-secondary); } -.b05-routeedit__label-toggle { - padding: 1px 6px; - border: 1px solid var(--color-border); - border-radius: var(--radius-4, 4px); - background: var(--color-surface); - color: var(--color-text-body); - font: inherit; - cursor: pointer; -} - -.b05-routeedit__label-toggle:disabled { - color: var(--color-text-secondary); - cursor: default; -} - .b05-routeedit__curve-label { font-weight: 600; white-space: nowrap; diff --git a/B05_Profile/B05_Profile_UI_Style_RouteRecommend.css b/B05_Profile/B05_Profile_UI_Style_RouteRecommend.css index 506d81ce4..5eef6bf43 100644 --- a/B05_Profile/B05_Profile_UI_Style_RouteRecommend.css +++ b/B05_Profile/B05_Profile_UI_Style_RouteRecommend.css @@ -188,27 +188,13 @@ } .b05-routerec__pill { - padding: 8px var(--spacing-8); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - border: 1px solid var(--color-border); - border-radius: 999px; - background: var(--color-surface); - color: var(--color-text-body); - font-size: var(--text-body); - cursor: pointer; } -.b05-routerec__pill:hover:not(:disabled) { - border-color: var(--color-primary); -} - -.b05-routerec__pill.is-on { - border-color: var(--color-primary); - background: var(--color-primary); - color: var(--color-on-primary, #fff); - font-weight: 600; +.b05-routerec__pill:disabled { + cursor: progress; } /* 제목줄 오른쪽 위 [적용] · [닫기](공용 겉틀 닫기 단추 자리 · PLAN 77-3). */ @@ -217,11 +203,6 @@ gap: var(--spacing-8); } -.b05-routerec__pill:disabled { - cursor: progress; - opacity: 0.7; -} - .b05-routerec__main { display: grid; grid-template-columns: minmax(0, 1fr); @@ -371,36 +352,7 @@ box-shadow: 0 0 0 1px var(--color-danger, #dc2626); } -/* 고정 단추 — 자물쇠. 눌린 것 = 고정 · 저절로 정해진 비중은 눌린 채 꺼져(잠김 표시). */ -.b05-routerec__lock { - display: inline-flex; - align-items: center; - justify-content: center; - width: 28px; - height: 26px; - padding: 0; - border: 1px solid var(--color-border); - border-radius: var(--radius-4, 4px); - background: var(--color-surface); - color: var(--color-text-secondary); - cursor: pointer; -} - -.b05-routerec__lock:hover:not(:disabled) { - border-color: var(--color-primary); -} - -.b05-routerec__lock.is-on { - border-color: var(--color-primary); - background: var(--color-primary); - color: var(--color-on-primary, #fff); -} - -.b05-routerec__lock:disabled { - cursor: not-allowed; - opacity: 0.55; -} - +/* 저절로 정해진 비중은 고정 단추가 눌린 채 꺼져(잠김 표시) — 색은 공용 꼴(`ui-btn--glass`)을 쓴다. */ .b05-routerec__lock.is-auto { opacity: 0.8; border-style: dashed; diff --git a/resources/tester/helper_b05_routeedit_initial.cjs b/resources/tester/helper_b05_routeedit_initial.cjs index a9762095c..1e673fdc1 100644 --- a/resources/tester/helper_b05_routeedit_initial.cjs +++ b/resources/tester/helper_b05_routeedit_initial.cjs @@ -40,6 +40,8 @@ function loadTs(file) { cache.set(full, box.exports); const localRequire = (request) => { if (STUBS[request]) return STUBS[request]; + if (request.endsWith(".css")) return {}; + if (request.startsWith("@ui/")) return loadTs(path.join(ROOT, "ui_template", `${request.slice(4)}.ts`)); if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); return require(request); }; diff --git a/resources/tester/test_63_4_route_profile.py b/resources/tester/test_63_4_route_profile.py index d9bf18b4c..3f77662cf 100644 --- a/resources/tester/test_63_4_route_profile.py +++ b/resources/tester/test_63_4_route_profile.py @@ -120,5 +120,8 @@ def test_균형_종단은_캐시에_안_남는다() -> None: assert "덮개 모자란 관 1/2" in result["line"] assert "누가토량 -40㎥(허용 ±500㎥)" in result["line"] assert result["svg"].count("토공 최소' in result["table"] - assert '누가토량' in result["table"] and "반입" not in result["table"] + assert "토공 최소" in result["table"] + assert ( + '누가토량' in result["table"] + and "반입" not in result["table"] + )