- 헬퍼 동작 그대로(풀이 함수 본문은 옮겨 담음) · 제품 코드 안 고침 - 확인: 이 묶음 헬퍼를 쓰는 시험 218 개 전 218 통과 → 후 218 통과 (시험별 junit 대조 · 어긋남 0) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
112 lines
4.4 KiB
JavaScript
112 lines
4.4 KiB
JavaScript
/* PLAN 266-1 단위검증 헬퍼 — [상세] 처음 가로 범위 = 카드 반폭.
|
|
* 실제 TS(`_Cross_View_Metrics` 칸 맞춤 · `b_svg_zoom_pan` homeScale)를 Node 로 돌림.
|
|
* 처음 배율 = 뷰 `detailChart` 와 같은 식(base.ppm / fitted.ppm · 짝 시험이 원본 줄을 확인).
|
|
* 결과 = 표준출력 JSON 한 벌. 짝 = `test_266_detail_home.py`.
|
|
*/
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
|
|
|
|
const ROOT = path.join(__dirname, "..", "..");
|
|
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]}`)();
|
|
const CROSS_HEIGHT = Number(/export const CROSS_HEIGHT = (\d+)/.exec(common)[1]);
|
|
const STUBS = {
|
|
"./B06_Section_UI_Section_Common": {
|
|
CROSS_PAD,
|
|
CROSS_HEIGHT,
|
|
validElevation: (s) => s.valid !== false && Number.isFinite(s.elevation_m),
|
|
},
|
|
};
|
|
const { loadTs } = makeLoader({
|
|
root: ROOT,
|
|
cache: false,
|
|
requireFor: (full) => (request) => STUBS[request] ?? {},
|
|
});
|
|
// 휠 방향과 무관한 시험(처음으로 · 범위) — 337 기본 밂 뒤에도 당김 수치 그대로(pull 박음)
|
|
globalThis.localStorage = {
|
|
getItem: (k) => (k === "aislo:pref:input-prefs" ? '{"wheelZoom":"pull"}' : null),
|
|
};
|
|
STUBS["./input_prefs"] = loadTs(path.join(ROOT, "A00_Common", "input_prefs.ts")); // 휠 방향(332)
|
|
const metrics = loadTs(path.join(B06, "B06_Section_UI_Cross_View_Metrics.ts"));
|
|
const { attachSvgZoomPan } = loadTs(path.join(ROOT, "A00_Common", "b_svg_zoom_pan.ts"));
|
|
|
|
function fakeSvg(width, height) {
|
|
const listeners = {};
|
|
const attrs = {};
|
|
return {
|
|
attrs,
|
|
setAttribute: (name, value) => (attrs[name] = String(value)),
|
|
getAttribute: (name) => attrs[name] ?? null,
|
|
classList: { add() {}, remove() {} },
|
|
addEventListener: (type, fn) => (listeners[type] ??= []).push(fn),
|
|
fire: (type, event) =>
|
|
(listeners[type] ?? []).forEach((fn) =>
|
|
fn({ preventDefault() {}, stopPropagation() {}, pointerId: 1, ...event }),
|
|
),
|
|
setPointerCapture() {},
|
|
releasePointerCapture() {},
|
|
getBoundingClientRect: () => ({ left: 0, top: 0, width, height }),
|
|
};
|
|
}
|
|
|
|
// 가파른 비탈 — ±30 m 샘플 · 1 m 마다 · 1 m 에 0.6 m 오름(칸 맞춤이 가로를 넓히게)
|
|
const section = {
|
|
samples: Array.from({ length: 61 }, (_, i) => ({
|
|
offset_m: i - 30,
|
|
elevation_m: 100 + 0.6 * (i - 30),
|
|
})),
|
|
};
|
|
|
|
/** 반폭 · 칸 크기로 상세 창을 연 셈 → 처음 · 줌 · 팬 · 원복 뒤 보이는 가로 범위(m). */
|
|
function open(halfWidth, W, H) {
|
|
const base = metrics.crossPlotBase(section, 1, W, halfWidth, 100);
|
|
const fitted = metrics.fitPlotBase(base, H);
|
|
const homeScale = base && fitted ? base.pixelsPerMeter / fitted.pixelsPerMeter : 1;
|
|
const svg = fakeSvg(W, H);
|
|
const layer = fakeSvg(W, H);
|
|
let state = null;
|
|
const handle = attachSvgZoomPan({
|
|
svg,
|
|
layer,
|
|
widthPx: W,
|
|
heightPx: H,
|
|
pad: CROSS_PAD,
|
|
maxScale: 16,
|
|
content: { x: -W, y: -H, width: 3 * W, height: 3 * H },
|
|
wheelZoom: true,
|
|
homeScale,
|
|
onChange: (next) => (state = next),
|
|
});
|
|
// 보이는 플롯 가로 끝(svg x) → 도형 층 x → 측점 거리(m)
|
|
const range = () => {
|
|
const s = state ?? { scale: 1, tx: 0, ty: 0 };
|
|
const toM = (x) =>
|
|
fitted.minOffset + ((x - s.tx) / s.scale - CROSS_PAD.left) / fitted.pixelsPerMeter;
|
|
return [toM(CROSS_PAD.left), toM(W - CROSS_PAD.right)].map((v) => Math.round(v * 1000) / 1000);
|
|
};
|
|
const out = { homeScale, widened: fitted !== base, first: range() };
|
|
svg.fire("wheel", { clientX: W / 2, clientY: H / 2, deltaY: 100 });
|
|
out.wheelScale = state.scale / homeScale;
|
|
svg.fire("pointerdown", { button: 1, clientX: 100, clientY: 100 });
|
|
svg.fire("pointermove", { clientX: 160, clientY: 100 });
|
|
svg.fire("pointerup", {});
|
|
out.panned = range()[0] !== out.first[0];
|
|
handle.reset(); // [⤢]
|
|
out.reset = range();
|
|
for (let i = 0; i < 40; i++) svg.fire("wheel", { clientX: W / 2, clientY: H / 2, deltaY: -100 });
|
|
out.minScale = state.scale; // 줄이면 칸 맞춤 전체(1배)까지
|
|
svg.fire("dblclick", {});
|
|
out.dblclick = range();
|
|
return out;
|
|
}
|
|
|
|
process.stdout.write(
|
|
JSON.stringify({
|
|
global15: open(15, 1200, 300),
|
|
station10: open(10, 1200, 300),
|
|
tall15: open(15, 1200, 900),
|
|
}),
|
|
);
|