d48a8a1076
Refs #122 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
100 lines
5.3 KiB
JavaScript
100 lines
5.3 KiB
JavaScript
/**
|
|
* #122: static/js/image_viewer.js (<tmf-viewer>) con plain node: matematica pura (TmfViewerMath) e
|
|
* registrazione del Custom Element una sola volta. Il DOM vero si prova in Chrome (fixtures/image_viewer_demo.html).
|
|
*
|
|
* Il modulo si carica senza window/document/customElements.
|
|
* Run from src/frontend/flask_app: node tests/js/test_image_viewer.js
|
|
*/
|
|
'use strict';
|
|
const path = require('path');
|
|
|
|
const MOD = path.join(__dirname, '..', '..', 'static', 'js', 'image_viewer.js');
|
|
|
|
let failures = 0;
|
|
let total = 0;
|
|
function check(label, cond, extra) {
|
|
total++;
|
|
if (cond) console.log('ok - ' + label);
|
|
else { failures++; console.log('FAIL - ' + label + (extra ? ' :: ' + extra : '')); }
|
|
}
|
|
const near = (a, b) => Math.abs(a - b) < 1e-9;
|
|
|
|
// --- caricamento in node senza globali del browser ---
|
|
check('node senza window/document/customElements/HTMLElement',
|
|
typeof window === 'undefined' && typeof document === 'undefined' &&
|
|
typeof customElements === 'undefined' && typeof HTMLElement === 'undefined');
|
|
const { TmfViewerMath: M } = require(MOD);
|
|
check('esporta TmfViewerMath con le quattro funzioni',
|
|
M && ['fitZoom', 'clampZoom', 'zoomAround', 'pct'].every((k) => typeof M[k] === 'function'));
|
|
|
|
// --- fitZoom ---
|
|
check('fitZoom: box largo, immagine alta -> limita l\'altezza', near(M.fitZoom(800, 400, 1000, 1000), 0.4));
|
|
check('fitZoom: box alto, immagine larga -> limita la larghezza', near(M.fitZoom(400, 800, 1000, 500), 0.4));
|
|
check('fitZoom: immagine piu\' piccola del box -> zoom > 1', near(M.fitZoom(800, 600, 400, 300), 2));
|
|
check('fitZoom: box esatto -> 1', near(M.fitZoom(640, 480, 640, 480), 1));
|
|
check('fitZoom: boxH Infinity (fit-height) -> solo larghezza', near(M.fitZoom(500, Infinity, 1000, 4000), 0.5));
|
|
check('fitZoom: 0 se box nascosto o immagine senza dimensioni',
|
|
M.fitZoom(0, 400, 10, 10) === 0 && M.fitZoom(400, 0, 10, 10) === 0 &&
|
|
M.fitZoom(400, 400, 0, 10) === 0 && M.fitZoom(400, 400, 10, 0) === 0);
|
|
check('fitZoom: 0 con NaN, negativi, undefined',
|
|
M.fitZoom(NaN, 400, 10, 10) === 0 && M.fitZoom(-5, 400, 10, 10) === 0 && M.fitZoom(400, 400, undefined, 10) === 0 &&
|
|
M.fitZoom(400, 400, 10, null) === 0);
|
|
|
|
// --- clampZoom ---
|
|
check('clampZoom: dentro i limiti resta uguale', near(M.clampZoom(1, 0.5), 1));
|
|
check('clampZoom: sotto fit*0.5 -> fit*0.5', near(M.clampZoom(0.01, 0.5), 0.25));
|
|
check('clampZoom: sopra 8 -> 8', near(M.clampZoom(20, 0.5), 8));
|
|
check('clampZoom: fit sopra 1 alza il massimo a fit*8 (DXF in mm, fit ~10)', near(M.clampZoom(20, 12), 20) && near(M.clampZoom(200, 12), 96) && near(M.clampZoom(11, 12), 11));
|
|
check('clampZoom: con fit piccolo il massimo resta 8', near(M.clampZoom(20, 0.5), 8) && near(M.clampZoom(20, 1), 8) && near(M.clampZoom(100, 2), 16));
|
|
check('clampZoom: limiti esatti', near(M.clampZoom(0.25, 0.5), 0.25) && near(M.clampZoom(8, 0.5), 8));
|
|
|
|
// --- zoomAround ---
|
|
{
|
|
// il punto del contenuto sotto (px,py) prima e dopo lo zoom deve essere lo stesso
|
|
const z0 = 0.5, z1 = 1.25, sl = 120, st = 40, px = 300, py = 200;
|
|
const r = M.zoomAround(z0, z1, sl, st, px, py);
|
|
const before = { x: (sl + px) / z0, y: (st + py) / z0 }; // coordinate logiche sotto il puntatore
|
|
const after = { x: (r.scrollLeft + px) / z1, y: (r.scrollTop + py) / z1 };
|
|
check('zoomAround: il punto sotto il puntatore resta fermo', near(before.x, after.x) && near(before.y, after.y),
|
|
JSON.stringify([before, after]));
|
|
const same = M.zoomAround(2, 2, 50, 60, 10, 10);
|
|
check('zoomAround: z1 == z0 non sposta lo scroll', near(same.scrollLeft, 50) && near(same.scrollTop, 60));
|
|
const c = M.zoomAround(1, 2, 0, 0, 100, 50);
|
|
check('zoomAround: da scroll 0 raddoppiando (100,50) -> scroll (100,50)', near(c.scrollLeft, 100) && near(c.scrollTop, 50));
|
|
const o = M.zoomAround(2, 1, 100, 100, 0, 0);
|
|
check('zoomAround: puntatore all\'origine, riduzione -> scroll dimezzato', near(o.scrollLeft, 50) && near(o.scrollTop, 50));
|
|
}
|
|
|
|
// --- pct ---
|
|
check('pct: 1 -> "100 %"', M.pct(1) === '100 %', M.pct(1));
|
|
check('pct: arrotonda 0.4567 -> "46 %"', M.pct(0.4567) === '46 %', M.pct(0.4567));
|
|
check('pct: 8 -> "800 %", 0 -> "0 %"', M.pct(8) === '800 %' && M.pct(0) === '0 %');
|
|
|
|
// --- define una sola volta, anche caricando due volte ---
|
|
{
|
|
const defined = {};
|
|
let defineCalls = 0;
|
|
globalThis.HTMLElement = class {};
|
|
globalThis.customElements = {
|
|
get: (n) => defined[n],
|
|
define: (n, c) => { defineCalls++; defined[n] = c; },
|
|
};
|
|
delete require.cache[require.resolve(MOD)];
|
|
require(MOD);
|
|
delete require.cache[require.resolve(MOD)];
|
|
require(MOD);
|
|
check('customElements.define("tmf-viewer") una sola volta con due caricamenti',
|
|
defineCalls === 1 && typeof defined['tmf-viewer'] === 'function', 'chiamate: ' + defineCalls);
|
|
const C = defined['tmf-viewer'];
|
|
check('osserva src, width, height, fit-height, no-save, save-name',
|
|
['src', 'width', 'height', 'fit-height', 'no-save', 'save-name'].every((a) => C.observedAttributes.includes(a)));
|
|
check('espone fit, zoomBy, overlay, labels, zoom sul prototipo',
|
|
typeof C.prototype.fit === 'function' && typeof C.prototype.zoomBy === 'function' &&
|
|
['overlay', 'labels', 'zoom'].every((k) => Object.getOwnPropertyDescriptor(C.prototype, k)));
|
|
delete globalThis.customElements;
|
|
delete globalThis.HTMLElement;
|
|
}
|
|
|
|
console.log('\n' + (total - failures) + '/' + total + ' controlli ok');
|
|
process.exit(failures ? 1 : 0);
|