diff --git a/B06_Section/B06_Section_UI_Cross_Detail.ts b/B06_Section/B06_Section_UI_Cross_Detail.ts index 6a233b179..3d7e62700 100644 --- a/B06_Section/B06_Section_UI_Cross_Detail.ts +++ b/B06_Section/B06_Section_UI_Cross_Detail.ts @@ -9,7 +9,7 @@ * 아래 = B07 횡단 수량표(값 · 칸 배치 모두 서버 `cross-quantity-table` 한 길 · 캐시 설계로 · 열 때 부름). * 공용 큰 창(`wide`) · 공용 막(밖 누르면 닫힘) · 스크롤 없음(150-2). * 그림 = 공용 줌 · 팬(164-1 · 휠 = 커서 고정 줌 · 가운데 단추 팬 · 더블클릭 원복 · [+ − ⤢]) — - * 복제 자식을 g 하나로 감싸 축 · 눈금째 한 장으로 움직임. + * 카드처럼 도형 층만 움직이고 축 · 눈금은 제자리에서 보이는 범위로 다시 그림(179-3). * 모달이 열린 채 카드를 다시 그리면 그림 · 표를 새로 맞춤. * ========================================================================== */ @@ -30,14 +30,19 @@ import { type ZoomPanState, } from "../A00_Common/b_svg_zoom_pan"; import { buildZoomControls } from "./B06_Section_UI_Cross_View_Zoom"; +import { createCrossAxes, IDENTITY_VIEW, type CrossAxesOptions } from "./B06_Section_UI_Cross_Axes"; /** 페이지가 한 번 걸어 줌(`_UI_Page_Design_Sync`) — 카드 줄은 프로젝트 · 노선을 모름. */ export interface CrossDetailHost { projectId: string; routeId: () => number | null; detail: () => SectionDetailResponse | null; - /** 칸 크기(W × H px)로 새로 그린 카드 svg(173-4 · 뷰 `detailChart`). */ - chart: (stationId: string, widthPx: number, heightPx: number) => SVGSVGElement | null; + /** 칸 크기(W × H px)로 새로 그린 카드 svg + 축 옵션(173-4 · 179-3 · 뷰 `detailChart`). */ + chart: ( + stationId: string, + widthPx: number, + heightPx: number, + ) => { svg: SVGSVGElement; axes: CrossAxesOptions | null } | null; } let host: CrossDetailHost | null = null; @@ -121,17 +126,32 @@ export function buildQuantityTable(sheet: CrossQuantitySheet, title: string): HT /* ── 그림 줌 · 팬(164-1) ─────────────────────────────────────────────── */ -/** 복제 자식을 g 하나로 감싸 공용 줌 · 팬을 검(축 · 눈금째 한 장) · 휠 = 커서 고정 줌. */ +/** 도형 층에만 공용 줌 · 팬(179-3 · 축 · 눈금 · 축선 · clip 제자리) · 휠 = 커서 고정 줌 · + * 바뀔 때마다 축을 보이는 범위로 다시 그림 + 십자 크기 되돌림(카드 `_Cross_View_Frame` 꼴). */ export function attachDetailZoom( clone: SVGSVGElement, + axes: CrossAxesOptions | null, initial: ZoomPanState | undefined, onChange: (state: ZoomPanState) => void, -): ZoomPanHandle { - const [, , width, height] = (clone.getAttribute("viewBox") ?? "").split(/[\s,]+/).map(Number); - const layer = document.createElementNS("http://www.w3.org/2000/svg", "g"); - layer.append(...clone.childNodes); +): ZoomPanHandle | null { + const layer = clone.querySelector("g.b06-chart__plot-layer"); + if (!layer) return null; // 칸 = viewBox(칸 크기로 그림 · 173-4) → 커서 고정 · 팬 속도 정확 - clone.append(layer); + const [, , width, height] = (clone.getAttribute("viewBox") ?? "").split(/[\s,]+/).map(Number); + const axisLayer = clone.querySelector("g.b06-chart__axes"); + const renderAxes = axes && axisLayer ? createCrossAxes(axisLayer, axes) : null; + const marker = layer.querySelector("g.b06-chart__center"); + const markerLine = marker?.querySelector("line"); + const cx = Number(markerLine?.getAttribute("x1")); + const cy = Number(markerLine?.getAttribute("y1")) + 18; + const follow = (state: ZoomPanState): void => { + renderAxes?.(state); + marker?.setAttribute( + "transform", + `translate(${cx} ${cy}) scale(${1 / Math.max(state.scale, 1e-6)}) translate(${-cx} ${-cy})`, + ); + }; + follow(initial ?? IDENTITY_VIEW); return attachSvgZoomPan({ svg: clone, layer, @@ -143,7 +163,10 @@ export function attachDetailZoom( content: { x: -width, y: -height, width: 3 * width, height: 3 * height }, wheelZoom: true, initial, - onChange, + onChange: (state) => { + follow(state); + onChange(state); + }, }); } @@ -164,12 +187,16 @@ let tableTimer = 0; let drawing = false; /** 칸 크기로 새로 그림 → 복제(듣개 없음) · clip id 를 카드와 가름(같은 id 면 카드 clip 을 씀). */ -function drawChart(section: CrossSection, widthPx: number, heightPx: number): SVGSVGElement | null { +function drawChart( + section: CrossSection, + widthPx: number, + heightPx: number, +): { clone: SVGSVGElement; axes: CrossAxesOptions | null } | null { drawing = true; try { - const svg = host?.chart(section.station_id, widthPx, heightPx); - if (!svg) return null; - const clone = svg.cloneNode(true) as SVGSVGElement; + const drawn = host?.chart(section.station_id, widthPx, heightPx); + if (!drawn) return null; + const clone = drawn.svg.cloneNode(true) as SVGSVGElement; clone.querySelectorAll("clipPath[id]").forEach((clip) => { const id = clip.id; clip.id = `b06-detail-${id}`; @@ -177,7 +204,7 @@ function drawChart(section: CrossSection, widthPx: number, heightPx: number): SV .querySelectorAll(`[clip-path="url(#${id})"]`) .forEach((el) => el.setAttribute("clip-path", `url(#${clip.id})`)); }); - return clone; + return { clone, axes: drawn.axes }; } finally { drawing = false; } @@ -246,11 +273,12 @@ export function openCrossDetail(section: CrossSection): void { const size = `${width}x${height}`; if (size !== drawnSize) zoom = undefined; drawnSize = size; - const clone = drawChart(state.section, width, height); - if (!clone) cad.replaceChildren(document.createTextNode("그림 없음")); + const drawn = drawChart(state.section, width, height); + if (!drawn) cad.replaceChildren(document.createTextNode("그림 없음")); else { - const handle = attachDetailZoom(clone, zoom, (next) => (zoom = next)); - cad.replaceChildren(clone, buildZoomControls(handle)); + const { clone, axes } = drawn; + const handle = attachDetailZoom(clone, axes, zoom, (next) => (zoom = next)); + cad.replaceChildren(clone, ...(handle ? [buildZoomControls(handle)] : [])); } showPicked(); }, diff --git a/B06_Section/B06_Section_UI_Section_View.ts b/B06_Section/B06_Section_UI_Section_View.ts index 805ac4c5d..a5a2517d5 100644 --- a/B06_Section/B06_Section_UI_Section_View.ts +++ b/B06_Section/B06_Section_UI_Section_View.ts @@ -37,6 +37,7 @@ import { import { type CrossPlotBase, crossPlotBase, + crossPlotFromBase, effectiveCardHalfWidth, fitPlotBase, naturalHeightAtWidth, @@ -561,9 +562,23 @@ export function createSectionView( // 카드 줌 · 팬은 카드 몫 — 모달 그림은 1배에서 시작(카드 상태는 되돌려 둠) const cardZoom = cardZoomStates.get(stationId); cardZoomStates.delete(stationId); - const card = buildCrossCard(section, heightPx, base && fitPlotBase(base, heightPx), widthPx); + const fitted = base && fitPlotBase(base, heightPx); + const card = buildCrossCard(section, heightPx, fitted, widthPx); if (cardZoom) cardZoomStates.set(stationId, cardZoom); - return card.querySelector("svg.b06-section__chart"); + const svg = card.querySelector("svg.b06-section__chart"); + if (!svg) return null; + // 축 다시 그리기 옵션(179-3) — 카드 `createCrossAxes` 와 같은 기하 + const plot = fitted && crossPlotFromBase(fitted, heightPx); + const axes = plot && { + widthPx, + heightPx, + pixelsPerMeter: plot.pixelsPerMeter, + maxOffset: plot.maxOffset, + displayMax: plot.displayMax, + elevationMid: plot.elevationMid, + exaggeration: Math.max(currentExaggeration, 0.1), + }; + return { svg, axes }; }, focusStation(stationId) { if (stationId === null) clearSelection(); diff --git a/B06_Section/B06_Section_UI_Section_View_Types.ts b/B06_Section/B06_Section_UI_Section_View_Types.ts index 0e7439732..07a991790 100644 --- a/B06_Section/B06_Section_UI_Section_View_Types.ts +++ b/B06_Section/B06_Section_UI_Section_View_Types.ts @@ -11,6 +11,7 @@ import type { } from "./B06_Section_Api_Fetch"; import type { SectionStructureEdit } from "./B06_Section_UI_Section_View_Menu"; import type { StructureInstance, StructureType } from "../B05_Profile/B05_Profile_Api_Structures"; +import type { CrossAxesOptions } from "./B06_Section_UI_Cross_Axes"; /** * 상태 행·요약줄·하단 접기 손잡이·테두리가 먹는 세로 공간의 **어림값**. @@ -37,8 +38,13 @@ export interface SectionViewController { refreshCard: (chainageM: number) => void; /** 여러 측점을 한꺼번에 교체한다 — 상단 패널은 **마지막에 한 번만** 다시 그린다. */ refreshCards: (chainages: ReadonlyArray) => void; - /** [상세] 모달 그림(173-4) — 카드 그리기 함수로 칸 크기(W × H px)에 맞춰 새로 그린 svg(없으면 null). */ - detailChart: (stationId: string, widthPx: number, heightPx: number) => SVGSVGElement | null; + /** [상세] 모달 그림(173-4) — 카드 그리기 함수로 칸 크기(W × H px)에 맞춰 새로 그린 svg(없으면 null) + * · 축 다시 그리기 옵션(179-3 · 기하 없으면 null). */ + detailChart: ( + stationId: string, + widthPx: number, + heightPx: number, + ) => { svg: SVGSVGElement; axes: CrossAxesOptions | null } | null; /** 좌측 구조물 목록에서 고른 측점 카드를 선택하고 화면에 드러낸다 — 재클릭 토글 없음 * (2026-08-29 B05/B06 일원화: 목록 클릭 → 해당 카드 스크롤·강조). null = 카드 선택 풂(54-8). */ focusStation: (stationId: string | null) => void; diff --git a/B06_Section/B06_Section_UI_Style_Cross_Detail.css b/B06_Section/B06_Section_UI_Style_Cross_Detail.css index a50607922..d5218d7e2 100644 --- a/B06_Section/B06_Section_UI_Style_Cross_Detail.css +++ b/B06_Section/B06_Section_UI_Style_Cross_Detail.css @@ -54,9 +54,11 @@ } .b06-detail__qtable { + /* 이름 칸 바탕 = 데이터 표 머리색(179-3 · `ui_template_data_table` --dt-head) */ + --b06-detail-head: color-mix(in srgb, var(--color-accent) 6%, var(--color-surface)); width: 100%; border-collapse: collapse; - font-size: var(--text-caption); + font-size: var(--text-body-sm); font-variant-numeric: tabular-nums; } @@ -70,7 +72,8 @@ .b06-detail__qtable th { font-weight: 500; - color: var(--color-text-secondary); + background: var(--b06-detail-head); + color: var(--color-text); } .b06-detail__qtable tr:first-child > th { @@ -100,10 +103,18 @@ /* 그림에 대상이 없는 칸(암석 띠 없음 · 면적 0 · 사토장 없음) — 회색 · 누름 없음 */ .b06-detail__qtable [data-key][data-empty] { cursor: default; - background: none; color: var(--color-text-muted); } +/* 빈 칸 = 값 칸만 바탕 지움 · 이름 칸은 머리색 그대로(글만 회색) */ +.b06-detail__qtable td[data-key][data-empty] { + background: none; +} + +.b06-detail__qtable th[data-key][data-empty] { + background: var(--b06-detail-head); +} + /* 그림 강조 한 벌 = 붉은 계통(164-2 · 카드 색은 그대로) — 띠 · 판정 선 · 중심 십자 · 사토장 */ .b06-detail__cad .b06-chart__area.is-active > polygon { fill: color-mix(in srgb, var(--color-danger) 35%, transparent); diff --git a/resources/tester/helper_164_detail_zoom.cjs b/resources/tester/helper_164_detail_zoom.cjs index ada95dc88..75bdba7bb 100644 --- a/resources/tester/helper_164_detail_zoom.cjs +++ b/resources/tester/helper_164_detail_zoom.cjs @@ -11,11 +11,28 @@ const B06 = path.join(ROOT, "B06_Section"); const common = fs.readFileSync(path.join(B06, "B06_Section_UI_Section_Common.ts"), "utf8"); // 카드 여백은 원본에서 읽음(화면과 같은 값) const CROSS_PAD = Function(`return ${/export const CROSS_PAD = (\{[^}]*\})/.exec(common)[1]}`)(); +// 축 눈금 간격도 원본 함수 그대로(179-3 · 축 다시 그리기) +const niceSrc = /export function niceTickStep[\s\S]*?\n\}/.exec(common.replace(/\r/g, ""))[0]; +const niceTickStep = ts + .transpileModule(niceSrc.replace("export ", ""), { compilerOptions: {} }) + .outputText.concat("\nreturn niceTickStep;"); +const svgElement = (tag, attrs = {}) => { + const node = el(tag); + for (const [name, value] of Object.entries(attrs)) node.setAttribute(name, value); + return node; +}; +const svgText = (text, attrs) => Object.assign(svgElement("text", attrs), { textContent: text }); const STUBS = { "@ui/ui_template_modal": { openModal: () => {} }, "../A00_Common/b_page_state": {}, "./B06_Section_Api_Fetch": {}, - "./B06_Section_UI_Section_Common": { CROSS_PAD, L: (key) => key }, + "./B06_Section_UI_Section_Common": { + CROSS_PAD, + L: (key) => key, + niceTickStep: Function(niceTickStep)(), + svgElement, + svgText, + }, "./B06_Section_UI_Cross_Detail_Pick": { DETAIL_TARGETS: {} }, }; const resolve = (base) => (fs.existsSync(`${base}.ts`) ? `${base}.ts` : base); @@ -52,6 +69,24 @@ function el(tag) { node.parent = self; self.childNodes.push(node); }), + replaceChildren: (...nodes) => { + self.childNodes.splice(0).forEach((node) => (node.parent = null)); + self.append(...nodes); + }, + // 「tag.class」 · 「tag」 만 — 깊이 우선 첫 것 + querySelector: (selector) => { + const [tagName, cls] = selector.split("."); + const walk = (node) => { + for (const child of node.childNodes) { + const classes = (child.attrs.class ?? "").split(" "); + if (child.tag === tagName && (!cls || classes.includes(cls))) return child; + const found = walk(child); + if (found) return found; + } + return null; + }; + return walk(self); + }, addEventListener: (type, fn) => (listeners[type] ??= []).push(fn), fire: (type, event) => { const full = { preventDefault() {}, stopPropagation() {}, pointerId: 1, ...event }; @@ -69,16 +104,42 @@ globalThis.document = { createElementNS: (_ns, tag) => el(tag), createElement: e const { attachDetailZoom } = loadTs(path.join(B06, "B06_Section_UI_Cross_Detail.ts")); -const svg = el("svg"); -svg.setAttribute("viewBox", "0 0 600 300"); -svg.append(el("rect"), el("g"), el("g")); +// 카드 svg 꼴 — 바탕 · 축 층 · clip 그룹 > 도형 층 > 십자(중심 300,150) +const AXES = { + widthPx: 600, + heightPx: 300, + pixelsPerMeter: 10, + maxOffset: 24, + displayMax: 120, + elevationMid: 110, + exaggeration: 1, +}; +function chart() { + const svg = el("svg"); + svg.setAttribute("viewBox", "0 0 600 300"); + const axes = svgElement("g", { class: "b06-chart__axes" }); + const clipGroup = svgElement("g", { "clip-path": "url(#c)" }); + const plot = svgElement("g", { class: "b06-chart__plot-layer" }); + const center = svgElement("g", { class: "b06-chart__center" }); + center.append(svgElement("line", { x1: 300, y1: 132, x2: 300, y2: 168 })); + plot.append(center); + clipGroup.append(plot); + svg.append(svgElement("rect"), axes, clipGroup, svgElement("line", { class: "b06-chart__axis" })); + return { svg, axes, plot, center }; +} +const ticks = (axes) => + axes.childNodes.filter((node) => node.tag === "text").map((node) => node.textContent); +const { svg, axes, plot: layer, center } = chart(); let saved; -const handle = attachDetailZoom(svg, undefined, (next) => (saved = next)); -const layer = svg.childNodes[0]; +const handle = attachDetailZoom(svg, AXES, undefined, (next) => (saved = next)); +const ticksBefore = ticks(axes); const out = { - wrapped: svg.childNodes.length === 1 && layer.tag === "g" && layer.childNodes.length === 3, + // 179-3 — 감싸지 않음 · 자식 넷 그대로 + wrapped: svg.childNodes.length === 1 && svg.childNodes[0].tag === "g", + children: svg.childNodes.length, ratio: svg.attrs["style:--ratio"], before: layer.getAttribute("transform"), + ticksBefore: ticksBefore.length, }; // 휠 당김(deltaY > 0) = 확대 · 커서(화면 600,300 = svg 300,150) 고정 svg.fire("wheel", { clientX: 600, clientY: 300, deltaY: 100 }); @@ -97,19 +158,35 @@ svg.fire("pointerdown", { button: 1, clientX: 100, clientY: 100 }); svg.fire("pointermove", { clientX: 300, clientY: 140 }); svg.fire("pointerup", {}); out.middlePan = { ...saved }; +// 팬 뒤 — 도형 층만 transform · 축 층 · 바깥 그룹 transform 없음 · 눈금 글 바뀜 +out.plotTransform = layer.getAttribute("transform"); +out.axesTransform = axes.getAttribute("transform"); +out.clipTransform = svg.childNodes[2].getAttribute("transform"); +out.ticksChanged = JSON.stringify(ticks(axes)) !== JSON.stringify(ticksBefore); +out.ticksAfter = ticks(axes).length; out.panClickStopped = svg.fire("click", { stopPropagation() { this.stopped = true; }, }).stopped; // 다시 떠도 배율 이어 감 -const again = el("svg"); -again.setAttribute("viewBox", "0 0 600 300"); -attachDetailZoom(again, saved, () => {}); -out.restored = again.childNodes[0].getAttribute("transform"); +const again = chart(); +attachDetailZoom(again.svg, AXES, saved, () => {}); +out.restored = again.plot.getAttribute("transform"); +// 다시 떠도 축은 이어 간 범위로(1배 원래 눈금과 다름) +out.restoredTicksShifted = JSON.stringify(ticks(again.axes)) !== JSON.stringify(ticksBefore); +// 축 옵션 없음 · 도형 층 없음 — 줌은 걸고 축은 그대로 / 줌 없음 +const bare = chart(); +out.noAxesHandle = attachDetailZoom(bare.svg, null, undefined, () => {}) !== null; +const flat = el("svg"); +flat.setAttribute("viewBox", "0 0 600 300"); +out.noLayer = attachDetailZoom(flat, AXES, undefined, () => {}); // 더블클릭 원복 svg.fire("dblclick", {}); out.reset = layer.getAttribute("transform"); +// 확대 — 십자는 중심에서 배율을 되돌림(크기 그대로) +svg.fire("wheel", { clientX: 600, clientY: 300, deltaY: 100 }); +out.centerTransform = center.getAttribute("transform"); out.max = (() => { for (let i = 0; i < 60; i++) svg.fire("wheel", { clientX: 600, clientY: 300, deltaY: 100 }); return saved.scale; diff --git a/resources/tester/test_164_detail_zoom.py b/resources/tester/test_164_detail_zoom.py index 04b48be77..c8b59ecfc 100644 --- a/resources/tester/test_164_detail_zoom.py +++ b/resources/tester/test_164_detail_zoom.py @@ -40,10 +40,24 @@ def out() -> dict: return json.loads(proc.stdout) -def test_복제_자식을_g_하나로_감쌈_비율(out): - assert out["wrapped"] is True # 축 · 눈금 · 도형이 한 장으로 움직임 +def test_감싸지_않음_비율(out): + # 179-3 — 복제를 g 하나로 감싸지 않음(축 · 눈금 · 도형 한 장 → 도형 층만) + assert out["wrapped"] is False and out["children"] == 4 assert out.get("ratio") is None # 173-4 — 칸 = viewBox(칸 크기로 새로 그림) · 비율 변수 없음 assert out["before"] is None + assert out["ticksBefore"] > 0 + + +def test_축_고정_도형_층만_움직임_눈금_다시(out): + # 179-3 — 팬 뒤 도형 층만 transform · 축 층 · clip 그룹 제자리 · 눈금 글은 보이는 범위로 + assert out["plotTransform"] == "translate(100 20) scale(1)" + assert out["axesTransform"] is None and out["clipTransform"] is None + assert out["ticksChanged"] is True and out["ticksAfter"] > 0 + assert out["restoredTicksShifted"] is True # 다시 떠도 이어 간 범위로 + # 확대 — 십자는 중심(300,150)에서 배율 되돌림 + assert out["centerTransform"].startswith("translate(300 150) scale(0.869") + assert out["noAxesHandle"] is True # 축 옵션 없음 = 줌만 + assert out["noLayer"] is None # 도형 층 없음 = 줌 없음 def test_휠_당기면_확대_커서_고정(out): @@ -69,8 +83,14 @@ def test_모달_배선_단추_칸_비율(): src = _read("B06_Section_UI_Cross_Detail.ts") css = _read("B06_Section_UI_Style_Cross_Detail.css") assert "wheelZoom: true" in src and "attachSvgZoomPan({" in src - assert "cad.replaceChildren(clone, buildZoomControls(handle))" in src - assert "attachDetailZoom(clone, zoom, (next) => (zoom = next))" in src + assert "cad.replaceChildren(clone, ...(handle ? [buildZoomControls(handle)] : []))" in src + assert "attachDetailZoom(clone, axes, zoom, (next) => (zoom = next))" in src + assert "layer.append(...clone.childNodes)" not in src # 감싸기 지움(179-3) + assert 'clone.querySelector("g.b06-chart__plot-layer")' in src + assert "createCrossAxes(axisLayer, axes)" in src + view = _read("B06_Section_UI_Section_View.ts") + assert "const plot = fitted && crossPlotFromBase(fitted, heightPx);" in view + assert "return { svg, axes };" in view assert "position: relative;" in css # 173-4 — 그림 = 칸 크기로 새로 그림 · svg 칸 가득 assert ".b06-detail__cad > svg {\n display: block;\n width: 100%;\n height: 100%;" in css @@ -102,3 +122,27 @@ def test_빈칸_누름_없음_이름_칸_첫_키(): assert 'if (!key || el.hasAttribute("data-empty")) return;' in src assert 'cell("th", groupL, { row: 2 }, keyL)' in src assert 'cell("th", groupM, { row: 2 }, keyM)' in src + + +def test_표_글자_14px_이름_칸_머리색_빈칸_값칸만(): + # 179-3 — 표 글자 14px · 이름 칸(th) 바탕 = 데이터 표 머리색 · 빈 칸 바탕 지움은 값 칸(td)만 + css = _read("B06_Section_UI_Style_Cross_Detail.css") + table = css.split(".b06-detail__qtable {", 1)[1].split("}", 1)[0] + assert "font-size: var(--text-body-sm);" in table + assert ( + "--b06-detail-head: color-mix(in srgb, var(--color-accent) 6%, var(--color-surface));" + in table + ) + head = css.split(".b06-detail__qtable th {", 1)[1].split("}", 1)[0] + assert "background: var(--b06-detail-head);" in head and "color: var(--color-text);" in head + empty = css.split(".b06-detail__qtable [data-key][data-empty] {", 1)[1].split("}", 1)[0] + assert "background" not in empty + assert ".b06-detail__qtable td[data-key][data-empty] {\n background: none;" in css + assert ( + ".b06-detail__qtable th[data-key][data-empty] {\n background: var(--b06-detail-head);" + in css + ) + theme = open( + os.path.join(ROOT, "ui_template", "ui_template_theme.css"), encoding="utf-8" + ).read() + assert "--text-body-sm: 14px;" in theme diff --git a/resources/tester/test_173_areas_detail.py b/resources/tester/test_173_areas_detail.py index 215419903..0ac4e246e 100644 --- a/resources/tester/test_173_areas_detail.py +++ b/resources/tester/test_173_areas_detail.py @@ -87,7 +87,7 @@ def test_상세_테마_토큰_어두운_고정색_없음(): def test_상세_칸_크기로_새로_그림_배선(): src = _read("B06_Section_UI_Cross_Detail.ts") assert "const { clientWidth: width, clientHeight: height } = cad;" in src - assert "const clone = drawChart(state.section, width, height);" in src + assert "const drawn = drawChart(state.section, width, height);" in src assert "host?.chart(section.station_id, widthPx, heightPx)" in src assert "new ResizeObserver(() => state.paint())" in src and "resize.disconnect()" in src # 카드 clip id 와 가름 · 그리는 동안 refresh 되먹임 막음 @@ -99,7 +99,12 @@ def test_상세_칸_크기로_새로_그림_배선(): assert "base && fitPlotBase(base, heightPx)" in view assert "cardZoomStates.delete(stationId);" in view types = _read("B06_Section_UI_Section_View_Types.ts") - assert "detailChart: (stationId: string, widthPx: number, heightPx: number) =>" in types + # 179-3 — svg + 축 옵션 + assert " ) => { svg: SVGSVGElement; axes: CrossAxesOptions | null } | null;" in types + assert ( + " detailChart: (\n stationId: string,\n widthPx: number,\n heightPx: number," + in types + ) sync = _read("B06_Section_UI_Page_Design_Sync.ts") assert "ctx.view().detailChart(stationId, widthPx, heightPx)" in sync