/** * #122: static/js/image_viewer.js () 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);