Files
TieMeasureFlow/src/frontend/flask_app/tests/js/test_image_viewer.js
T

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);