Files
Aislo/resources/tester/test_cross_axes_zoom.mjs
T
eomsangdonandClaude Opus 5 0ef32b5279 chore(tester): 시험을 resources/tester/ 로 옮김 — 창끼리 건너가게
⚠ **뿌리** — `tmp/` 는 창 사이에 안 건너감(실측 확인: 상대 창이 놓은 `tmp/_sync_probe.txt`
가 시간을 두고 두 번 봐도 안 보임). 그래서 **정본(등록부 스키마)만 건너가고 그것을 읽는
시험은 안 건너가** 오늘 두 번, 같은 시험이 **연 창은 통과·받은 창은 실패**가 됐음.

- `tmp/tests/*` 를 `resources/tester/` 로 **복사**(127 파일). 내용은 **한 줄도 안 고침**
- `tmp/tests` 는 **남겨 둠** — 되돌릴 자리(사용자 지시)
- 실행: `./venv/Scripts/python.exe -m pytest resources/tester/ -q`
  옮기기 전과 **같은 수**: 617 통과 / 22 건너뜀 / 실패 0

⇒ 이제 시험·예외·까닭이 **정본과 함께** 움직임. 오늘 세운 「예외는 정본 스키마에」와 짝임.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 17:12:30 +09:00

71 lines
2.9 KiB
JavaScript

// 줌·팬 시 축 눈금이 도형과 맞는지 — Cross_Axes의 역산·배치 로직 복제 검증.
// 실행: node tmp/tests/test_cross_axes_zoom.mjs
import assert from "node:assert/strict";
const CROSS_PAD = { left: 58, right: 20, top: 10, bottom: 52 };
function niceTickStep(span, targetCount, maxCount) {
if (!(span > 0)) return 1;
const exponent = Math.floor(Math.log10(span / targetCount));
let best = Math.pow(10, exponent + 2);
let bestError = Infinity;
for (const power of [exponent - 1, exponent, exponent + 1, exponent + 2]) {
for (const mantissa of [1, 2, 5]) {
const step = mantissa * Math.pow(10, power);
const count = Math.floor(span / step) + 1;
if (count > Math.max(2, maxCount)) continue;
const error = Math.abs(count - targetCount);
if (error < bestError) {
bestError = error;
best = step;
}
}
}
return best;
}
const widthPx = 630;
const heightPx = 250;
const ppm = (widthPx - CROSS_PAD.left - CROSS_PAD.right) / 24; // 반폭 12m
const maxOffset = 12;
const plotLeft = CROSS_PAD.left;
const plotRight = widthPx - CROSS_PAD.right;
const x = (offset) => plotLeft + (maxOffset - offset) * ppm;
function xTicks({ scale, tx }) {
const offsetAt = (px) => maxOffset - ((px - tx) / scale - plotLeft) / ppm;
const high = offsetAt(plotLeft);
const low = offsetAt(plotRight);
const step = niceTickStep(high - low, 7, Math.floor((plotRight - plotLeft) / 48));
const out = [];
for (let o = Math.ceil(low / step) * step; o <= high + 1e-9; o += step) {
out.push({ offset: o, screenX: tx + scale * x(o) });
}
return { out, step, low, high };
}
// 원배율: 눈금이 플롯 안, 라벨 자리 = 도형 자리.
const base = xTicks({ scale: 1, tx: 0 });
assert.ok(base.out.length >= 4, `원배율 눈금 부족 ${base.out.length}`);
for (const t of base.out) {
assert.ok(t.screenX >= plotLeft - 1e-6 && t.screenX <= plotRight + 1e-6, "원배율 눈금 이탈");
assert.ok(Math.abs(t.screenX - x(t.offset)) < 1e-9, "원배율 자리 어긋남");
}
// 4배 확대(중앙 고정): 보이는 범위가 1/4로 좁아지고, 눈금 자리가 변환된 도형 자리와 같아야 한다.
const scale = 4;
const cx = plotLeft + (plotRight - plotLeft) / 2;
const tx = cx - cx * scale;
const zoomed = xTicks({ scale, tx });
assert.ok(
Math.abs(zoomed.high - zoomed.low - 24 / scale) < 1e-6,
`확대 범위 오류 ${zoomed.high - zoomed.low}`,
);
assert.ok(zoomed.step < base.step, `확대인데 눈금 간격이 안 촘촘해짐 ${zoomed.step}`);
assert.ok(zoomed.out.length >= 4, `확대 눈금 부족 ${zoomed.out.length}`);
for (const t of zoomed.out) {
assert.ok(t.screenX >= plotLeft - 1e-6 && t.screenX <= plotRight + 1e-6, "확대 눈금 이탈");
// 도형은 transform으로 옮겨 그려진다 — 같은 offset의 도형 자리와 눈금 자리가 일치해야 한다.
assert.ok(Math.abs(t.screenX - (tx + scale * x(t.offset))) < 1e-9, "확대 자리 어긋남");
}
console.log("ok");