⚠ **뿌리** — `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>
71 lines
2.9 KiB
JavaScript
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");
|